Files
pillar/src/scripts/js/es6/common/vuecomponents/table/Table.js

274 lines
9.4 KiB
JavaScript

import './rows/renderer/Head'
import './rows/renderer/Row'
import './columns/filter/ColumnFilter'
import './rows/filter/RowFilter'
import {UnitOfWorkTracker} from '../mixins/UnitOfWorkTracker'
import {RowFilter} from './rows/filter/RowFilter'
class ComponentState {
/**
* Serializable state of this component.
*
* @param {Object} rowFilter
* @param {Object} columnFilter
*/
constructor(rowFilter, columnFilter) {
this.rowFilter = rowFilter;
this.columnFilter = columnFilter
}
}
const TEMPLATE =`
<div class="pillar-table-container"
:class="$options.name"
>
<div class="pillar-table-menu">
<pillar-table-row-filter
:rowObjects="sortedRowObjects"
:config="rowFilterConfig"
:componentState="(componentState || {}).rowFilter"
@visible-row-objects-changed="onVisibleRowObjectsChanged"
@component-state-changed="onRowFilterStateChanged"
/>
<pillar-table-actions
@item-clicked="onItemClicked"
/>
<pillar-table-column-filter
:columns="columns"
:componentState="(componentState || {}).columnFilter"
@visible-columns-changed="onVisibleColumnsChanged"
@component-state-changed="onColumnFilterStateChanged"
/>
</div>
<div class="pillar-table">
<pillar-table-head
:columns="visibleColumns"
@sort="onSort"
/>
<transition-group name="pillar-table-row" tag="div" class="pillar-table-row-group">
<pillar-table-row
v-for="rowObject in visibleRowObjects"
:columns="visibleColumns"
:rowObject="rowObject"
:key="rowObject.getId()"
@item-clicked="onItemClicked"
/>
</transition-group>
</div>
</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 is-initialized When all rows has been fetched, and are initialized.
* @emits selected-items-changed(selectedItems) When selected rows has changed.
* @emits component-state-changed(newState) When table state changed. Filtered rows, visible columns...
*/
let PillarTable = Vue.component('pillar-table-base', {
template: TEMPLATE,
mixins: [UnitOfWorkTracker],
props: {
selectedIds: {
type: Array,
default: () => {return []}
},
canChangeSelectionCB: {
type: Function,
default: () => true
},
canMultiSelect: {
type: Boolean,
default: true
},
componentState: {
// Instance of ComponentState (but type Object since it has been deserialized)
type: Object,
default: undefined
}
},
data: function() {
return {
currentlySelectedIds: [],
columns: [],
visibleColumns: [],
visibleRowObjects: [],
rowsSource: undefined, // Override with your implementations of RowSource
columnFactory: undefined, // Override with your implementations of ColumnFactoryBase
rowFilterConfig: undefined,
isInitialized: false,
rowFilterState: (this.componentState || {}).rowFilter,
columnFilterState: (this.componentState || {}).columnFilter,
compareRowsCB: (row1, row2) => 0
}
},
computed: {
rowObjects() {
return this.rowsSource.rowObjects || [];
},
/**
* Rows sorted with a column sorter
*/
sortedRowObjects() {
return this.rowObjects.concat().sort(this.compareRowsCB);
},
rowAndChildObjects() {
let all = [];
for (const row of this.rowObjects) {
all.push(row, ...row.getChildObjects());
}
return all;
},
selectedItems() {
return this.rowAndChildObjects.filter(it => it.isSelected)
.map(it => it.underlyingObject);
},
currentComponentState() {
if (this.isInitialized) {
return new ComponentState(
this.rowFilterState,
this.columnFilterState
);
}
return undefined;
}
},
watch: {
selectedIds(newValue) {
this.currentlySelectedIds = newValue;
},
currentlySelectedIds(newValue) {
this.rowAndChildObjects.forEach(item => {
item.isSelected = newValue.includes(item.getId());
});
},
selectedItems(newValue, oldValue) {
// Deep compare to avoid spamming un needed events
let hasChanged = JSON.stringify(newValue ) !== JSON.stringify(oldValue);
if (hasChanged) {
this.$emit('selected-items-changed', newValue);
}
},
isInitialized(newValue) {
if (newValue) {
this.$emit('is-initialized');
}
},
currentComponentState(newValue, oldValue) {
if (this.isInitialized) {
// Deep compare to avoid spamming un needed events
let hasChanged = JSON.stringify(newValue ) !== JSON.stringify(oldValue);
if (hasChanged) {
this.$emit('component-state-changed', newValue);
}
}
}
},
created() {
this.unitOfWork(
Promise.all([
this.columnFactory.thenGetColumns(),
this.rowsSource.thenGetRowObjects()
])
.then((resp) => {
this.columns = resp[0];
return this.rowsSource.thenInit();
})
.then(() => {
if (this.currentlySelectedIds.length === 0) {
this.currentlySelectedIds = this.selectedIds;
} else {
// User has clicked on a row while we inited the rows. Keep that selection!
}
this.isInitialized = true;
})
.catch((err) => {toastr.error(pillar.utils.messageFromError(err), 'Loading table failed')})
);
},
methods: {
onVisibleColumnsChanged(visibleColumns) {
this.visibleColumns = visibleColumns;
},
onColumnFilterStateChanged(newComponentState) {
this.columnFilterState = newComponentState;
},
onVisibleRowObjectsChanged(visibleRowObjects) {
this.visibleRowObjects = visibleRowObjects;
},
onRowFilterStateChanged(newComponentState) {
this.rowFilterState = newComponentState;
},
onSort(column, direction) {
function compareRows(r1, r2) {
return column.compareRows(r1, r2) * direction;
}
this.compareRowsCB = compareRows;
},
onItemClicked(clickEvent, itemId) {
if(!this.canChangeSelectionCB()) return;
if(this.isMultiToggleClick(clickEvent) && this.canMultiSelect) {
let slectedIdsWithoutClicked = this.currentlySelectedIds.filter(id => id !== itemId);
if (slectedIdsWithoutClicked.length < this.currentlySelectedIds.length) {
this.currentlySelectedIds = slectedIdsWithoutClicked;
} else {
this.currentlySelectedIds = [itemId, ...this.currentlySelectedIds];
}
} else if(this.isSelectBetweenClick(clickEvent) && this.canMultiSelect) {
if (this.currentlySelectedIds.length > 0) {
let betweenA = this.currentlySelectedIds[this.currentlySelectedIds.length -1];
let betweenB = itemId;
this.currentlySelectedIds = this.rowsBetween(betweenA, betweenB).map(it => it.getId());
} else {
this.currentlySelectedIds = [itemId];
}
}
else {
this.currentlySelectedIds = [itemId];
}
},
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;
return this.visibleRowObjects.filter((it) => {
if (hasFoundLast) return false;
if (!hasFoundFirst) {
hasFoundFirst = [id1, id2].includes(it.getId());
return hasFoundFirst;
}
hasFoundLast = [id1, id2].includes(it.getId());
return true;
})
}
},
components: {
'pillar-table-row-filter': RowFilter,
'pillar-table-actions': {template:'<div/>'},
}
});
export { PillarTable }