diff --git a/misc/demo/grid-save.html b/misc/demo/grid-save.html new file mode 100644 index 0000000000..8a19c65df6 --- /dev/null +++ b/misc/demo/grid-save.html @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + +

Grid

+
+ + + +
+
+ + + + + \ No newline at end of file diff --git a/misc/tutorial/208_save_state.ngdoc b/misc/tutorial/208_save_state.ngdoc index d9c6809240..e2880435bc 100644 --- a/misc/tutorial/208_save_state.ngdoc +++ b/misc/tutorial/208_save_state.ngdoc @@ -30,6 +30,7 @@ default: - saveVisible - saveSort - saveFilter +- savePinning @example In this example we provide a button to save the grid state. You can then modify the grid layout @@ -38,17 +39,17 @@ to something different, and use the restore button to set the grid back the way - var app = angular.module('app', ['ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.saveState', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.resizeColumns', 'ui.grid.moveColumns' ]); + var app = angular.module('app', ['ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.saveState', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.resizeColumns', 'ui.grid.moveColumns', 'ui.grid.pinning' ]); app.controller('MainCtrl', ['$scope', '$http', '$interval', function ($scope, $http, $interval) { $scope.gridOptions = { saveFocus: false, saveScroll: true, - enableFiltering: true, onRegisterApi: function(gridApi){ $scope.gridApi = gridApi; } }; + $scope.gridOptions.enableFiltering = true; $scope.state = {}; $scope.saveState = function() { @@ -68,7 +69,7 @@ to something different, and use the restore button to set the grid back the way
-
+
@@ -80,6 +81,7 @@ to something different, and use the restore button to set the grid back the way height: 400px; }
+ var gridTestUtils = require('../../test/e2e/gridTestUtils.spec.js'); describe( '208 save state', function() { diff --git a/src/features/pinning/js/pinning.js b/src/features/pinning/js/pinning.js index 19c92fac6a..7030f3b1e8 100644 --- a/src/features/pinning/js/pinning.js +++ b/src/features/pinning/js/pinning.js @@ -16,7 +16,15 @@ var module = angular.module('ui.grid.pinning', ['ui.grid']); - module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', function (gridUtil, GridRenderContainer, i18nService) { + module.constant('uiGridPinningConstants', { + container: { + LEFT: 'left', + RIGHT: 'right', + NONE: '' + } + }); + + module.service('uiGridPinningService', ['gridUtil', 'GridRenderContainer', 'i18nService', 'uiGridPinningConstants', function (gridUtil, GridRenderContainer, i18nService, uiGridPinningConstants) { var service = { initializeGrid: function (grid) { @@ -24,6 +32,54 @@ // Register a column builder to add new menu items for pinning left and right grid.registerColumnBuilder(service.pinningColumnBuilder); + + /** + * @ngdoc object + * @name ui.grid.pinning.api:PublicApi + * + * @description Public Api for pinning feature + */ + var publicApi = { + events: { + pinning: { + /** + * @ngdoc event + * @name columnPin + * @eventOf ui.grid.pinning.api:PublicApi + * @description raised when column pin state has changed + *
+               *   gridApi.pinning.on.columnPinned(scope, function(colDef){})
+               * 
+ * @param {object} colDef the column that was changed + * @param {string} container the render container the column is in ('left', 'right', '') + */ + columnPinned: function(colDef, container) { + } + } + }, + methods: { + pinning: { + /** + * @ngdoc function + * @name pinColumn + * @methodOf ui.grid.pinning.api:PublicApi + * @description pin column left, right, or none + *
+               *   gridApi.pinning.pinColumn(col, uiGridPinningConstants.container.LEFT)
+               * 
+ * @param {gridColumn} col the column being pinned + * @param {string} container one of the recognised types + * from uiGridPinningConstants + */ + pinColumn: function(col, container) { + service.pinColumn(grid, col, container); + } + } + } + }; + + grid.api.registerEventsFromObject(publicApi.events); + grid.api.registerMethodsFromObject(publicApi.methods); }, defaultGridOptions: function (gridOptions) { @@ -124,11 +180,7 @@ return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'left'; }, action: function () { - this.context.col.renderContainer = 'left'; - this.context.col.width = this.context.col.drawnWidth; - this.context.col.grid.createLeftContainer(); - - col.grid.refresh(); + service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.LEFT); } }; @@ -140,11 +192,7 @@ return typeof(this.context.col.renderContainer) === 'undefined' || !this.context.col.renderContainer || this.context.col.renderContainer !== 'right'; }, action: function () { - this.context.col.renderContainer = 'right'; - this.context.col.width = this.context.col.drawnWidth; - this.context.col.grid.createRightContainer(); - - col.grid.refresh(); + service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.RIGHT); } }; @@ -156,9 +204,7 @@ return typeof(this.context.col.renderContainer) !== 'undefined' && this.context.col.renderContainer !== null && this.context.col.renderContainer !== 'body'; }, action: function () { - this.context.col.renderContainer = null; - - col.grid.refresh(); + service.pinColumn(this.context.col.grid, this.context.col, uiGridPinningConstants.container.UNPIN); } }; @@ -171,6 +217,32 @@ if (!gridUtil.arrayContainsObjectWithProperty(col.menuItems, 'name', 'ui.grid.pinning.unpin')) { col.menuItems.push(removePinAction); } + }, + + pinColumn: function(grid, col, container) { + if (container === uiGridPinningConstants.container.NONE) { + col.renderContainer = null; + } + else { + col.renderContainer = container; + col.width = col.drawnWidth; + if (container === uiGridPinningConstants.container.LEFT) { + grid.createLeftContainer(); + } + else if (container === uiGridPinningConstants.container.RIGHT) { + grid.createRightContainer(); + } + } + + // Need to call refresh twice; once to move our column over to the new render container and then + // a second time to update the grid viewport dimensions with our adjustments + grid.refresh() + .then(function () { + grid.refresh() + .then(function() { + grid.api.pinning.raise.columnPinned( col.colDef, container ); + }); + }); } }; diff --git a/src/features/pinning/test/uiGridPinningService.spec.js b/src/features/pinning/test/uiGridPinningService.spec.js index ac66b19f94..3c518e20b7 100644 --- a/src/features/pinning/test/uiGridPinningService.spec.js +++ b/src/features/pinning/test/uiGridPinningService.spec.js @@ -1,14 +1,16 @@ /* global _ */ describe('ui.grid.pinning uiGridPinningService', function () { var uiGridPinningService; + var uiGridPinningConstants; var gridClassFactory; var grid; var GridRenderContainer; beforeEach(module('ui.grid.pinning')); - beforeEach(inject(function (_uiGridPinningService_,_gridClassFactory_, $templateCache, _GridRenderContainer_) { + beforeEach(inject(function (_uiGridPinningService_,_gridClassFactory_, $templateCache, _GridRenderContainer_, _uiGridPinningConstants_) { uiGridPinningService = _uiGridPinningService_; + uiGridPinningConstants = _uiGridPinningConstants_; gridClassFactory = _gridClassFactory_; GridRenderContainer = _GridRenderContainer_; @@ -131,4 +133,73 @@ describe('ui.grid.pinning uiGridPinningService', function () { }); + describe('pinColumn', function() { + + var previousWidth; + + beforeEach(function() { + spyOn(grid, 'createLeftContainer').andCallThrough(); + spyOn(grid, 'createRightContainer').andCallThrough(); + previousWidth = grid.columns[0].drawnWidth; + }); + + describe('left', function() { + + beforeEach(function() { + grid.api.pinning.pinColumn(grid.columns[0], uiGridPinningConstants.container.LEFT); + }); + + it('should set renderContainer to be left', function(){ + expect(grid.columns[0].renderContainer).toEqual('left'); + }); + + it('should call createLeftContainer', function() { + expect(grid.createLeftContainer).toHaveBeenCalled(); + }); + + it('should set width based on previous setting', function() { + expect(grid.width).toEqual(previousWidth); + }); + + }); + + describe('right', function() { + + beforeEach(function() { + grid.api.pinning.pinColumn(grid.columns[0], uiGridPinningConstants.container.RIGHT); + }); + + it('should set renderContainer to be right', function(){ + expect(grid.columns[0].renderContainer).toEqual('right'); + }); + + it('should call createLeftContainer', function() { + expect(grid.createRightContainer).toHaveBeenCalled(); + }); + + it('should set width based on previous setting', function() { + expect(grid.width).toEqual(previousWidth); + }); + + }); + + describe('none', function() { + + beforeEach(function() { + grid.api.pinning.pinColumn(grid.columns[0], uiGridPinningConstants.container.NONE); + }); + + it('should set renderContainer to be null', function(){ + expect(grid.columns[0].renderContainer).toBeNull(); + }); + + it('should NOT call either container creation methods', function() { + expect(grid.createLeftContainer).not.toHaveBeenCalled(); + expect(grid.createRightContainer).not.toHaveBeenCalled(); + }); + + }); + + }); + }); \ No newline at end of file diff --git a/src/features/saveState/js/saveState.js b/src/features/saveState/js/saveState.js index 109c8353b0..2b25a19943 100644 --- a/src/features/saveState/js/saveState.js +++ b/src/features/saveState/js/saveState.js @@ -20,7 +20,7 @@ *
*/ - var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping']); + var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning']); /** * @ngdoc object @@ -243,7 +243,16 @@ * *
Defaults to false */ - gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true; + gridOptions.saveGroupingExpandedStates = gridOptions.saveGroupingExpandedStates === true; + /** + * @ngdoc object + * @name savePinning + * @propertyOf ui.grid.saveState.api:GridOptions + * @description Save pinning state for columns. + * + *
Defaults to true + */ + gridOptions.savePinning = gridOptions.savePinning !== false; }, @@ -295,8 +304,10 @@ if ( state.grouping ){ service.restoreGrouping( grid, state.grouping ); } - - grid.queueGridRefresh(); + + // refresh twice due to rendering issue. + // specific case: save with column pinned left, hide that column, then restore + grid.refresh().then(function(){ grid.refresh(); }); }, @@ -304,8 +315,8 @@ * @ngdoc function * @name saveColumns * @methodOf ui.grid.saveState.service:uiGridSaveStateService - * @description Saves the column setup, including sort, filters, ordering - * and column widths. + * @description Saves the column setup, including sort, filters, ordering, + * pinning and column widths. * * Works through the current columns, storing them in order. Stores the * column name, then the visible flag, width, sort and filters for each column. @@ -335,6 +346,10 @@ if ( grid.options.saveFilter ){ savedColumn.filters = angular.copy ( column.filters ); } + + if ( !!grid.api.pinning && grid.options.savePinning ){ + savedColumn.pinned = column.renderContainer ? column.renderContainer : ''; + } columns.push( savedColumn ); }); @@ -466,8 +481,8 @@ * @ngdoc function * @name restoreColumns * @methodOf ui.grid.saveState.service:uiGridSaveStateService - * @description Restores the columns, including order, visible, width - * sort and filters. + * @description Restores the columns, including order, visible, width, + * pinning, sort and filters. * * @param {Grid} grid the grid whose state we'd like to restore * @param {object} columnsState the list of columns we had before, with their state @@ -503,6 +518,10 @@ grid.columns[currentIndex].filters = angular.copy( columnState.filters ); grid.api.core.raise.filterChanged(); } + + if ( !!grid.api.pinning && grid.options.savePinning && grid.columns[currentIndex].renderContainer !== columnState.pinned ){ + grid.api.pinning.pinColumn(grid.columns[currentIndex], columnState.pinned); + } if ( grid.options.saveOrder && currentIndex !== index ){ var column = grid.columns.splice( currentIndex, 1 )[0]; diff --git a/src/features/saveState/test/saveState.spec.js b/src/features/saveState/test/saveState.spec.js index 94e126bf5f..180b99daf8 100644 --- a/src/features/saveState/test/saveState.spec.js +++ b/src/features/saveState/test/saveState.spec.js @@ -4,6 +4,7 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { var uiGridSelectionService; var uiGridCellNavService; var uiGridGroupingService; + var uiGridPinningService; var gridClassFactory; var grid; var $compile; @@ -15,12 +16,13 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { beforeEach(inject(function (_uiGridSaveStateService_, _gridClassFactory_, _uiGridSaveStateConstants_, _$compile_, _$rootScope_, _$document_, _uiGridSelectionService_, - _uiGridCellNavService_, _uiGridGroupingService_ ) { + _uiGridCellNavService_, _uiGridGroupingService_, _uiGridPinningService_ ) { uiGridSaveStateService = _uiGridSaveStateService_; uiGridSaveStateConstants = _uiGridSaveStateConstants_; uiGridSelectionService = _uiGridSelectionService_; uiGridCellNavService = _uiGridCellNavService_; uiGridGroupingService = _uiGridGroupingService_; + uiGridPinningService = _uiGridPinningService_; gridClassFactory = _gridClassFactory_; $compile = _$compile_; $scope = _$rootScope_.$new(); @@ -28,10 +30,10 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { grid = gridClassFactory.createGrid({}); grid.options.columnDefs = [ - {field: 'col1', name: 'col1', displayName: 'Col1', width: 50}, + {field: 'col1', name: 'col1', displayName: 'Col1', width: 50, pinnedLeft:true }, {field: 'col2', name: 'col2', displayName: 'Col2', width: '*', type: 'number'}, {field: 'col3', name: 'col3', displayName: 'Col3', width: 100}, - {field: 'col4', name: 'col4', displayName: 'Col4', width: 200} + {field: 'col4', name: 'col4', displayName: 'Col4', width: 200, pinnedRight:true } ]; _uiGridSaveStateService_.initializeGrid(grid); @@ -75,7 +77,8 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { saveFilter: true, saveSelection: true, saveGrouping: true, - saveGroupingExpandedStates: false + saveGroupingExpandedStates: false, + savePinning: true }); }); @@ -91,7 +94,8 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { saveFilter: false, saveSelection: false, saveGrouping: false, - saveGroupingExpandedStates: true + saveGroupingExpandedStates: true, + savePinning: false }; uiGridSaveStateService.defaultGridOptions(options); expect( options ).toEqual({ @@ -104,7 +108,8 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { saveFilter: false, saveSelection: false, saveGrouping: false, - saveGroupingExpandedStates: true + saveGroupingExpandedStates: true, + savePinning: false }); }); }); @@ -133,6 +138,40 @@ describe('ui.grid.saveState uiGridSaveStateService', function () { { name: 'col4' } ]); }); + + describe('pinning enabled', function() { + + beforeEach(function(){ + uiGridPinningService.initializeGrid(grid); + grid.buildColumns(); + grid.columns[2].visible = false; + grid.setVisibleColumns(grid.columns); + }); + + it('save columns', function() { + expect( uiGridSaveStateService.saveColumns( grid ) ).toEqual([ + { name: 'col1', visible: true, width: 50, sort: [], filters: [ {} ], pinned: 'left' }, + { name: 'col2', visible: true, width: '*', sort: [], filters: [ {} ], pinned: '' }, + { name: 'col3', visible: false, width: 100, sort: [], filters: [ {} ], pinned: '' }, + { name: 'col4', visible: true, width: 200, sort: [], filters: [ {} ], pinned: 'right' } + ]); + }); + + it('save columns with most options turned off', function() { + grid.options.saveWidths = false; + grid.options.saveVisible = false; + grid.options.saveSort = false; + grid.options.saveFilter = false; + grid.options.savePinning = false; + + expect( uiGridSaveStateService.saveColumns( grid ) ).toEqual([ + { name: 'col1' }, + { name: 'col2' }, + { name: 'col3' }, + { name: 'col4' } + ]); + }); + }); });