diff --git a/src/scripts/js/es6/common/vuecomponents/table/Table.js b/src/scripts/js/es6/common/vuecomponents/table/Table.js
index 8c4ffe0b..8167dc18 100644
--- a/src/scripts/js/es6/common/vuecomponents/table/Table.js
+++ b/src/scripts/js/es6/common/vuecomponents/table/Table.js
@@ -1,8 +1,9 @@
import './rows/renderer/Head'
import './rows/renderer/Row'
-import './filter/ColumnFilter'
-import './filter/RowFilter'
+import './columns/filter/ColumnFilter'
+import './rows/filter/RowFilter'
import {UnitOfWorkTracker} from '../mixins/UnitOfWorkTracker'
+import {RowFilter} from './rows/filter/RowFilter'
/**
* Table State
@@ -23,6 +24,19 @@ class TableState {
}
}
+class ComponentState {
+ /**
+ * Serializable state of this component.
+ *
+ * @param {Object} rowFilter
+ * @param {Object} columnFilter
+ */
+ constructor(rowFilter, columnFilter) {
+ this.rowFilter = rowFilter;
+ this.columnFilter = columnFilter
+ }
+}
+
const TEMPLATE =`
@@ -71,6 +90,7 @@ const TEMPLATE =`
*
* @emits isInitialized When all rows has been fetched, and are initialized.
* @emits selectItemsChanged(selectedItems) When selected rows has changed.
+ * @emits componentStateChanged(newState) When table state changed. Filtered rows, visible columns...
*/
let PillarTable = Vue.component('pillar-table-base', {
template: TEMPLATE,
@@ -88,15 +108,23 @@ let PillarTable = Vue.component('pillar-table-base', {
type: Boolean,
default: true
},
+ componentState: {
+ // Instance of ComponentState
+ type: Object,
+ default: undefined
+ }
},
data: function() {
return {
columns: [],
visibleColumns: [],
visibleRowObjects: [],
- rowsSource: undefined, // Override with your implementations of ColumnFactoryBase
- columnFactory: undefined, // Override with your implementations of RowSource
+ 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
}
},
@@ -120,6 +148,15 @@ let PillarTable = Vue.component('pillar-table-base', {
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: {
@@ -130,8 +167,8 @@ let PillarTable = Vue.component('pillar-table-base', {
},
selectedItems(newValue, oldValue) {
// Deep compare to avoid spamming un needed events
- let hasChanged = JSON.stringify(newValue ) === JSON.stringify(oldValue);
- if (!hasChanged) {
+ let hasChanged = JSON.stringify(newValue ) !== JSON.stringify(oldValue);
+ if (hasChanged) {
this.$emit('selectItemsChanged', newValue);
}
},
@@ -139,6 +176,15 @@ let PillarTable = Vue.component('pillar-table-base', {
if (newValue) {
this.$emit('isInitialized');
}
+ },
+ 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('componentStateChanged', newValue);
+ }
+ }
}
},
created() {
@@ -169,9 +215,15 @@ let PillarTable = Vue.component('pillar-table-base', {
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;
@@ -228,6 +280,9 @@ let PillarTable = Vue.component('pillar-table-base', {
return true;
})
}
+ },
+ components: {
+ 'pillar-table-row-filter': RowFilter
}
});
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 c2c93851..c6b4f91d 100644
--- a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js
+++ b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js
@@ -4,13 +4,12 @@ import { CellDefault } from '../cells/renderer/CellDefault'
* Column logic
*/
-let nextColumnId = 0;
export class ColumnBase {
constructor(displayName, columnType) {
- this._id = nextColumnId++;
this.displayName = displayName;
this.columnType = columnType;
this.isMandatory = false;
+ this.includedByDefault = true;
this.isSortable = true;
this.isHighLighted = 0;
}
diff --git a/src/scripts/js/es6/common/vuecomponents/table/columns/filter/ColumnFilter.js b/src/scripts/js/es6/common/vuecomponents/table/columns/filter/ColumnFilter.js
new file mode 100644
index 00000000..04e0eff4
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/columns/filter/ColumnFilter.js
@@ -0,0 +1,130 @@
+import '../../../menu/DropDown'
+
+const TEMPLATE =`
+
+`;
+
+class ColumnState{
+ constructor() {
+ this.displayName;
+ this.isVisible;
+ this.isMandatory;
+ }
+
+ static createDefault(column) {
+ let state = new ColumnState;
+ state.displayName = column.displayName;
+ state.isVisible = !!column.includedByDefault;
+ state.isMandatory = !!column.isMandatory;
+ return state;
+ }
+}
+
+class ComponentState {
+ /**
+ * Serializable state of this component.
+ *
+ * @param {Array} selected The columns that should be visible
+ */
+ constructor(selected) {
+ this.selected = selected;
+ }
+}
+
+/**
+ * Component to select what columns to render in the table.
+ *
+ * @emits visibleColumnsChanged(columns) When visible columns has changed
+ * @emits componentStateChanged(newState) When column filter state changed.
+ */
+let Filter = Vue.component('pillar-table-column-filter', {
+ template: TEMPLATE,
+ props: {
+ columns: Array, // Instances of ColumnBase
+ componentState: Object, // Instance of ComponentState
+ },
+ data() {
+ return {
+ columnStates: this.createInitialColumnStates(), // Instances of ColumnState
+ }
+ },
+ computed: {
+ visibleColumns() {
+ return this.columns.filter((candidate) => {
+ return candidate.isMandatory || this.isColumnStateVisible(candidate);
+ });
+ },
+ columnFilterState() {
+ return new ComponentState(this.visibleColumns.map(it => it.displayName));
+ }
+ },
+ watch: {
+ columns() {
+ this.columnStates = this.createInitialColumnStates();
+ },
+ visibleColumns(visibleColumns) {
+ this.$emit('visibleColumnsChanged', visibleColumns);
+ },
+ columnFilterState(newValue) {
+ this.$emit('componentStateChanged', newValue);
+ }
+ },
+ created() {
+ this.$emit('visibleColumnsChanged', this.visibleColumns);
+ },
+ methods: {
+ createInitialColumnStates() {
+ let columnStateCB = ColumnState.createDefault;
+ if (this.componentState && this.componentState.selected) {
+ let selected = this.componentState.selected;
+ columnStateCB = (column) => {
+ let state = ColumnState.createDefault(column);
+ state.isVisible = selected.includes(column.displayName);
+ return state;
+ }
+ }
+
+ return this.columns.reduce((states, c) => {
+ if(!c.isMandatory) {
+ states.push(columnStateCB(c));
+ }
+ return states;
+ }, []);
+ },
+ isColumnStateVisible(column) {
+ for (let state of this.columnStates) {
+ if (state.displayName === column.displayName) {
+ return state.isVisible;
+ }
+ }
+ return false;
+ },
+ toggleColumn(column) {
+ column.isVisible = !column.isVisible;
+ }
+ },
+});
+
+export { Filter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js b/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js
deleted file mode 100644
index 0ca380a4..00000000
--- a/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js
+++ /dev/null
@@ -1,91 +0,0 @@
-import '../../menu/DropDown'
-
-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, // Instances of ColumnBase
- },
- data() {
- return {
- columnStates: [], // Instances of ColumnState
- }
- },
- computed: {
- visibleColumns() {
- return this.columns.filter((candidate) => {
- return candidate.isMandatory || this.isColumnStateVisible(candidate);
- });
- }
- },
- watch: {
- columns() {
- this.columnStates = this.setColumnStates();
- },
- visibleColumns(visibleColumns) {
- this.$emit('visibleColumnsChanged', visibleColumns);
- }
- },
- created() {
- this.$emit('visibleColumnsChanged', this.visibleColumns);
- },
- methods: {
- setColumnStates() {
- return this.columns.reduce((states, c) => {
- if (!c.isMandatory) {
- states.push(
- new ColumnState(c._id, c.displayName, true)
- );
- }
- return states;
- }, [])
- },
- isColumnStateVisible(column) {
- for (let state of this.columnStates) {
- if (state.id === column._id) {
- return state.isVisible;
- }
- }
- return false;
- },
- },
-});
-
-export { Filter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js b/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js
deleted file mode 100644
index 6282c200..00000000
--- a/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js
+++ /dev/null
@@ -1,48 +0,0 @@
-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: {
- rowObjects: Array
- },
- data() {
- return {
- nameQuery: '',
- }
- },
- computed: {
- nameQueryLoweCase() {
- return this.nameQuery.toLowerCase();
- },
- visibleRowObjects() {
- return this.rowObjects.filter((row) => {
- return this.filterByName(row);
- });
- }
- },
- watch: {
- visibleRowObjects(visibleRowObjects) {
- this.$emit('visibleRowObjectsChanged', visibleRowObjects);
- }
- },
- created() {
- this.$emit('visibleRowObjectsChanged', this.visibleRowObjects);
- },
- methods: {
- filterByName(rowObject) {
- return rowObject.getName().toLowerCase().indexOf(this.nameQueryLoweCase) !== -1;
- },
- },
-});
-
-export { RowFilter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/filter/EnumFilter.js b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/EnumFilter.js
new file mode 100644
index 00000000..12e75f44
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/EnumFilter.js
@@ -0,0 +1,153 @@
+const TEMPLATE =`
+
+
+
+
+
+`;
+
+class EnumState{
+ constructor(displayName, value, isVisible) {
+ this.displayName = displayName;
+ this.value = value;
+ this.isVisible = isVisible;
+ }
+}
+
+class ComponentState {
+ /**
+ * Serializable state of this component.
+ *
+ * @param {Array} selected The enums that should be visible
+ */
+ constructor(selected) {
+ this.selected = selected;
+ }
+}
+
+/**
+ * Filter row objects based on enumeratable values.
+ *
+ * @emits visibleRowObjectsChanged(rowObjects) When the objects to be visible has changed.
+ * @emits componentStateChanged(newState) When row filter state changed.
+ */
+let EnumFilter = {
+ template: TEMPLATE,
+ props: {
+ label: String,
+ availableValues: Array, // Array with valid values [{value: abc, displayName: xyz},...]
+ componentState: Object, // Instance of ComponentState.
+ valueExtractorCB: {
+ // Callback to extract enumvalue from a rowObject
+ type: Function,
+ default: (rowObject) => {throw Error("Not Implemented")}
+ },
+ rowObjects: Array,
+ },
+ data() {
+ return {
+ enumVisibilities: this.initEnumVisibilities(),
+ }
+ },
+ computed: {
+ visibleRowObjects() {
+ return this.rowObjects.filter((row) => {
+ return this.shouldBeVisible(row);
+ });
+ },
+ includesRows() {
+ for (const key in this.enumVisibilities) {
+ if(!this.enumVisibilities[key].isVisible) return false;
+ }
+ return true;
+ },
+ enumButtonClasses() {
+ return {
+ 'filter-active': !this.includesRows
+ }
+ },
+ currentComponentState() {
+ let visibleEnums = [];
+ for (const key in this.enumVisibilities) {
+ const enumState = this.enumVisibilities[key];
+ if (enumState.isVisible) {
+ visibleEnums.push(enumState.value);
+ }
+ }
+
+ return new ComponentState(visibleEnums);
+ }
+ },
+ watch: {
+ visibleRowObjects(visibleRowObjects) {
+ this.$emit('visibleRowObjectsChanged', visibleRowObjects);
+ },
+ currentComponentState(newValue) {
+ this.$emit('componentStateChanged', newValue);
+ }
+ },
+ created() {
+ this.$emit('visibleRowObjectsChanged', this.visibleRowObjects);
+ },
+ methods: {
+ shouldBeVisible(rowObject) {
+ let value = this.valueExtractorCB(rowObject);
+ if (typeof this.enumVisibilities[value] === 'undefined') {
+ console.warn(`RowObject ${rowObject.getId()} has an invalid ${this.label} enum: ${value}`)
+ return true;
+ }
+ return this.enumVisibilities[value].isVisible;
+ },
+ initEnumVisibilities() {
+ let initialValueCB = () => true;
+ if (this.componentState && this.componentState.selected) {
+ initialValueCB = (val) => {
+ return this.componentState.selected.includes(val.value);
+ };
+ }
+
+ return this.availableValues.reduce((agg, val)=> {
+ agg[val.value] = new EnumState(val.displayName, val.value, initialValueCB(val));
+ return agg;
+ }, {});
+ },
+ toggleEnum(value) {
+ this.enumVisibilities[value].isVisible = !this.enumVisibilities[value].isVisible;
+ },
+ toggleAll() {
+ let newValue = !this.includesRows;
+ for (const key in this.enumVisibilities) {
+ this.enumVisibilities[key].isVisible = newValue;
+ }
+ }
+ },
+};
+
+export { EnumFilter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/filter/NameFilter.js b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/NameFilter.js
new file mode 100644
index 00000000..53e2a33b
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/NameFilter.js
@@ -0,0 +1,35 @@
+import {TextFilter} from './TextFilter'
+
+const TEMPLATE =`
+
+`;
+/**
+ * Filter row objects based on there name.
+ *
+ * @emits visibleRowObjectsChanged(rowObjects) When the objects to be visible has changed.
+ * @emits componentStateChanged(newState) When row filter state changed.
+ */
+let NameFilter = {
+ template: TEMPLATE,
+ props: {
+ componentState: Object, // Instance of object that componentStateChanged emitted. To restore previous state.
+ rowObjects: Array,
+ },
+ methods: {
+ extractName(rowObject) {
+ return rowObject.getName();
+ },
+ },
+ components: {
+ 'text-filter': TextFilter,
+ },
+};
+
+export { NameFilter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/filter/RowFilter.js b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/RowFilter.js
new file mode 100644
index 00000000..be8dcd37
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/RowFilter.js
@@ -0,0 +1,25 @@
+import {NameFilter} from './NameFilter'
+
+const TEMPLATE =`
+
+
+
+`;
+
+let RowFilter = {
+ template: TEMPLATE,
+ props: {
+ rowObjects: Array,
+ componentState: Object
+ },
+ components: {
+ 'name-filter': NameFilter
+ }
+};
+
+export { RowFilter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/filter/StatusFilter.js b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/StatusFilter.js
new file mode 100644
index 00000000..7361449a
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/StatusFilter.js
@@ -0,0 +1,48 @@
+import {EnumFilter} from './EnumFilter'
+
+const TEMPLATE =`
+
+`;
+/**
+ * Filter row objects based on there status.
+ *
+ * @emits visibleRowObjectsChanged(rowObjects) When the objects to be visible has changed.
+ * @emits componentStateChanged(newState) When row filter state changed.
+ */
+let StatusFilter = {
+ template: TEMPLATE,
+ props: {
+ availableStatuses: Array, // Array with valid values ['abc', 'xyz']
+ componentState: Object, // Instance of object that componentStateChanged emitted. To restore previous state.
+ rowObjects: Array,
+ },
+ computed: {
+ availableEnumValues() {
+ let statusCopy = this.availableStatuses.concat().sort()
+ return statusCopy.map(status =>{
+ return {
+ value: status,
+ displayName: status.replace(/-|_/g, ' ') // Replace -(dash) and _(underscore) with space
+ }
+ });
+ }
+ },
+ methods: {
+ extractStatus(rowObject) {
+ return rowObject.getStatus();
+ },
+ },
+ components: {
+ 'enum-filter': EnumFilter,
+ },
+};
+
+export { StatusFilter }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/rows/filter/TextFilter.js b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/TextFilter.js
new file mode 100644
index 00000000..bab98d14
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/filter/TextFilter.js
@@ -0,0 +1,86 @@
+const TEMPLATE =`
+
+`;
+
+class ComponentState {
+ /**
+ * Serializable state of this component.
+ *
+ * @param {String} textQuery
+ */
+ constructor(textQuery) {
+ this.textQuery = textQuery;
+ }
+}
+
+/**
+ * Component to filter rowobjects by a text value
+ *
+ * @emits visibleRowObjectsChanged(rowObjects) When the objects to be visible has changed.
+ * @emits componentStateChanged(newState) When row filter state changed. Filter query...
+ */
+let TextFilter = {
+ template: TEMPLATE,
+ props: {
+ label: String,
+ rowObjects: Array,
+ componentState: {
+ // Instance of ComponentState
+ type: Object,
+ default: undefined
+ },
+ valueExtractorCB: {
+ // Callback to extract text to filter from a rowObject
+ type: Function,
+ default: (rowObject) => {throw Error("Not Implemented")}
+ }
+ },
+ data() {
+ return {
+ textQuery: (this.componentState || {}).textQuery || '',
+ }
+ },
+ computed: {
+ textQueryLoweCase() {
+ return this.textQuery.toLowerCase();
+ },
+ visibleRowObjects() {
+ return this.rowObjects.filter((row) => {
+ return this.filterByText(row);
+ });
+ },
+ textInputClasses() {
+ return {
+ 'filter-active': this.textQuery.length > 0
+ };
+ },
+ currentComponentState() {
+ return new ComponentState(this.textQuery);
+ },
+ placeholderText() {
+ return `Filter by ${this.label}`;
+ }
+ },
+ watch: {
+ visibleRowObjects(visibleRowObjects) {
+ this.$emit('visibleRowObjectsChanged', visibleRowObjects);
+ },
+ currentComponentState(newValue) {
+ this.$emit('componentStateChanged', newValue);
+ }
+ },
+ created() {
+ this.$emit('visibleRowObjectsChanged', this.visibleRowObjects);
+ },
+ methods: {
+ filterByText(rowObject) {
+ return (this.valueExtractorCB(rowObject) || '').toLowerCase().indexOf(this.textQueryLoweCase) !== -1;
+ },
+ },
+};
+
+export { TextFilter }
diff --git a/src/styles/components/_pillar_table.sass b/src/styles/components/_pillar_table.sass
index da3e4070..5a6e1d97 100644
--- a/src/styles/components/_pillar_table.sass
+++ b/src/styles/components/_pillar_table.sass
@@ -112,6 +112,9 @@ $thumbnail-max-height: calc(110px * (9/16))
display: flex
flex-direction: row
+ .action
+ cursor: pointer
+
.settings-menu
display: flex
flex-direction: column
@@ -123,10 +126,17 @@ $thumbnail-max-height: calc(110px * (9/16))
text-transform: capitalize
z-index: $z-index-base + 1
box-shadow: 0 2px 5px rgba(black, .4)
+ user-select: none
.pillar-table-row-filter
display: flex
flex-direction: row
+
+ input.filter-active
+ background-color: rgba($color-info, .50)
+
+ .pi-filter.filter-active
+ color: $color-info
.pillar-table-actions
margin-left: auto