UNPKG

datatables.net-colreorder

Version:

ColReorder for DataTables

1,059 lines (1,048 loc) 37.1 kB
/*! ColReorder 3.0.0 for DataTables * Copyright (c) SpryMedia Ltd - datatables.net/license */ (function(factory){ if (typeof define === 'function' && define.amd) { // AMD define(['datatables.net'], function (dt) { return factory(window, document, dt); }); } else if (typeof exports === 'object') { // CommonJS var cjsRequires = function (root) { if (! root.DataTable) { require('datatables.net')(root); } }; if (typeof window === 'undefined') { module.exports = function (root) { if (! root) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } cjsRequires(root); return factory(root, root.document, root.DataTable); }; } else { cjsRequires(window); module.exports = factory(window, window.document, window.DataTable); } } else { // Browser factory(window, document, window.DataTable); } }(function(window, document, DataTable) { 'use strict'; var Dom = DataTable.Dom; var Api = DataTable.Api; var util = DataTable.util; /** * Mutate an array, moving a set of elements into a new index position * * @param arr Array to modify * @param from Start move index * @param count Number of elements to move * @param to Index where the start element will move to */ function arrayMove(arr, from, count, to) { let movers = arr.splice(from, count); // Add delete and start to the array, so we can use it for the `apply` movers.unshift(0); // splice delete param movers.unshift(to < from ? to : to - count + 1); // splice start param arr.splice.apply(arr, movers); } /** * Run finishing activities after one or more columns have been reordered. * * @param dt DataTable being operated on - must be a single table instance */ function finalise(dt) { // Cache invalidation. Always read from the data object rather // than reading back from the DOM since it could have been // changed by a renderer dt.rows().invalidate('data'); // Redraw the header / footer. Its a little bit of a hack this, as DT // doesn't expose the header draw as an API method. It calls state // saving, so we don't need to here. dt.column(0).visible(dt.column(0).visible()); dt.columns.adjust(); // Fire an event so other plug-ins can update let order = dt.colReorder.order(); dt.trigger('columns-reordered', [ { order: order, mapping: invertKeyValues(order) } ]); } /** * Get the original indexes in their current order * * @param dt DataTable being operated on - must be a single table instance * @returns Original indexes in current order */ function getOrder(dt) { return dt.settings()[0].columns.map(function (col) { return col._crOriginalIdx; }); } /** * Manipulate a header / footer array in DataTables settings to reorder * the columns. */ function headerUpdate(structure, map, from, to) { let done = []; for (let i = 0; i < structure.length; i++) { let headerRow = structure[i]; arrayMove(headerRow, from[0], from.length, to); for (let j = 0; j < headerRow.length; j++) { let cell = headerRow[j].cell; // Only work on a DOM element once, otherwise we risk remapping a // remapped value (etc). if (done.includes(cell)) { continue; } let indexes = cell.getAttribute('data-dt-column').split(','); let mapped = indexes .map(function (idx) { return map[idx]; }) .join(','); // Update data attributes for the new column position cell.setAttribute('data-dt-column', mapped); done.push(cell); } } } /** * Setup for ColReorder API operations * * @param dt DataTable(s) being operated on - might have multiple tables! */ function init(api) { // Assign the original column index to a parameter that we can lookup. // On the first pass (i.e. when the parameter hasn't yet been set), the // index order will be the original order, so this is quite a simple // assignment. api.columns().iterator('column', function (s, idx) { let columns = s.columns; if (columns[idx]._crOriginalIdx === undefined) { columns[idx]._crOriginalIdx = idx; } }); } /** * Switch the key value pairing of an index array to be value key (i.e. the old value is now the * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ]. * * @param array arr Array to switch around */ function invertKeyValues(arr) { let result = []; for (let i = 0; i < arr.length; i++) { result[arr[i]] = i; } return result; } function transposeArray(arr, inverted) { for (let i = 0; i < arr.length; i++) { arr[i] = inverted[arr[i]]; } arr.sort(); return arr; } /** * Move one or more columns from one index to another. * * This method has a lot of knowledge about how DataTables works internally. * If DataTables changes how it handles cells, columns, etc, then this * method would need to be updated accordingly. * * @param dt DataTable being operated on - must be a single table instance * @param from Column indexes to move * @param to Destination index (starting if multiple) */ function move(dt, from, to) { let i, j; let settings = dt.settings()[0]; let columns = settings.columns; let newOrder = columns.map(function (col, idx) { return idx; }); // The to column in already inside the from column(s) (might be the same) // no change required if (from.includes(to)) { return; } // A reverse index array so we can look up new indexes from old arrayMove(newOrder, from[0], from.length, to); let reverseIndexes = invertKeyValues(newOrder); // Main column arrayMove(columns, from[0], from.length, to); // Per row manipulations for (i = 0; i < settings.data.length; i++) { var data = settings.data[i]; // Allow for sparse array if (!data) { continue; } var cells = data.cells; // Not yet rendered if (!cells) { continue; } // Array of cells arrayMove(cells, from[0], from.length, to); for (j = 0; j < cells.length; j++) { // Reinsert into the document in the new order if (data.tr && cells[j] && columns[j].visible) { data.tr.appendChild(cells[j]); } // Update lookup index if (cells[j] && cells[j]._DT_CellIndex) { cells[j]._DT_CellIndex.column = j; } } } // Per column manipulation for (i = 0; i < columns.length; i++) { let column = columns[i]; // Data column sorting transposeArray(column.orderData, reverseIndexes); // Update the column indexes column.idx = reverseIndexes[column.idx]; // Reorder the colgroup > col elements for the new order if (column.visible) { settings.colgroup.append(column.colEl); } } // Header and footer headerUpdate(settings.header, reverseIndexes, from, to); headerUpdate(settings.footer, reverseIndexes, from, to); // Search - core. Here the `searches` object is keyed by the columns that // the search applies to, which needs to be updated, but also there is a // `columns` array for each property which must also be updated. util.object.each(settings.searches, (key, search) => { let columns = transposeArray(search.columns, reverseIndexes); // In case there is an overlap between those which have been reordered // and those still to be done, we need to use a name that indicates that // it has been reordered if (key !== '*') { settings.searches[columns.join(',') + '-transposed'] = search; delete settings.searches[key]; } }); // And then delete the transposed ones util.object.each(settings.searches, (key, search) => { if (key.includes('-transposed')) { settings.searches[key.replace('-transposed', '')] = search; delete settings.searches[key]; } }); // Search - fixed. Similar to `searches` but there is an inner object that // is keyed by the name of the fixed search. util.object.each(settings.searchesFixed, (key, fixed) => { util.object.each(settings.searchesFixed[key], (name, search) => { transposeArray(search.columns, reverseIndexes); }); // Top level object is named by columns if (key !== '*') { settings.searchesFixed[key + '-transposed'] = settings.searchesFixed[key]; delete settings.searchesFixed[key]; } }); // And then delete the transposed ones util.object.each(settings.searchesFixed, (key, fixed) => { if (key.includes('-transposed')) { let first = fixed[Object.keys(fixed)[0]]; settings.searchesFixed[first.columns.join(',')] = fixed; delete settings.searchesFixed[key]; } }); // Ordering indexes update - note that the sort listener on the // header works out the index to apply on each draw, so it doesn't // need to be updated here. orderingIndexes(reverseIndexes, settings.order); if (Array.isArray(settings.orderFixed)) { orderingIndexes(reverseIndexes, settings.orderFixed); } else if (settings.orderFixed.pre) { orderingIndexes(reverseIndexes, settings.orderFixed.pre); } else if (settings.orderFixed.post) { orderingIndexes(reverseIndexes, settings.orderFixed.pre); } settings.lastOrder.forEach(function (el) { el.src = reverseIndexes[el.src]; }); // Fire an event so other plug-ins can update dt.trigger('column-reorder', [ dt.settings()[0], { from: from, to: to, mapping: reverseIndexes } ]); } /** * Update the indexing for ordering arrays * * @param map Reverse index map * @param order Array to update */ function orderingIndexes(map, order) { // Can happen if the order was deleted from a saved state if (!order) { return; } for (let i = 0; i < order.length; i++) { let el = order[i]; if (typeof el === 'number') { // Just a number order[i] = map[el]; } else if (util.is.plainObject(el) && el.idx !== undefined) { // New index in an object style el.idx = map[el.idx]; } else if (Array.isArray(el) && typeof el[0] === 'number') { // The good old fixes length array el[0] = map[el[0]]; } // No need to update if in object + .name style } } /** * Take an index array for the current positioned, reordered to what you want * them to be. * * @param dt DataTable being operated on - must be a single table instance * @param order Indexes from current order, positioned as you want them to be */ function setOrder(dt, order, original) { let changed = false; let i; if (order.length !== dt.columns().count()) { dt.error('ColReorder - column count mismatch'); return; } // The order given is based on the original indexes, rather than the // existing ones, so we need to translate from the original to current // before then doing the order if (original) { order = transpose(dt, order, 'toCurrent'); } // The API is array index as the desired position, but our algorithm below is // for array index as the current position. So we need to invert for it to work. let setOrder = invertKeyValues(order); // Move columns, one by one with validation disabled! for (i = 0; i < setOrder.length; i++) { let currentIndex = setOrder.indexOf(i); if (i !== currentIndex) { // Reorder our switching error arrayMove(setOrder, currentIndex, 1, i); // Do the reorder move(dt, [currentIndex], i); changed = true; } } // Reorder complete if (changed) { finalise(dt); } } /** * Convert the DataTables header structure array into a 2D array where each * element has a reference to its TH/TD cell (regardless of spanning). * * @param structure Header / footer structure object * @returns 2D array of header cells */ function structureFill(structure) { let filledIn = []; for (let row = 0; row < structure.length; row++) { filledIn.push([]); for (let col = 0; col < structure[row].length; col++) { let cell = structure[row][col]; if (cell) { for (let rowInner = 0; rowInner < cell.rowspan; rowInner++) { if (!filledIn[row + rowInner]) { filledIn[row + rowInner] = []; } for (let colInner = 0; colInner < cell.colspan; colInner++) { filledIn[row + rowInner][col + colInner] = cell.cell; } } } } } return filledIn; } /** * Convert the index type * * @param dt DataTable to work on * @param idx Index to transform * @param dir Transform direction * @returns Converted number(s) */ function transpose(dt, idx, dir) { var order = dt.colReorder.order(); var columns = dt.settings()[0].columns; if (dir === 'toCurrent' || dir === 'fromOriginal') { // Given an original index, want the current return !Array.isArray(idx) ? order.indexOf(idx) : idx.map(function (index) { return order.indexOf(index); }); } // Given a current index, want the original return !Array.isArray(idx) ? columns[idx]._crOriginalIdx : idx.map(function (index) { return columns[index]._crOriginalIdx; }); } /** * Validate that a requested move is okay. This includes bound checking * and that it won't split colspan'ed cells. * * @param table API instance * @param from Column indexes to move * @param to Destination index (starting if multiple) * @returns Validation result */ function validateMove(table, from, to) { let columns = table.columns().count(); // Sanity and bound checking if (from[0] < to && to < from[from.length]) { return false; } if (from[0] < 0 && from[from.length - 1] > columns) { return false; } if (to < 0 && to > columns) { return false; } // No change - it's valid if (from.includes(to)) { return true; } if (!validateStructureMove(table.table().header.structure(), from, to)) { return false; } if (!validateStructureMove(table.table().footer.structure(), from, to)) { return false; } return true; } /** * For a given structure check that the move is valid. * @param structure * @param from * @param to * @returns */ function validateStructureMove(structure, from, to) { let header = structureFill(structure); let i; // Shuffle the header cells around for (i = 0; i < header.length; i++) { arrayMove(header[i], from[0], from.length, to); } // Sanity check that the headers are next to each other for (i = 0; i < header.length; i++) { let seen = []; for (let j = 0; j < header[i].length; j++) { let cell = header[i][j]; if (!seen.includes(cell)) { // Hasn't been seen before seen.push(cell); } else if (seen[seen.length - 1] !== cell) { // Has been seen before and is not the previous cell - validation failed return false; } } } return true; } // Sanity check if (!DataTable.versionCheck('3')) { throw 'Warning: ColReorder requires DataTables 3 or newer'; } /** * This is one possible UI for column reordering in DataTables. In this case * columns are reordered by clicking and dragging a column header. It calculates * where columns can be dropped based on the column header used to start the drag * and then `colReorder.move()` method to alter the DataTable. */ class ColReorder { disable() { this.c.enable = false; return this; } enable(flag = true) { if (flag === false) { return this.disable(); } this.c.enable = true; return this; } constructor(dt, opts) { this.dom = { drag: null }; this.c = { columns: null, enable: null, headerRows: null, order: null }; this.s = { dropZones: [], mouse: { absLeft: -1, offset: { x: -1, y: -1 }, start: { x: -1, y: -1 }, target: null, targets: [] }, scrollInterval: null }; let ctx = dt.settings()[0]; // Check if ColReorder already has been initialised on this DataTable - only // one can exist. if (ctx._colReorder) { return; } dt.settings()[0]._colReorder = this; this.dt = dt; util.object.assign(this.c, ColReorder.defaults, opts); init(dt); dt.on('stateSaveParams', function (e, s, d) { d.colReorder = getOrder(dt); }); dt.on('destroy', function () { dt.off('.colReorder'); dt.colReorder.reset(); }); // Initial ordering / state restoring let loaded = dt.state.loaded(); let order = this.c.order; if (loaded && loaded.colReorder && dt.columns().count() === loaded.colReorder.length) { order = loaded.colReorder; } if (order) { dt.ready(function () { setOrder(dt, order, true); }); } dt.table() .header.structure() .forEach((row, rowIdx) => { let headerRows = this.c.headerRows; for (let i = 0; i < row.length; i++) { if (!headerRows || headerRows.includes(rowIdx)) { if (row[i] && row[i].cell) { this._addListener(row[i].cell); } } } }); } /** * Attach the mouse down listener to an element to start a column reorder action * * @param el */ _addListener(el) { let that = this; Dom.s(el) .on('selectstart.colReorder', function () { return false; }) .on('mousedown.colReorder touchstart.colReorder', function (e) { // Ignore middle and right click if (e.type === 'mousedown' && e.which !== 1) { return; } // Ignore if disabled if (!that.c.enable) { return; } // ColumnControl integration - if there is a CC reorder button in the header // then the mousedown is limited to that let btn = Dom.s(this).find('button.dtcc-button_reorder'); if (btn.count() && e.target !== btn.get(0) && btn.find(e.target).count() === 0) { return; } that._mouseDown(e, this); }); } /** * Create the element that is dragged around the page */ _createDragNode() { let origCell = this.s.mouse.target; let origTr = origCell.parent(); let origThead = origTr.parent(); let origTable = origThead.parent(); let cloneCell = origCell.clone(); // This is a slightly odd combination of jQuery and DOM, but it is the // fastest and least resource intensive way I could think of cloning // the table with just a single header cell in it. this.dom.drag = Dom.s(origTable.get(0).cloneNode(false)) .classAdd('dtcr-cloned') .append(Dom.s(origThead.get(0).cloneNode(false)).append(Dom.s(origTr.get(0).cloneNode(false)).append(cloneCell.get(0)))) .css({ position: 'absolute', top: '0', left: '0', width: origCell.width('outer') + 'px', height: origCell.height('outer') + 'px' }) .appendTo('body'); } /** * Get cursor position regardless of mouse or touch input * * @param e Event * @param prop Property name to get * @returns Value - assuming a number here */ _cursorPosition(e, prop) { return e.type.indexOf('touch') !== -1 ? e.originalEvent.touches[0][prop] : e[prop]; } /** * Cache values at start * * @param e Triggering event * @param cell Cell that the action started on * @returns */ _mouseDown(e, cell) { let target = Dom.s(e.target).closest('th, td'); let offset = target.offset(); let moveableColumns = this.dt.columns(this.c.columns).indexes().toArray(); let moveColumnIndexes = Dom.s(cell) .attr('data-dt-column') .split(',') .map(function (val) { return parseInt(val, 10); }); // Don't do anything for columns which are not selected as moveable for (let j = 0; j < moveColumnIndexes.length; j++) { if (!moveableColumns.includes(moveColumnIndexes[j])) { return false; } } this.s.mouse.start.x = this._cursorPosition(e, 'pageX'); this.s.mouse.start.y = this._cursorPosition(e, 'pageY'); this.s.mouse.offset.x = this._cursorPosition(e, 'pageX') - offset.left; this.s.mouse.offset.y = this._cursorPosition(e, 'pageY') - offset.top; this.s.mouse.target = target; this.s.mouse.targets = moveColumnIndexes; // Classes to highlight the columns being moved for (let i = 0; i < moveColumnIndexes.length; i++) { let cells = Dom.s(this.dt .cells(null, moveColumnIndexes[i], { page: 'current' }) .nodes() .toArray()); let klass = 'dtcr-moving'; if (i === 0) { klass += ' dtcr-moving-first'; } if (i === moveColumnIndexes.length - 1) { klass += ' dtcr-moving-last'; } cells.classAdd(klass); } this._regions(moveColumnIndexes); this._scrollRegions(); /* Add event handlers to the document */ Dom.s(document) .on('mousemove.colReorder touchmove.colReorder', (e) => { this._mouseMove(e); }) .on('mouseup.colReorder touchend.colReorder', (e) => { this._mouseUp(e); }); } _mouseMove(e) { if (this.dom.drag === null) { // Only create the drag element if the mouse has moved a specific distance from the start // point - this allows the user to make small mouse movements when sorting and not have a // possibly confusing drag element showing up if (Math.pow(Math.pow(this._cursorPosition(e, 'pageX') - this.s.mouse.start.x, 2) + Math.pow(this._cursorPosition(e, 'pageY') - this.s.mouse.start.y, 2), 0.5) < 5) { return; } Dom.s(document.body).classAdd('dtcr-dragging'); this._createDragNode(); } // Position the element - we respect where in the element the click occurred this.dom.drag.css({ left: this._cursorPosition(e, 'pageX') - this.s.mouse.offset.x, top: this._cursorPosition(e, 'pageY') - this.s.mouse.offset.y }); // Find cursor's left position relative to the table const tableNode = this.dt.table().node(); let tableOffset = Dom.s(tableNode).offset().left; let cursorMouseLeft = this._cursorPosition(e, 'pageX') - tableOffset; let cursorInlineStart; if (this._isRtl()) { const tableWidth = tableNode.clientWidth; cursorInlineStart = tableWidth - cursorMouseLeft; } else { cursorInlineStart = cursorMouseLeft; } let dropZone = this.s.dropZones.find(function (zone) { if (zone.inlineStart <= cursorInlineStart && cursorInlineStart <= zone.inlineStart + zone.width) { return true; } return false; }); this.s.mouse.absLeft = this._cursorPosition(e, 'pageX'); if (!dropZone) { return; } if (!dropZone.self) { this._move(dropZone, cursorInlineStart); } } _mouseUp(e) { Dom.s(document).off('.colReorder'); Dom.s(document.body).classRemove('dtcr-dragging'); if (this.dom.drag) { this.dom.drag.remove(); this.dom.drag = null; // Once the element is removed, Firefox will then complete the mouse event // sequence by firing a click event on the element that the mouse is now // over. This means that a click event happens on the header cell triggering // a sort. A setTimeout on the remove doesn't appear to work around this. // // Therefore, we need to add a click event listener that will kill the // bubbling of the event, and then _almost_ immediately remove it. this.s.mouse.target.on('click.dtcr', function (e) { return false; }); setTimeout(() => { this.s.mouse.target.off('.dtcr'); }, 10); } if (this.s.scrollInterval) { clearInterval(this.s.scrollInterval); } Dom.s(this.dt.cells('.dtcr-moving').nodes().toArray()).classRemove('dtcr-moving dtcr-moving-first dtcr-moving-last'); } /** * Shift columns around * * @param dropZone Where to move to * @param cursorInlineStart Cursor position, relative to the inline start (left or right) of the table */ _move(dropZone, cursorInlineStart) { let that = this; this.dt.colReorder.move(this.s.mouse.targets, dropZone.colIdx); // Update the targets this.s.mouse.targets = this.s.mouse.target .attr('data-dt-column') .split(',') .map(function (val) { return parseInt(val, 10); }); this._regions(this.s.mouse.targets); let visibleTargets = this.s.mouse.targets.filter(function (val) { return that.dt.column(val).visible(); }); // If the column being moved is smaller than the column it is replacing, // the drop zones might need a correction to allow for this since, otherwise // we might immediately be changing the column order as soon as it was placed. // Find the drop zone for the first in the list of targets - is its // left greater than the mouse position. If so, it needs correcting let dz = this.s.dropZones.find(function (zone) { return zone.colIdx === visibleTargets[0]; }); let dzIdx = this.s.dropZones.indexOf(dz); if (dz.inlineStart > cursorInlineStart) { let previousDiff = dz.inlineStart - cursorInlineStart; let previousDz = this.s.dropZones[dzIdx - 1]; dz.inlineStart -= previousDiff; dz.width += previousDiff; if (previousDz) { previousDz.width -= previousDiff; } } // And for the last in the list dz = this.s.dropZones.find(function (zone) { return zone.colIdx === visibleTargets[visibleTargets.length - 1]; }); if (dz.inlineStart + dz.width < cursorInlineStart) { let nextDiff = cursorInlineStart - (dz.inlineStart + dz.width); let nextDz = this.s.dropZones[dzIdx + 1]; dz.width += nextDiff; if (nextDz) { nextDz.inlineStart += nextDiff; nextDz.width -= nextDiff; } } } /** * Determine the boundaries for where drops can happen and where they would * insert into. */ _regions(moveColumns) { let that = this; let dropZones = []; let totalWidth = 0; let negativeCorrect = 0; let allowedColumns = this.dt.columns(this.c.columns).indexes().toArray(); let widths = this.dt.columns().widths(); // Each column is a drop zone this.dt.columns().every(function (colIdx, tabIdx, i) { if (!this.visible()) { return; } let columnWidth = widths[colIdx]; // Check that we are allowed to move into this column - if not, need // to offset the widths if (!allowedColumns.includes(colIdx)) { totalWidth += columnWidth; return; } let valid = validateMove(that.dt, moveColumns, colIdx); if (valid) { // New drop zone. Note that it might have it's offset moved // by the final condition in this logic set dropZones.push({ colIdx: colIdx, inlineStart: totalWidth - negativeCorrect, self: moveColumns[0] <= colIdx && colIdx <= moveColumns[moveColumns.length - 1], width: columnWidth + negativeCorrect }); } else if (colIdx < moveColumns[0]) { // Not valid and before the column(s) to be moved - the drop // zone for the previous valid drop point is extended if (dropZones.length) { dropZones[dropZones.length - 1].width += columnWidth; } } else if (colIdx > moveColumns[moveColumns.length - 1]) { // Not valid and after the column(s) to be moved - the next // drop zone to be created will be extended negativeCorrect += columnWidth; } totalWidth += columnWidth; }); this.s.dropZones = dropZones; // this._drawDropZones(); } /** * Check if the table is scrolling or not. It is it the `table` isn't the same for * the header and body parents. * * @returns */ _isScrolling() { return this.dt.table().body().parentNode !== this.dt.table().header().parentNode; } /** * Set an interval clock that will check to see if the scrolling of the table body should be moved * as the mouse moves on the scroll (allowing a drag and drop to columns which aren't yet visible) */ _scrollRegions() { if (!this._isScrolling()) { // Not scrolling - nothing to do return; } let that = this; let tableLeft = Dom.s(this.dt.table().container()).offset().left; let tableWidth = Dom.s(this.dt.table().container()).width('outer'); let mouseBuffer = 75; let scrollContainer = this.dt.table().body().parentElement.parentElement; this.s.scrollInterval = setInterval(function () { let mouseLeft = that.s.mouse.absLeft; // On initial mouse down the mouse position can be -1, which we want to ignore if (mouseLeft === -1) { return; } if (mouseLeft < tableLeft + mouseBuffer && scrollContainer.scrollLeft) { scrollContainer.scrollLeft -= 5; } else if (mouseLeft > tableLeft + tableWidth - mouseBuffer && scrollContainer.scrollLeft < scrollContainer.scrollWidth) { scrollContainer.scrollLeft += 5; } }, 25); } // This is handy for debugging where the drop zones actually are! // private _drawDropZones () { // let dropZones = this.s.dropZones; // $('div.allan').remove(); // for (let i=0 ; i<dropZones.length ; i++) { // let zone = dropZones[i]; // $(this.dt.table().container()).append( // $('<div>') // .addClass('allan') // .css({ // position: 'absolute', // top: 0, // width: zone.width - 4, // height: 20, // left: zone.left + 2, // border: '1px solid red', // }) // ); // } // } _isRtl() { return Dom.s(this.dt.table().node()).css('direction') === 'rtl'; } } ColReorder.defaults = { columns: '', enable: true, headerRows: null, order: null }; ColReorder.version = '3.0.0'; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * UI interaction class */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * DataTables API integration */ /** Enable mouse column reordering */ Api.register('colReorder.enable()', function (flag) { return this.iterator('table', function (ctx) { if (ctx._colReorder) { ctx._colReorder.enable(flag); } }); }); /** Disable mouse column reordering */ Api.register('colReorder.disable()', function () { return this.iterator('table', function (ctx) { if (ctx._colReorder) { ctx._colReorder.disable(); } }); }); /** * Change the ordering of the columns in the DataTable. */ Api.register('colReorder.move()', function (from, to) { init(this); if (!Array.isArray(from)) { from = [from]; } if (!validateMove(this, from, to)) { this.error('ColReorder - invalid move'); return this; } return this.tables().every(function () { move(this, from, to); finalise(this); }); }); Api.register('colReorder.order()', function (set, original) { init(this); if (!set) { return this.context.length ? getOrder(this) : null; } return this.tables().every(function () { setOrder(this, set, original); }); }); Api.register('colReorder.reset()', function () { init(this); return this.tables().every(function () { var order = this.columns() .every(function (i) { return i; }) .flatten() .toArray(); setOrder(this, order, true); }); }); Api.register('colReorder.transpose()', function (idx, dir) { init(this); if (!dir) { dir = 'toCurrent'; } return transpose(this, idx, dir); }); DataTable.ColReorder = ColReorder; // Called when DataTables is going to load a state. That might be // before the table is ready (state saving) or after (state restoring). // Also note that it happens _before_ preInit (below). Dom.s(document).on('stateLoadInit.dt', function (e, settings, state) { if (e.namespace !== 'dt') { return; } let dt = new Api(settings); if (state.colReorder && dt.columns().count() === state.colReorder.length) { if (dt.ready()) { // Table is fully loaded - do the column reordering here // so that the stored indexes are in the correct place // e.g. column visibility setOrder(dt, state.colReorder, true); } else { // If the table is not ready, column reordering is done // after it becomes fully ready. That means that saved // column indexes need to be updated for where those columns // currently are. Any properties which refer to column indexes // would need to be updated here. // State's ordering indexes orderingIndexes(state.colReorder, state.order); // State's columns array - sort by restore index if (state.columns) { for (let i = 0; i < state.columns.length; i++) { state.columns[i]._cr_sort = state.colReorder[i]; } state.columns.sort(function (a, b) { return a._cr_sort - b._cr_sort; }); } } } }); Dom.s(document).on('preInit.dt', function (e, settings) { if (e.namespace !== 'dt') { return; } var init = settings.init.colReorder; var defaults = DataTable.defaults.colReorder; if (init || defaults) { let opts = {}; if (util.is.plainObject(defaults)) { util.object.assign(opts, defaults); } if (util.is.plainObject(init)) { util.object.assign(opts, init); } if (init !== false) { let dt = new DataTable.Api(settings); new ColReorder(dt, opts); } } }); return DataTable; }));