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
+
+Save
+Restore
+
+
+
+
+
+
+
+
\ 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' }
+ ]);
+ });
+ });
});