Added comments and minor refactoring
This commit is contained in:
@@ -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
|
||||
|
@@ -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 =`
|
||||
<div class="pillar-table-container"
|
||||
@@ -40,6 +58,20 @@ const TEMPLATE =`
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* 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 }
|
||||
|
@@ -4,6 +4,10 @@ const TEMPLATE =`
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
|
@@ -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: {
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -22,6 +22,11 @@ const TEMPLATE =`
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
|
@@ -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
|
||||
|
@@ -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 }
|
||||
|
||||
|
@@ -1,10 +0,0 @@
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-column"/>
|
||||
`;
|
||||
|
||||
Vue.component('pillar-table-column', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
column: Object
|
||||
},
|
||||
});
|
@@ -25,14 +25,27 @@ const TEMPLATE =`
|
||||
</div>
|
||||
`;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,9 @@ const TEMPLATE =`
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* @emits visibleRowObjectsChanged(rowObjects) When the what objects to be visible has changed.
|
||||
*/
|
||||
let RowFilter = Vue.component('pillar-table-row-filter', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
|
@@ -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 }
|
||||
|
@@ -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())
|
||||
|
@@ -9,7 +9,9 @@ const TEMPLATE =`
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* @emits sort(column,direction) When a column head has been clicked
|
||||
*/
|
||||
Vue.component('pillar-table-head', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
|
@@ -15,7 +15,9 @@ const TEMPLATE =`
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
/**
|
||||
* @emits item-clicked(mouseEvent,itemId) When a RowObject has been clicked
|
||||
*/
|
||||
Vue.component('pillar-table-row', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
|
Reference in New Issue
Block a user