/*!
* ui-grid - v4.6.0 - 2018-06-21
* Copyright (c) 2018 ; License: MIT
*/
(function () {
'use strict';
/**
* @ngdoc overview
* @name ui.grid.saveState
* @description
*
* # ui.grid.saveState
*
*
Stable This feature is stable. There should no longer be breaking api changes without a deprecation warning.
*
* This module provides the ability to save the grid state, and restore
* it when the user returns to the page.
*
* No UI is provided, the caller should provide their own UI/buttons
* as appropriate. Usually the navigate events would be used to save
* the grid state and restore it.
*
*
*
*
*
*/
var module = angular.module('ui.grid.saveState', ['ui.grid', 'ui.grid.selection', 'ui.grid.cellNav', 'ui.grid.grouping', 'ui.grid.pinning', 'ui.grid.treeView']);
/**
* @ngdoc object
* @name ui.grid.saveState.constant:uiGridSaveStateConstants
*
* @description constants available in save state module
*/
module.constant('uiGridSaveStateConstants', {
featureName: 'saveState'
});
/**
* @ngdoc service
* @name ui.grid.saveState.service:uiGridSaveStateService
*
* @description Services for saveState feature
*/
module.service('uiGridSaveStateService', ['$q', 'uiGridSaveStateConstants', 'gridUtil', '$compile', '$interval', 'uiGridConstants',
function ($q, uiGridSaveStateConstants, gridUtil, $compile, $interval, uiGridConstants ) {
var service = {
initializeGrid: function (grid) {
//add feature namespace and any properties to grid for needed state
grid.saveState = {};
this.defaultGridOptions(grid.options);
/**
* @ngdoc object
* @name ui.grid.saveState.api:PublicApi
*
* @description Public Api for saveState feature
*/
var publicApi = {
events: {
saveState: {
}
},
methods: {
saveState: {
/**
* @ngdoc function
* @name save
* @methodOf ui.grid.saveState.api:PublicApi
* @description Packages the current state of the grid into
* an object, and provides it to the user for saving
* @returns {object} the state as a javascript object that can be saved
*/
save: function () {
return service.save(grid);
},
/**
* @ngdoc function
* @name restore
* @methodOf ui.grid.saveState.api:PublicApi
* @description Restores the provided state into the grid
* @param {scope} $scope a scope that we can broadcast on
* @param {object} state the state that should be restored into the grid
* @returns {object} the promise created by refresh
*/
restore: function ( $scope, state) {
return service.restore(grid, $scope, state);
}
}
}
};
grid.api.registerEventsFromObject(publicApi.events);
grid.api.registerMethodsFromObject(publicApi.methods);
},
defaultGridOptions: function (gridOptions) {
//default option to true unless it was explicitly set to false
/**
* @ngdoc object
* @name ui.grid.saveState.api:GridOptions
*
* @description GridOptions for saveState feature, these are available to be
* set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
*/
/**
* @ngdoc object
* @name saveWidths
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the current column widths. Note that unless
* you've provided the user with some way to resize their columns (say
* the resize columns feature), then this makes little sense.
*
Defaults to true
*/
gridOptions.saveWidths = gridOptions.saveWidths !== false;
/**
* @ngdoc object
* @name saveOrder
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Restore the current column order. Note that unless
* you've provided the user with some way to reorder their columns (for
* example the move columns feature), this makes little sense.
*
Defaults to true
*/
gridOptions.saveOrder = gridOptions.saveOrder !== false;
/**
* @ngdoc object
* @name saveScroll
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the current scroll position. Note that this
* is saved as the percentage of the grid scrolled - so if your
* user returns to a grid with a significantly different number of
* rows (perhaps some data has been deleted) then the scroll won't
* actually show the same rows as before. If you want to scroll to
* a specific row then you should instead use the saveFocus option, which
* is the default.
*
* Note that this element will only be saved if the cellNav feature is
* enabled
*
Defaults to false
*/
gridOptions.saveScroll = gridOptions.saveScroll === true;
/**
* @ngdoc object
* @name saveFocus
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the current focused cell. On returning
* to this focused cell we'll also scroll. This option is
* preferred to the saveScroll option, so is set to true by
* default. If saveScroll is set to true then this option will
* be disabled.
*
* By default this option saves the current row number and column
* number, and returns to that row and column. However, if you define
* a saveRowIdentity function, then it will return you to the currently
* selected column within that row (in a business sense - so if some
* rows have been deleted, it will still find the same data, presuming it
* still exists in the list. If it isn't in the list then it will instead
* return to the same row number - i.e. scroll percentage)
*
* Note that this option will do nothing if the cellNav
* feature is not enabled.
*
*
Defaults to true (unless saveScroll is true)
*/
gridOptions.saveFocus = gridOptions.saveScroll !== true && gridOptions.saveFocus !== false;
/**
* @ngdoc object
* @name saveRowIdentity
* @propertyOf ui.grid.saveState.api:GridOptions
* @description A function that can be called, passing in a rowEntity,
* and that will return a unique id for that row. This might simply
* return the `id` field from that row (if you have one), or it might
* concatenate some fields within the row to make a unique value.
*
* This value will be used to find the same row again and set the focus
* to it, if it exists when we return.
*
*
Defaults to undefined
*/
/**
* @ngdoc object
* @name saveVisible
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save whether or not columns are visible.
*
*
Defaults to true
*/
gridOptions.saveVisible = gridOptions.saveVisible !== false;
/**
* @ngdoc object
* @name saveSort
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the current sort state for each column
*
*
Defaults to true
*/
gridOptions.saveSort = gridOptions.saveSort !== false;
/**
* @ngdoc object
* @name saveFilter
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the current filter state for each column
*
*
Defaults to true
*/
gridOptions.saveFilter = gridOptions.saveFilter !== false;
/**
* @ngdoc object
* @name saveSelection
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the currently selected rows. If the `saveRowIdentity` callback
* is defined, then it will save the id of the row and select that. If not, then
* it will attempt to select the rows by row number, which will give the wrong results
* if the data set has changed in the mean-time.
*
* Note that this option only does anything
* if the selection feature is enabled.
*
*
Defaults to true
*/
gridOptions.saveSelection = gridOptions.saveSelection !== false;
/**
* @ngdoc object
* @name saveGrouping
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the grouping configuration. If set to true and the
* grouping feature is not enabled then does nothing.
*
*
Defaults to true
*/
gridOptions.saveGrouping = gridOptions.saveGrouping !== false;
/**
* @ngdoc object
* @name saveGroupingExpandedStates
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the grouping row expanded states. If set to true and the
* grouping feature is not enabled then does nothing.
*
* This can be quite a bit of data, in many cases you wouldn't want to save this
* information.
*
*
Defaults to false
*/
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;
/**
* @ngdoc object
* @name saveTreeView
* @propertyOf ui.grid.saveState.api:GridOptions
* @description Save the treeView configuration. If set to true and the
* treeView feature is not enabled then does nothing.
*
*
Defaults to true
*/
gridOptions.saveTreeView = gridOptions.saveTreeView !== false;
},
/**
* @ngdoc function
* @name save
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Saves the current grid state into an object, and
* passes that object back to the caller
* @param {Grid} grid the grid whose state we'd like to save
* @returns {object} the state ready to be saved
*/
save: function (grid) {
var savedState = {};
savedState.columns = service.saveColumns( grid );
savedState.scrollFocus = service.saveScrollFocus( grid );
savedState.selection = service.saveSelection( grid );
savedState.grouping = service.saveGrouping( grid );
savedState.treeView = service.saveTreeView( grid );
savedState.pagination = service.savePagination( grid );
return savedState;
},
/**
* @ngdoc function
* @name restore
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Applies the provided state to the grid
*
* @param {Grid} grid the grid whose state we'd like to restore
* @param {scope} $scope a scope that we can broadcast on
* @param {object} state the state we'd like to restore
* @returns {object} the promise created by refresh
*/
restore: function( grid, $scope, state ){
if ( state.columns ) {
service.restoreColumns( grid, state.columns );
}
if ( state.scrollFocus ){
service.restoreScrollFocus( grid, $scope, state.scrollFocus );
}
if ( state.selection ){
service.restoreSelection( grid, state.selection );
}
if ( state.grouping ){
service.restoreGrouping( grid, state.grouping );
}
if ( state.treeView ){
service.restoreTreeView( grid, state.treeView );
}
if ( state.pagination ){
service.restorePagination( grid, state.pagination );
}
return grid.refresh();
},
/**
* @ngdoc function
* @name saveColumns
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @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.
*
* @param {Grid} grid the grid whose state we'd like to save
* @returns {array} the columns state ready to be saved
*/
saveColumns: function( grid ) {
var columns = [];
grid.getOnlyDataColumns().forEach( function( column ) {
var savedColumn = {};
savedColumn.name = column.name;
if ( grid.options.saveVisible ){
savedColumn.visible = column.visible;
}
if ( grid.options.saveWidths ){
savedColumn.width = column.width;
}
// these two must be copied, not just pointed too - otherwise our saved state is pointing to the same object as current state
if ( grid.options.saveSort ){
savedColumn.sort = angular.copy( column.sort );
}
if ( grid.options.saveFilter ){
savedColumn.filters = [];
column.filters.forEach( function( filter ){
var copiedFilter = {};
angular.forEach( filter, function( value, key) {
if ( key !== 'condition' && key !== '$$hashKey' && key !== 'placeholder'){
copiedFilter[key] = value;
}
});
savedColumn.filters.push(copiedFilter);
});
}
if ( !!grid.api.pinning && grid.options.savePinning ){
savedColumn.pinned = column.renderContainer ? column.renderContainer : '';
}
columns.push( savedColumn );
});
return columns;
},
/**
* @ngdoc function
* @name saveScrollFocus
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Saves the currently scroll or focus.
*
* If cellNav isn't present then does nothing - we can't return
* to the scroll position without cellNav anyway.
*
* If the cellNav module is present, and saveFocus is true, then
* it saves the currently focused cell. If rowIdentity is present
* then saves using rowIdentity, otherwise saves visibleRowNum.
*
* If the cellNav module is not present, and saveScroll is true, then
* it approximates the current scroll row and column, and saves that.
*
* @param {Grid} grid the grid whose state we'd like to save
* @returns {object} the selection state ready to be saved
*/
saveScrollFocus: function( grid ){
if ( !grid.api.cellNav ){
return {};
}
var scrollFocus = {};
if ( grid.options.saveFocus ){
scrollFocus.focus = true;
var rowCol = grid.api.cellNav.getFocusedCell();
if ( rowCol !== null ) {
if ( rowCol.col !== null ){
scrollFocus.colName = rowCol.col.colDef.name;
}
if ( rowCol.row !== null ){
scrollFocus.rowVal = service.getRowVal( grid, rowCol.row );
}
}
}
if ( grid.options.saveScroll || grid.options.saveFocus && !scrollFocus.colName && !scrollFocus.rowVal ) {
scrollFocus.focus = false;
if ( grid.renderContainers.body.prevRowScrollIndex ){
scrollFocus.rowVal = service.getRowVal( grid, grid.renderContainers.body.visibleRowCache[ grid.renderContainers.body.prevRowScrollIndex ]);
}
if ( grid.renderContainers.body.prevColScrollIndex ){
scrollFocus.colName = grid.renderContainers.body.visibleColumnCache[ grid.renderContainers.body.prevColScrollIndex ].name;
}
}
return scrollFocus;
},
/**
* @ngdoc function
* @name saveSelection
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Saves the currently selected rows, if the selection feature is enabled
* @param {Grid} grid the grid whose state we'd like to save
* @returns {array} the selection state ready to be saved
*/
saveSelection: function( grid ){
if ( !grid.api.selection || !grid.options.saveSelection ){
return [];
}
var selection = grid.api.selection.getSelectedGridRows().map( function( gridRow ) {
return service.getRowVal( grid, gridRow );
});
return selection;
},
/**
* @ngdoc function
* @name saveGrouping
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Saves the grouping state, if the grouping feature is enabled
* @param {Grid} grid the grid whose state we'd like to save
* @returns {object} the grouping state ready to be saved
*/
saveGrouping: function( grid ){
if ( !grid.api.grouping || !grid.options.saveGrouping ){
return {};
}
return grid.api.grouping.getGrouping( grid.options.saveGroupingExpandedStates );
},
/**
* @ngdoc function
* @name savePagination
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Saves the pagination state, if the pagination feature is enabled
* @param {Grid} grid the grid whose state we'd like to save
* @returns {object} the pagination state ready to be saved
*/
savePagination: function( grid ) {
if ( !grid.api.pagination || !grid.options.paginationPageSize ){
return {};
}
return {
paginationCurrentPage: grid.options.paginationCurrentPage,
paginationPageSize: grid.options.paginationPageSize
};
},
/**
* @ngdoc function
* @name saveTreeView
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Saves the tree view state, if the tree feature is enabled
* @param {Grid} grid the grid whose state we'd like to save
* @returns {object} the tree view state ready to be saved
*/
saveTreeView: function( grid ){
if ( !grid.api.treeView || !grid.options.saveTreeView ){
return {};
}
return grid.api.treeView.getTreeView();
},
/**
* @ngdoc function
* @name getRowVal
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Helper function that gets either the rowNum or
* the saveRowIdentity, given a gridRow
* @param {Grid} grid the grid the row is in
* @param {GridRow} gridRow the row we want the rowNum for
* @returns {object} an object containing { identity: true/false, row: rowNumber/rowIdentity }
*
*/
getRowVal: function( grid, gridRow ){
if ( !gridRow ) {
return null;
}
var rowVal = {};
if ( grid.options.saveRowIdentity ){
rowVal.identity = true;
rowVal.row = grid.options.saveRowIdentity( gridRow.entity );
} else {
rowVal.identity = false;
rowVal.row = grid.renderContainers.body.visibleRowCache.indexOf( gridRow );
}
return rowVal;
},
/**
* @ngdoc function
* @name restoreColumns
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @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
*/
restoreColumns: function( grid, columnsState ){
var isSortChanged = false;
columnsState.forEach( function( columnState, index ) {
var currentCol = grid.getColumn( columnState.name );
if ( currentCol && !grid.isRowHeaderColumn(currentCol) ){
if ( grid.options.saveVisible &&
( currentCol.visible !== columnState.visible ||
currentCol.colDef.visible !== columnState.visible ) ){
currentCol.visible = columnState.visible;
currentCol.colDef.visible = columnState.visible;
grid.api.core.raise.columnVisibilityChanged(currentCol);
}
if ( grid.options.saveWidths && currentCol.width !== columnState.width){
currentCol.width = columnState.width;
currentCol.hasCustomWidth = true;
}
if ( grid.options.saveSort &&
!angular.equals(currentCol.sort, columnState.sort) &&
!( currentCol.sort === undefined && angular.isEmpty(columnState.sort) ) ){
currentCol.sort = angular.copy( columnState.sort );
isSortChanged = true;
}
if ( grid.options.saveFilter &&
!angular.equals(currentCol.filters, columnState.filters ) ){
columnState.filters.forEach( function( filter, index ){
angular.extend( currentCol.filters[index], filter );
if ( typeof(filter.term) === 'undefined' || filter.term === null ){
delete currentCol.filters[index].term;
}
});
grid.api.core.raise.filterChanged();
}
if ( !!grid.api.pinning && grid.options.savePinning && currentCol.renderContainer !== columnState.pinned ){
grid.api.pinning.pinColumn(currentCol, columnState.pinned);
}
var currentIndex = grid.getOnlyDataColumns().indexOf( currentCol );
if (currentIndex !== -1) {
if (grid.options.saveOrder && currentIndex !== index) {
var column = grid.columns.splice(currentIndex + grid.rowHeaderColumns.length, 1)[0];
grid.columns.splice(index + grid.rowHeaderColumns.length, 0, column);
}
}
}
});
if ( isSortChanged ) {
grid.api.core.raise.sortChanged( grid, grid.getColumnSorting() );
}
},
/**
* @ngdoc function
* @name restoreScrollFocus
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Scrolls to the position that was saved. If focus is true, then
* sets focus to the specified row/col. If focus is false, then scrolls to the
* specified row/col.
*
* @param {Grid} grid the grid whose state we'd like to restore
* @param {scope} $scope a scope that we can broadcast on
* @param {object} scrollFocusState the scroll/focus state ready to be restored
*/
restoreScrollFocus: function( grid, $scope, scrollFocusState ){
if ( !grid.api.cellNav ){
return;
}
var colDef, row;
if ( scrollFocusState.colName ){
var colDefs = grid.options.columnDefs.filter( function( colDef ) { return colDef.name === scrollFocusState.colName; });
if ( colDefs.length > 0 ){
colDef = colDefs[0];
}
}
if ( scrollFocusState.rowVal && scrollFocusState.rowVal.row ){
if ( scrollFocusState.rowVal.identity ){
row = service.findRowByIdentity( grid, scrollFocusState.rowVal );
} else {
row = grid.renderContainers.body.visibleRowCache[ scrollFocusState.rowVal.row ];
}
}
var entity = row && row.entity ? row.entity : null ;
if ( colDef || entity ) {
if (scrollFocusState.focus ){
grid.api.cellNav.scrollToFocus( entity, colDef );
} else {
grid.scrollTo( entity, colDef );
}
}
},
/**
* @ngdoc function
* @name restoreSelection
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Selects the rows that are provided in the selection
* state. If you are using `saveRowIdentity` and more than one row matches the identity
* function then only the first is selected.
* @param {Grid} grid the grid whose state we'd like to restore
* @param {object} selectionState the selection state ready to be restored
*/
restoreSelection: function( grid, selectionState ){
if ( !grid.api.selection ){
return;
}
grid.api.selection.clearSelectedRows();
selectionState.forEach( function( rowVal ) {
if ( rowVal.identity ){
var foundRow = service.findRowByIdentity( grid, rowVal );
if ( foundRow ){
grid.api.selection.selectRow( foundRow.entity );
}
} else {
grid.api.selection.selectRowByVisibleIndex( rowVal.row );
}
});
},
/**
* @ngdoc function
* @name restoreGrouping
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Restores the grouping configuration, if the grouping feature
* is enabled.
* @param {Grid} grid the grid whose state we'd like to restore
* @param {object} groupingState the grouping state ready to be restored
*/
restoreGrouping: function( grid, groupingState ){
if ( !grid.api.grouping || typeof(groupingState) === 'undefined' || groupingState === null || angular.equals(groupingState, {}) ){
return;
}
grid.api.grouping.setGrouping( groupingState );
},
/**
* @ngdoc function
* @name restoreTreeView
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Restores the tree view configuration, if the tree view feature
* is enabled.
* @param {Grid} grid the grid whose state we'd like to restore
* @param {object} treeViewState the tree view state ready to be restored
*/
restoreTreeView: function( grid, treeViewState ){
if ( !grid.api.treeView || typeof(treeViewState) === 'undefined' || treeViewState === null || angular.equals(treeViewState, {}) ){
return;
}
grid.api.treeView.setTreeView( treeViewState );
},
/**
* @ngdoc function
* @name restorePagination
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Restores the pagination information, if pagination is enabled.
* @param {Grid} grid the grid whose state we'd like to restore
* @param {object} pagination the pagination object to be restored
* @param {number} pagination.paginationCurrentPage the page number to restore
* @param {number} pagination.paginationPageSize the number of items displayed per page
*/
restorePagination: function( grid, pagination ){
if ( !grid.api.pagination || !grid.options.paginationPageSize ){
return;
}
grid.options.paginationCurrentPage = pagination.paginationCurrentPage;
grid.options.paginationPageSize = pagination.paginationPageSize;
},
/**
* @ngdoc function
* @name findRowByIdentity
* @methodOf ui.grid.saveState.service:uiGridSaveStateService
* @description Finds a row given it's identity value, returns the first found row
* if any are found, otherwise returns null if no rows are found.
* @param {Grid} grid the grid whose state we'd like to restore
* @param {object} rowVal the row we'd like to find
* @returns {gridRow} the found row, or null if none found
*/
findRowByIdentity: function( grid, rowVal ){
if ( !grid.options.saveRowIdentity ){
return null;
}
var filteredRows = grid.rows.filter( function( gridRow ) {
if ( grid.options.saveRowIdentity( gridRow.entity ) === rowVal.row ){
return true;
} else {
return false;
}
});
if ( filteredRows.length > 0 ){
return filteredRows[0];
} else {
return null;
}
}
};
return service;
}
]);
/**
* @ngdoc directive
* @name ui.grid.saveState.directive:uiGridSaveState
* @element div
* @restrict A
*
* @description Adds saveState features to grid
*
* @example
var app = angular.module('app', ['ui.grid', 'ui.grid.saveState']);
app.controller('MainCtrl', ['$scope', function ($scope) {
$scope.data = [
{ name: 'Bob', title: 'CEO' },
{ name: 'Frank', title: 'Lowly Developer' }
];
$scope.gridOptions = {
columnDefs: [
{name: 'name'},
{name: 'title', enableCellEdit: true}
],
data: $scope.data
};
}]);
*/
module.directive('uiGridSaveState', ['uiGridSaveStateConstants', 'uiGridSaveStateService', 'gridUtil', '$compile',
function (uiGridSaveStateConstants, uiGridSaveStateService, gridUtil, $compile) {
return {
replace: true,
priority: 0,
require: '^uiGrid',
scope: false,
link: function ($scope, $elm, $attrs, uiGridCtrl) {
uiGridSaveStateService.initializeGrid(uiGridCtrl.grid);
}
};
}
]);
})();