From 865573cd61df80ed5de22b8fd5da424d4c9dcd8b Mon Sep 17 00:00:00 2001 From: Brian Hann Date: Wed, 28 Jan 2015 14:59:27 -0600 Subject: [PATCH] fix(Grid): Allow col reordering with column defs Once initialized, the grid was displaying buggy behavior when swapping column definitions in or out. This change fixes that behavior, and allows for Grid.buildColumns() to reorder columns according to the order of columnDefs by supplying an option parameter with the property `orderByColumnDefs` set to true. This also required changing the call to buildColumns() within the grid's dataWatchFunction so that it uses this option. Fixes #1948 --- Gruntfile.js | 5 +- misc/demo/col-swap.html | 92 +++++++++++++++++++ src/js/core/directives/ui-grid-cell.js | 11 ++- src/js/core/directives/ui-grid-header-cell.js | 15 ++- src/js/core/directives/ui-grid.js | 2 +- src/js/core/factories/Grid.js | 43 ++++++++- src/js/core/factories/GridColumn.js | 2 +- test/unit/core/directives/uiGridCell.spec.js | 52 +++++++++++ 8 files changed, 215 insertions(+), 7 deletions(-) create mode 100644 misc/demo/col-swap.html diff --git a/Gruntfile.js b/Gruntfile.js index 10a3327d1e..fb8bcba6b4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -272,8 +272,11 @@ module.exports = function(grunt) { /* Protractor */ browser: false, + /* Lodash */ + _: false, + /* jquery (testing only) */ - $:false, + $: false, jQuery: false, diff --git a/misc/demo/col-swap.html b/misc/demo/col-swap.html new file mode 100644 index 0000000000..0345ad8474 --- /dev/null +++ b/misc/demo/col-swap.html @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + +

Grid

+ +
+
+
+ + + +
+
+ + + + + \ No newline at end of file diff --git a/src/js/core/directives/ui-grid-cell.js b/src/js/core/directives/ui-grid-cell.js index 1c4103e547..5e3de7bc57 100644 --- a/src/js/core/directives/ui-grid-cell.js +++ b/src/js/core/directives/ui-grid-cell.js @@ -41,7 +41,8 @@ angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUt } }, post: function($scope, $elm, $attrs, uiGridCtrl) { - $elm.addClass($scope.col.getColClass(false)); + var initColClass = $scope.col.getColClass(false); + $elm.addClass(initColClass); var classAdded; var updateClass = function( grid ){ @@ -74,6 +75,14 @@ angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUt if ( classAdded || $scope.col.cellClass ){ updateClass(); } + + // See if the column's internal class has changed + var newColClass = $scope.col.getColClass(false); + if (newColClass !== initColClass) { + $elm.removeClass(initColClass); + $elm.addClass(newColClass); + initColClass = newColClass; + } } }; diff --git a/src/js/core/directives/ui-grid-header-cell.js b/src/js/core/directives/ui-grid-header-cell.js index e0a819a99d..e781f63722 100644 --- a/src/js/core/directives/ui-grid-header-cell.js +++ b/src/js/core/directives/ui-grid-header-cell.js @@ -30,7 +30,8 @@ $scope.renderContainer = uiGridCtrl.grid.renderContainers[renderContainerCtrl.containerId]; - $elm.addClass($scope.col.getColClass(false)); + var initColClass = $scope.col.getColClass(false); + $elm.addClass(initColClass); // Hide the menu by default $scope.menuShown = false; @@ -65,6 +66,18 @@ var rightMostContainer = $scope.grid.renderContainers['right'] ? $scope.grid.renderContainers['right'] : $scope.grid.renderContainers['body']; $scope.isLastCol = ( $scope.col === rightMostContainer.visibleColumnCache[ rightMostContainer.visibleColumnCache.length - 1 ] ); }; + + $scope.$watch('col', function (n, o) { + if (n !== o) { + // See if the column's internal class has changed + var newColClass = $scope.col.getColClass(false); + if (newColClass !== initColClass) { + $elm.removeClass(initColClass); + $elm.addClass(newColClass); + initColClass = newColClass; + } + } + }); updateClass(); diff --git a/src/js/core/directives/ui-grid.js b/src/js/core/directives/ui-grid.js index ba3eda59a6..a4c38f659d 100644 --- a/src/js/core/directives/ui-grid.js +++ b/src/js/core/directives/ui-grid.js @@ -49,7 +49,7 @@ function columnDefsWatchFunction(n, o) { if (n && n !== o) { self.grid.options.columnDefs = n; - self.grid.buildColumns() + self.grid.buildColumns({ orderByColumnDefs: true }) .then(function(){ self.grid.preCompileCellTemplates(); diff --git a/src/js/core/factories/Grid.js b/src/js/core/factories/Grid.js index 02820b86a3..14ae4b5f42 100644 --- a/src/js/core/factories/Grid.js +++ b/src/js/core/factories/Grid.js @@ -561,9 +561,19 @@ angular.module('ui.grid') * @methodOf ui.grid.class:Grid * @description creates GridColumn objects from the columnDefinition. Calls each registered * columnBuilder to further process the column + * @param {object} options An object contains options to use when building columns + * + * * **orderByColumnDefs**: defaults to **false**. When true, `buildColumns` will reorder existing columns according to the order within the column definitions. + * * @returns {Promise} a promise to load any needed column resources */ - Grid.prototype.buildColumns = function buildColumns() { + Grid.prototype.buildColumns = function buildColumns(opts) { + var options = { + orderByColumnDefs: false + }; + + angular.extend(options, opts); + // gridUtil.logDebug('buildColumns'); var self = this; var builderPromises = []; @@ -605,7 +615,36 @@ angular.module('ui.grid') builderPromises.push(builder.call(self, colDef, col, self.options)); }); }); - + + /*** Reorder columns if necessary ***/ + if (!!options.orderByColumnDefs) { + // Create a shallow copy of the columns as a cache + var columnCache = self.columns.slice(0); + + // We need to allow for the "row headers" when mapping from the column defs array to the columns array + // If we have a row header in columns[0] and don't account for it we'll overwrite it with the column in columnDefs[0] + var rowHeaderOffset = self.rowHeaderColumns.length; + + // Go through all the column defs + for (i = 0; i < self.options.columnDefs.length; i++) { + // If the column at this index has a different name than the column at the same index in the column defs... + if (self.columns[i + rowHeaderOffset].name !== self.options.columnDefs[i].name) { + // Replace the one in the cache with the appropriate column + columnCache[i + rowHeaderOffset] = self.getColumn(self.options.columnDefs[i].name); + } + else { + // Otherwise just copy over the one from the initial columns + columnCache[i + rowHeaderOffset] = self.columns[i + rowHeaderOffset]; + } + } + + // Empty out the columns array, non-destructively + self.columns.length = 0; + + // And splice in the updated, ordered columns from the cache + Array.prototype.splice.apply(self.columns, [0, 0].concat(columnCache)); + } + return $q.all(builderPromises).then(function(){ if (self.rows.length > 0){ self.assignTypes(); diff --git a/src/js/core/factories/GridColumn.js b/src/js/core/factories/GridColumn.js index 65d9cfd3ca..f3ac24e1a1 100644 --- a/src/js/core/factories/GridColumn.js +++ b/src/js/core/factories/GridColumn.js @@ -105,7 +105,7 @@ angular.module('ui.grid') self.grid = grid; self.uid = uid; - self.updateColumnDef(colDef, true ); + self.updateColumnDef(colDef, true); } diff --git a/test/unit/core/directives/uiGridCell.spec.js b/test/unit/core/directives/uiGridCell.spec.js index 0481ca6006..dca035f5a9 100644 --- a/test/unit/core/directives/uiGridCell.spec.js +++ b/test/unit/core/directives/uiGridCell.spec.js @@ -86,5 +86,57 @@ describe('uiGridCell', function () { })); }); + it('should change a columns class when its uid changes', inject(function (gridUtil, $compile, uiGridConstants) { + // Reset the UIDs (used by columns) so they're fresh and clean + gridUtil.resetUids(); + + // Set up a couple basic columns + $scope.gridOptions = { + columnDefs: [{ field: 'name', width: 100 }, { field: 'age', width: 50 }], + data: [ + { name: 'Bob', age: 50 } + ] + }; + + // Create a grid elements + var gridElm = angular.element('
'); + + // Compile the grid and attach it to the document, as the widths won't be right if it's unattached + $compile(gridElm)($scope); + document.body.appendChild(gridElm[0]); + $scope.$digest(); + + // Get the first column and its root column class + var firstCol = $(gridElm).find('.ui-grid-cell').first(); + var firstHeaderCell = $(gridElm).find('.ui-grid-header-cell').first(); + var classRegEx = new RegExp('^' + uiGridConstants.COL_CLASS_PREFIX); + var class1 = _(firstCol[0].classList).find(function(c) { return classRegEx.test(c); }); + + // The first column should be 100px wide because we said it should be + expect(firstCol.outerWidth()).toEqual(100, 'first cell is 100px'); + expect(firstHeaderCell.innerWidth()).toEqual(100, "header cell is 100px"); + + // Now swap the columns in the column defs + $scope.gridOptions.columnDefs = [{ field: 'age', width: 50 }, { field: 'name', width: 100 }]; + $scope.$digest(); + var firstColAgain = $(gridElm).find('.ui-grid-cell').first(); + var firstHeaderCellAgain = $(gridElm).find('.ui-grid-header-cell').first(); + var class2 = _(firstColAgain[0].classList).find(function(c) { return classRegEx.test(c); }); + + // The column root classes should have changed + expect(class2).not.toEqual(class1); + + // The first column should now be 50px wide + expect(firstColAgain.outerWidth()).toEqual(50, 'first cell again is 50'); + expect(firstHeaderCellAgain.innerWidth()).toEqual(50, 'header cell again is 50px'); + + // ... and the last column should now be 100px wide + var lastCol = $(gridElm).find('.ui-grid-cell').last(); + var lastHeaderCell = $(gridElm).find('.ui-grid-header-cell').last(); + expect(lastCol.outerWidth()).toEqual(100, 'last cell again is 100px'); + expect(lastHeaderCell.innerWidth()).toEqual(100, 'last header cell again is 100px'); + + angular.element(gridElm).remove(); + })); }); \ No newline at end of file