diff --git a/misc/tutorial/115_headerCellClass.ngdoc b/misc/tutorial/115_headerCellClass.ngdoc new file mode 100644 index 0000000000..085ff1e607 --- /dev/null +++ b/misc/tutorial/115_headerCellClass.ngdoc @@ -0,0 +1,77 @@ +@ngdoc overview +@name Tutorial: 115 HeaderCellClass +@description + +A class name or function returning a class name can be assigned to each columnDef. + +In this example, we will set the font color of header column 0 to blue, and conditionally +set the background and foreground color of the header if the sort direction is ASC + + + + var app = angular.module('app', ['ngAnimate', 'ui.grid']); + + app.controller('MainCtrl', ['$scope', '$http', 'uiGridConstants', function ($scope, $http, uiGridConstants) { + $scope.gridOptions = { + enableSorting: true, + columnDefs: [ + { field: 'name', headerCellClass: 'blue' }, + { field: 'company', + headerCellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { + if (col.sort.direction === uiGridConstants.ASC) { + return 'red'; + } + } + } + ], + onRegisterApi: function( gridApi ) { + $scope.gridApi = gridApi; + $scope.gridApi.core.on.sortChanged( $scope, function( grid, sort ) { + $scope.gridApi.core.notifyDataChange( $scope.gridApi.grid, uiGridConstants.dataChange.COLUMN ); + }) + } + }; + + $http.get('/data/100.json') + .success(function(data) { + $scope.gridOptions.data = data; + }); + }]); + + +
+
+
+
+
+
+ + .grid { + width: 500px; + height: 200px; + } + .red { color: red; background-color: yellow !important; } + .blue { color: blue; } + + + var gridTestUtils = require('../../test/e2e/gridTestUtils.spec.js'); + describe( '115 header cell class', function() { + it('grid should have two visible columns', function () { + gridTestUtils.expectHeaderColumnCount( 'grid1', 2 ); + }); + + it('cell classes', function () { + // blue for header 0 + expect( gridTestUtils.headerCell( 'grid1', 0 ).getCssValue('color')).toEqual('rgba(0, 0, 255, 1)'); + + // header 2 starts with no coloring, but colors when sort is ASC + expect( gridTestUtils.headerCell( 'grid1', 1 ).getCssValue('color')).toEqual('rgba(44, 62, 80, 1)', 'normal foreground'); + + gridTestUtils.clickHeaderCell( 'grid1', 1 ); + expect( gridTestUtils.headerCell( 'grid1', 1 ).getCssValue('color')).toEqual('rgba(255, 0, 0, 1)', 'red highlight'); + + }); + }); + +
+ diff --git a/src/features/cellnav/js/cellnav.js b/src/features/cellnav/js/cellnav.js index b9df19ec00..910608c3b2 100644 --- a/src/features/cellnav/js/cellnav.js +++ b/src/features/cellnav/js/cellnav.js @@ -554,7 +554,7 @@ function setFocused() { var div = $elm.find('div'); - console.log('setFocused: ' + div[0].parentElement.className); + // gridUtil.logDebug('setFocused: ' + div[0].parentElement.className); div[0].focus(); div.attr("tabindex", 0); $scope.grid.queueRefresh(); diff --git a/src/features/edit/js/gridEdit.js b/src/features/edit/js/gridEdit.js index 2ba0f595a9..07bd9c0f50 100644 --- a/src/features/edit/js/gridEdit.js +++ b/src/features/edit/js/gridEdit.js @@ -405,7 +405,7 @@ } function beginEditFocus(evt) { - console.log('begin edit'); + // gridUtil.logDebug('begin edit'); evt.stopPropagation(); beginEdit(); } @@ -569,6 +569,7 @@ isFocusedBeforeEdit = false; inEdit = false; registerBeginEditEvents(); + $scope.grid.api.core.notifyDataChange( $scope.grid, uiGridConstants.dataChange.EDIT ); } function cancelEdit() { diff --git a/src/features/importer/js/importer.js b/src/features/importer/js/importer.js index e763fd7d20..f4657df668 100644 --- a/src/features/importer/js/importer.js +++ b/src/features/importer/js/importer.js @@ -52,15 +52,17 @@ * * @description Services for importer feature */ - module.service('uiGridImporterService', ['$q', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window', - function ($q, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) { + module.service('uiGridImporterService', ['$q', 'uiGridConstants', 'uiGridImporterConstants', 'gridUtil', '$compile', '$interval', 'i18nService', '$window', + function ($q, uiGridConstants, uiGridImporterConstants, gridUtil, $compile, $interval, i18nService, $window) { var service = { - initializeGrid: function (grid) { + initializeGrid: function ($scope, grid) { //add feature namespace and any properties to grid for needed state - grid.importer = {}; + grid.importer = { + $scope: $scope + }; this.defaultGridOptions(grid.options); @@ -625,6 +627,14 @@ * @description Inserts our new objects into the grid data, and * sets the rows to dirty if the rowEdit feature is being used * + * Does this by registering a watch on dataChanges, which essentially + * is waiting on the result of the grid data watch, and downstream processing. + * + * When the callback is called, it deregisters itself - we don't want to run + * again next time data is added. + * + * If we never get called, we deregister on destroy. + * * @param {Grid} grid the grid we're importing into * @param {array} newObjects the objects we want to insert into the grid data * @returns {object} the new object @@ -634,10 +644,17 @@ var callbackId = grid.registerDataChangeCallback( function() { grid.api.rowEdit.setRowsDirty( grid, newObjects ); grid.deregisterDataChangeCallback( callbackId ); - }); + }, [uiGridConstants.dataChange.ROW] ); + + var deregisterClosure = function() { + grid.deregisterDataChangeCallback( callbackId ); + }; + + grid.importer.$scope.$on( '$destroy', deregisterClosure ); } grid.options.importerDataAddCallback( grid, newObjects ); + }, @@ -703,7 +720,7 @@ require: '^uiGrid', scope: false, link: function ($scope, $elm, $attrs, uiGridCtrl) { - uiGridImporterService.initializeGrid(uiGridCtrl.grid); + uiGridImporterService.initializeGrid($scope, uiGridCtrl.grid); } }; } diff --git a/src/features/importer/test/importer.spec.js b/src/features/importer/test/importer.spec.js index 795e7c7217..e5464d7964 100644 --- a/src/features/importer/test/importer.spec.js +++ b/src/features/importer/test/importer.spec.js @@ -51,7 +51,7 @@ describe('ui.grid.importer uiGridImporterService', function () { grid = gridClassFactory.createGrid(gridOptions); - _uiGridImporterService_.initializeGrid(grid); + _uiGridImporterService_.initializeGrid($scope, grid); grid.buildColumns(); grid.modifyRows(grid.options.data); grid.rows[1].visible = false; @@ -215,8 +215,9 @@ describe('ui.grid.importer uiGridImporterService', function () { uiGridImporterService.importJsonClosure( grid )( testFile ); grid.modifyRows($scope.data); + angular.forEach( grid.dataChangeCallbacks, function( callback, uid ) { - callback( grid ); + callback.callback( grid ); }); expect( $scope.data.length ).toEqual(5, 'data should now have 5 rows'); @@ -225,7 +226,7 @@ describe('ui.grid.importer uiGridImporterService', function () { expect( grid.rows.length ).toEqual(5, 'grid should now have 5 rows'); expect( grid.rows[3].isDirty ).toEqual( true ); expect( grid.rows[4].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length).toEqual(2); + expect( grid.rowEdit.dirtyRows.length).toEqual(2); }); }); @@ -283,7 +284,7 @@ describe('ui.grid.importer uiGridImporterService', function () { grid.modifyRows($scope.data); angular.forEach( grid.dataChangeCallbacks, function( callback, uid ) { - callback( grid ); + callback.callback( grid ); }); expect( $scope.data.length ).toEqual(5, 'data should now have 5 rows'); @@ -292,7 +293,7 @@ describe('ui.grid.importer uiGridImporterService', function () { expect( grid.rows.length ).toEqual(5, 'grid should now have 5 rows'); expect( grid.rows[3].isDirty ).toEqual( true ); expect( grid.rows[4].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length).toEqual(2); + expect( grid.rowEdit.dirtyRows.length).toEqual(2); }); }); @@ -481,7 +482,7 @@ describe('ui.grid.importer uiGridImporterService', function () { grid.modifyRows($scope.data); angular.forEach( grid.dataChangeCallbacks, function( callback, uid ) { - callback( grid ); + callback.callback( grid ); }); expect( $scope.data.length ).toEqual(5, 'data should now have 5 rows'); @@ -490,7 +491,7 @@ describe('ui.grid.importer uiGridImporterService', function () { expect( grid.rows.length ).toEqual(5, 'grid should now have 5 rows'); expect( grid.rows[3].isDirty ).toEqual( true ); expect( grid.rows[4].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length).toEqual(2); + expect( grid.rowEdit.dirtyRows.length).toEqual(2); }); }); diff --git a/src/features/pinning/test/pinning.spec.js b/src/features/pinning/test/pinning.spec.js index 82a0583860..a3d09649e0 100644 --- a/src/features/pinning/test/pinning.spec.js +++ b/src/features/pinning/test/pinning.spec.js @@ -82,7 +82,6 @@ describe('enables pinning when gridOptions.enablePinning is true', function () { it('should add pinned containers to the DOM', function () { - console.log(grid); var leftContainer = $(grid).find('[ui-grid-pinned-container*=left]'); expect(leftContainer.size()).toEqual(1); @@ -199,7 +198,6 @@ }) .first(); - console.log(updateContainerDimensionsFunction()); }); }); }); diff --git a/src/features/row-edit/js/gridRowEdit.js b/src/features/row-edit/js/gridRowEdit.js index 0e8986ed73..7039c84d8d 100644 --- a/src/features/row-edit/js/gridRowEdit.js +++ b/src/features/row-edit/js/gridRowEdit.js @@ -46,6 +46,9 @@ * * @description Public Api for rowEdit feature */ + + grid.rowEdit = {}; + var publicApi = { events: { rowEdit: { @@ -113,7 +116,7 @@ * */ getDirtyRows: function (grid) { - return grid.rowEditDirtyRows ? grid.rowEditDirtyRows : []; + return grid.rowEdit.dirtyRows ? grid.rowEdit.dirtyRows : []; }, /** * @ngdoc method @@ -128,7 +131,7 @@ * */ getErrorRows: function (grid) { - return grid.rowEditErrorRows ? grid.rowEditErrorRows : []; + return grid.rowEdit.errorRows ? grid.rowEdit.errorRows : []; }, /** * @ngdoc method @@ -272,8 +275,8 @@ delete gridRow.isDirty; delete gridRow.isError; delete gridRow.rowEditSaveTimer; - self.removeRow( grid.rowEditErrorRows, gridRow ); - self.removeRow( grid.rowEditDirtyRows, gridRow ); + self.removeRow( grid.rowEdit.errorRows, gridRow ); + self.removeRow( grid.rowEdit.dirtyRows, gridRow ); }; }, @@ -295,11 +298,11 @@ gridRow.isError = true; - if (!grid.rowEditErrorRows){ - grid.rowEditErrorRows = []; + if (!grid.rowEdit.errorRows){ + grid.rowEdit.errorRows = []; } - if (!service.isRowPresent( grid.rowEditErrorRows, gridRow ) ){ - grid.rowEditErrorRows.push( gridRow ); + if (!service.isRowPresent( grid.rowEdit.errorRows, gridRow ) ){ + grid.rowEdit.errorRows.push( gridRow ); } }; }, @@ -310,7 +313,7 @@ * @methodOf ui.grid.rowEdit.service:uiGridRowEditService * @name removeRow * @description Removes a row from a cache of rows - either - * grid.rowEditErrorRows or grid.rowEditDirtyRows. If the row + * grid.rowEdit.errorRows or grid.rowEdit.dirtyRows. If the row * is not present silently does nothing. * @param {array} rowArray the array from which to remove the row * @param {GridRow} gridRow the row that should be removed @@ -361,7 +364,7 @@ */ flushDirtyRows: function(grid){ var promises = []; - angular.forEach(grid.rowEditDirtyRows, function( gridRow ){ + angular.forEach(grid.rowEdit.dirtyRows, function( gridRow ){ service.saveRow( grid, gridRow )(); promises.push( gridRow.rowEditSavePromise ); }); @@ -387,13 +390,13 @@ if ( !gridRow ){ gridUtil.logError( 'Unable to find rowEntity in grid data, dirty flag cannot be set' ); return; } if ( newValue !== previousValue || gridRow.isDirty ){ - if ( !grid.rowEditDirtyRows ){ - grid.rowEditDirtyRows = []; + if ( !grid.rowEdit.dirtyRows ){ + grid.rowEdit.dirtyRows = []; } if ( !gridRow.isDirty ){ gridRow.isDirty = true; - grid.rowEditDirtyRows.push( gridRow ); + grid.rowEdit.dirtyRows.push( gridRow ); } delete gridRow.isError; @@ -553,13 +556,13 @@ myDataRows.forEach( function( value, index ){ gridRow = grid.getRow( value ); if ( gridRow ){ - if ( !grid.rowEditDirtyRows ){ - grid.rowEditDirtyRows = []; + if ( !grid.rowEdit.dirtyRows ){ + grid.rowEdit.dirtyRows = []; } if ( !gridRow.isDirty ){ gridRow.isDirty = true; - grid.rowEditDirtyRows.push( gridRow ); + grid.rowEdit.dirtyRows.push( gridRow ); } delete gridRow.isError; diff --git a/src/features/row-edit/test/uiGridRowEditService.spec.js b/src/features/row-edit/test/uiGridRowEditService.spec.js index 77f7beb573..4925475262 100644 --- a/src/features/row-edit/test/uiGridRowEditService.spec.js +++ b/src/features/row-edit/test/uiGridRowEditService.spec.js @@ -133,7 +133,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.api.edit.raise.afterCellEdit( grid.options.data[0], grid.options.columnDefs[0], '1', '1' ); expect( uiGridRowEditService.saveRow ).not.toHaveBeenCalled(); expect( grid.rows[0].isDirty ).toEqual( undefined ); - expect( grid.rowEditDirtyRows ).toEqual( undefined ); + expect( grid.rowEdit.dirtyRows ).toEqual( undefined ); expect( grid.rows[0].rowEditSaveTimer ).toEqual( undefined ); }); @@ -149,7 +149,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.api.edit.raise.afterCellEdit( grid.options.data[0], grid.options.columnDefs[0], '1', '2' ); expect( uiGridRowEditService.saveRow ).toHaveBeenCalledWith( grid, grid.rows[0] ); expect( grid.rows[0].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length ).toEqual( 1 ); + expect( grid.rowEdit.dirtyRows.length ).toEqual( 1 ); expect( grid.rows[0].rowEditSaveTimer ).not.toEqual( undefined ); @@ -166,14 +166,14 @@ describe('ui.grid.edit uiGridRowEditService', function () { return function() { called = true;}; }); grid.rows[0].isDirty = true; - grid.rowEditDirtyRows = [ grid.rows[0] ]; + grid.rowEdit.dirtyRows = [ grid.rows[0] ]; expect( grid.rows[0].rowEditSaveTimer ).toEqual( undefined ); grid.api.edit.raise.afterCellEdit( grid.options.data[0], grid.options.columnDefs[0], '1', '1' ); expect( uiGridRowEditService.saveRow ).toHaveBeenCalledWith( grid, grid.rows[0] ); expect( grid.rows[0].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length ).toEqual( 1 ); + expect( grid.rowEdit.dirtyRows.length ).toEqual( 1 ); expect( grid.rows[0].rowEditSaveTimer ).not.toEqual( undefined ); @@ -196,7 +196,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.api.edit.raise.afterCellEdit( grid.options.data[0], grid.options.columnDefs[0], '1', '2' ); expect( uiGridRowEditService.saveRow ).not.toHaveBeenCalled(); expect( grid.rows[0].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length ).toEqual( 1 ); + expect( grid.rowEdit.dirtyRows.length ).toEqual( 1 ); expect( grid.rows[0].rowEditSaveTimer ).toEqual( undefined ); @@ -221,7 +221,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.api.edit.raise.afterCellEdit( grid.options.data[0], grid.options.columnDefs[0], '1', '2' ); expect( uiGridRowEditService.saveRow ).toHaveBeenCalledWith( grid, grid.rows[0] ); expect( grid.rows[0].isDirty ).toEqual( true ); - expect( grid.rowEditDirtyRows.length ).toEqual( 1 ); + expect( grid.rowEdit.dirtyRows.length ).toEqual( 1 ); // old interval not called $interval.flush(200); @@ -329,8 +329,8 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.rows[0].isDirty = true; grid.rows[0].isError = true; - grid.rowEditDirtyRows = [ grid.rows[0] ]; - grid.rowEditErrorRows = [ grid.rows[0] ]; + grid.rowEdit.dirtyRows = [ grid.rows[0] ]; + grid.rowEdit.errorRows = [ grid.rows[0] ]; grid.api.rowEdit.on.saveRow( $scope, function(){ grid.api.rowEdit.setSavePromise(grid, grid.options.data[0], promise.promise); }); @@ -339,8 +339,8 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[0].isSaving ).toEqual(true); expect( grid.rows[0].isDirty ).toEqual(true); expect( grid.rows[0].isError ).toEqual(true); - expect( grid.rowEditDirtyRows.length ).toEqual(1); - expect( grid.rowEditErrorRows.length ).toEqual(1); + expect( grid.rowEdit.dirtyRows.length ).toEqual(1); + expect( grid.rowEdit.errorRows.length ).toEqual(1); promise.resolve(1); $rootScope.$apply(); @@ -348,15 +348,15 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[0].isSaving ).toEqual(undefined); expect( grid.rows[0].isDirty ).toEqual(undefined); expect( grid.rows[0].isError ).toEqual(undefined); - expect( grid.rowEditDirtyRows.length ).toEqual(0); - expect( grid.rowEditErrorRows.length ).toEqual(0); + expect( grid.rowEdit.dirtyRows.length ).toEqual(0); + expect( grid.rowEdit.errorRows.length ).toEqual(0); }); it( 'saveRow on dirty row, promise rejected so goes to error state', function() { var promise = $q.defer(); grid.rows[0].isDirty = true; - grid.rowEditDirtyRows = [ grid.rows[0] ]; + grid.rowEdit.dirtyRows = [ grid.rows[0] ]; grid.api.rowEdit.on.saveRow( $scope, function(){ grid.api.rowEdit.setSavePromise(grid, grid.options.data[0], promise.promise); }); @@ -365,8 +365,8 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[0].isSaving ).toEqual(true); expect( grid.rows[0].isDirty ).toEqual(true); expect( grid.rows[0].isError ).toEqual(undefined); - expect( grid.rowEditDirtyRows.length ).toEqual(1); - expect( grid.rowEditErrorRows ).toEqual(undefined); + expect( grid.rowEdit.dirtyRows.length ).toEqual(1); + expect( grid.rowEdit.errorRows ).toEqual(undefined); promise.reject(); $rootScope.$apply(); @@ -374,8 +374,8 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[0].isSaving ).toEqual(undefined); expect( grid.rows[0].isDirty ).toEqual(true); expect( grid.rows[0].isError ).toEqual(true); - expect( grid.rowEditDirtyRows.length ).toEqual(1); - expect( grid.rowEditErrorRows.length ).toEqual(1); + expect( grid.rowEdit.dirtyRows.length ).toEqual(1); + expect( grid.rowEdit.errorRows.length ).toEqual(1); }); }); @@ -397,7 +397,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.rows[2].isDirty = true; grid.rows[3].isDirty = true; - grid.rowEditDirtyRows = [ grid.rows[0], grid.rows[2], grid.rows[3] ]; + grid.rowEdit.dirtyRows = [ grid.rows[0], grid.rows[2], grid.rows[3] ]; grid.api.rowEdit.on.saveRow( $scope, function( rowEntity ){ grid.api.rowEdit.setSavePromise(grid, rowEntity, promises[promiseCounter].promise); @@ -411,14 +411,14 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[1].isSaving ).toEqual(undefined); expect( grid.rows[2].isSaving ).toEqual(true); expect( grid.rows[3].isSaving ).toEqual(true); - expect( grid.rowEditDirtyRows.length ).toEqual(3); + expect( grid.rowEdit.dirtyRows.length ).toEqual(3); promises[0].resolve(1); $rootScope.$apply(); expect( grid.rows[0].isSaving ).toEqual(undefined); expect( grid.rows[0].isDirty ).toEqual(undefined); - expect( grid.rowEditDirtyRows.length ).toEqual(2); + expect( grid.rowEdit.dirtyRows.length ).toEqual(2); expect( success ).toEqual(false); promises[1].resolve(1); @@ -429,7 +429,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[2].isDirty ).toEqual(undefined); expect( grid.rows[3].isSaving ).toEqual(undefined); expect( grid.rows[3].isDirty ).toEqual(undefined); - expect( grid.rowEditDirtyRows.length ).toEqual(0); + expect( grid.rowEdit.dirtyRows.length ).toEqual(0); expect( success ).toEqual(true); expect( failure ).toEqual(false); }); @@ -444,7 +444,7 @@ describe('ui.grid.edit uiGridRowEditService', function () { grid.rows[2].isDirty = true; grid.rows[3].isDirty = true; - grid.rowEditDirtyRows = [ grid.rows[0], grid.rows[2], grid.rows[3] ]; + grid.rowEdit.dirtyRows = [ grid.rows[0], grid.rows[2], grid.rows[3] ]; grid.api.rowEdit.on.saveRow( $scope, function( rowEntity ){ grid.api.rowEdit.setSavePromise(grid, rowEntity, promises[promiseCounter].promise); @@ -458,14 +458,14 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[1].isSaving ).toEqual(undefined); expect( grid.rows[2].isSaving ).toEqual(true); expect( grid.rows[3].isSaving ).toEqual(true); - expect( grid.rowEditDirtyRows.length ).toEqual(3); + expect( grid.rowEdit.dirtyRows.length ).toEqual(3); promises[0].resolve(1); $rootScope.$apply(); expect( grid.rows[0].isSaving ).toEqual(undefined); expect( grid.rows[0].isDirty ).toEqual(undefined); - expect( grid.rowEditDirtyRows.length ).toEqual(2); + expect( grid.rowEdit.dirtyRows.length ).toEqual(2); expect( success ).toEqual(false); promises[1].reject(); @@ -477,8 +477,8 @@ describe('ui.grid.edit uiGridRowEditService', function () { expect( grid.rows[2].isError ).toEqual(true); expect( grid.rows[3].isSaving ).toEqual(undefined); expect( grid.rows[3].isDirty ).toEqual(undefined); - expect( grid.rowEditDirtyRows.length ).toEqual(1); - expect( grid.rowEditErrorRows.length ).toEqual(1); + expect( grid.rowEdit.dirtyRows.length ).toEqual(1); + expect( grid.rowEdit.errorRows.length ).toEqual(1); expect( success ).toEqual(false); expect( failure ).toEqual(true); }); diff --git a/src/js/core/constants.js b/src/js/core/constants.js index e0d2e4a42f..6ec5d51762 100644 --- a/src/js/core/constants.js +++ b/src/js/core/constants.js @@ -80,7 +80,14 @@ }, // TODO(c0bra): Create full list of these somehow. NOTE: do any allow a space before or after them? - CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'] + CURRENCY_SYMBOLS: ['ƒ', '$', '£', '$', '¤', '¥', '៛', '₩', '₱', '฿', '₫'], + + dataChange: { + ALL: 'all', + EDIT: 'edit', + ROW: 'row', + COLUMN: 'column' + } }); })(); \ 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 9afb1ec6c7..c08a901a0e 100644 --- a/src/js/core/directives/ui-grid-cell.js +++ b/src/js/core/directives/ui-grid-cell.js @@ -42,16 +42,36 @@ angular.module('ui.grid').directive('uiGridCell', ['$compile', '$parse', 'gridUt }, post: function($scope, $elm, $attrs, uiGridCtrl) { $elm.addClass($scope.col.getColClass(false)); - if ($scope.col.cellClass) { - //var contents = angular.element($elm[0].getElementsByClassName('ui-grid-cell-contents')); + + var classAdded; + var updateClass = function( grid ){ var contents = $elm; + if ( classAdded ){ + contents.removeClass( classAdded ); + classAdded = null; + } + if (angular.isFunction($scope.col.cellClass)) { - contents.addClass($scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex)); + classAdded = $scope.col.cellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex); } else { - contents.addClass($scope.col.cellClass); + classAdded = $scope.col.cellClass; } + contents.addClass(classAdded); + }; + + if ($scope.col.cellClass) { + updateClass(); } + + // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs + var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN, uiGridConstants.dataChange.EDIT]); + + var deregisterFunction = function() { + $scope.grid.deregisterDataChangeCallback( watchUid ); + }; + + $scope.$on( '$destroy', deregisterFunction ); } }; } diff --git a/src/js/core/directives/ui-grid-header-cell.js b/src/js/core/directives/ui-grid-header-cell.js index ef0d4826a2..071f5d9479 100644 --- a/src/js/core/directives/ui-grid-header-cell.js +++ b/src/js/core/directives/ui-grid-header-cell.js @@ -44,6 +44,39 @@ var $contentsElm = angular.element( $elm[0].querySelectorAll('.ui-grid-cell-contents') ); + + // apply any headerCellClass + var classAdded; + var updateClass = function( grid ){ + var contents = $elm; + if ( classAdded ){ + contents.removeClass( classAdded ); + classAdded = null; + } + + if (angular.isFunction($scope.col.headerCellClass)) { + classAdded = $scope.col.headerCellClass($scope.grid, $scope.row, $scope.col, $scope.rowRenderIndex, $scope.colRenderIndex); + } + else { + classAdded = $scope.col.headerCellClass; + } + contents.addClass(classAdded); + }; + + if ($scope.col.headerCellClass) { + updateClass(); + } + + // Register a data change watch that would get triggered whenever someone edits a cell or modifies column defs + var watchUid = $scope.grid.registerDataChangeCallback( updateClass, [uiGridConstants.dataChange.COLUMN]); + + var deregisterFunction = function() { + $scope.grid.deregisterDataChangeCallback( watchUid ); + }; + + $scope.$on( '$destroy', deregisterFunction ); + + // Figure out whether this column is sortable or not if (uiGridCtrl.grid.options.enableSorting && $scope.col.enableSorting) { $scope.sortable = true; diff --git a/src/js/core/directives/ui-grid.js b/src/js/core/directives/ui-grid.js index 51085edd24..806b4e7feb 100644 --- a/src/js/core/directives/ui-grid.js +++ b/src/js/core/directives/ui-grid.js @@ -55,6 +55,8 @@ self.grid.preCompileCellTemplates(); + self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.COLUMN); + self.grid.refresh(); }); } @@ -84,9 +86,7 @@ $scope.$evalAsync(function() { self.grid.refreshCanvas(true); - angular.forEach( self.grid.dataChangeCallbacks, function( callback, uid ){ - callback( self.grid ); - }); + self.grid.callDataChangeCallbacks(uiGridConstants.dataChange.ROW); }); }); }); diff --git a/src/js/core/factories/Grid.js b/src/js/core/factories/Grid.js index 17c0b64d68..e0d4019133 100644 --- a/src/js/core/factories/Grid.js +++ b/src/js/core/factories/Grid.js @@ -214,6 +214,23 @@ angular.module('ui.grid') * */ self.api.registerEvent( 'core', 'sortChanged' ); + + /** + * @ngdoc method + * @name notifyDataChange + * @methodOf ui.grid.core.api:PublicApi + * @description Notify the grid that a data or config change has occurred, + * where that change isn't something the grid was otherwise noticing. This + * might be particularly relevant where you've changed values within the data + * and you'd like cell classes to be re-evaluated, or changed config within + * the columnDef and you'd like headerCellClasses to be re-evaluated. + * @param {Grid} grid the grid + * @param {string} type one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN), which tells + * us which refreshes to fire. + * + */ + self.api.registerMethod( 'core', 'notifyDataChange', this.notifyDataChange ); }; /** @@ -262,18 +279,41 @@ angular.module('ui.grid') this.rowBuilders.push(rowBuilder); }; + /** * @ngdoc function * @name registerDataChangeCallback * @methodOf ui.grid.class:Grid - * @description When the data watch notices a change in the data, all the callbacks - * in the dataWatchCallback array will be called. + * @description When a data change occurs, the data change callbacks of the specified type + * will be called. The rules are: + * + * - when the data watch fires, that is considered a ROW change (the data watch only notices + * added or removed rows) + * - when the api is called to inform us of a change, the declared type of that change is used + * - when a cell edit completes, the EDIT callbacks are triggered + * - when the columnDef watch fires, the COLUMN callbacks are triggered + * + * For a given event: + * - ALL calls ROW, EDIT, COLUMN and ALL callbacks + * - ROW calls ROW and ALL callbacks + * - EDIT calls EDIT and ALL callbacks + * - COLUMN calls COLUMN and ALL callbacks + * * @param {function(grid)} callback function to be called + * @param {array} types the types of data change you want to be informed of. Values from + * the uiGridConstants.dataChange values ( ALL, EDIT, ROW, COLUMN ). Optional and defaults to + * ALL * @returns {string} uid of the callback, can be used to deregister it again */ - Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback) { + Grid.prototype.registerDataChangeCallback = function registerDataChangeCallback(callback, types) { var uid = gridUtil.nextUid(); - this.dataChangeCallbacks[uid] = callback; + if ( !types ){ + types = [uiGridConstants.dataChange.ALL]; + } + if ( !Array.isArray(types)){ + gridUtil.logError("Expected types to be an array or null in registerDataChangeCallback, value passed was: " + types ); + } + this.dataChangeCallbacks[uid] = { callback: callback, types: types }; return uid; }; @@ -288,6 +328,50 @@ angular.module('ui.grid') delete this.dataChangeCallbacks[uid]; }; + /** + * @ngdoc function + * @name callDataChangeCallbacks + * @methodOf ui.grid.class:Grid + * @description Calls the callbacks based on the type of data change that + * has occurred. Always calls the ALL callbacks, calls the ROW, EDIT and COLUMN callbacks if the + * event type is matching, or if the type is ALL. + * @param {number} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.callDataChangeCallbacks = function callDataChangeCallbacks(type) { + angular.forEach( this.dataChangeCallbacks, function( callback, uid ){ + if ( callback.types.indexOf( uiGridConstants.dataChange.ALL ) !== -1 || + callback.types.indexOf( type ) !== -1 || + type === uiGridConstants.dataChange.ALL ) { + callback.callback( this ); + } + }); + }; + + /** + * @ngdoc function + * @name notifyDataChange + * @methodOf ui.grid.class:Grid + * @description Notifies us that a data change has occurred, used in the public + * api for users to tell us when they've changed data or some other event that + * our watches cannot pick up + * @param {Grid} grid the grid + * @param {string} type the type of event that occurred - one of the + * uiGridConstants.dataChange values (ALL, ROW, EDIT, COLUMN) + */ + Grid.prototype.notifyDataChange = function notifyDataChange(grid, type) { + var constants = uiGridConstants.dataChange; + if ( type === constants.ALL || + type === constants.COLUMN || + type === constants.EDIT || + type === constants.ROW ){ + grid.callDataChangeCallbacks( type ); + } else { + gridUtil.logError("Notified of a data change, but the type was not recognised, so no action taken, type was: " + type); + } + }; + + /** * @ngdoc function * @name getColumn diff --git a/src/js/core/factories/GridColumn.js b/src/js/core/factories/GridColumn.js index c6fab1c58f..0777822a19 100644 --- a/src/js/core/factories/GridColumn.js +++ b/src/js/core/factories/GridColumn.js @@ -405,6 +405,15 @@ angular.module('ui.grid') */ self.cellClass = colDef.cellClass; + /** + * @ngdoc property + * @name headerCellClass + * @propertyOf ui.grid.class:GridOptions.columnDef + * @description headerCellClass can be a string specifying the class to append to a cell + * or it can be a function(row,rowRenderIndex, col, colRenderIndex) that returns a class name + * + */ + self.headerCellClass = colDef.headerCellClass; /** * @ngdoc property diff --git a/test/unit/core/directives/ui-grid-header-cell.spec.js b/test/unit/core/directives/ui-grid-header-cell.spec.js index 84b110a957..ee4c654237 100644 --- a/test/unit/core/directives/ui-grid-header-cell.spec.js +++ b/test/unit/core/directives/ui-grid-header-cell.spec.js @@ -1,5 +1,5 @@ describe('uiGridHeaderCell', function () { - var grid, $scope, $compile, $document, $timeout, $window, recompile, $animate; + var grid, $scope, $compile, $document, $timeout, $window, recompile, $animate, uiGridConstants, columnDefs; var data = [ { "name": "Ethel Price", "gender": "female", "company": "Enersol" }, @@ -8,27 +8,36 @@ describe('uiGridHeaderCell', function () { { "name": "Wilder Gonzales", "gender": "male", "company": "Geekko" } ]; - var columnDefs = [ - { name: 'name'}, - { name: 'gender' }, + columnDefs = [ + { name: 'name', headerCellClass: 'testClass' }, + { name: 'gender', headerCellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { + if ( col.colDef.noClass ){ + return ''; + } else { + return 'funcCellClass'; + } + } + }, { name: 'company' } ]; beforeEach(module('ui.grid')); beforeEach(module('ngAnimateMock')); - beforeEach(inject(function (_$compile_, $rootScope, _$document_, _$timeout_, _$window_, _$animate_) { + beforeEach(inject(function (_$compile_, $rootScope, _$document_, _$timeout_, _$window_, _$animate_, _uiGridConstants_) { $scope = $rootScope; $compile = _$compile_; $document = _$document_; $timeout = _$timeout_; $window = _$window_; $animate = _$animate_; + uiGridConstants = _uiGridConstants_; $scope.gridOpts = { enableSorting: true, columnDefs: columnDefs, - data: data + data: data, + onRegisterApi: function( gridApi ){ $scope.gridApi = gridApi; } }; recompile = function () { @@ -146,4 +155,24 @@ describe('uiGridHeaderCell', function () { // TODO(c0bra): Allow extra items to be added to a column menu through columnDefs }); + describe('headerCellClass', function () { + var headerCell1, + headerCell2; + + beforeEach(function () { + headerCell1 = $(grid).find('.ui-grid-header-cell:nth(0)'); + headerCell2 = $(grid).find('.ui-grid-header-cell:nth(1)'); + }); + + it('should have the headerCellClass class, from string', inject(function () { + expect(headerCell1.hasClass('testClass')).toBe(true); + })); + + it('should get cellClass from function, and remove it when data changes', inject(function () { + expect(headerCell2.hasClass('funcCellClass')).toBe(true); + columnDefs[1].noClass = true; + $scope.gridApi.core.notifyDataChange( $scope.gridApi.grid, uiGridConstants.dataChange.COLUMN ); + expect(headerCell2.hasClass('funcCellClass')).toBe(false); + })); + }); }); \ No newline at end of file diff --git a/test/unit/core/directives/uiGridCell.spec.js b/test/unit/core/directives/uiGridCell.spec.js index 7d0d3c02cd..4be7cbc4a7 100644 --- a/test/unit/core/directives/uiGridCell.spec.js +++ b/test/unit/core/directives/uiGridCell.spec.js @@ -1,13 +1,14 @@ describe('uiGridCell', function () { - var gridCell, $scope, $compile, $timeout, GridColumn, recompile, grid; + var gridCell, $scope, $compile, $timeout, GridColumn, recompile, grid, uiGridConstants; beforeEach(module('ui.grid')); - beforeEach(inject(function (_$compile_, $rootScope, _$timeout_, _GridColumn_, gridClassFactory) { + beforeEach(inject(function (_$compile_, $rootScope, _$timeout_, _GridColumn_, gridClassFactory, _uiGridConstants_) { $scope = $rootScope; $compile = _$compile_; $timeout = _$timeout_; GridColumn = _GridColumn_; + uiGridConstants = _uiGridConstants_; $scope.grid = gridClassFactory.createGrid(); @@ -46,15 +47,23 @@ describe('uiGridCell', function () { expect(gridCell.hasClass('testClass')).toBe(true); })); - it('should get cellClass from function', inject(function () { + it('should get cellClass from function, and remove it when data changes', inject(function () { $scope.col.cellClass = function (grid, row, col, rowRenderIndex, colRenderIndex) { if (rowRenderIndex === 2 && colRenderIndex === 2) { - return 'funcCellClass'; + if ( col.noClass ){ + return ''; + } else { + return 'funcCellClass'; + } } }; recompile(); var displayHtml = gridCell.html(); expect(gridCell.hasClass('funcCellClass')).toBe(true); + + $scope.col.noClass = true; + $scope.grid.api.core.notifyDataChange( $scope.grid, uiGridConstants.dataChange.COLUMN ); + expect(gridCell.hasClass('funcCellClass')).toBe(false); })); }); diff --git a/test/unit/core/factories/Grid.spec.js b/test/unit/core/factories/Grid.spec.js index 8649d3517e..d26b3fbef1 100644 --- a/test/unit/core/factories/Grid.spec.js +++ b/test/unit/core/factories/Grid.spec.js @@ -596,13 +596,61 @@ describe('Grid factory', function () { }); - describe( 'register and deregister data change callbacks', function() { + describe( 'data change callbacks', function() { it( 'register then deregister data change callback', function() { var uid = grid.registerDataChangeCallback( function() {}); - expect( grid.dataChangeCallbacks[uid]).toEqual( jasmine.any(Function)); + expect( grid.dataChangeCallbacks[uid]).toEqual( { callback: jasmine.any(Function), types: [ uiGridConstants.dataChange.ALL ] } ); grid.deregisterDataChangeCallback( uid ); expect( grid.dataChangeCallbacks ).toEqual( {} ); }); + + describe( 'mix of callbacks being called', function() { + var called; + var constants; + + beforeEach( function() { + called = []; + constants = uiGridConstants.dataChange; + + // this function will push it's type into the called array when it's called + var createCallbackFunction = function( type ){ + return function( grid ){ + called.push( type ); + }; + }; + + grid.registerDataChangeCallback( createCallbackFunction( constants.ALL ), [constants.ALL] ); + grid.registerDataChangeCallback( createCallbackFunction( constants.ROW ), [constants.ROW] ); + grid.registerDataChangeCallback( createCallbackFunction( constants.EDIT ), [constants.EDIT] ); + grid.registerDataChangeCallback( createCallbackFunction( constants.COLUMN ), [constants.COLUMN] ); + grid.registerDataChangeCallback( createCallbackFunction( constants.COLUMN + constants.EDIT ), [constants.COLUMN, constants.EDIT] ); + }); + + it( 'call of type ALL', function() { + grid.callDataChangeCallbacks( constants.ALL ); + expect( called ).toEqual( [ constants.ALL, constants.ROW, constants.EDIT, constants.COLUMN, constants.COLUMN + constants.EDIT]); + }); + + it( 'call of type ROW', function() { + grid.callDataChangeCallbacks( constants.ROW ); + expect( called ).toEqual( [ constants.ALL, constants.ROW ]); + }); + + it( 'call of type EDIT', function() { + grid.callDataChangeCallbacks( constants.EDIT ); + expect( called ).toEqual( [ constants.ALL, constants.EDIT, constants.COLUMN + constants.EDIT ]); + }); + + it( 'call of type COLUMN', function() { + grid.callDataChangeCallbacks( constants.COLUMN ); + expect( called ).toEqual( [ constants.ALL, constants.COLUMN, constants.COLUMN + constants.EDIT ]); + }); + + it( 'call works via api', function() { + grid.api.core.notifyDataChange( grid, constants.COLUMN ); + expect( called ).toEqual( [ constants.ALL, constants.COLUMN, constants.COLUMN + constants.EDIT ]); + }); + }); }); });