slickgrid
Version:
A lightning fast JavaScript grid/spreadsheet
261 lines (226 loc) • 7.23 kB
text/typescript
import { SlickEvent as SlickEvent_, Utils as Utils_ } from '../slick.core.js';
import type { Column, ColumnSort, SlickPlugin } from '../models/index.js';
import type { SlickGrid } from '../slick.grid.js';
// for (iife) load Slick methods from global Slick object, or use imports for (esm)
const SlickEvent = IIFE_ONLY ? Slick.Event : SlickEvent_;
const Utils = IIFE_ONLY ? Slick.Utils : Utils_;
export interface SlickStateOption {
/** optional grid state clientId */
cid: string;
/** default columns when loadnig the grid */
defaultColumns: Column[];
/** local storage key prefix */
key_prefix: string;
/** should we scroll the grid into view? */
scrollRowIntoView: boolean;
/** local storage wrapper */
storage: LocalStorageWrapper;
}
export interface CurrentState {
columns: Array<{ id: string | number; width: number | undefined; }>;
sortcols: ColumnSort[];
userData: any;
viewport: { top: number; bottom: number; leftPx: number; rightPx: number; };
}
class LocalStorageWrapper {
protected localStorage = window.localStorage;
constructor() {
if (typeof localStorage === 'undefined') {
console.error('localStorage is not available. slickgrid statepersistor disabled.');
}
}
get<T = any>(key: string) {
return new Promise<T>((resolve, reject) => {
if (!localStorage) {
reject('missing localStorage');
return;
}
try {
const d = localStorage.getItem(key);
if (d) {
return resolve(JSON.parse(d) as T);
}
resolve({} as T);
} catch (exc) {
reject(exc);
}
});
}
set(key: string, obj: any) {
if (!localStorage) { return; }
if (typeof obj !== 'undefined') {
obj = JSON.stringify(obj);
}
localStorage.setItem(key, obj);
}
};
export class SlickState implements SlickPlugin {
// --
// public API
pluginName = 'State' as const;
onStateChanged = new SlickEvent<CurrentState>('onStateChanged');
// --
// protected props
protected _grid!: SlickGrid;
protected _cid = '';
protected _store: LocalStorageWrapper;
protected _options: SlickStateOption;
protected _state?: CurrentState;
protected _userData = {
state: null,
current: null
};
constructor(options: Partial<SlickStateOption>) {
const defaults = {
key_prefix: 'slickgrid:',
storage: new LocalStorageWrapper(),
scrollRowIntoView: true
};
this._options = Utils.extend(true, {}, defaults, options);
this._store = this._options.storage;
}
init(grid: SlickGrid) {
this._grid = grid;
this._cid = grid.cid || this._options.cid;
Utils.addSlickEventPubSubWhenDefined(grid.getPubSubService(), this);
if (this._cid) {
this._grid.onColumnsResized.subscribe(this.save.bind(this));
this._grid.onColumnsReordered.subscribe(this.save.bind(this));
this._grid.onSort.subscribe(this.save.bind(this));
} else {
console.warn('grid has no client id. state persisting is disabled.');
}
}
destroy() {
this._grid.onSort.unsubscribe(this.save.bind(this));
this._grid.onColumnsReordered.unsubscribe(this.save.bind(this));
this._grid.onColumnsResized.unsubscribe(this.save.bind(this));
this.save();
}
save() {
if (this._cid && this._store) {
this._state = {
sortcols: this.getSortColumns(),
viewport: this._grid.getViewport(),
columns: this.getColumns(),
userData: null
};
this._state.userData = this._userData.current;
this.setUserDataFromState(this._state.userData);
this.onStateChanged.notify(this._state);
return this._store.set(this._options.key_prefix + this._cid, this._state);
}
}
restore() {
return new Promise((resolve, reject) => {
if (!this._cid) {
reject('missing client id');
return;
}
if (!this._store) {
reject('missing store');
return;
}
this._store.get<CurrentState>(this._options.key_prefix + this._cid)
.then((state) => {
if (state) {
if (state.sortcols) {
this._grid.setSortColumns(state.sortcols || []);
}
if (state.viewport && this._options.scrollRowIntoView) {
this._grid.scrollRowIntoView(state.viewport.top, true);
}
if (state.columns) {
const defaultColumns = this._options.defaultColumns;
if (defaultColumns) {
const defaultColumnsLookup: Record<number | string, Column> = {};
defaultColumns.forEach((colDef) => defaultColumnsLookup[colDef.id] = colDef);
const cols: Array<{ id: string | number; width: number | undefined; }> = [];
(state.columns || []).forEach((columnDef) => {
if (defaultColumnsLookup[columnDef.id]) {
cols.push(Utils.extend(true, {}, defaultColumnsLookup[columnDef.id], {
width: columnDef.width,
headerCssClass: (columnDef as Column).headerCssClass
}));
}
});
state.columns = cols;
}
this._grid.setColumns(state.columns as Column[]);
}
this.setUserDataFromState(state.userData);
}
resolve(state);
})
.catch((e) => {
reject(e);
});
});
}
/**
* allows users to add their own data to the grid state
* this function does not trigger the save() function, so the actual act of writing the state happens in save()
* therefore, it's necessary to call save() function after setting user-data
*
* @param data
* @return {State}
*/
setUserData(data: any) {
this._userData.current = data;
return this;
}
/**
*
* @internal
* @param data
* @return {State}
*/
setUserDataFromState(data: any) {
this._userData.state = data;
return this.setUserData(data);
}
/**
* returns current value of user-data
* @return {Object}
*/
getUserData() {
return this._userData.current;
}
/**
* returns user-data found in saved state
*
* @return {Object}
*/
getStateUserData() {
return this._userData.state;
}
/**
* Sets user-data to the value read from state
* @return {State}
*/
resetUserData() {
this._userData.current = this._userData.state;
return this;
}
getColumns() {
return this._grid.getColumns().map((col) => ({
id: col.id,
width: col.width
}));
}
getSortColumns() {
return this._grid.getSortColumns();
}
reset() {
this._store.set(this._options.key_prefix + this._cid, {});
this.setUserDataFromState(null);
}
}
// extend Slick namespace on window object when building as iife
if (IIFE_ONLY && window.Slick) {
Utils.extend(true, window, {
Slick: {
State: SlickState
}
});
}