From 45549a3bce35ad89d813301c54f24e2f2e3711f4 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 23 May 2018 17:46:58 -0400 Subject: [PATCH 01/35] WIP GridStateModel --- admin/tabs/configs/ConfigPanel.js | 32 +++--- cmp/grid/ColChooserModel.js | 6 +- cmp/grid/GridModel.js | 8 ++ cmp/grid/GridStateModel.js | 160 ++++++++++++++++++++++++++++++ cmp/grid/index.js | 1 + columns/Utils.js | 2 +- svc/LocalStorageService.js | 3 + 7 files changed, 195 insertions(+), 17 deletions(-) create mode 100644 cmp/grid/GridStateModel.js diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index 9cb38bbd47..e8e1387c23 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -9,6 +9,7 @@ import {button} from '@xh/hoist/kit/blueprint'; import {XH, HoistComponent} from '@xh/hoist/core'; import {restGrid, RestGridModel, RestStore} from '@xh/hoist/cmp/rest'; import {fragment} from '@xh/hoist/cmp/layout'; +import {colChooserButton, GridStateModel} from '@xh/hoist/cmp/grid'; import {boolCheckCol, baseCol} from '@xh/hoist/columns/Core'; import {nameCol} from '@xh/hoist/admin/columns/Columns'; import {Icon} from '@xh/hoist/icon'; @@ -22,6 +23,8 @@ export class ConfigPanel extends Component { differModel = new ConfigDifferModel({}); gridModel = new RestGridModel({ + enableColChooser: true, + gridStateModel: new GridStateModel({trackColumns: true, xhStateId: 'configPanel'}), store: new RestStore({ url: 'rest/configAdmin', reloadLookupsOnLoad: true, @@ -94,15 +97,15 @@ export class ConfigPanel extends Component { sortBy: 'name', groupBy: 'groupName', columns: this.filterForEnv([ - nameCol({fixedWidth: 200}), - baseCol({field: 'valueType', headerName: 'Type', fixedWidth: 80, align: 'center'}), - this.valCol({field: 'prodValue', env: 'Production'}), - this.valCol({field: 'betaValue', env: 'Beta'}), - this.valCol({field: 'stageValue', env: 'Staging'}), - this.valCol({field: 'devValue', env: 'Development'}), - boolCheckCol({field: 'clientVisible', headerName: 'Client?', fixedWidth: 75}), + nameCol({fixedWidth: 200, xhId: 'configName'}), + baseCol({field: 'valueType', headerName: 'Type', fixedWidth: 80, align: 'center', xhId: 'configType'}), + this.valCol({field: 'prodValue', env: 'Production', xhId: 'configProdVal'}), + this.valCol({field: 'betaValue', env: 'Beta', xhId: 'configBetaVal'}), + this.valCol({field: 'stageValue', env: 'Staging', xhId: 'configStageVal'}), + this.valCol({field: 'devValue', env: 'Development', xhId: 'configDevVal'}), + boolCheckCol({field: 'clientVisible', headerName: 'Client?', fixedWidth: 75, xhId: 'configClientVisible'}), baseCol({field: 'groupName', headerName: 'Group', fixedWidth: 100}), - baseCol({field: 'note', minWidth: 60}) + baseCol({field: 'note', minWidth: 60, xhId: 'configNote'}) ]), editors: this.filterForEnv([ {field: 'name'}, @@ -157,11 +160,14 @@ export class ConfigPanel extends Component { } extraToolbarItems = () => { - return button({ - icon: Icon.diff(), - text: 'Compare w/ Remote', - onClick: this.onDifferBtnClick - }); + return [ + button({ + icon: Icon.diff(), + text: 'Compare w/ Remote', + onClick: this.onDifferBtnClick + }), + colChooserButton({gridModel: this.gridModel.gridModel}) + ]; } onDifferBtnClick = () => { diff --git a/cmp/grid/ColChooserModel.js b/cmp/grid/ColChooserModel.js index 36b454bc4c..4792a6eff0 100644 --- a/cmp/grid/ColChooserModel.js +++ b/cmp/grid/ColChooserModel.js @@ -11,10 +11,10 @@ import {LeftRightChooserModel} from '@xh/hoist/cmp/leftrightchooser'; @HoistModel() export class ColChooserModel { - gridModel = null - lrModel = null + gridModel = null; + lrModel = null; - @observable isOpen = false + @observable isOpen = false; constructor(gridModel) { this.gridModel = gridModel; diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 0376d5d6bd..dea3e142ee 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -25,6 +25,7 @@ export class GridModel { selection = null; contextMenuFn = null; colChooserModel = null; + gridStateModel = null; @observable.ref agApi = null; @observable.ref columns = []; @@ -81,6 +82,7 @@ export class GridModel { sortBy = [], groupBy = null, enableColChooser = false, + gridStateModel = null, contextMenuFn = () => this.defaultContextMenu() }) { this.store = store; @@ -91,11 +93,17 @@ export class GridModel { this.emptyText = emptyText; if (enableColChooser) { + console.log('setting up col chooser'); this.colChooserModel = new ColChooserModel(this); } this.setGroupBy(groupBy); this.setSortBy(sortBy); + + if (gridStateModel) { + this.gridStateModel = gridStateModel; + this.gridStateModel.init(this); + } } exportDataAsExcel(params) { diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js new file mode 100644 index 0000000000..3b2a375305 --- /dev/null +++ b/cmp/grid/GridStateModel.js @@ -0,0 +1,160 @@ +/* + * This file belongs to Hoist, an application development toolkit + * developed by Extremely Heavy Industries (www.xh.io | info@xh.io) + * + * Copyright © 2018 Extremely Heavy Industries Inc. + */ +import {XH, HoistModel} from '@xh/hoist/core'; +import {action, computed, observable} from '@xh/hoist/mobx'; +import {cloneDeep, find} from 'lodash'; + +@HoistModel() +export class GridStateModel { + + trackColumns = true; + + gridModel = null; + xhStateId = null; + + state = {}; + userState = null; + defaultState = null; + + + constructor({trackColumns, xhStateId}) { + this.trackColumns = trackColumns; + this.xhStateId = xhStateId; + } + + init(gridModel) { + this.gridModel = gridModel; + + this.ensureCompatible(); + + if (this.trackColumns) { + this.addReaction({ + track: () => this.gridModel.columns, + run: this.onColumnsChanged // need this fat arrow? + }); + } + + this.initializeState(); + } + + initializeState() { + this.userState = this.readState(this.getStateKey()); + this.defaultState = this.readStateFromGrid(); + console.log(this.userState); + this.loadState(this.userState); + } + + readStateFromGrid() { + return { + columns: this.getColumnState() + }; + } + + + //-------------------------- + // For Extension / Override // ??? really? + //-------------------------- + readState(stateKey) { + console.log('reading state', XH.localStorageService.get(stateKey, {})); + return XH.localStorageService.get(stateKey, {}); + } + + saveState(stateKey, state) { + console.log('saving state'); + XH.localStorageService.set(stateKey, state); + } + + resetState(stateKey) { + XH.localStorageService.remove(stateKey); + } + + + loadState(state) { + this.state = cloneDeep(state || this.readState(this.getStateKey()) || {}); + this.updateGridColumns(); + } + + + //-------------------------- + // Columns + //-------------------------- + onColumnsChanged() { + if (this.trackColumns) { + console.log(this, 'onColCHanged'); + this.state.columns = this.getColumnState(); + this.saveStateChange(); + } + } + + getColumnState() { + if (!this.trackColumns) return undefined; + + const ret = []; + + this.gridModel.columns.forEach(it => { + const colSpec = { + xhId: it.xhId, + // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 + hide: it.hide + }; + + if (it.xhId != null) { + ret.push(colSpec); + } + }); + + debugger; + + return ret; + } + + updateGridColumns() { + const {gridModel} = this, + state = this.state, + cols = gridModel.cloneColumns(); + + if (this.trackColumns && state.columns) { + state.columns.forEach(colState => { + const col = find(cols, {xhId: colState.xhId}); + if (!col) return; + + col.hide = colState.hide; + }); + debugger; + gridModel.setColumns(cols); + } + + } + + //-------------------------- + // Helper + //-------------------------- + saveStateChange() { + if (this.state && !this._resetting) { // ?? + this.saveState(this.getStateKey(), this.state); + } + } + + getStateKey() { + const xhStateId = this.xhStateId; + if (!xhStateId) { + throw XH.exception('GridModel must have a xhStateId in order to use the xhGridState plugin with storage enabled'); + } + return 'gridState.' + xhStateId; + } + + ensureCompatible() { + const cols = this.gridModel.columns; + // colsWithoutXhId = cols.filter(col => !col.getXhId()); + // + // + // if (this.trackColumns && colsWithoutXhId.length) { + // throw XH.exception('xhGridState plugin with "trackColumns=true" requires all columns to have an xhId'); + // } + } + +} \ No newline at end of file diff --git a/cmp/grid/index.js b/cmp/grid/index.js index d7183cfc03..92cf61ff69 100644 --- a/cmp/grid/index.js +++ b/cmp/grid/index.js @@ -7,4 +7,5 @@ export * from './Grid'; export * from './GridModel'; +export * from './GridStateModel'; export * from './ColChooserButton'; \ No newline at end of file diff --git a/columns/Utils.js b/columns/Utils.js index d58664ad11..528ea7125a 100644 --- a/columns/Utils.js +++ b/columns/Utils.js @@ -12,7 +12,7 @@ import {castArray, defaults, isNumber, omit, startCase} from 'lodash'; const hoistColConfigs = [ 'align', 'elementRenderer', 'fixedWidth', 'flex', 'chooserDescription', 'chooserGroup', 'chooserName', 'excludeFromChooser', - 'agColDef' + 'agColDef', 'xhId' ]; /** diff --git a/svc/LocalStorageService.js b/svc/LocalStorageService.js index d8b69f70e8..6bf9b3ae30 100644 --- a/svc/LocalStorageService.js +++ b/svc/LocalStorageService.js @@ -17,6 +17,9 @@ export class LocalStorageService { const storage = this.getInstance(), val = storage.get(key, defaultValue); + console.log('val', val); + console.log('defaultValue', defaultValue) + if (!val) throw XH.exception(`Key '${key}' not found`); return val; From c72160656909792865592edf9864c86abc5cb4fb Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 23 May 2018 17:48:12 -0400 Subject: [PATCH 02/35] Clean up debuggers --- cmp/grid/GridStateModel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 3b2a375305..24ac8b4a38 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -107,8 +107,6 @@ export class GridStateModel { } }); - debugger; - return ret; } @@ -124,7 +122,7 @@ export class GridStateModel { col.hide = colState.hide; }); - debugger; + gridModel.setColumns(cols); } From 7a93ffb1d778527e689fda5d6c32d42ed0202cc5 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 23 May 2018 17:49:21 -0400 Subject: [PATCH 03/35] Linting --- cmp/grid/GridStateModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 24ac8b4a38..4481fd1f3c 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -5,7 +5,6 @@ * Copyright © 2018 Extremely Heavy Industries Inc. */ import {XH, HoistModel} from '@xh/hoist/core'; -import {action, computed, observable} from '@xh/hoist/mobx'; import {cloneDeep, find} from 'lodash'; @HoistModel() @@ -146,7 +145,7 @@ export class GridStateModel { } ensureCompatible() { - const cols = this.gridModel.columns; + // const cols = this.gridModel.columns; // colsWithoutXhId = cols.filter(col => !col.getXhId()); // // From 098e808f2c3228739a4c87eae159079eff7af3de Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 09:08:49 -0400 Subject: [PATCH 04/35] Clean up comments and console.logs --- cmp/grid/GridModel.js | 1 - cmp/grid/GridStateModel.js | 9 +++------ svc/LocalStorageService.js | 3 --- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index dea3e142ee..29f06cda55 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -93,7 +93,6 @@ export class GridModel { this.emptyText = emptyText; if (enableColChooser) { - console.log('setting up col chooser'); this.colChooserModel = new ColChooserModel(this); } diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 4481fd1f3c..7d26765957 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -33,7 +33,7 @@ export class GridStateModel { if (this.trackColumns) { this.addReaction({ track: () => this.gridModel.columns, - run: this.onColumnsChanged // need this fat arrow? + run: this.onColumnsChanged }); } @@ -43,7 +43,7 @@ export class GridStateModel { initializeState() { this.userState = this.readState(this.getStateKey()); this.defaultState = this.readStateFromGrid(); - console.log(this.userState); + this.loadState(this.userState); } @@ -58,12 +58,10 @@ export class GridStateModel { // For Extension / Override // ??? really? //-------------------------- readState(stateKey) { - console.log('reading state', XH.localStorageService.get(stateKey, {})); return XH.localStorageService.get(stateKey, {}); } saveState(stateKey, state) { - console.log('saving state'); XH.localStorageService.set(stateKey, state); } @@ -83,7 +81,6 @@ export class GridStateModel { //-------------------------- onColumnsChanged() { if (this.trackColumns) { - console.log(this, 'onColCHanged'); this.state.columns = this.getColumnState(); this.saveStateChange(); } @@ -97,7 +94,7 @@ export class GridStateModel { this.gridModel.columns.forEach(it => { const colSpec = { xhId: it.xhId, - // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 + // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? hide: it.hide }; diff --git a/svc/LocalStorageService.js b/svc/LocalStorageService.js index 6bf9b3ae30..d8b69f70e8 100644 --- a/svc/LocalStorageService.js +++ b/svc/LocalStorageService.js @@ -17,9 +17,6 @@ export class LocalStorageService { const storage = this.getInstance(), val = storage.get(key, defaultValue); - console.log('val', val); - console.log('defaultValue', defaultValue) - if (!val) throw XH.exception(`Key '${key}' not found`); return val; From 7e9dc9bb17e5de2a2ca7bf994fec9cef5bd286a3 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 09:23:04 -0400 Subject: [PATCH 05/35] Reinstate check for xhIds, give config groupCol an xhId --- admin/tabs/configs/ConfigPanel.js | 2 +- cmp/grid/GridStateModel.js | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index e8e1387c23..28b6fe7daa 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -104,7 +104,7 @@ export class ConfigPanel extends Component { this.valCol({field: 'stageValue', env: 'Staging', xhId: 'configStageVal'}), this.valCol({field: 'devValue', env: 'Development', xhId: 'configDevVal'}), boolCheckCol({field: 'clientVisible', headerName: 'Client?', fixedWidth: 75, xhId: 'configClientVisible'}), - baseCol({field: 'groupName', headerName: 'Group', fixedWidth: 100}), + baseCol({field: 'groupName', headerName: 'Group', fixedWidth: 100, xhId: 'configGroupName'}), baseCol({field: 'note', minWidth: 60, xhId: 'configNote'}) ]), editors: this.filterForEnv([ diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 7d26765957..e339b076be 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -142,13 +142,12 @@ export class GridStateModel { } ensureCompatible() { - // const cols = this.gridModel.columns; - // colsWithoutXhId = cols.filter(col => !col.getXhId()); - // - // - // if (this.trackColumns && colsWithoutXhId.length) { - // throw XH.exception('xhGridState plugin with "trackColumns=true" requires all columns to have an xhId'); - // } + const cols = this.gridModel.columns, + colsWithoutXhId = cols.filter(col => !col.xhId); + + if (this.trackColumns && colsWithoutXhId.length) { + throw XH.exception('GridStateModel with "trackColumns=true" requires all columns to have an xhId'); + } } } \ No newline at end of file From 10a19a02453deef2c53191e506ddcab64aed3805 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 12:24:16 -0400 Subject: [PATCH 06/35] Stateful column ordering --- cmp/grid/Grid.js | 64 +++++++++++++++++++++++++++++++++++++- cmp/grid/GridModel.js | 15 +++++++++ cmp/grid/GridStateModel.js | 26 +++++++++------- 3 files changed, 93 insertions(+), 12 deletions(-) diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index a8d22dade1..efa5d16d64 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -91,7 +91,17 @@ class Grid extends Component { onSelectionChanged: this.onSelectionChanged, onSortChanged: this.onSortChanged, onGridSizeChanged: this.onGridSizeChanged, - onComponentStateChanged: this.onComponentStateChanged + onComponentStateChanged: this.onComponentStateChanged, + onColumnVisible: this.onColumnVisible, + onColumnResized: this.onColumnResized, + onColumnMoved: this.onColumnMoved, + onDragStopped: this.onDragStopped, + onColumnRowGroupChanged: this.onColumnRowGroupChanged, + onNewColumnsLoaded: this.onNewColumnsLoaded, + onGridColumnsChanged: this.onGridColumnsChanged, + onDisplayedColumnsChanged: this.onDisplayedColumnsChanged, + onColumnEverythingChanged: this.onColumnEverythingChanged + }) }), colChooser({ @@ -101,6 +111,57 @@ class Grid extends Component { ); } + // given the way we are show/hiding these columns i.e. reloading all columns into an observable this is NOT firing as we'd need. + // onColumnVisible = (ev) => { + // if (this.model.gridStateModel) console.log('ColumnVisible change via grid event', ev); + // } + + // I think this fires too often to be useful. It fires for every column as ANY column size changes (due to resizing cols to fit i think) + // col width state is not necessarily going to be supported anyway. + // onColumnResized = (ev) => { + // if (this.model.gridStateModel) console.log('ColumnResized via grid event', ev); + // } + + + + + // this has the benefit of NOT firing on grid load, but needs to be used with 'dragStopped' + // onColumnMoved = (ev) => { + // const gridStateModel = this.model.gridStateModel; + // if (gridStateModel) { + // debugger; + // gridStateModel.onColumnsChanged(); + // } + // } + // + // this has the benefit of NOT firing on grid load, but needs to be used with 'dragStopped' + onDragStopped = (ev) => { + const gridStateModel = this.model.gridStateModel; + if (gridStateModel) { + this.model.syncColumnOrder(); + } + } + + // onColumnRowGroupChanged = (ev) => { + // if (this.model.gridStateModel) console.log('ColumnRowGroupChanged via grid event', ev); + // } + // + // onNewColumnsLoaded = (ev) => { + // if (this.model.gridStateModel) console.log('NewColumnsLoaded via grid event', ev); + // } + // + // onGridColumnsChanged = (ev) => { + // if (this.model.gridStateModel) console.log('GridColumnsChanged via grid event', ev, this.model); + // } + // + // onDisplayedColumnsChanged = (ev) => { + // if (this.model.gridStateModel) console.log('DiplayedColumnsChanged via grid event', ev); + // } + // + // onColumnEverythingChanged = (ev) => { + // if (this.model.gridStateModel) console.log('ColumnEverythingChanged via grid event', ev); + // } + //------------------------ // Implementation //------------------------ @@ -228,6 +289,7 @@ class Grid extends Component { model.setAgApi(api); api.setSortModel(model.sortBy); api.sizeColumnsToFit(); + if (this.model.gridStateModel) console.log('onGridReady', params); // this fires once, could be used to set a flag to check so that we don';t mes with state until it's finsihed. i.e. like in onBefore render in sencha world. } onNavigateToNextCell = (params) => { diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 29f06cda55..3ad718ec05 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -189,6 +189,21 @@ export class GridModel { } } + syncColumnOrder() { + const cols = this.columns, + agCols = this.agApi.columnController.gridColumns, + orderedCols = []; + + // This is best way I've found to check the column order in the grid (our observable doesn't change) + // Also, these have an 'actualWidth' should we choose to go down that road + agCols.forEach(agCol => { + const newCol = find(cols, {field: agCol.colDef.field}); // have to use field as agGrid doesn't retain xhId + orderedCols.push(newCol); + }); + + this.setColumns(orderedCols); + } + //----------------------- // Implementation //----------------------- diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index e339b076be..41bffc74c7 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -12,7 +12,7 @@ export class GridStateModel { trackColumns = true; - gridModel = null; + parent = null; xhStateId = null; state = {}; @@ -26,14 +26,14 @@ export class GridStateModel { } init(gridModel) { - this.gridModel = gridModel; + this.parent = gridModel; this.ensureCompatible(); if (this.trackColumns) { this.addReaction({ - track: () => this.gridModel.columns, - run: this.onColumnsChanged + track: () => this.parent.columns, // columns are not changing on reorder + run: this.onColumnsChanged // firing on load due to first state setting of columns, is this a problem? }); } @@ -89,9 +89,10 @@ export class GridStateModel { getColumnState() { if (!this.trackColumns) return undefined; - const ret = []; + const ret = [], + gridModel = this.parent; - this.gridModel.columns.forEach(it => { + gridModel.columns.forEach(it => { const colSpec = { xhId: it.xhId, // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? @@ -107,19 +108,22 @@ export class GridStateModel { } updateGridColumns() { - const {gridModel} = this, + const {parent} = this, state = this.state, - cols = gridModel.cloneColumns(); + cols = parent.cloneColumns(), + newColumns = []; + // going to need to deal with new columns and stale state here if (this.trackColumns && state.columns) { state.columns.forEach(colState => { const col = find(cols, {xhId: colState.xhId}); if (!col) return; col.hide = colState.hide; + newColumns.push(col); }); - gridModel.setColumns(cols); + parent.setColumns(newColumns); } } @@ -136,13 +140,13 @@ export class GridStateModel { getStateKey() { const xhStateId = this.xhStateId; if (!xhStateId) { - throw XH.exception('GridModel must have a xhStateId in order to use the xhGridState plugin with storage enabled'); + throw XH.exception('GridStateModel must have a xhStateId in order to store state'); } return 'gridState.' + xhStateId; } ensureCompatible() { - const cols = this.gridModel.columns, + const cols = this.parent.columns, colsWithoutXhId = cols.filter(col => !col.xhId); if (this.trackColumns && colsWithoutXhId.length) { From 5e226cb725112e2856e47e9147b1abd27d4febd1 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 13:11:54 -0400 Subject: [PATCH 07/35] Deal with stale column state and new columns in code --- cmp/grid/Grid.js | 2 -- cmp/grid/GridStateModel.js | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index efa5d16d64..93d919e349 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -123,8 +123,6 @@ class Grid extends Component { // } - - // this has the benefit of NOT firing on grid load, but needs to be used with 'dragStopped' // onColumnMoved = (ev) => { // const gridStateModel = this.model.gridStateModel; diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 41bffc74c7..921a7cadfb 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -108,19 +108,26 @@ export class GridStateModel { } updateGridColumns() { - const {parent} = this, - state = this.state, + const {parent, state} = this, cols = parent.cloneColumns(), - newColumns = []; + newColumns = [], + foundColumns = []; - // going to need to deal with new columns and stale state here if (this.trackColumns && state.columns) { state.columns.forEach(colState => { const col = find(cols, {xhId: colState.xhId}); - if (!col) return; + if (!col) return; // deals with stale col in state, will sorting on a stale col still be a problem? + col.hide = colState.hide; newColumns.push(col); + foundColumns.push(col); + }); + + cols.forEach((col, idx) => { + if (!find(foundColumns, {xhId: col.xhId})) { + newColumns.splice(idx, 0, col); + } }); parent.setColumns(newColumns); From 5e15cb46baa292ea46ec039b09ac34c56c9909bb Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 14:03:42 -0400 Subject: [PATCH 08/35] Stateful sorting --- admin/tabs/configs/ConfigPanel.js | 2 +- cmp/grid/Grid.js | 16 +++++++-------- cmp/grid/GridModel.js | 1 + cmp/grid/GridStateModel.js | 34 +++++++++++++++++++++++++++---- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index 28b6fe7daa..301e2f8a63 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -24,7 +24,7 @@ export class ConfigPanel extends Component { gridModel = new RestGridModel({ enableColChooser: true, - gridStateModel: new GridStateModel({trackColumns: true, xhStateId: 'configPanel'}), + gridStateModel: new GridStateModel({trackColumns: true, trackSort: true, xhStateId: 'configPanel'}), store: new RestStore({ url: 'rest/configAdmin', reloadLookupsOnLoad: true, diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index 93d919e349..e5d50fa9c2 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -92,15 +92,15 @@ class Grid extends Component { onSortChanged: this.onSortChanged, onGridSizeChanged: this.onGridSizeChanged, onComponentStateChanged: this.onComponentStateChanged, - onColumnVisible: this.onColumnVisible, - onColumnResized: this.onColumnResized, - onColumnMoved: this.onColumnMoved, + // onColumnVisible: this.onColumnVisible, + // onColumnResized: this.onColumnResized, + // onColumnMoved: this.onColumnMoved, onDragStopped: this.onDragStopped, - onColumnRowGroupChanged: this.onColumnRowGroupChanged, - onNewColumnsLoaded: this.onNewColumnsLoaded, - onGridColumnsChanged: this.onGridColumnsChanged, - onDisplayedColumnsChanged: this.onDisplayedColumnsChanged, - onColumnEverythingChanged: this.onColumnEverythingChanged + // onColumnRowGroupChanged: this.onColumnRowGroupChanged, + // onNewColumnsLoaded: this.onNewColumnsLoaded, + // onGridColumnsChanged: this.onGridColumnsChanged, + // onDisplayedColumnsChanged: this.onDisplayedColumnsChanged, + // onColumnEverythingChanged: this.onColumnEverythingChanged }) }), diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 3ad718ec05..d44d1df53c 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -189,6 +189,7 @@ export class GridModel { } } + // kinda want to set up this business with add Reacytion instateModel for symmetry, maybe I should put an observable on this model that track current agCols? syncColumnOrder() { const cols = this.columns, agCols = this.agApi.columnController.gridColumns, diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 921a7cadfb..9764ae8465 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -11,6 +11,7 @@ import {cloneDeep, find} from 'lodash'; export class GridStateModel { trackColumns = true; + trackSort = true; parent = null; xhStateId = null; @@ -20,8 +21,9 @@ export class GridStateModel { defaultState = null; - constructor({trackColumns, xhStateId}) { + constructor({trackColumns, trackSort, xhStateId}) { this.trackColumns = trackColumns; + this.trackSort = trackSort; this.xhStateId = xhStateId; } @@ -37,19 +39,27 @@ export class GridStateModel { }); } + if (this.trackSort) { + this.addReaction({ + track: () => this.parent.sortBy, // columns are not changing on reorder + run: this.onSortChanged // firing on load due to first state setting of columns, is this a problem? + }); + } + this.initializeState(); } initializeState() { this.userState = this.readState(this.getStateKey()); - this.defaultState = this.readStateFromGrid(); + this.defaultState = this.readStateFromGrid(); // for resetting? this.loadState(this.userState); } readStateFromGrid() { return { - columns: this.getColumnState() + columns: this.getColumnState(), + sortBy: this.parent.sortBy }; } @@ -73,6 +83,7 @@ export class GridStateModel { loadState(state) { this.state = cloneDeep(state || this.readState(this.getStateKey()) || {}); this.updateGridColumns(); + this.updateGridSort(); } @@ -80,7 +91,7 @@ export class GridStateModel { // Columns //-------------------------- onColumnsChanged() { - if (this.trackColumns) { + if (this.trackColumns) { // don't think I need this check. the 'listener' isn't added if not this.state.columns = this.getColumnState(); this.saveStateChange(); } @@ -135,6 +146,21 @@ export class GridStateModel { } + //-------------------------- + // Sort + //-------------------------- + onSortChanged() { + if (this.trackSort) { // see onColumnChanged above + this.state.sortBy = this.parent.sortBy; + this.saveStateChange(); + } + } + + updateGridSort() { + const sortBy = this.state.sortBy; + if (sortBy) this.parent.setSortBy(sortBy); + } + //-------------------------- // Helper //-------------------------- From 3a2f21c98571c10f99cb575d481f26f2a4597e6e Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 14:30:04 -0400 Subject: [PATCH 09/35] Don't set stateful sortBy on a grid if grid doesn't have a corresponding column --- cmp/grid/Grid.js | 2 +- cmp/grid/GridStateModel.js | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index e5d50fa9c2..f72e8dd9fe 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -95,7 +95,7 @@ class Grid extends Component { // onColumnVisible: this.onColumnVisible, // onColumnResized: this.onColumnResized, // onColumnMoved: this.onColumnMoved, - onDragStopped: this.onDragStopped, + onDragStopped: this.onDragStopped // onColumnRowGroupChanged: this.onColumnRowGroupChanged, // onNewColumnsLoaded: this.onNewColumnsLoaded, // onGridColumnsChanged: this.onGridColumnsChanged, diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 9764ae8465..bccd0d6aae 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -157,8 +157,11 @@ export class GridStateModel { } updateGridSort() { - const sortBy = this.state.sortBy; - if (sortBy) this.parent.setSortBy(sortBy); + const sortBy = this.state.sortBy, + cols = this.parent.columns, + gridHasCol = sortBy ? sortBy.some(it => find(cols, {field: it.colId})) : false; + + if (sortBy && gridHasCol) this.parent.setSortBy(sortBy); } //-------------------------- From b1435ac07760d67df92187c1bc476588e06b5241 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 17:18:41 -0400 Subject: [PATCH 10/35] Make stateful column ordering symmetrical with other stateful cmps --- cmp/grid/Grid.js | 53 +++----------------------------------- cmp/grid/GridModel.js | 19 ++++---------- cmp/grid/GridStateModel.js | 24 ++++++++++------- 3 files changed, 24 insertions(+), 72 deletions(-) diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index f72e8dd9fe..27db92e04b 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -111,55 +111,6 @@ class Grid extends Component { ); } - // given the way we are show/hiding these columns i.e. reloading all columns into an observable this is NOT firing as we'd need. - // onColumnVisible = (ev) => { - // if (this.model.gridStateModel) console.log('ColumnVisible change via grid event', ev); - // } - - // I think this fires too often to be useful. It fires for every column as ANY column size changes (due to resizing cols to fit i think) - // col width state is not necessarily going to be supported anyway. - // onColumnResized = (ev) => { - // if (this.model.gridStateModel) console.log('ColumnResized via grid event', ev); - // } - - - // this has the benefit of NOT firing on grid load, but needs to be used with 'dragStopped' - // onColumnMoved = (ev) => { - // const gridStateModel = this.model.gridStateModel; - // if (gridStateModel) { - // debugger; - // gridStateModel.onColumnsChanged(); - // } - // } - // - // this has the benefit of NOT firing on grid load, but needs to be used with 'dragStopped' - onDragStopped = (ev) => { - const gridStateModel = this.model.gridStateModel; - if (gridStateModel) { - this.model.syncColumnOrder(); - } - } - - // onColumnRowGroupChanged = (ev) => { - // if (this.model.gridStateModel) console.log('ColumnRowGroupChanged via grid event', ev); - // } - // - // onNewColumnsLoaded = (ev) => { - // if (this.model.gridStateModel) console.log('NewColumnsLoaded via grid event', ev); - // } - // - // onGridColumnsChanged = (ev) => { - // if (this.model.gridStateModel) console.log('GridColumnsChanged via grid event', ev, this.model); - // } - // - // onDisplayedColumnsChanged = (ev) => { - // if (this.model.gridStateModel) console.log('DiplayedColumnsChanged via grid event', ev); - // } - // - // onColumnEverythingChanged = (ev) => { - // if (this.model.gridStateModel) console.log('ColumnEverythingChanged via grid event', ev); - // } - //------------------------ // Implementation //------------------------ @@ -303,6 +254,10 @@ class Grid extends Component { this.model.setSortBy(ev.api.getSortModel()); } + onDragStopped = (ev) => { + this.model.setGridColumnOrder(ev.api.columnController.gridColumns); + } + onGridSizeChanged = (ev) => { ev.api.sizeColumnsToFit(); } diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index d44d1df53c..214625e542 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -29,6 +29,7 @@ export class GridModel { @observable.ref agApi = null; @observable.ref columns = []; + @observable.ref gridColumnOrder = []; @observable.ref sortBy = []; @observable groupBy = null; @@ -189,20 +190,10 @@ export class GridModel { } } - // kinda want to set up this business with add Reacytion instateModel for symmetry, maybe I should put an observable on this model that track current agCols? - syncColumnOrder() { - const cols = this.columns, - agCols = this.agApi.columnController.gridColumns, - orderedCols = []; - - // This is best way I've found to check the column order in the grid (our observable doesn't change) - // Also, these have an 'actualWidth' should we choose to go down that road - agCols.forEach(agCol => { - const newCol = find(cols, {field: agCol.colDef.field}); // have to use field as agGrid doesn't retain xhId - orderedCols.push(newCol); - }); - - this.setColumns(orderedCols); + @action + setGridColumnOrder(columns) { + const fieldArr = columns.map(col => col.colId || col.field); // have to use fields/colIds as agGrid doesn't retain xhId + this.gridColumnOrder = fieldArr; } //----------------------- diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index bccd0d6aae..f65a974fae 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -34,15 +34,15 @@ export class GridStateModel { if (this.trackColumns) { this.addReaction({ - track: () => this.parent.columns, // columns are not changing on reorder + track: () => [gridModel.columns, gridModel.gridColumnOrder], // 'columns' observable is not changed on reorder run: this.onColumnsChanged // firing on load due to first state setting of columns, is this a problem? }); } if (this.trackSort) { this.addReaction({ - track: () => this.parent.sortBy, // columns are not changing on reorder - run: this.onSortChanged // firing on load due to first state setting of columns, is this a problem? + track: () => gridModel.sortBy, + run: this.onSortChanged }); } @@ -100,18 +100,24 @@ export class GridStateModel { getColumnState() { if (!this.trackColumns) return undefined; - const ret = [], - gridModel = this.parent; + const gridModel = this.parent, + colOrder = gridModel.gridColumnOrder, + orderedCols = []; - gridModel.columns.forEach(it => { + colOrder.forEach(field => { + const col = find(gridModel.columns, {field}); + orderedCols.push(col); + }); + + const ret = orderedCols.map(it => { const colSpec = { xhId: it.xhId, // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? hide: it.hide }; - if (it.xhId != null) { - ret.push(colSpec); + if (it.xhId != null) { // do we need this? (we check in ensureCompatible) + return colSpec; } }); @@ -129,7 +135,6 @@ export class GridStateModel { const col = find(cols, {xhId: colState.xhId}); if (!col) return; // deals with stale col in state, will sorting on a stale col still be a problem? - col.hide = colState.hide; newColumns.push(col); foundColumns.push(col); @@ -141,6 +146,7 @@ export class GridStateModel { } }); + parent.setGridColumnOrder(newColumns); parent.setColumns(newColumns); } From bd6a2f01fe3f8bfd60d0223aea4252755472a427 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 24 May 2018 17:25:58 -0400 Subject: [PATCH 11/35] Clean ups --- admin/tabs/configs/ConfigPanel.js | 2 +- cmp/grid/Grid.js | 8 -------- cmp/grid/GridStateModel.js | 2 +- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index 301e2f8a63..f0d04c10f6 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -24,7 +24,7 @@ export class ConfigPanel extends Component { gridModel = new RestGridModel({ enableColChooser: true, - gridStateModel: new GridStateModel({trackColumns: true, trackSort: true, xhStateId: 'configPanel'}), + gridStateModel: new GridStateModel({xhStateId: 'configPanel', trackColumns: true, trackSort: true}), store: new RestStore({ url: 'rest/configAdmin', reloadLookupsOnLoad: true, diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index 27db92e04b..87c4cca4e6 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -92,15 +92,8 @@ class Grid extends Component { onSortChanged: this.onSortChanged, onGridSizeChanged: this.onGridSizeChanged, onComponentStateChanged: this.onComponentStateChanged, - // onColumnVisible: this.onColumnVisible, // onColumnResized: this.onColumnResized, - // onColumnMoved: this.onColumnMoved, onDragStopped: this.onDragStopped - // onColumnRowGroupChanged: this.onColumnRowGroupChanged, - // onNewColumnsLoaded: this.onNewColumnsLoaded, - // onGridColumnsChanged: this.onGridColumnsChanged, - // onDisplayedColumnsChanged: this.onDisplayedColumnsChanged, - // onColumnEverythingChanged: this.onColumnEverythingChanged }) }), @@ -238,7 +231,6 @@ class Grid extends Component { model.setAgApi(api); api.setSortModel(model.sortBy); api.sizeColumnsToFit(); - if (this.model.gridStateModel) console.log('onGridReady', params); // this fires once, could be used to set a flag to check so that we don';t mes with state until it's finsihed. i.e. like in onBefore render in sencha world. } onNavigateToNextCell = (params) => { diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index f65a974fae..2e7320918b 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -133,7 +133,7 @@ export class GridStateModel { if (this.trackColumns && state.columns) { state.columns.forEach(colState => { const col = find(cols, {xhId: colState.xhId}); - if (!col) return; // deals with stale col in state, will sorting on a stale col still be a problem? + if (!col) return; col.hide = colState.hide; newColumns.push(col); From 804ea1fe214d2cedd3f34ea10f6a2af76b50a179 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 25 May 2018 13:27:17 -0400 Subject: [PATCH 12/35] Apply xhId to ag-grid's colId --- columns/Utils.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/columns/Utils.js b/columns/Utils.js index 528ea7125a..19294795a4 100644 --- a/columns/Utils.js +++ b/columns/Utils.js @@ -30,6 +30,10 @@ export function fileColFactory(fileVals = {}) { ret.headerClass = castArray(ret.headerClass); ret.cellClass = castArray(ret.cellClass); + if (ret.xhId) { + ret.colId = ret.xhId; + } + if (ret.align === 'center') { ret.headerClass.push('xh-column-header-align-center'); ret.cellClass.push('xh-align-center'); From 6b42ae78db366247b7237bd412f5f44e6240fd91 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 25 May 2018 14:39:07 -0400 Subject: [PATCH 13/35] Clean ups and tweaks based on CR --- admin/columns/Columns.js | 1 - admin/tabs/configs/ConfigPanel.js | 20 +++---- cmp/grid/Grid.js | 2 +- cmp/grid/GridModel.js | 32 +++++++---- cmp/grid/GridStateModel.js | 94 ++++++++++++++----------------- columns/Utils.js | 7 +-- 6 files changed, 79 insertions(+), 77 deletions(-) diff --git a/admin/columns/Columns.js b/admin/columns/Columns.js index ed1371a8f1..5d8db65022 100644 --- a/admin/columns/Columns.js +++ b/admin/columns/Columns.js @@ -7,7 +7,6 @@ import {fileColFactory} from '@xh/hoist/columns/Utils.js'; - /** * Shared columns for the admin client. */ diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index f0d04c10f6..26ca147adc 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -9,7 +9,7 @@ import {button} from '@xh/hoist/kit/blueprint'; import {XH, HoistComponent} from '@xh/hoist/core'; import {restGrid, RestGridModel, RestStore} from '@xh/hoist/cmp/rest'; import {fragment} from '@xh/hoist/cmp/layout'; -import {colChooserButton, GridStateModel} from '@xh/hoist/cmp/grid'; +import {colChooserButton} from '@xh/hoist/cmp/grid'; import {boolCheckCol, baseCol} from '@xh/hoist/columns/Core'; import {nameCol} from '@xh/hoist/admin/columns/Columns'; import {Icon} from '@xh/hoist/icon'; @@ -24,7 +24,7 @@ export class ConfigPanel extends Component { gridModel = new RestGridModel({ enableColChooser: true, - gridStateModel: new GridStateModel({xhStateId: 'configPanel', trackColumns: true, trackSort: true}), + stateModel: {xhStateId: 'configPanel', trackColumns: true, trackSort: true}, store: new RestStore({ url: 'rest/configAdmin', reloadLookupsOnLoad: true, @@ -98,14 +98,14 @@ export class ConfigPanel extends Component { groupBy: 'groupName', columns: this.filterForEnv([ nameCol({fixedWidth: 200, xhId: 'configName'}), - baseCol({field: 'valueType', headerName: 'Type', fixedWidth: 80, align: 'center', xhId: 'configType'}), - this.valCol({field: 'prodValue', env: 'Production', xhId: 'configProdVal'}), - this.valCol({field: 'betaValue', env: 'Beta', xhId: 'configBetaVal'}), - this.valCol({field: 'stageValue', env: 'Staging', xhId: 'configStageVal'}), - this.valCol({field: 'devValue', env: 'Development', xhId: 'configDevVal'}), - boolCheckCol({field: 'clientVisible', headerName: 'Client?', fixedWidth: 75, xhId: 'configClientVisible'}), - baseCol({field: 'groupName', headerName: 'Group', fixedWidth: 100, xhId: 'configGroupName'}), - baseCol({field: 'note', minWidth: 60, xhId: 'configNote'}) + baseCol({field: 'valueType', headerName: 'Type', fixedWidth: 80, align: 'center'}), + this.valCol({field: 'prodValue', env: 'Production'}), + this.valCol({field: 'betaValue', env: 'Beta'}), + this.valCol({field: 'stageValue', env: 'Staging'}), + this.valCol({field: 'devValue', env: 'Development'}), + boolCheckCol({field: 'clientVisible', headerName: 'Client?', fixedWidth: 75}), + baseCol({field: 'groupName', headerName: 'Group', fixedWidth: 100}), + baseCol({field: 'note', minWidth: 60}) ]), editors: this.filterForEnv([ {field: 'name'}, diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index 87c4cca4e6..811c809182 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -247,7 +247,7 @@ class Grid extends Component { } onDragStopped = (ev) => { - this.model.setGridColumnOrder(ev.api.columnController.gridColumns); + this.model.syncColumnOrder(ev.api.columnController.gridColumns); } onGridSizeChanged = (ev) => { diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 214625e542..4fba6b0792 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -12,6 +12,7 @@ import {Icon} from '@xh/hoist/icon'; import {castArray, find, isString, orderBy} from 'lodash'; import {ColChooserModel} from './ColChooserModel'; +import {GridStateModel} from './GridStateModel'; /** * Core Model for a Grid, specifying the grid's data store, column definitions, @@ -25,11 +26,10 @@ export class GridModel { selection = null; contextMenuFn = null; colChooserModel = null; - gridStateModel = null; + stateModel = null; @observable.ref agApi = null; @observable.ref columns = []; - @observable.ref gridColumnOrder = []; @observable.ref sortBy = []; @observable groupBy = null; @@ -73,6 +73,7 @@ export class GridModel { * @param {string} [groupBy] - Column ID by which to group. * @param {boolean} [enableColChooser] - true to setup support for column chooser UI and * install a default context menu item to launch the chooser. + * @param {GridStateModel} stateModel - TODO: DOC THIS * @param {function} [contextMenuFn] - closure returning a StoreContextMenu(). */ constructor({ @@ -83,7 +84,7 @@ export class GridModel { sortBy = [], groupBy = null, enableColChooser = false, - gridStateModel = null, + stateModel = null, contextMenuFn = () => this.defaultContextMenu() }) { this.store = store; @@ -100,9 +101,8 @@ export class GridModel { this.setGroupBy(groupBy); this.setSortBy(sortBy); - if (gridStateModel) { - this.gridStateModel = gridStateModel; - this.gridStateModel.init(this); + if (stateModel) { + this.initGridState(stateModel); } } @@ -190,10 +190,16 @@ export class GridModel { } } - @action - setGridColumnOrder(columns) { - const fieldArr = columns.map(col => col.colId || col.field); // have to use fields/colIds as agGrid doesn't retain xhId - this.gridColumnOrder = fieldArr; + syncColumnOrder(columns) { + const orderedCols = []; + + columns.forEach(gridCol => { + const colId = gridCol.colId, + col = find(this.columns, {colId}); + orderedCols.push(col); + }); + + this.setColumns(orderedCols); } //----------------------- @@ -213,4 +219,10 @@ export class GridModel { XH.safeDestroy(this.colChooserModel); // TODO: How are Stores destroyed? } + + initGridState(stateModel) { + this.stateModel = stateModel instanceof GridStateModel ? stateModel : new GridStateModel(stateModel); + this.stateModel.init(this); + } + } \ No newline at end of file diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 2e7320918b..359258abd1 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -5,26 +5,25 @@ * Copyright © 2018 Extremely Heavy Industries Inc. */ import {XH, HoistModel} from '@xh/hoist/core'; -import {cloneDeep, find} from 'lodash'; +import {cloneDeep, find, uniqBy} from 'lodash'; @HoistModel() export class GridStateModel { - - trackColumns = true; - trackSort = true; - parent = null; xhStateId = null; state = {}; - userState = null; defaultState = null; - - constructor({trackColumns, trackSort, xhStateId}) { + /** + * @param {string} xhStateId - Unique grid identifier. + * @param {string} [trackColumns] - Save column visible state and ordering. + * @param {string} [trackSort] - Save grid sort. + */ + constructor({xhStateId, trackColumns = true, trackSort = true}) { + this.xhStateId = xhStateId; this.trackColumns = trackColumns; this.trackSort = trackSort; - this.xhStateId = xhStateId; } init(gridModel) { @@ -33,27 +32,22 @@ export class GridStateModel { this.ensureCompatible(); if (this.trackColumns) { - this.addReaction({ - track: () => [gridModel.columns, gridModel.gridColumnOrder], // 'columns' observable is not changed on reorder - run: this.onColumnsChanged // firing on load due to first state setting of columns, is this a problem? - }); + this.addReaction(this.columnReaction()); } + // simplify as above if (this.trackSort) { - this.addReaction({ - track: () => gridModel.sortBy, - run: this.onSortChanged - }); + this.addReaction(this.sortReaction()); } this.initializeState(); } initializeState() { - this.userState = this.readState(this.getStateKey()); + const userState = this.readState(this.getStateKey()); this.defaultState = this.readStateFromGrid(); // for resetting? - this.loadState(this.userState); + this.loadState(userState); } readStateFromGrid() { @@ -90,36 +84,30 @@ export class GridStateModel { //-------------------------- // Columns //-------------------------- - onColumnsChanged() { - if (this.trackColumns) { // don't think I need this check. the 'listener' isn't added if not - this.state.columns = this.getColumnState(); - this.saveStateChange(); - } + columnReaction() { + const {parent} = this; + return { + track: () => [parent.columns], + run: () => { + this.state.columns = this.getColumnState(); + this.saveStateChange(); + } + }; } getColumnState() { if (!this.trackColumns) return undefined; - const gridModel = this.parent, - colOrder = gridModel.gridColumnOrder, - orderedCols = []; - - colOrder.forEach(field => { - const col = find(gridModel.columns, {field}); - orderedCols.push(col); - }); - - const ret = orderedCols.map(it => { - const colSpec = { - xhId: it.xhId, - // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? - hide: it.hide - }; + const columns = this.parent.columns, + ret = columns.map(it => { + const colSpec = { + xhId: it.xhId, + // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? + hide: it.hide + }; - if (it.xhId != null) { // do we need this? (we check in ensureCompatible) return colSpec; - } - }); + }); return ret; } @@ -146,7 +134,6 @@ export class GridStateModel { } }); - parent.setGridColumnOrder(newColumns); parent.setColumns(newColumns); } @@ -155,11 +142,15 @@ export class GridStateModel { //-------------------------- // Sort //-------------------------- - onSortChanged() { - if (this.trackSort) { // see onColumnChanged above - this.state.sortBy = this.parent.sortBy; - this.saveStateChange(); - } + sortReaction() { + const {parent} = this; + return { + track: () => parent.sortBy, + run: () => { + this.state.sortBy = parent.sortBy; + this.saveStateChange(); + } + }; } updateGridSort() { @@ -189,10 +180,11 @@ export class GridStateModel { ensureCompatible() { const cols = this.parent.columns, - colsWithoutXhId = cols.filter(col => !col.xhId); + colsWithoutXhId = cols.filter(col => !col.xhId), + uniqueIds = cols.length == uniqBy(cols, 'xhId').length; - if (this.trackColumns && colsWithoutXhId.length) { - throw XH.exception('GridStateModel with "trackColumns=true" requires all columns to have an xhId'); + if (this.trackColumns && (colsWithoutXhId.length || !uniqueIds)) { + throw XH.exception('GridStateModel with "trackColumns=true" requires all columns to have a unique xhId'); } } diff --git a/columns/Utils.js b/columns/Utils.js index 19294795a4..e93c627cb5 100644 --- a/columns/Utils.js +++ b/columns/Utils.js @@ -27,13 +27,12 @@ export function fileColFactory(fileVals = {}) { return function(instanceVals = {}) { const ret = defaults(instanceVals, colVals, fileVals); + ret.xhId = ret.xhId || ret.field; + ret.colId = ret.xhId; + ret.headerClass = castArray(ret.headerClass); ret.cellClass = castArray(ret.cellClass); - if (ret.xhId) { - ret.colId = ret.xhId; - } - if (ret.align === 'center') { ret.headerClass.push('xh-column-header-align-center'); ret.cellClass.push('xh-align-center'); From 0fdf3d8e0465acbb7dfb29b30486329876b8418f Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 25 May 2018 15:48:50 -0400 Subject: [PATCH 14/35] Buffer calls to saveStateChange with lodash's debounce --- cmp/grid/GridStateModel.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 359258abd1..0126f4a05c 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -5,7 +5,7 @@ * Copyright © 2018 Extremely Heavy Industries Inc. */ import {XH, HoistModel} from '@xh/hoist/core'; -import {cloneDeep, find, uniqBy} from 'lodash'; +import {cloneDeep, debounce, find, uniqBy} from 'lodash'; @HoistModel() export class GridStateModel { @@ -59,7 +59,7 @@ export class GridStateModel { //-------------------------- - // For Extension / Override // ??? really? + // For Extension / Override //-------------------------- readState(stateKey) { return XH.localStorageService.get(stateKey, {}); @@ -164,11 +164,9 @@ export class GridStateModel { //-------------------------- // Helper //-------------------------- - saveStateChange() { - if (this.state && !this._resetting) { // ?? - this.saveState(this.getStateKey(), this.state); - } - } + saveStateChange = debounce(function() { + this.saveState(this.getStateKey(), this.state); + }, 500); getStateKey() { const xhStateId = this.xhStateId; From de67a89444c09d21b4400e5dac248e3e78e72192 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 25 May 2018 16:38:37 -0400 Subject: [PATCH 15/35] Add todo comment --- cmp/grid/GridStateModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 0126f4a05c..3fca27ddd2 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -156,7 +156,7 @@ export class GridStateModel { updateGridSort() { const sortBy = this.state.sortBy, cols = this.parent.columns, - gridHasCol = sortBy ? sortBy.some(it => find(cols, {field: it.colId})) : false; + gridHasCol = sortBy ? sortBy.some(it => find(cols, {field: it.colId})) : false; // might be pointless, doesn't hurt anything to just let it go if (sortBy && gridHasCol) this.parent.setSortBy(sortBy); } From 7f46dab5e1cc92465c485aeba6834f88dbf60734 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 25 May 2018 18:03:34 -0400 Subject: [PATCH 16/35] Don't setColumns if order hasn't changed --- cmp/grid/GridModel.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 686a94a401..be7356a634 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -9,7 +9,7 @@ import {action, observable} from '@xh/hoist/mobx'; import {StoreSelectionModel} from '@xh/hoist/data'; import {StoreContextMenu} from '@xh/hoist/cmp/contextmenu'; import {Icon} from '@xh/hoist/icon'; -import {defaults, castArray, find, isString, isPlainObject, orderBy} from 'lodash'; +import {defaults, castArray, findIndex, isString, isPlainObject, orderBy} from 'lodash'; import {ColChooserModel} from './ColChooserModel'; import {GridStateModel} from './GridStateModel'; @@ -206,13 +206,16 @@ export class GridModel { syncColumnOrder(columns) { const orderedCols = []; - columns.forEach(gridCol => { + let orderChanged = false; + columns.forEach((gridCol, idx) => { const colId = gridCol.colId, - col = find(this.columns, {colId}); - orderedCols.push(col); + colIdx = findIndex(this.columns, {colId}); + + if (idx !== colIdx) orderChanged = true; + orderedCols.push(columns[colIdx]); }); - this.setColumns(orderedCols); + if (orderChanged) this.setColumns(orderedCols); } //----------------------- From 5ce5c10dfbcde0cd9573509584a8b1cc24b1ac78 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Tue, 29 May 2018 14:41:01 -0400 Subject: [PATCH 17/35] Fix sync order columns bug --- cmp/grid/GridModel.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index be7356a634..ec46c18a35 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -212,10 +212,12 @@ export class GridModel { colIdx = findIndex(this.columns, {colId}); if (idx !== colIdx) orderChanged = true; - orderedCols.push(columns[colIdx]); + orderedCols.push(this.columns[colIdx]); }); - if (orderChanged) this.setColumns(orderedCols); + if (orderChanged) { + this.setColumns(orderedCols); + } } //----------------------- From efab1f180aa12c59be665c87962f598e01adea5f Mon Sep 17 00:00:00 2001 From: febbraiod Date: Tue, 29 May 2018 14:59:34 -0400 Subject: [PATCH 18/35] Replace incorrectly removed import (fixes grouping) --- cmp/grid/GridModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index ec46c18a35..6eb8c76a47 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -9,7 +9,7 @@ import {action, observable} from '@xh/hoist/mobx'; import {StoreSelectionModel} from '@xh/hoist/data'; import {StoreContextMenu} from '@xh/hoist/cmp/contextmenu'; import {Icon} from '@xh/hoist/icon'; -import {defaults, castArray, findIndex, isString, isPlainObject, orderBy} from 'lodash'; +import {defaults, castArray, find, findIndex, isString, isPlainObject, orderBy} from 'lodash'; import {ColChooserModel} from './ColChooserModel'; import {GridStateModel} from './GridStateModel'; From 006f0e5ccf67357b6fe402f8167f91b6fa548440 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Tue, 29 May 2018 16:59:14 -0400 Subject: [PATCH 19/35] Store/Rehydrate column width, example on pref panel --- admin/tabs/preferences/PreferencePanel.js | 5 +++-- cmp/grid/Grid.js | 11 ++++++++--- cmp/grid/GridModel.js | 22 ++++++++++++++++++---- cmp/grid/GridStateModel.js | 4 +++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/admin/tabs/preferences/PreferencePanel.js b/admin/tabs/preferences/PreferencePanel.js index 38caf4ad95..561584001f 100644 --- a/admin/tabs/preferences/PreferencePanel.js +++ b/admin/tabs/preferences/PreferencePanel.js @@ -15,6 +15,7 @@ import {nameCol} from '../../columns/Columns'; export class PreferencePanel extends Component { localModel = new RestGridModel({ + stateModel: {xhStateId: 'prefPanel', trackColumns: true, trackSort: true}, store: new RestStore({ url: 'rest/preferenceAdmin', fields: [ @@ -64,8 +65,8 @@ export class PreferencePanel extends Component { }, columns: [ boolCheckCol({field: 'local', fixedWidth: 70}), - nameCol({fixedWidth: 200}), - baseCol({field: 'type', fixedWidth: 100}), + nameCol({minWidth: 200}), + baseCol({field: 'type', minWidth: 100}), baseCol({field: 'defaultValue', minWidth: 150, maxWidth: 480}), baseCol({field: 'notes', minWidth: 200, flex: 1}) ], diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index 026d16e803..0ed52e9b42 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -87,7 +87,6 @@ class Grid extends Component { onSortChanged: this.onSortChanged, onGridSizeChanged: this.onGridSizeChanged, onComponentStateChanged: this.onComponentStateChanged, - // onColumnResized: this.onColumnResized, onDragStopped: this.onDragStopped }; @@ -269,7 +268,9 @@ class Grid extends Component { } onDragStopped = (ev) => { - this.model.syncColumnOrder(ev.api.columnController.gridColumns); + const gridColumns = ev.api.columnController.gridColumns + this.model.syncColumnOrder(gridColumns); + this.model.syncColumnWidths(gridColumns); } onGridSizeChanged = (ev) => { @@ -277,7 +278,11 @@ class Grid extends Component { } onComponentStateChanged = (ev) => { - ev.api.sizeColumnsToFit(); + // would like to either get rid of this or suppress on setColumns somehow + // change made https://github.com/exhi/hoist-react/commit/9e72fc2032f76664528425af86f265bc26a27c82 + // this is cause the grid to jump if I want to round trip the grid column widths from the agGrid column to our gridModel columns + + // ev.api.sizeColumnsToFit(); } } diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 6eb8c76a47..59e537dcbb 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -204,15 +204,16 @@ export class GridModel { } syncColumnOrder(columns) { - const orderedCols = []; + const xhColumns = this.columns, + orderedCols = []; - let orderChanged = false; + let orderChanged = false; // drag event that triggers this method also fires on col resize columns.forEach((gridCol, idx) => { const colId = gridCol.colId, - colIdx = findIndex(this.columns, {colId}); + colIdx = findIndex(xhColumns, {colId}); if (idx !== colIdx) orderChanged = true; - orderedCols.push(this.columns[colIdx]); + orderedCols.push(xhColumns[colIdx]); }); if (orderChanged) { @@ -220,6 +221,19 @@ export class GridModel { } } + syncColumnWidths(columns) { + const xhColumns = this.cloneColumns(); + + columns.forEach((gridCol) => { + const colId = gridCol.colId, + xhCol = find(xhColumns, {colId}); + + xhCol.width = gridCol.actualWidth; + }); + + this.setColumns(xhColumns); + } + //----------------------- // Implementation //----------------------- diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 3fca27ddd2..ed6c30b734 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -103,7 +103,8 @@ export class GridStateModel { const colSpec = { xhId: it.xhId, // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? - hide: it.hide + hide: it.hide, + width: it.width }; return colSpec; @@ -124,6 +125,7 @@ export class GridStateModel { if (!col) return; col.hide = colState.hide; + col.width = colState.width; // if column is fixed width should we still do this? What if it wasn't fixed but is now? newColumns.push(col); foundColumns.push(col); }); From a3654bf794cdb659d6d10618ef01c182ab621e1a Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 30 May 2018 12:34:01 -0400 Subject: [PATCH 20/35] Changes from review --- admin/tabs/configs/ConfigPanel.js | 9 ++- admin/tabs/preferences/PreferencePanel.js | 2 +- admin/tabs/services/ServiceModel.js | 3 +- cmp/grid/Grid.js | 9 +-- cmp/grid/GridModel.js | 94 +++++++++++++---------- cmp/grid/GridStateModel.js | 35 ++++----- 6 files changed, 79 insertions(+), 73 deletions(-) diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index 26ca147adc..365775d1d8 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -24,7 +24,7 @@ export class ConfigPanel extends Component { gridModel = new RestGridModel({ enableColChooser: true, - stateModel: {xhStateId: 'configPanel', trackColumns: true, trackSort: true}, + stateModel: 'configPanel', store: new RestStore({ url: 'rest/configAdmin', reloadLookupsOnLoad: true, @@ -97,7 +97,7 @@ export class ConfigPanel extends Component { sortBy: 'name', groupBy: 'groupName', columns: this.filterForEnv([ - nameCol({fixedWidth: 200, xhId: 'configName'}), + nameCol({fixedWidth: 200}), baseCol({field: 'valueType', headerName: 'Type', fixedWidth: 80, align: 'center'}), this.valCol({field: 'prodValue', env: 'Production'}), this.valCol({field: 'betaValue', env: 'Beta'}), @@ -160,13 +160,16 @@ export class ConfigPanel extends Component { } extraToolbarItems = () => { + const gridModel = this.gridModel.gridModel; return [ button({ icon: Icon.diff(), text: 'Compare w/ Remote', onClick: this.onDifferBtnClick }), - colChooserButton({gridModel: this.gridModel.gridModel}) + colChooserButton({gridModel}), + button({text: 'GroupBy Type', onClick: () => gridModel.setGroupBy('valueType')}), + button({text: 'Clear Grouping', onClick: () => gridModel.setGroupBy(null)}) ]; } diff --git a/admin/tabs/preferences/PreferencePanel.js b/admin/tabs/preferences/PreferencePanel.js index 561584001f..7b9853e1b9 100644 --- a/admin/tabs/preferences/PreferencePanel.js +++ b/admin/tabs/preferences/PreferencePanel.js @@ -15,7 +15,7 @@ import {nameCol} from '../../columns/Columns'; export class PreferencePanel extends Component { localModel = new RestGridModel({ - stateModel: {xhStateId: 'prefPanel', trackColumns: true, trackSort: true}, + stateModel: {xhStateId: 'prefPanel', trackSort: true}, store: new RestStore({ url: 'rest/preferenceAdmin', fields: [ diff --git a/admin/tabs/services/ServiceModel.js b/admin/tabs/services/ServiceModel.js index 70710512cf..724fea20b2 100644 --- a/admin/tabs/services/ServiceModel.js +++ b/admin/tabs/services/ServiceModel.js @@ -7,13 +7,14 @@ import {XH, HoistModel} from '@xh/hoist/core'; import {ToastManager} from '@xh/hoist/toast'; import {UrlStore} from '@xh/hoist/data'; -import {GridModel} from '@xh/hoist/cmp/grid'; +import {GridModel, GridStateModel} from '@xh/hoist/cmp/grid'; import {baseCol} from '@xh/hoist/columns/Core'; @HoistModel() export class ServiceModel { gridModel = new GridModel({ + stateModel: new GridStateModel({xhStateId: 'prefPanel', trackColumns: true}), store: new UrlStore({ url: 'serviceAdmin/listServices', processRawData: this.processRawData, diff --git a/cmp/grid/Grid.js b/cmp/grid/Grid.js index 0ed52e9b42..3c7a1253d8 100644 --- a/cmp/grid/Grid.js +++ b/cmp/grid/Grid.js @@ -268,9 +268,8 @@ class Grid extends Component { } onDragStopped = (ev) => { - const gridColumns = ev.api.columnController.gridColumns + const gridColumns = ev.api.columnController.gridColumns; this.model.syncColumnOrder(gridColumns); - this.model.syncColumnWidths(gridColumns); } onGridSizeChanged = (ev) => { @@ -278,11 +277,7 @@ class Grid extends Component { } onComponentStateChanged = (ev) => { - // would like to either get rid of this or suppress on setColumns somehow - // change made https://github.com/exhi/hoist-react/commit/9e72fc2032f76664528425af86f265bc26a27c82 - // this is cause the grid to jump if I want to round trip the grid column widths from the agGrid column to our gridModel columns - - // ev.api.sizeColumnsToFit(); + ev.api.sizeColumnsToFit(); } } diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 59e537dcbb..6122913d90 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -9,7 +9,7 @@ import {action, observable} from '@xh/hoist/mobx'; import {StoreSelectionModel} from '@xh/hoist/data'; import {StoreContextMenu} from '@xh/hoist/cmp/contextmenu'; import {Icon} from '@xh/hoist/icon'; -import {defaults, castArray, find, findIndex, isString, isPlainObject, orderBy} from 'lodash'; +import {defaults, castArray, find, isEqual, isString, isPlainObject, orderBy} from 'lodash'; import {ColChooserModel} from './ColChooserModel'; import {GridStateModel} from './GridStateModel'; @@ -83,8 +83,6 @@ export class GridModel { this.store = store; this.columns = columns; this.contextMenuFn = contextMenuFn; - - this.selModel = this.parseSelModel(selModel, store); this.emptyText = emptyText; if (enableColChooser) { @@ -94,9 +92,8 @@ export class GridModel { this.setGroupBy(groupBy); this.setSortBy(sortBy); - if (stateModel) { - this.initGridState(stateModel); - } + this.selModel = this.initSelModel(selModel, store); + this.stateModel = this.initStateModel(stateModel); } exportDataAsExcel(params) { @@ -144,7 +141,12 @@ export class GridModel { @action setGroupBy(field) { - const cols = this.columns; + const cols = this.columns, + groupCol = find(cols, {field}); + + // If we have an invalid groupBy field do not set + // Allow a falsey field to mean 'no grouping' + if (field && !groupCol) return; cols.forEach(it => { if (it.rowGroup) { @@ -153,14 +155,11 @@ export class GridModel { } }); - if (field) { - const col = find(cols, {field}); - if (col) { - col.rowGroup = true; - col.hide = true; - } + if (groupCol) { + groupCol.rowGroup = true; + groupCol.hide = true; } - + this.columns = [...cols]; } @@ -168,6 +167,17 @@ export class GridModel { setSortBy(sortBy) { // Normalize string, and partially specified values sortBy = castArray(sortBy); + + // If any sort prop is invalid do not set + const sortIsValid = sortBy.every(it => { + const field = it.colId || it, + col = find(this.columns, {field}); + + return col && !col.hide; + }); + + if (!sortIsValid) return; + sortBy = sortBy.map(it => { if (isString(it)) it = {colId: it}; it.sort = it.sort || 'asc'; @@ -207,33 +217,23 @@ export class GridModel { const xhColumns = this.columns, orderedCols = []; - let orderChanged = false; // drag event that triggers this method also fires on col resize - columns.forEach((gridCol, idx) => { + columns.forEach((gridCol) => { const colId = gridCol.colId, - colIdx = findIndex(xhColumns, {colId}); + col = find(xhColumns, {colId}); - if (idx !== colIdx) orderChanged = true; - orderedCols.push(xhColumns[colIdx]); + orderedCols.push(col); }); + // Can be no-op as drag event that triggers this method also fires on col resize + const oldIdOrder = columns.map(it => it.colId), + newIdOrder = xhColumns.map(it => it.colId), + orderChanged = !isEqual(newIdOrder, oldIdOrder); + if (orderChanged) { this.setColumns(orderedCols); } } - syncColumnWidths(columns) { - const xhColumns = this.cloneColumns(); - - columns.forEach((gridCol) => { - const colId = gridCol.colId, - xhCol = find(xhColumns, {colId}); - - xhCol.width = gridCol.actualWidth; - }); - - this.setColumns(xhColumns); - } - //----------------------- // Implementation //----------------------- @@ -247,8 +247,7 @@ export class GridModel { } } - - parseSelModel(selModel, store) { + initSelModel(selModel, store) { if (selModel instanceof StoreSelectionModel) { return selModel; } @@ -261,21 +260,36 @@ export class GridModel { let mode = 'single'; if (isString(selModel)) { mode = selModel; - } else if (selModel === null) { + } else if (selModel === null) { mode = 'disabled'; } return new StoreSelectionModel({mode, store}); } + initStateModel(stateModel) { + if (!stateModel) return; + let ret; + + if (stateModel instanceof GridStateModel) { + ret = stateModel; + } + + if (isPlainObject(stateModel)) { + ret = new GridStateModel(stateModel); + } + + if (isString(stateModel)) { + ret = new GridStateModel({xhStateId: stateModel}); + } + + ret.init(this); + return ret; + } + destroy() { XH.safeDestroy(this.colChooserModel); // TODO: How are Stores destroyed? } - initGridState(stateModel) { - this.stateModel = stateModel instanceof GridStateModel ? stateModel : new GridStateModel(stateModel); - this.stateModel.init(this); - } - } \ No newline at end of file diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index ed6c30b734..223aae8ad9 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -6,6 +6,7 @@ */ import {XH, HoistModel} from '@xh/hoist/core'; import {cloneDeep, debounce, find, uniqBy} from 'lodash'; +import {SECONDS} from '@xh/hoist/utils/DateTimeUtils'; @HoistModel() export class GridStateModel { @@ -35,7 +36,6 @@ export class GridStateModel { this.addReaction(this.columnReaction()); } - // simplify as above if (this.trackSort) { this.addReaction(this.sortReaction()); } @@ -87,7 +87,7 @@ export class GridStateModel { columnReaction() { const {parent} = this; return { - track: () => [parent.columns], + track: () => parent.columns, run: () => { this.state.columns = this.getColumnState(); this.saveStateChange(); @@ -98,19 +98,16 @@ export class GridStateModel { getColumnState() { if (!this.trackColumns) return undefined; - const columns = this.parent.columns, - ret = columns.map(it => { - const colSpec = { - xhId: it.xhId, - // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? - hide: it.hide, - width: it.width - }; + const columns = this.parent.columns; - return colSpec; - }); - - return ret; + return columns.map(it => { + return { + xhId: it.xhId, + // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? + hide: it.hide, + width: it.width + }; + }); } updateGridColumns() { @@ -125,7 +122,6 @@ export class GridStateModel { if (!col) return; col.hide = colState.hide; - col.width = colState.width; // if column is fixed width should we still do this? What if it wasn't fixed but is now? newColumns.push(col); foundColumns.push(col); }); @@ -156,11 +152,8 @@ export class GridStateModel { } updateGridSort() { - const sortBy = this.state.sortBy, - cols = this.parent.columns, - gridHasCol = sortBy ? sortBy.some(it => find(cols, {field: it.colId})) : false; // might be pointless, doesn't hurt anything to just let it go - - if (sortBy && gridHasCol) this.parent.setSortBy(sortBy); + const sortBy = this.state.sortBy; + if (sortBy) this.parent.setSortBy(sortBy); } //-------------------------- @@ -168,7 +161,7 @@ export class GridStateModel { //-------------------------- saveStateChange = debounce(function() { this.saveState(this.getStateKey(), this.state); - }, 500); + }, 5 * SECONDS); getStateKey() { const xhStateId = this.xhStateId; From ac184f9b003ee6bcdd2b358f82535ecca7f8f5a6 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 30 May 2018 13:50:15 -0400 Subject: [PATCH 21/35] Column chooser can reset state if grid has a stateModel --- cmp/grid/ColChooser.js | 18 +++++++++++++++++- cmp/grid/GridStateModel.js | 19 ++++++++++++++++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/cmp/grid/ColChooser.js b/cmp/grid/ColChooser.js index f02f795b32..d7decea681 100644 --- a/cmp/grid/ColChooser.js +++ b/cmp/grid/ColChooser.js @@ -16,7 +16,7 @@ import {Icon} from '@xh/hoist/icon'; export class ColChooser extends Component { render() { - const {isOpen, lrModel} = this.model; + const {isOpen, gridModel, lrModel} = this.model; if (!isOpen) return null; @@ -32,6 +32,12 @@ export class ColChooser extends Component { item: leftRightChooser({model: lrModel}) }), toolbar( + button({ + text: 'Reset', + icon: Icon.refresh({cls: 'xh-red'}), + omit: !gridModel.stateModel, + onClick: this.restoreDefaults + }), filler(), button({ text: 'Cancel', @@ -53,5 +59,15 @@ export class ColChooser extends Component { this.onClose(); } + restoreDefaults = () => { + const model = this.model, + gridModel = model.gridModel, + stateModel = gridModel.stateModel; + + stateModel.resetStateAsync().then(() => { + model.syncChooserData(); + }); + } + } export const colChooser = elemFactory(ColChooser); \ No newline at end of file diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 223aae8ad9..eb72af0f63 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -7,6 +7,7 @@ import {XH, HoistModel} from '@xh/hoist/core'; import {cloneDeep, debounce, find, uniqBy} from 'lodash'; import {SECONDS} from '@xh/hoist/utils/DateTimeUtils'; +import {start, resolve} from '@xh/hoist/promise'; @HoistModel() export class GridStateModel { @@ -45,7 +46,7 @@ export class GridStateModel { initializeState() { const userState = this.readState(this.getStateKey()); - this.defaultState = this.readStateFromGrid(); // for resetting? + this.defaultState = this.readStateFromGrid(); this.loadState(userState); } @@ -80,6 +81,21 @@ export class GridStateModel { this.updateGridSort(); } + // why does this need to be async? Is this in case there is a db based override? + resetStateAsync() { + const defaultState = this.defaultState; + + if (!defaultState) resolve(); // shouldn't there always be one? + + return start(() => { + this._resetting = true; + this.loadState(defaultState); + }).then(() => { + this.resetState(this.getStateKey()); + }).finally(() =>{ + this._resetting = false; + }); + } //-------------------------- // Columns @@ -160,6 +176,7 @@ export class GridStateModel { // Helper //-------------------------- saveStateChange = debounce(function() { + // sencha checks that there is a state (which I think there always should be) and if we are resetting (should we do the same)? this.saveState(this.getStateKey(), this.state); }, 5 * SECONDS); From f1f89887ed86dd322e85c96e16b6f814f84ca5ef Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 30 May 2018 13:56:40 -0400 Subject: [PATCH 22/35] Remove unneeded test code --- admin/tabs/preferences/PreferencePanel.js | 5 ++--- admin/tabs/services/ServiceModel.js | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/admin/tabs/preferences/PreferencePanel.js b/admin/tabs/preferences/PreferencePanel.js index 7b9853e1b9..38caf4ad95 100644 --- a/admin/tabs/preferences/PreferencePanel.js +++ b/admin/tabs/preferences/PreferencePanel.js @@ -15,7 +15,6 @@ import {nameCol} from '../../columns/Columns'; export class PreferencePanel extends Component { localModel = new RestGridModel({ - stateModel: {xhStateId: 'prefPanel', trackSort: true}, store: new RestStore({ url: 'rest/preferenceAdmin', fields: [ @@ -65,8 +64,8 @@ export class PreferencePanel extends Component { }, columns: [ boolCheckCol({field: 'local', fixedWidth: 70}), - nameCol({minWidth: 200}), - baseCol({field: 'type', minWidth: 100}), + nameCol({fixedWidth: 200}), + baseCol({field: 'type', fixedWidth: 100}), baseCol({field: 'defaultValue', minWidth: 150, maxWidth: 480}), baseCol({field: 'notes', minWidth: 200, flex: 1}) ], diff --git a/admin/tabs/services/ServiceModel.js b/admin/tabs/services/ServiceModel.js index 724fea20b2..70710512cf 100644 --- a/admin/tabs/services/ServiceModel.js +++ b/admin/tabs/services/ServiceModel.js @@ -7,14 +7,13 @@ import {XH, HoistModel} from '@xh/hoist/core'; import {ToastManager} from '@xh/hoist/toast'; import {UrlStore} from '@xh/hoist/data'; -import {GridModel, GridStateModel} from '@xh/hoist/cmp/grid'; +import {GridModel} from '@xh/hoist/cmp/grid'; import {baseCol} from '@xh/hoist/columns/Core'; @HoistModel() export class ServiceModel { gridModel = new GridModel({ - stateModel: new GridStateModel({xhStateId: 'prefPanel', trackColumns: true}), store: new UrlStore({ url: 'serviceAdmin/listServices', processRawData: this.processRawData, From 54ba0162ca58d85635e018d7e61f248cae8f40b2 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 30 May 2018 14:00:03 -0400 Subject: [PATCH 23/35] Document stateModel in gridModel constructor --- cmp/grid/GridModel.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 6122913d90..a3e0d86129 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -66,7 +66,8 @@ export class GridModel { * @param {string} [groupBy] - Column ID by which to group. * @param {boolean} [enableColChooser] - true to setup support for column chooser UI and * install a default context menu item to launch the chooser. - * @param {GridStateModel} stateModel - TODO: DOC THIS + * @param {(GridStateModel|Object|String)} [stateModel] - state model to use, + * config to create one, or xhStateId property for a state model * @param {function} [contextMenuFn] - closure returning a StoreContextMenu(). */ constructor({ From 254b0bce2ffefc3f51cf57ffd3cb08b9889acb9f Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 30 May 2018 14:07:36 -0400 Subject: [PATCH 24/35] Remove sencha specific column handling --- cmp/grid/GridStateModel.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index eb72af0f63..2c467e29e7 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -74,7 +74,6 @@ export class GridStateModel { XH.localStorageService.remove(stateKey); } - loadState(state) { this.state = cloneDeep(state || this.readState(this.getStateKey()) || {}); this.updateGridColumns(); @@ -119,7 +118,6 @@ export class GridStateModel { return columns.map(it => { return { xhId: it.xhId, - // hidden: it.isHidden() && (!groupField || it.dataIndex != groupField) // See Hoist #425 sencha specific? hide: it.hide, width: it.width }; From eee0971c3053954ab394ab00720c107df0a04650 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Wed, 30 May 2018 14:18:06 -0400 Subject: [PATCH 25/35] Remove comments to move to PR --- cmp/grid/GridStateModel.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 2c467e29e7..7f4b7fc71e 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -58,7 +58,6 @@ export class GridStateModel { }; } - //-------------------------- // For Extension / Override //-------------------------- @@ -80,11 +79,10 @@ export class GridStateModel { this.updateGridSort(); } - // why does this need to be async? Is this in case there is a db based override? resetStateAsync() { const defaultState = this.defaultState; - if (!defaultState) resolve(); // shouldn't there always be one? + if (!defaultState) resolve(); return start(() => { this._resetting = true; @@ -174,7 +172,6 @@ export class GridStateModel { // Helper //-------------------------- saveStateChange = debounce(function() { - // sencha checks that there is a state (which I think there always should be) and if we are resetting (should we do the same)? this.saveState(this.getStateKey(), this.state); }, 5 * SECONDS); From e4adeb921b05b86436d9533070fb8f0e18286019 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Mon, 4 Jun 2018 11:30:18 -0400 Subject: [PATCH 26/35] Use undo icon for resetting grid state --- cmp/grid/ColChooser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmp/grid/ColChooser.js b/cmp/grid/ColChooser.js index c1619ebad3..40f07361c9 100644 --- a/cmp/grid/ColChooser.js +++ b/cmp/grid/ColChooser.js @@ -31,7 +31,7 @@ export class ColChooser extends Component { toolbar( button({ text: 'Reset', - icon: Icon.refresh({cls: 'xh-red'}), + icon: Icon.undo({cls: 'xh-red'}), omit: !gridModel.stateModel, onClick: this.restoreDefaults }), From ed475c7805409023536937c31da756357b3a9321 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Thu, 7 Jun 2018 09:32:18 -0400 Subject: [PATCH 27/35] Correct comment --- cmp/grid/GridModel.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index a18b0a0a20..b18f457c0d 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -141,8 +141,7 @@ export class GridModel { groupCol = find(cols, {field}); // If we have an invalid groupBy field do not set - // Allow a falsey field to mean 'no grouping' - // this comment isn't accurate a falsey field here means "dont change the grouping" + // Let a falsey field through to mean 'no grouping' if (field && !groupCol) return; cols.forEach(it => { From 530e7951678a522794afb2fe6b18aeb07e4e3c05 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 15:17:37 -0400 Subject: [PATCH 28/35] Improvements from CR. WIP, untested. --- cmp/grid/ColChooser.js | 5 +-- cmp/grid/GridModel.js | 54 ++++++++++++----------- cmp/grid/GridStateModel.js | 88 +++++++++++++++++++------------------- 3 files changed, 73 insertions(+), 74 deletions(-) diff --git a/cmp/grid/ColChooser.js b/cmp/grid/ColChooser.js index 40f07361c9..74a8d470ec 100644 --- a/cmp/grid/ColChooser.js +++ b/cmp/grid/ColChooser.js @@ -57,9 +57,8 @@ export class ColChooser extends Component { } restoreDefaults = () => { - const model = this.model, - gridModel = model.gridModel, - stateModel = gridModel.stateModel; + const {model} = this, + {stateModel} = model.gridModel; stateModel.resetStateAsync().then(() => { model.syncChooserData(); diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index b18f457c0d..84671ead89 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -135,13 +135,17 @@ export class GridModel { this.agApi = agApi; } + /** + * Set row grouping + * + * This method is no-op if provided a field without a corresponding column + * A falsey field argument will reset grouping entirely + */ @action setGroupBy(field) { const cols = this.columns, groupCol = find(cols, {field}); - // If we have an invalid groupBy field do not set - // Let a falsey field through to mean 'no grouping' if (field && !groupCol) return; cols.forEach(it => { @@ -159,30 +163,31 @@ export class GridModel { this.columns = [...cols]; } + /** + * Set sort by column + * + * This method is no-op if provided any sorters without a corresponding column + */ @action setSortBy(sortBy) { // Normalize string, and partially specified values sortBy = castArray(sortBy); - - // If any sort prop is invalid do not set - const sortIsValid = sortBy.every(it => { - const field = it.colId || it, - col = find(this.columns, {field}); - - return col && !col.hide; - }); - - if (!sortIsValid) return; - sortBy = sortBy.map(it => { if (isString(it)) it = {colId: it}; it.sort = it.sort || 'asc'; return it; }); + const sortIsValid = sortBy.every(it => { + find(this.columns, {field: it.colId}); + }); + + if (!sortIsValid) return; + this.sortBy = sortBy; } + /** Load the underlying store. */ loadAsync(...args) { return this.store.loadAsync(...args); @@ -209,25 +214,22 @@ export class GridModel { } } - syncColumnOrder(columns) { + syncColumnOrder(agColumns) { const xhColumns = this.columns, - orderedCols = []; + newIdOrder = agColumns.map(it => it.colId), + oldIdOrder = xhColumns.map(it => it.colId), + orderChanged = !isEqual(newIdOrder, oldIdOrder); - columns.forEach((gridCol) => { - const colId = gridCol.colId, - col = find(xhColumns, {colId}); + // Can be no-op as drag event that triggers this method also fires on col resize + if (!orderChanged) return; + const orderedCols = []; + agColumns.forEach(gridCol => { + const col = find(xhColumns, {colId: gridCol.colId}); orderedCols.push(col); }); - // Can be no-op as drag event that triggers this method also fires on col resize - const oldIdOrder = columns.map(it => it.colId), - newIdOrder = xhColumns.map(it => it.colId), - orderChanged = !isEqual(newIdOrder, oldIdOrder); - - if (orderChanged) { - this.setColumns(orderedCols); - } + this.setColumns(orderedCols); } //----------------------- diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 7f4b7fc71e..2da9dd02ec 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -19,8 +19,8 @@ export class GridStateModel { /** * @param {string} xhStateId - Unique grid identifier. - * @param {string} [trackColumns] - Save column visible state and ordering. - * @param {string} [trackSort] - Save grid sort. + * @param {string} [trackColumns] - Set to true to save column visible state and ordering. + * @param {string} [trackSort] - Set to true to save sort. */ constructor({xhStateId, trackColumns = true, trackSort = true}) { this.xhStateId = xhStateId; @@ -44,23 +44,15 @@ export class GridStateModel { this.initializeState(); } - initializeState() { - const userState = this.readState(this.getStateKey()); - this.defaultState = this.readStateFromGrid(); - - this.loadState(userState); - } - - readStateFromGrid() { - return { - columns: this.getColumnState(), - sortBy: this.parent.sortBy - }; - } //-------------------------- // For Extension / Override //-------------------------- + getStateKey() { + const xhStateId = this.xhStateId; + return 'gridState.' + xhStateId; + } + readState(stateKey) { return XH.localStorageService.get(stateKey, {}); } @@ -73,27 +65,36 @@ export class GridStateModel { XH.localStorageService.remove(stateKey); } - loadState(state) { - this.state = cloneDeep(state || this.readState(this.getStateKey()) || {}); - this.updateGridColumns(); - this.updateGridSort(); - } - resetStateAsync() { - const defaultState = this.defaultState; - - if (!defaultState) resolve(); - return start(() => { - this._resetting = true; - this.loadState(defaultState); - }).then(() => { + this.loadState(this.defaultState); this.resetState(this.getStateKey()); - }).finally(() =>{ - this._resetting = false; }); } + //-------------------------- + // Implementation + //-------------------------- + initializeState() { + const userState = this.readState(this.getStateKey()); + this.defaultState = this.readStateFromGrid(); + + this.loadState(userState); + } + + readStateFromGrid() { + return { + columns: this.getColumnState(), + sortBy: this.parent.sortBy + }; + } + + loadState(state) { + this.state = cloneDeep(state); + if (this.trackColumns) this.updateGridColumns(); + if (this.trackSort) this.updateGridSort(); + } + //-------------------------- // Columns //-------------------------- @@ -109,9 +110,7 @@ export class GridStateModel { } getColumnState() { - if (!this.trackColumns) return undefined; - - const columns = this.parent.columns; + const {columns} = this.parent; return columns.map(it => { return { @@ -128,16 +127,18 @@ export class GridStateModel { newColumns = [], foundColumns = []; - if (this.trackColumns && state.columns) { + if (state.columns) { state.columns.forEach(colState => { const col = find(cols, {xhId: colState.xhId}); - if (!col) return; + if (!col) return; // Do not attempt to include stale column state col.hide = colState.hide; newColumns.push(col); foundColumns.push(col); }); + // Any parent columns that were not found in state are newly added to the code + // Insert these columns at the index they are specified cols.forEach((col, idx) => { if (!find(foundColumns, {xhId: col.xhId})) { newColumns.splice(idx, 0, col); @@ -164,7 +165,7 @@ export class GridStateModel { } updateGridSort() { - const sortBy = this.state.sortBy; + const {sortBy} = this.state; if (sortBy) this.parent.setSortBy(sortBy); } @@ -175,19 +176,16 @@ export class GridStateModel { this.saveState(this.getStateKey(), this.state); }, 5 * SECONDS); - getStateKey() { - const xhStateId = this.xhStateId; - if (!xhStateId) { - throw XH.exception('GridStateModel must have a xhStateId in order to store state'); - } - return 'gridState.' + xhStateId; - } - ensureCompatible() { - const cols = this.parent.columns, + const xhStateId = this.xhStateId, + cols = this.parent.columns, colsWithoutXhId = cols.filter(col => !col.xhId), uniqueIds = cols.length == uniqBy(cols, 'xhId').length; + if (!xhStateId) { + throw XH.exception('GridStateModel must have a xhStateId in order to store state'); + } + if (this.trackColumns && (colsWithoutXhId.length || !uniqueIds)) { throw XH.exception('GridStateModel with "trackColumns=true" requires all columns to have a unique xhId'); } From 983faefbf9e8255c1c6120255222c65d68411562 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 15:53:52 -0400 Subject: [PATCH 29/35] Better commenting of updateGridColumns method --- cmp/grid/GridStateModel.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 2da9dd02ec..fcff5533ba 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -127,6 +127,7 @@ export class GridStateModel { newColumns = [], foundColumns = []; + // Match columns in state to columns in code, apply stateful properties, and add to new columns in stateful order. if (state.columns) { state.columns.forEach(colState => { const col = find(cols, {xhId: colState.xhId}); @@ -137,8 +138,8 @@ export class GridStateModel { foundColumns.push(col); }); - // Any parent columns that were not found in state are newly added to the code - // Insert these columns at the index they are specified + // Any parent columns that were not found in state are newly added to the code. + // Insert these columns in position based on the index at which they are defined. cols.forEach((col, idx) => { if (!find(foundColumns, {xhId: col.xhId})) { newColumns.splice(idx, 0, col); From ca44aa12063c2f9e275dfa6be85b86016e8252ab Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 15:55:14 -0400 Subject: [PATCH 30/35] Remove unused import --- cmp/grid/GridStateModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index fcff5533ba..478f031b54 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -7,7 +7,7 @@ import {XH, HoistModel} from '@xh/hoist/core'; import {cloneDeep, debounce, find, uniqBy} from 'lodash'; import {SECONDS} from '@xh/hoist/utils/DateTimeUtils'; -import {start, resolve} from '@xh/hoist/promise'; +import {start} from '@xh/hoist/promise'; @HoistModel() export class GridStateModel { From 066a86bbb770a569f2cf5ff2f0a747c21bf4ec90 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 16:14:44 -0400 Subject: [PATCH 31/35] Debug set sortBy --- cmp/grid/GridModel.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 84671ead89..2c084eea2f 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -178,9 +178,7 @@ export class GridModel { return it; }); - const sortIsValid = sortBy.every(it => { - find(this.columns, {field: it.colId}); - }); + const sortIsValid = sortBy.every(it => find(this.columns, {colId: it.colId})); if (!sortIsValid) return; From cd774cf61b432fe408214c1bc988d472e765ef8d Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 16:25:13 -0400 Subject: [PATCH 32/35] Use fat arrow for debounced saveStateChange method --- cmp/grid/GridStateModel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 478f031b54..be41394052 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -173,7 +173,7 @@ export class GridStateModel { //-------------------------- // Helper //-------------------------- - saveStateChange = debounce(function() { + saveStateChange = debounce(() => { this.saveState(this.getStateKey(), this.state); }, 5 * SECONDS); From 09a43fcde5eaa5dc09a725b0fe3eb033a85ef898 Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 16:29:39 -0400 Subject: [PATCH 33/35] Doc tweaks --- cmp/grid/GridStateModel.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index be41394052..6590e7ac82 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -19,8 +19,8 @@ export class GridStateModel { /** * @param {string} xhStateId - Unique grid identifier. - * @param {string} [trackColumns] - Set to true to save column visible state and ordering. - * @param {string} [trackSort] - Set to true to save sort. + * @param {string} [trackColumns] - Set to true to save visible state and ordering of columns. + * @param {string} [trackSort] - Set to true to save sorting. */ constructor({xhStateId, trackColumns = true, trackSort = true}) { this.xhStateId = xhStateId; From 9c8690f4f5ef58ff36f8b21179c6f4e2da23173c Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 17:02:18 -0400 Subject: [PATCH 34/35] Minor clean ups --- cmp/grid/GridModel.js | 7 ++-- cmp/grid/GridStateModel.js | 66 ++++++++++++++++++-------------------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/cmp/grid/GridModel.js b/cmp/grid/GridModel.js index 2c084eea2f..17247272d3 100644 --- a/cmp/grid/GridModel.js +++ b/cmp/grid/GridModel.js @@ -138,8 +138,8 @@ export class GridModel { /** * Set row grouping * - * This method is no-op if provided a field without a corresponding column - * A falsey field argument will reset grouping entirely + * This method is no-op if provided a field without a corresponding column. + * A falsey field argument will remove grouping entirely. */ @action setGroupBy(field) { @@ -179,7 +179,6 @@ export class GridModel { }); const sortIsValid = sortBy.every(it => find(this.columns, {colId: it.colId})); - if (!sortIsValid) return; this.sortBy = sortBy; @@ -213,12 +212,12 @@ export class GridModel { } syncColumnOrder(agColumns) { + // Check for no-op const xhColumns = this.columns, newIdOrder = agColumns.map(it => it.colId), oldIdOrder = xhColumns.map(it => it.colId), orderChanged = !isEqual(newIdOrder, oldIdOrder); - // Can be no-op as drag event that triggers this method also fires on col resize if (!orderChanged) return; const orderedCols = []; diff --git a/cmp/grid/GridStateModel.js b/cmp/grid/GridStateModel.js index 6590e7ac82..53376d443b 100644 --- a/cmp/grid/GridStateModel.js +++ b/cmp/grid/GridStateModel.js @@ -11,7 +11,7 @@ import {start} from '@xh/hoist/promise'; @HoistModel() export class GridStateModel { - parent = null; + gridModel = null; xhStateId = null; state = {}; @@ -29,7 +29,7 @@ export class GridStateModel { } init(gridModel) { - this.parent = gridModel; + this.gridModel = gridModel; this.ensureCompatible(); @@ -85,7 +85,7 @@ export class GridStateModel { readStateFromGrid() { return { columns: this.getColumnState(), - sortBy: this.parent.sortBy + sortBy: this.gridModel.sortBy }; } @@ -99,9 +99,9 @@ export class GridStateModel { // Columns //-------------------------- columnReaction() { - const {parent} = this; + const {gridModel} = this; return { - track: () => parent.columns, + track: () => gridModel.columns, run: () => { this.state.columns = this.getColumnState(); this.saveStateChange(); @@ -110,7 +110,7 @@ export class GridStateModel { } getColumnState() { - const {columns} = this.parent; + const {columns} = this.gridModel; return columns.map(it => { return { @@ -122,44 +122,42 @@ export class GridStateModel { } updateGridColumns() { - const {parent, state} = this, - cols = parent.cloneColumns(), - newColumns = [], + const {gridModel, state} = this, + cols = gridModel.cloneColumns(), foundColumns = []; + if (!state.columns) return; + // Match columns in state to columns in code, apply stateful properties, and add to new columns in stateful order. - if (state.columns) { - state.columns.forEach(colState => { - const col = find(cols, {xhId: colState.xhId}); - if (!col) return; // Do not attempt to include stale column state - - col.hide = colState.hide; - newColumns.push(col); - foundColumns.push(col); - }); - - // Any parent columns that were not found in state are newly added to the code. - // Insert these columns in position based on the index at which they are defined. - cols.forEach((col, idx) => { - if (!find(foundColumns, {xhId: col.xhId})) { - newColumns.splice(idx, 0, col); - } - }); - - parent.setColumns(newColumns); - } + state.columns.forEach(colState => { + const col = find(cols, {xhId: colState.xhId}); + if (!col) return; // Do not attempt to include stale column state. + + col.hide = colState.hide; + foundColumns.push(col); + }); + + // Any grid columns that were not found in state are newly added to the code. + // Insert these columns in position based on the index at which they are defined. + const newColumns = [...foundColumns]; + cols.forEach((col, idx) => { + if (!find(foundColumns, {xhId: col.xhId})) { + newColumns.splice(idx, 0, col); + } + }); + gridModel.setColumns(newColumns); } //-------------------------- // Sort //-------------------------- sortReaction() { - const {parent} = this; + const {gridModel} = this; return { - track: () => parent.sortBy, + track: () => gridModel.sortBy, run: () => { - this.state.sortBy = parent.sortBy; + this.state.sortBy = gridModel.sortBy; this.saveStateChange(); } }; @@ -167,7 +165,7 @@ export class GridStateModel { updateGridSort() { const {sortBy} = this.state; - if (sortBy) this.parent.setSortBy(sortBy); + if (sortBy) this.gridModel.setSortBy(sortBy); } //-------------------------- @@ -179,7 +177,7 @@ export class GridStateModel { ensureCompatible() { const xhStateId = this.xhStateId, - cols = this.parent.columns, + cols = this.gridModel.columns, colsWithoutXhId = cols.filter(col => !col.xhId), uniqueIds = cols.length == uniqBy(cols, 'xhId').length; From 567b484f874f6220f91757c843fdfd1aa82cd1eb Mon Sep 17 00:00:00 2001 From: febbraiod Date: Fri, 8 Jun 2018 17:02:55 -0400 Subject: [PATCH 35/35] Revert test code --- admin/tabs/configs/ConfigPanel.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/admin/tabs/configs/ConfigPanel.js b/admin/tabs/configs/ConfigPanel.js index 2d6d537b26..06102a9280 100644 --- a/admin/tabs/configs/ConfigPanel.js +++ b/admin/tabs/configs/ConfigPanel.js @@ -9,7 +9,6 @@ import {button} from '@xh/hoist/kit/blueprint'; import {XH, HoistComponent} from '@xh/hoist/core'; import {restGrid, RestGridModel, RestStore} from '@xh/hoist/cmp/rest'; import {fragment} from '@xh/hoist/cmp/layout'; -import {colChooserButton} from '@xh/hoist/cmp/grid'; import {boolCheckCol, baseCol} from '@xh/hoist/columns/Core'; import {nameCol} from '@xh/hoist/admin/columns/Columns'; import {Icon} from '@xh/hoist/icon'; @@ -23,8 +22,6 @@ export class ConfigPanel extends Component { differModel = new ConfigDifferModel({}); gridModel = new RestGridModel({ - enableColChooser: true, - stateModel: 'configPanel', store: new RestStore({ url: 'rest/configAdmin', reloadLookupsOnLoad: true, @@ -131,17 +128,11 @@ export class ConfigPanel extends Component { } extraToolbarItems = () => { - const gridModel = this.gridModel.gridModel; - return [ - button({ - icon: Icon.diff(), - text: 'Compare w/ Remote', - onClick: this.onDifferBtnClick - }), - colChooserButton({gridModel}), - button({text: 'GroupBy Type', onClick: () => gridModel.setGroupBy('valueType')}), - button({text: 'Clear Grouping', onClick: () => gridModel.setGroupBy(null)}) - ]; + return button({ + icon: Icon.diff(), + text: 'Compare w/ Remote', + onClick: this.onDifferBtnClick + }); } onDifferBtnClick = () => {