diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..e60a2f2 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,14 @@ +{ + "extends": "airbnb", + "env": { + "es6": true, + "browser": true, + "commonjs": true + }, + "rules": { + "indent": ["error", 4, { "outerIIFEBody": 0 }], + "no-trailing-spaces": ["error", { "skipBlankLines": true }], + "no-underscore-dangle": ["error", { "allowAfterThis": true }], + "no-mixed-operators": ["error", { "allowSamePrecedence": true }] + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..936e5c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules/ +/package-lock.json diff --git a/anotherExample.html b/anotherExample.html index 51e3952..3bd6ee8 100644 --- a/anotherExample.html +++ b/anotherExample.html @@ -1,5 +1,6 @@ + diff --git a/dataTables.rowsGroup.js b/dataTables.rowsGroup.js index d82a2ed..ea8925e 100644 --- a/dataTables.rowsGroup.js +++ b/dataTables.rowsGroup.js @@ -38,248 +38,247 @@ * */ -(function($){ +/* eslint-disable padded-blocks */ +(function init($) { +/* eslint-enable padded-blocks */ -ShowedDataSelectorModifier = { - order: 'current', - page: 'current', - search: 'applied', + +function toggleDirection(dir) { + return dir === 'asc' ? 'desc' : 'asc'; } -GroupedColumnsOrderDir = 'asc'; +const ShowedDataSelectorModifier = { + order: 'current', + page: 'current', + search: 'applied', +}; + +const GroupedColumnsOrderDir = 'asc'; /* * columnsForGrouping: array of DTAPI:cell-selector for columns for which rows grouping is applied */ -var RowsGroup = function ( dt, columnsForGrouping ) -{ - this.table = dt.table(); - this.columnsForGrouping = columnsForGrouping; - // set to True when new reorder is applied by RowsGroup to prevent order() looping - this.orderOverrideNow = false; - this.mergeCellsNeeded = false; // merge after init - this.order = [] - - var self = this; - dt.on('order.dt', function ( e, settings) { - if (!self.orderOverrideNow) { - self.orderOverrideNow = true; - self._updateOrderAndDraw() - } else { - self.orderOverrideNow = false; - } - }) - - dt.on('preDraw.dt', function ( e, settings) { - if (self.mergeCellsNeeded) { - self.mergeCellsNeeded = false; - self._mergeCells() - } - }) - - dt.on('column-visibility.dt', function ( e, settings) { - self.mergeCellsNeeded = true; - }) +class RowsGroup { + constructor(dt, columnsForGrouping) { + this.table = dt.table(); + this.columnsForGrouping = columnsForGrouping; + // set to True when new reorder is applied by RowsGroup to prevent order() looping + this.orderOverrideNow = false; + this.mergeCellsNeeded = false; // merge after init + this.order = []; + + dt.on('order.dt.rowsGroup', () => { + if (!this.orderOverrideNow) { + this.orderOverrideNow = true; + this._updateOrderAndDraw(); + } else { + this.orderOverrideNow = false; + } + }); + + dt.on('preDraw.dt.rowsGroup', () => { + if (this.mergeCellsNeeded) { + this.mergeCellsNeeded = false; + this._mergeCells(); + } + }); + + dt.on('column-visibility.dt.rowsGroup', () => { + this.mergeCellsNeeded = true; + }); - dt.on('search.dt', function ( e, settings) { - // This might to increase the time to redraw while searching on tables - // with huge shown columns - self.mergeCellsNeeded = true; - }) + dt.on('search.dt.rowsGroup', () => { + // This might to increase the time to redraw while searching on tables + // with huge shown columns + this.mergeCellsNeeded = true; + }); - dt.on('page.dt', function ( e, settings) { - self.mergeCellsNeeded = true; - }) + dt.on('page.dt.rowsGroup', () => { + this.mergeCellsNeeded = true; + }); - dt.on('length.dt', function ( e, settings) { - self.mergeCellsNeeded = true; - }) + dt.on('length.dt.rowsGroup', () => { + this.mergeCellsNeeded = true; + }); - dt.on('xhr.dt', function ( e, settings) { - self.mergeCellsNeeded = true; - }) + dt.on('xhr.dt.rowsGroup', () => { + this.mergeCellsNeeded = true; + }); - this._updateOrderAndDraw(); - -/* Events sequence while Add row (also through Editor) - * addRow() function - * draw() function - * preDraw() event - * mergeCells() - point 1 - * Appended new row breaks visible elements because the mergeCells() on previous step doesn't apllied to already processing data - * order() event - * _updateOrderAndDraw() - * preDraw() event - * mergeCells() - * Appended new row now has properly visibility as on current level it has already applied changes from first mergeCells() call (point 1) - * draw() event - */ -}; + this._updateOrderAndDraw(); + /* Events sequence while Add row (also through Editor) + * addRow() function + * draw() function + * preDraw() event + * mergeCells() - point 1 + * Appended new row breaks visible elements because the mergeCells() + * on previous step doesn't apply to already processing data + * order() event + * _updateOrderAndDraw() + * preDraw() event + * mergeCells() + * Appended new row now has properly visibility as on current level + * it has already applied changes from first mergeCells() call (point 1) + * draw() event + */ + } -RowsGroup.prototype = { - setMergeCells: function(){ - this.mergeCellsNeeded = true; - }, + setMergeCells() { + this.mergeCellsNeeded = true; + } - mergeCells: function() - { - this.setMergeCells(); - this.table.draw(); - }, + mergeCells() { + this.setMergeCells(); + this.table.draw(); + } - _getOrderWithGroupColumns: function (order, groupedColumnsOrderDir) - { - if (groupedColumnsOrderDir === undefined) - groupedColumnsOrderDir = GroupedColumnsOrderDir - - var self = this; - var groupedColumnsIndexes = this.columnsForGrouping.map(function(columnSelector){ - return self.table.column(columnSelector).index() - }) - var groupedColumnsKnownOrder = order.filter(function(columnOrder){ - return groupedColumnsIndexes.indexOf(columnOrder[0]) >= 0 - }) - var nongroupedColumnsOrder = order.filter(function(columnOrder){ - return groupedColumnsIndexes.indexOf(columnOrder[0]) < 0 - }) - var groupedColumnsKnownOrderIndexes = groupedColumnsKnownOrder.map(function(columnOrder){ - return columnOrder[0] - }) - var groupedColumnsOrder = groupedColumnsIndexes.map(function(iColumn){ - var iInOrderIndexes = groupedColumnsKnownOrderIndexes.indexOf(iColumn) - if (iInOrderIndexes >= 0) - return [iColumn, groupedColumnsKnownOrder[iInOrderIndexes][1]] - else - return [iColumn, groupedColumnsOrderDir] - }) - - groupedColumnsOrder.push.apply(groupedColumnsOrder, nongroupedColumnsOrder) - return groupedColumnsOrder; - }, + _getOrderWithGroupColumns(order, groupedColumnsOrderDir = GroupedColumnsOrderDir) { + const groupedColumnsIndexes = this.columnsForGrouping.map( + columnSelector => this.table.column(columnSelector).index()); + const groupedColumnsKnownOrder = order.filter( + columnOrder => groupedColumnsIndexes.indexOf(columnOrder[0]) >= 0); + const nongroupedColumnsOrder = order.filter( + columnOrder => groupedColumnsIndexes.indexOf(columnOrder[0]) < 0); + const groupedColumnsKnownOrderIndexes = groupedColumnsKnownOrder.map( + columnOrder => columnOrder[0]); + const groupedColumnsOrder = groupedColumnsIndexes.map((iColumn) => { + const iInOrderIndexes = groupedColumnsKnownOrderIndexes.indexOf(iColumn); + if (iInOrderIndexes >= 0) { + return [iColumn, groupedColumnsKnownOrder[iInOrderIndexes][1]]; + } + return [iColumn, groupedColumnsOrderDir]; + }); + + groupedColumnsOrder.push(...nongroupedColumnsOrder); + return groupedColumnsOrder; + } - // Workaround: the DT reset ordering to 'asc' from multi-ordering if user order on one column (without shift) - // but because we always has multi-ordering due to grouped rows this happens every time - _getInjectedMonoSelectWorkaround: function(order) - { - if (order.length === 1) { - // got mono order - workaround here - var orderingColumn = order[0][0] - var previousOrder = this.order.map(function(val){ - return val[0] - }) - var iColumn = previousOrder.indexOf(orderingColumn); - if (iColumn >= 0) { - // assume change the direction, because we already has that in previos order - return [[orderingColumn, this._toogleDirection(this.order[iColumn][1])]] - } // else This is the new ordering column. Proceed as is. - } // else got milti order - work normal - return order; - }, - - _mergeCells: function() - { - var columnsIndexes = this.table.columns(this.columnsForGrouping, ShowedDataSelectorModifier).indexes().toArray() - var showedRowsCount = this.table.rows(ShowedDataSelectorModifier)[0].length - this._mergeColumn(0, showedRowsCount - 1, columnsIndexes) - }, - - // the index is relative to the showed data - // (selector-modifier = {order: 'current', page: 'current', search: 'applied'}) index - _mergeColumn: function(iStartRow, iFinishRow, columnsIndexes) - { - var columnsIndexesCopy = columnsIndexes.slice() - currentColumn = columnsIndexesCopy.shift() - currentColumn = this.table.column(currentColumn, ShowedDataSelectorModifier) - - var columnNodes = currentColumn.nodes() - var columnValues = currentColumn.data() - - var newSequenceRow = iStartRow, - iRow; - for (iRow = iStartRow + 1; iRow <= iFinishRow; ++iRow) { - - if (columnValues[iRow] === columnValues[newSequenceRow]) { - $(columnNodes[iRow]).hide() - } else { - $(columnNodes[newSequenceRow]).show() - $(columnNodes[newSequenceRow]).attr('rowspan', (iRow-1) - newSequenceRow + 1) - - if (columnsIndexesCopy.length > 0) - this._mergeColumn(newSequenceRow, (iRow-1), columnsIndexesCopy) - - newSequenceRow = iRow; - } - - } - $(columnNodes[newSequenceRow]).show() - $(columnNodes[newSequenceRow]).attr('rowspan', (iRow-1)- newSequenceRow + 1) - if (columnsIndexesCopy.length > 0) - this._mergeColumn(newSequenceRow, (iRow-1), columnsIndexesCopy) - }, - - _toogleDirection: function(dir) - { - return dir == 'asc'? 'desc': 'asc'; - }, - - _updateOrderAndDraw: function() - { - this.mergeCellsNeeded = true; - - var currentOrder = this.table.order(); - currentOrder = this._getInjectedMonoSelectWorkaround(currentOrder); - this.order = this._getOrderWithGroupColumns(currentOrder) - this.table.order($.extend(true, Array(), this.order)) - this.table.draw() - }, -}; - + /* Workaround: the DT reset ordering to 'asc' from multi-ordering + * if user order on one column (without shift) + * but because we always have multi-ordering due to grouped rows + * this happens every time + */ + _getInjectedMonoSelectWorkaround(order) { + if (order.length === 1) { + // got mono order - workaround here + const orderingColumn = order[0][0]; + const previousOrder = this.order.map(val => val[0]); + const iColumn = previousOrder.indexOf(orderingColumn); + if (iColumn >= 0) { + // assume change the direction, because we already has that in previos order + return [[orderingColumn, toggleDirection(this.order[iColumn][1])]]; + } // else This is the new ordering column. Proceed as is. + } // else got milti order - work normal + return order; + } + + _mergeCells() { + const columnsIndexes = this.table + .columns(this.columnsForGrouping, ShowedDataSelectorModifier) + .indexes().toArray(); + const showedRowsCount = this.table.rows(ShowedDataSelectorModifier)[0].length; + this._mergeColumn(0, showedRowsCount - 1, columnsIndexes); + } + + // the index is relative to the showed data + // (selector-modifier = {order: 'current', page: 'current', search: 'applied'}) index + _mergeColumn(iStartRow, iFinishRow, columnsIndexes) { + const columnsIndexesCopy = columnsIndexes.slice(); + let currentColumn = columnsIndexesCopy.shift(); + currentColumn = this.table.column(currentColumn, ShowedDataSelectorModifier); + + const columnNodes = currentColumn.nodes(); + const columnValues = currentColumn.data(); + + let newSequenceRow = iStartRow; + for (let iRow = iStartRow + 1; iRow <= iFinishRow; iRow += 1) { + if (columnValues[iRow] === columnValues[newSequenceRow]) { + $(columnNodes[iRow]).hide(); + } else { + $(columnNodes[newSequenceRow]).show(); + $(columnNodes[newSequenceRow]).attr('rowspan', (iRow - 1) - newSequenceRow + 1); + + if (columnsIndexesCopy.length > 0) { + this._mergeColumn(newSequenceRow, (iRow - 1), columnsIndexesCopy); + } + + newSequenceRow = iRow; + } + } + $(columnNodes[newSequenceRow]).show(); + $(columnNodes[newSequenceRow]).attr('rowspan', iFinishRow - newSequenceRow + 1); + if (columnsIndexesCopy.length > 0) { + this._mergeColumn(newSequenceRow, iFinishRow, columnsIndexesCopy); + } + } + + _updateOrderAndDraw() { + this.mergeCellsNeeded = true; + + let currentOrder = this.table.order(); + currentOrder = this._getInjectedMonoSelectWorkaround(currentOrder); + this.order = this._getOrderWithGroupColumns(currentOrder); + this.table.order($.extend(true, [], this.order)); + this.table.draw(); + } +} -$.fn.dataTable.RowsGroup = RowsGroup; -$.fn.DataTable.RowsGroup = RowsGroup; +/* eslint-disable no-param-reassign */ +if ($.fn.dataTable) $.fn.dataTable.RowsGroup = RowsGroup; +if ($.fn.DataTable) $.fn.DataTable.RowsGroup = RowsGroup; +/* eslint-enable no-param-reassign */ // Automatic initialisation listener -$(document).on( 'init.dt', function ( e, settings ) { - if ( e.namespace !== 'dt' ) { - return; - } +$(document).on('init.dt.rowsGroup', (e, settings) => { + if (e.namespace !== 'dt') { + return; + } + + const api = new $.fn.dataTable.Api(settings); + + if (settings.oInit.rowsGroup || + $.fn.dataTable.defaults.rowsGroup) { + const options = settings.oInit.rowsGroup ? + settings.oInit.rowsGroup : + $.fn.dataTable.defaults.rowsGroup; + const rowsGroup = new RowsGroup(api, options); + $.fn.dataTable.Api.register('rowsgroup.update()', function update() { + rowsGroup.mergeCells(); + return this; + }); + $.fn.dataTable.Api.register('rowsgroup.updateNextDraw()', function updateNextDraw() { + rowsGroup.setMergeCells(); + return this; + }); + } +}); - var api = new $.fn.dataTable.Api( settings ); - - if ( settings.oInit.rowsGroup || - $.fn.dataTable.defaults.rowsGroup ) - { - options = settings.oInit.rowsGroup? - settings.oInit.rowsGroup: - $.fn.dataTable.defaults.rowsGroup; - var rowsGroup = new RowsGroup( api, options ); - $.fn.dataTable.Api.register( 'rowsgroup.update()', function () { - rowsGroup.mergeCells(); - return this; - } ); - $.fn.dataTable.Api.register( 'rowsgroup.updateNextDraw()', function () { - rowsGroup.setMergeCells(); - return this; - } ); - } -} ); -}(jQuery)); +/* eslint-disable-line padded-blocks */ +}( + (typeof require === 'function') + ? require('jQuery') + : window.jQuery +)); /* -TODO: Provide function which determines the all s and s with "rowspan" html-attribute is parent (groupped) for the specified or . To use in selections, editing or hover styles. +TODO: Provide function which determines the all s and s with "rowspan" + html-attribute is parent (groupped) for the specified or . + To use in selections, editing or hover styles. TODO: Feature Use saved order direction for grouped columns - Split the columns into grouped and ungrouped. - - user = grouped+ungrouped - grouped = grouped - saved = grouped+ungrouped - - For grouped uses following order: user -> saved (because 'saved' include 'grouped' after first initialisation). This should be done with saving order like for 'groupedColumns' - For ungrouped: uses only 'user' input ordering + Split the columns into grouped and ungrouped. + + user = grouped+ungrouped + grouped = grouped + saved = grouped+ungrouped + + For grouped uses following order: user -> saved + (because 'saved' include 'grouped' after first initialisation). + This should be done with saving order like for 'groupedColumns' + For ungrouped: uses only 'user' input ordering */ diff --git a/example.html b/example.html index 7b7cced..a2a9903 100644 --- a/example.html +++ b/example.html @@ -1,5 +1,6 @@ + diff --git a/package.json b/package.json new file mode 100644 index 0000000..84f3e03 --- /dev/null +++ b/package.json @@ -0,0 +1,29 @@ +{ + "name": "datatables-rowsgroup", + "version": "2.0.0", + "description": "Datatables plugin that groups rows (rowspan)", + "main": "dataTables.rowsGroup.js", + "scripts": { + "test": "eslint *.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ashl1/datatables-rowsgroup.git" + }, + "author": "Alexey Shildyakov", + "license": "MIT", + "bugs": { + "url": "https://github.com/ashl1/datatables-rowsgroup/issues" + }, + "homepage": "https://github.com/ashl1/datatables-rowsgroup#readme", + "dependencies": { + "jQuery": "^1.7.4" + }, + "devDependencies": { + "eslint": "^4.6.1", + "eslint-config-airbnb": "^15.1.0", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-jsx-a11y": "^5.1.1", + "eslint-plugin-react": "^7.3.0" + } +}