// // o---------------------------------------------------------------------------------o // | This file is part of the RGraph package - you can learn more at: | // | | // | https://www.rgraph.net/license.html | // | | // | RGraph is dual-licensed under the Open Source GPL license. That means that it's | // | free to use and there are no restrictions on what you can use RGraph for! | // | If the GPL license does not suit you however, then there's an inexpensive | // | commercial license option available. See the URL above for more details. | // o---------------------------------------------------------------------------------o // // Having this here means that the RGraph libraries can be // included in any order, instead of you having to include // the common core library first. // // Define the RGraph global variable RGraph = window.RGraph || {isrgraph:true,isRGraph: true,rgraph:true}; // // The constructor. This function sets up the object. // RGraph.Datagrid = RGraph.DataGrid = function (conf) { // // This facilitates easy clearing of the datagrid. It // empties the nodes of the container div tag. // this.clear = function () { RGraph.fireCustomEvent(this, 'beforeclear'); this.container.replaceChildren(); RGraph.fireCustomEvent(this, 'clear'); }; // // This facilitates easy redrawing of the datagrid. It //clears the datagrid and the draws it again. // this.redraw = function () { RGraph.fireCustomEvent(this, 'beforeredraw'); this.clear(); this.draw(); RGraph.fireCustomEvent(this, 'redraw'); }; // // Adds styles to the document. // // @param string selector A standa\rd CSS selector. // @param string style The styles to add // this.addStylesBySelector = function (selector, styles) { // TODO Alternative - create a style element // and add the style to it. // // Using: // //document.head.insertAdjacentHTML('beforeend', '[STYLE]') //var els = document.querySelectorAll(selector); //for (var i=0; i a) { return 1; } else if (b < a) { return -1; } else { return 0; } } } else if (dir === 1) { // Allow for a custom sort function // if (RGraph.isFunction (obj.properties.sortableCompare)) { return (obj.properties.sortableCompare)(obj, col, dir, a[col].value, b[col].value); } else if (RGraph.isArray (obj.properties.sortableCompare) && RGraph.isFunction (obj.properties.sortableCompare[col])) { return (obj.properties.sortableCompare[col])(obj, col, dir, a[col].value, b[col].value); } else { var a = a[col].value; var b = b[col].value; if (RGraph.isString(a)) a = a.toLowerCase(); if (RGraph.isString(b)) b = b.toLowerCase(); if (a > b) { return 1; } else if (a < b) { return -1; } else { return 0; } } } else { a[col].original_index - b[col].original_index; } }); RGraph.fireCustomEvent(obj, 'sort'); return data; }; // // This function saves the size of the columns // to a variable in localStorage (in the browser). // this.saveColumnSizesToLocalStorage = function (obj) { if (obj.properties.columnsResizablePersistent) { var str = JSON.stringify(obj.columnWidths); window.localStorage[obj.resizableColumnsPersistentLocalStorageKey] = str; } }; // // This function loads the size of the columns // into the object. // this.loadColumnSizesFromLocalStorage = function (obj) { if (obj.properties.columnsResizablePersistent && window.localStorage[obj.resizableColumnsPersistentLocalStorageKey] && window.localStorage[obj.resizableColumnsPersistentLocalStorageKey].length) { var data = JSON.parse(window.localStorage[obj.resizableColumnsPersistentLocalStorageKey]); return data; } }; // // This function allows you to reset column widths back // to their original, unaltered state. // // @param boolean twice This function can be run twice, // and is by default. This is due // to a strange bug. // this.resetColumnWidths = function (twice = true) { this.columnWidths = []; this.calculateColumnWidths(); window.localStorage[this.resizableColumnsPersistentLocalStorageKey] = ''; this.redraw(); // // Strange but necessary. // if (twice) { this.resetColumnWidths(false); } }; // // This function is for saving the sort state to // localStorage - making for persistent sorting. // this.saveSortDataToLocalStorage = function (obj) { if (obj.properties.sortablePersistent) { var key = obj.sortableColumnPersistentLocalStorageKey; window.localStorage[key] = JSON.stringify([ obj.sortColumn, obj.sortDir ]); } }; // // This function is for loading the sort information from // localStorage. // this.loadSortDataFromLocalStorage = function (obj) { if (obj.properties.sortablePersistent) { var key = obj.sortableColumnPersistentLocalStorageKey; var str = localStorage[obj.sortableColumnPersistentLocalStorageKey]; var arr = []; if (str) { arr = JSON.parse(str); } return arr; } }; // // This function allows you to reset the sort data back // to the default "not sorted" state // this.resetSortData = function () { window.localStorage[this.sortableColumnPersistentLocalStorageKey] = ''; this.sortColumn = null; this.sortDir = null; // // Sort the array so that the data goes back to its // original order // this.data.sort(function (a,b) { return a[0].original_row_index - b[0].original_row_index; }); this.redraw(); }; // Returns the data // // @param mixed ... This can be a string noting the cell // to get. It's optional and if not given // the whole data is returned as 2D array. // this.getData = function () { var args = RGraph.getArgs(arguments, 'id'); var out = []; // // Return ALL of the data. Simply call the function with //no arguments: // // myDatagrid.getData(); // // if (RGraph.isNullish(args.id)) { for (var i=0; i (data.length - 1) ) { return this.append(args.data); } if (args.index < 0) { args.index = args.index + data.length; } if (args.index < 0) { args.index = 0; } // // Insert the data into the data array at the correct // place // for (var i=0,newData=[]; i 0 ? obj.datagrid_count + 1 : 1; RGraph.Registry.set('rgraph-datagrid-count', obj.datagrid_count); }); // // Make a copy of the IDs if they've been given too // so that when reset, we can revert back to them. // RGraph.runOnce('make-a-copy-of-the-rowids-property', function () { this.original_ids = RGraph.arrayClone(obj.properties.rowsIDs, true); }); if (this.properties.pagingPersistent && window.localStorage[this.pagingCurrentPageNumberLocalStorageKey] && window.localStorage[this.pagingCurrentPageNumberLocalStorageKey].length > 0) { this.properties.pagingCurrent = parseInt(window.localStorage[this.pagingCurrentPageNumberLocalStorageKey]) || 1; } // // // Go through the data and add the ID to the data // because we now have access to the properties. // if (this.properties.rowsIDs && this.properties.rowsIDs.length) { RGraph.runOnce('add-user_id-to-data-in-the-draw-function-' + this.id, function () { for (var i=0; i -1 || searchWords[sw].indexOf('?') > -1) ) { searchWords[sw] = '/' + searchWords[sw].replace('*','.*').replace(/\?/g,'.') + '/i' } } for (var j=0; j' + encodedStr, // 'text/html' //); //var formattedValue = dom.body.textContent; for (var k=0; k= obj.properties.pagingMaxpagelinks) { pagingLinksLast -= 1; } } else if (obj.properties.pagingCurrent > (pagingMaxpages - (obj.properties.pagingMaxpagelinks / 2) ) ) { var pagingLinksFirst = pagingMaxpages - obj.properties.pagingMaxpagelinks; var pagingLinksLast = pagingMaxpages; if (pagingLinksFirst < 1) { pagingLinksFirst = 1; //var pagingLinksLast = pagingLinksFirst + obj.properties.pagingMaxpagelinks - 1; // pagingLinksLast = Math.min(pagingLinksLast, pagingMaxpages); } if (pagingLinksLast - pagingLinksFirst >= obj.properties.pagingMaxpagelinks) { pagingLinksFirst += 1; } } else { var pagingLinksFirst = (obj.properties.pagingCurrent - Math.floor(obj.properties.pagingMaxpagelinks / 2)) || 1; var pagingLinksLast = obj.properties.pagingCurrent + Math.floor(obj.properties.pagingMaxpagelinks / 2); pagingLinksLast = Math.min(pagingLinksLast, pagingMaxpages); if (pagingLinksLast > pagingMaxpages) { pagingLinksLast = pagingMaxpages; var pagingLinksFirst = pagingLinksLast - obj.properties.pagingMaxpagelinks + 1; } if (pagingLinksLast - pagingLinksFirst >= obj.properties.pagingMaxpagelinks) { pagingLinksLast -= 1; } } for (var i=pagingLinksFirst; i<=pagingLinksLast; i++) { // Create a link that allows the user to go // to another page. In the onclick event of // the link do the page swapping. // var span = document.createElement('span'); span.style.padding = '3px'; if (obj.properties.pagingCurrent !== i ) { (function (index) { span.onclick = function (e) { obj.setPage(index); }; })(i); } else { span.className = 'rgraph-datagrid-paging-current'; // CSS for this link is defined as part of the defaultCss } span.insertAdjacentHTML('beforeend', i); // Add the link to the end of the table div.insertAdjacentElement('beforeend', span); } // Show the "showing..." text var startIndex = ( obj.properties.pagingCurrent - 1) * obj.properties.pagingPerpage + 1; startIndex = startIndex > 0 ? startIndex : 0; var endIndex = Math.min(startIndex + obj.properties.pagingPerpage - 1, obj.data.length); div.insertAdjacentHTML('afterbegin', `Showing ${obj.data.length > 0 ? startIndex : 0} - ${endIndex} of ${RGraph.numberFormat({object: this, number:obj.data.length})} ` + ''); if (pagingMaxpages > obj.properties.pagingMaxpagelinks) { var span = document.createElement('span'); span.onclick = function (e) { var xy = RGraph.getCanvasXY(span); // Show a little div tag with all of the // page numbers in it. var div = document.createElement('div'); div.id = 'rgraph-datagrid-paging-page-selector'; div.style.position = 'absolute'; div.style.width = '50px'; div.style.maxHeight = '200px'; div.style.backgroundColor = 'white'; div.style.overflowY = 'auto'; div.style.fontSize = '20pt'; div.style.overflowX = 'hidden'; div.style.boxShadow = '0 0 3px 5px #ddd'; obj.container.appendChild(div); // Add the page numbers to the DIV for (var pn=1; pn<=pagingMaxpages; ++pn) { var pnDiv = document.createElement('div'); pnDiv.style.width = '100%'; pnDiv.insertAdjacentHTML('afterbegin', pn); div.appendChild(pnDiv); (function (index) { pnDiv.onclick = function (e) { obj.setPage(index); }; })(pn); } // Now position the div div.style.top = ((xy[1] - div.offsetHeight) > 0 ? (xy[1] - div.offsetHeight) : 10) + 'px'; div.style.left = xy[0] + 'px'; div.onmousedown = function (e) { e.stopPropagation(); } window.addEventListener('mousedown', function (e) { if (div && div.parentNode) { div.parentNode.removeChild(div); } }, false); }; span.insertAdjacentHTML('beforeend','...'); div.insertAdjacentElement('beforeend', span); } }); } else { var start = 0; var endRow = this.data.length; } var tr = document.createElement('tr'); // Add all of the header cells if (this.properties.columnsHeaders && this.properties.columnsHeaders.length) { for (var i=0; i tag state.siblingNode = state.node.nextSibling; state.index = parseInt(state.node.getAttribute('data-column-index')); state.initialX = e.pageX; state.initialWidth = state.object.columnWidths[state.index]; state.tableWidth = state.object.table.offsetWidth; state.initialWidthSibling = state.object.columnWidths[state.index + 1]; state.node.setAttribute('data-initial-width', state.initialWidth); // Disable selections whilst dragging state.object.table.onselectstart = function(e) {e.preventDefault();}; // Set a flag on the object noting that resizing is taking // place state.resizing = true; obj.state.resizing = true; RGraph.fireCustomEvent(obj, 'resizebegin'); e.stopPropagation(); }); } // IMPORTANT // // Don't need to use the runOnce function // here because when the datagrid is // redrawn the HTML table tag is removed // from the DOM and everything is // recreated. // window.addEventListener('mouseup', function (e) { if (state && state.resizing) { state.mousedown = false; // Reenable selecting state.object.table.onselectstart = null; // Reset the resizing flag state.resizing = false; obj.state.resizing = false; RGraph.fireCustomEvent(obj, 'resizeend'); e.stopPropagation(); } }, false); window.addEventListener('mousemove', function (e) { if (state.mousedown) { var delta = e.pageX - state.initialX; // Calculate the new width var newWidth = state.initialWidth + parseFloat(delta); // Resize the column resizeColumnTo(state.object, state.node, delta, newWidth); } }, false); // // This function handles the resizing of a // column. // var resizeColumnTo = function (obj, node, delta, width) { // Set the width of the tbody to prevent it // from growing when showing scrollbars var tbody = obj.table.querySelector('tbody'); tbody.style.width = obj.container.offsetWidth + 'px'; // // Get the index of the colum that's being resized // var index = parseInt(node.getAttribute('data-column-index')); var maxWidth = obj.columnWidths[index] + obj.columnWidths[index + 1]; // Constrain the column size to between 20px and // 2 * column size - 20 if (width < 20) { return; } if (width > (maxWidth - 20)) { return; } // Apply the new width // // IS THIS NECESSARY? // //node.width = width; // Update the columnWidths variable state.object.columnWidths[index] = width; // Apply the new width to the child div that sits inside the // th node.querySelector('div').style.width = width + 'px'; // Update the initial-width attribute node.setAttribute('data-initial-width', width); // // Apply the new width to the next column // var widthSibling = parseFloat(state.initialWidthSibling + (-1 * delta)); // // IS THIS NECESSARY? // //node.nextSibling.width = widthSibling; // // Update the sibling width in the coumnWidths variable // state.object.columnWidths[index + 1] = widthSibling // // Apply the new width to the child div that sits inside the // next column // node.nextSibling.querySelector('div').style.width = widthSibling + 'px'; // Now apply the new width to the td tags that sit // inside the tbody var tds = state.object.table.querySelectorAll('tbody td'); for (var i=0; i