From 4136da110f4859c98ebc813e04f7189d90b1335c Mon Sep 17 00:00:00 2001 From: Tobias Johansson Date: Thu, 14 Mar 2019 10:30:23 +0100 Subject: [PATCH] Added comments and minor refactoring --- .../js/es6/common/vuecomponents/init.js | 6 +- .../es6/common/vuecomponents/table/Table.js | 62 ++++++++++++++++--- .../table/cells/renderer/CellDefault.js | 4 ++ .../table/cells/renderer/CellPrettyDate.js | 4 ++ .../table/cells/renderer/CellProxy.js | 21 ++++++- .../table/cells/renderer/HeadCell.js | 5 ++ .../vuecomponents/table/columns/ColumnBase.js | 13 +++- .../table/columns/ColumnFactoryBase.js | 9 ++- .../table/columns/renderer/Column.js | 10 --- .../table/filter/ColumnFilter.js | 27 +++++--- .../vuecomponents/table/filter/RowFilter.js | 3 + .../vuecomponents/table/rows/RowObjectBase.js | 33 +++++----- .../table/rows/RowObjectsSourceBase.js | 14 ++++- .../vuecomponents/table/rows/renderer/Head.js | 4 +- .../vuecomponents/table/rows/renderer/Row.js | 4 +- 15 files changed, 162 insertions(+), 57 deletions(-) delete mode 100644 src/scripts/js/es6/common/vuecomponents/table/columns/renderer/Column.js diff --git a/src/scripts/js/es6/common/vuecomponents/init.js b/src/scripts/js/es6/common/vuecomponents/init.js index 5f42b6b3..6cd6b180 100644 --- a/src/scripts/js/es6/common/vuecomponents/init.js +++ b/src/scripts/js/es6/common/vuecomponents/init.js @@ -2,13 +2,13 @@ import './comments/CommentTree' import './customdirectives/click-outside' import { UnitOfWorkTracker } from './mixins/UnitOfWorkTracker' import { BrowserHistoryState, StateSaveMode } from './mixins/BrowserHistoryState' -import { PillarTable } from './table/Table' +import { PillarTable, TableState } from './table/Table' import { CellPrettyDate } from './table/cells/renderer/CellPrettyDate' import { CellDefault } from './table/cells/renderer/CellDefault' import { ColumnBase } from './table/columns/ColumnBase' import { ColumnFactoryBase } from './table/columns/ColumnFactoryBase' import { RowObjectsSourceBase } from './table/rows/RowObjectsSourceBase' -import { RowBase, RowState } from './table/rows/RowObjectBase' +import { RowBase } from './table/rows/RowObjectBase' import { RowFilter } from './table/filter/RowFilter' let mixins = { @@ -19,6 +19,7 @@ let mixins = { let table = { PillarTable, + TableState, columns: { ColumnBase, ColumnFactoryBase, @@ -32,7 +33,6 @@ let table = { rows: { RowObjectsSourceBase, RowBase, - RowState, }, filter: { RowFilter diff --git a/src/scripts/js/es6/common/vuecomponents/table/Table.js b/src/scripts/js/es6/common/vuecomponents/table/Table.js index 810b6fd0..bc9149b2 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/Table.js +++ b/src/scripts/js/es6/common/vuecomponents/table/Table.js @@ -3,7 +3,25 @@ import './rows/renderer/Row' import './filter/ColumnFilter' import './filter/RowFilter' import {UnitOfWorkTracker} from '../mixins/UnitOfWorkTracker' -import {RowState} from './rows/RowObjectBase' + +/** + * Table State + * + * Used to restore a table to a given state. + */ +class TableState { + constructor(selectedIds) { + this.selectedIds = selectedIds || []; + } + + /** + * Apply state to row + * @param {RowBase} rowObject + */ + applyRowState(rowObject) { + rowObject.isSelected = this.selectedIds.includes(rowObject.getId()); + } +} const TEMPLATE =`
`; +/** + * The table renders RowObject instances for the rows, and ColumnBase instances for the Columns. + * Extend the table to fit your needs. + * + * Usage: + * Extend RowBase to wrap the data you want in your row + * Extend ColumnBase once per column type you need + * Extend RowObjectsSourceBase to fetch and initialize your rows + * Extend ColumnFactoryBase to create the rows for your table + * Extend This Table with your ColumnFactory and RowSource + * + * @emits isInitialized When all rows has been fetched, and are initialized. + * @emits selectItemsChanged(selectedItems) When selected rows has changed. + */ let PillarTable = Vue.component('pillar-table-base', { template: TEMPLATE, mixins: [UnitOfWorkTracker], @@ -64,15 +96,18 @@ let PillarTable = Vue.component('pillar-table-base', { visibleRowObjects: [], rowsSource: {}, isInitialized: false, - compareRows: (row1, row2) => 0 + compareRowsCB: (row1, row2) => 0 } }, computed: { rowObjects() { return this.rowsSource.rowObjects || []; }, + /** + * Rows sorted with a column sorter + */ sortedRowObjects() { - return this.rowObjects.concat().sort(this.compareRows); + return this.rowObjects.concat().sort(this.compareRowsCB); }, rowAndChildObjects() { let all = []; @@ -105,19 +140,19 @@ let PillarTable = Vue.component('pillar-table-base', { let columnFactory = new this.$options.columnFactory(this.projectId); this.rowsSource = new this.$options.rowsSource(this.projectId); - let rowState = new RowState(this.selectedIds); + let tableState = new TableState(this.selectedIds); this.unitOfWork( Promise.all([ columnFactory.thenGetColumns(), - this.rowsSource.thenFetchObjects() + this.rowsSource.thenGetRowObjects() ]) .then((resp) => { this.columns = resp[0]; return this.rowsSource.thenInit(); }) .then(() => { - this.rowAndChildObjects.forEach(rowState.applyState.bind(rowState)); + this.rowAndChildObjects.forEach(tableState.applyRowState.bind(tableState)); this.isInitialized = true; }) ); @@ -133,10 +168,11 @@ let PillarTable = Vue.component('pillar-table-base', { function compareRows(r1, r2) { return column.compareRows(r1, r2) * direction; } - this.compareRows = compareRows; + this.compareRowsCB = compareRows; }, onItemClicked(clickEvent, itemId) { if(!this.canChangeSelectionCB()) return; + if(this.isMultiToggleClick(clickEvent) && this.canMultiSelect) { let slectedIdsWithoutClicked = this.selectedIds.filter(id => id !== itemId); if (slectedIdsWithoutClicked.length < this.selectedIds.length) { @@ -144,7 +180,7 @@ let PillarTable = Vue.component('pillar-table-base', { } else { this.selectedIds = [itemId, ...this.selectedIds]; } - } else if(this.isSelectBetween(clickEvent) && this.canMultiSelect) { + } else if(this.isSelectBetweenClick(clickEvent) && this.canMultiSelect) { if (this.selectedIds.length > 0) { let betweenA = this.selectedIds[this.selectedIds.length -1]; let betweenB = itemId; @@ -162,13 +198,19 @@ let PillarTable = Vue.component('pillar-table-base', { } } }, - isSelectBetween(clickEvent) { + isSelectBetweenClick(clickEvent) { return clickEvent.shiftKey; }, isMultiToggleClick(clickEvent) { return clickEvent.ctrlKey || clickEvent.metaKey; // Mac command key }, + /** + * Get visible rows between id1 and id2 + * @param {String} id1 + * @param {String} id2 + * @returns {Array(RowObjects)} + */ rowsBetween(id1, id2) { let hasFoundFirst = false; let hasFoundLast = false; @@ -185,4 +227,4 @@ let PillarTable = Vue.component('pillar-table-base', { } }); -export { PillarTable } +export { PillarTable, TableState } diff --git a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellDefault.js b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellDefault.js index e3c433be..8a9a71f1 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellDefault.js +++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellDefault.js @@ -4,6 +4,10 @@ const TEMPLATE =`
`; +/** + * Default cell renderer. Takes raw cell value and formats it. + * Override for custom formatting of value. + */ let CellDefault = Vue.component('pillar-cell-default', { template: TEMPLATE, props: { diff --git a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellPrettyDate.js b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellPrettyDate.js index f158d552..dc884568 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellPrettyDate.js +++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellPrettyDate.js @@ -1,5 +1,9 @@ import { CellDefault } from './CellDefault' +/** + * Formats raw values as "pretty date". + * Expects rawCellValue to be a date. + */ let CellPrettyDate = Vue.component('pillar-cell-pretty-date', { extends: CellDefault, computed: { diff --git a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellProxy.js b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellProxy.js index c5838cdc..ac3322de 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellProxy.js +++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellProxy.js @@ -10,22 +10,39 @@ const TEMPLATE =` /> `; +/** + * Renders the cell that the column requests. + * + * @emits item-clicked(mouseEvent,itemId) Re-emits if real cell is emitting it + */ let CellProxy = Vue.component('pillar-cell-proxy', { template: TEMPLATE, props: { - column: Object, - rowObject: Object + column: Object, // ColumnBase + rowObject: Object // RowObject }, computed: { + /** + * Raw unformated cell value + */ rawCellValue() { return this.column.getRawCellValue(this.rowObject) || ''; }, + /** + * Name of the cell render component to be rendered + */ cellRenderer() { return this.column.getCellRenderer(this.rowObject); }, + /** + * Css classes to apply to the cell + */ cellClasses() { return this.column.getCellClasses(this.rawCellValue, this.rowObject); }, + /** + * Cell tooltip + */ cellTitle() { return this.column.getCellTitle(this.rawCellValue, this.rowObject); } diff --git a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/HeadCell.js b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/HeadCell.js index 97facd37..3416b20c 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/HeadCell.js +++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/HeadCell.js @@ -22,6 +22,11 @@ const TEMPLATE =` `; +/** + * A cell in the Header of the table + * + * @emits sort(column,direction) When user clicks column sort arrows. + */ Vue.component('pillar-head-cell', { template: TEMPLATE, props: { diff --git a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js index 29cfa8f9..a3532e96 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js +++ b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js @@ -1,5 +1,9 @@ import { CellDefault } from '../cells/renderer/CellDefault' +/** + * Column logic + */ + let nextColumnId = 0; export class ColumnBase { constructor(displayName, columnType) { @@ -13,13 +17,18 @@ export class ColumnBase { /** * - * @param {*} rowObject + * @param {RowObject} rowObject * @returns {String} Name of the Cell renderer component */ getCellRenderer(rowObject) { return CellDefault.options.name; } + /** + * + * @param {RowObject} rowObject + * @returns {*} Raw unformated value + */ getRawCellValue(rowObject) { // Should be overridden throw Error('Not implemented'); @@ -38,7 +47,7 @@ export class ColumnBase { /** * Object with css classes to use on the header cell - * @returns {Any} Object with css classes + * @returns {Object} Object with css classes */ getHeaderCellClasses() { // Should be overridden diff --git a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js index 18c1fa6a..878782e9 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js +++ b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js @@ -1,10 +1,16 @@ +/** + * Provides the columns that are available in a table. + */ class ColumnFactoryBase{ constructor(projectId) { this.projectId = projectId; this.projectPromise; } - // Override this + /** + * To be overridden for your purposes + * @returns {Promise(ColumnBase)} The columns that are available in the table. + */ thenGetColumns() { throw Error('Not implemented') } @@ -19,3 +25,4 @@ class ColumnFactoryBase{ } export { ColumnFactoryBase } + diff --git a/src/scripts/js/es6/common/vuecomponents/table/columns/renderer/Column.js b/src/scripts/js/es6/common/vuecomponents/table/columns/renderer/Column.js deleted file mode 100644 index d22392bc..00000000 --- a/src/scripts/js/es6/common/vuecomponents/table/columns/renderer/Column.js +++ /dev/null @@ -1,10 +0,0 @@ -const TEMPLATE =` -
-`; - -Vue.component('pillar-table-column', { - template: TEMPLATE, - props: { - column: Object - }, -}); diff --git a/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js b/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js index 222e9532..0ca380a4 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js +++ b/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js @@ -25,14 +25,27 @@ const TEMPLATE =`
`; +class ColumnState{ + constructor(id, displayName, isVisible) { + this.id = id; + this.displayName = displayName; + this.isVisible = isVisible; + } +} + +/** + * Component to select what columns to render in the table. + * + * @emits visibleColumnsChanged(columns) When visible columns has changed + */ let Filter = Vue.component('pillar-table-column-filter', { template: TEMPLATE, props: { - columns: Array, + columns: Array, // Instances of ColumnBase }, data() { return { - columnStates: [], + columnStates: [], // Instances of ColumnState } }, computed: { @@ -57,18 +70,16 @@ let Filter = Vue.component('pillar-table-column-filter', { setColumnStates() { return this.columns.reduce((states, c) => { if (!c.isMandatory) { - states.push({ - _id: c._id, - displayName: c.displayName, - isVisible: true, - }); + states.push( + new ColumnState(c._id, c.displayName, true) + ); } return states; }, []) }, isColumnStateVisible(column) { for (let state of this.columnStates) { - if (state._id === column._id) { + if (state.id === column._id) { return state.isVisible; } } diff --git a/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js b/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js index 098627a2..6282c200 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js +++ b/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js @@ -7,6 +7,9 @@ const TEMPLATE =` `; +/** + * @emits visibleRowObjectsChanged(rowObjects) When the what objects to be visible has changed. + */ let RowFilter = Vue.component('pillar-table-row-filter', { template: TEMPLATE, props: { diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectBase.js b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectBase.js index a24c99b6..02394e43 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectBase.js +++ b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectBase.js @@ -1,26 +1,16 @@ -class RowState { - constructor(selectedIds) { - this.selectedIds = selectedIds || []; - } - - /** - * - * @param {RowBase} rowObject - */ - applyState(rowObject) { - rowObject.isSelected = this.selectedIds.includes(rowObject.getId()); - } -} - +/** + * Each object to be visualized in the table is wrapped in a RowBase object. Column cells interact with it, + */ class RowBase { constructor(underlyingObject) { this.underlyingObject = underlyingObject; this.isInitialized = false; - this.isVisible = true; this.isSelected = false; } - + /** + * Called after the row has been created to initalize async properties. Fetching child objects for instance + */ thenInit() { return this._thenInitImpl() .then(() => { @@ -28,6 +18,9 @@ class RowBase { }) } + /** + * Override to initialize async properties such as fetching child objects. + */ _thenInitImpl() { return Promise.resolve(); } @@ -44,15 +37,21 @@ class RowBase { return this.underlyingObject.properties; } + /** + * The css classes that should be applied to the row in the table + */ getRowClasses() { return { "is-busy": !this.isInitialized } } + /** + * A row could have children (shots has tasks for example). Children should also be instances of RowObject + */ getChildObjects() { return []; } } -export { RowBase, RowState } +export { RowBase } diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectsSourceBase.js b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectsSourceBase.js index cb3d4139..429b733a 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectsSourceBase.js +++ b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectsSourceBase.js @@ -1,14 +1,24 @@ +/** + * The provider of RowObjects to a table. + * Extend to fit your purpose. + */ class RowObjectsSourceBase { constructor(projectId) { this.projectId = projectId; this.rowObjects = []; } - // Override this - thenFetchObjects() { + /** + * Should be overriden to fetch and create the row objects to we rendered in the table. The Row objects should be stored in + * this.rowObjects + */ + thenGetRowObjects() { throw Error('Not implemented'); } + /** + * Inits all its row objects. + */ thenInit() { return Promise.all( this.rowObjects.map(it => it.thenInit()) diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Head.js b/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Head.js index d71a88b2..d614f8e1 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Head.js +++ b/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Head.js @@ -9,7 +9,9 @@ const TEMPLATE =` /> `; - +/** + * @emits sort(column,direction) When a column head has been clicked + */ Vue.component('pillar-table-head', { template: TEMPLATE, props: { diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Row.js b/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Row.js index a74758a9..7f2e0e31 100644 --- a/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Row.js +++ b/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Row.js @@ -15,7 +15,9 @@ const TEMPLATE =` /> `; - +/** + * @emits item-clicked(mouseEvent,itemId) When a RowObject has been clicked + */ Vue.component('pillar-table-row', { template: TEMPLATE, props: {