diff --git a/src/scripts/js/es6/common/api/init.js b/src/scripts/js/es6/common/api/init.js
index bbd9898d..dcb2cdb4 100644
--- a/src/scripts/js/es6/common/api/init.js
+++ b/src/scripts/js/es6/common/api/init.js
@@ -1 +1,3 @@
-export { thenMarkdownToHtml } from './markdown'
\ No newline at end of file
+export { thenMarkdownToHtml } from './markdown'
+export { thenGetProject } from './projects'
+export { thenGetNodes } from './nodes'
diff --git a/src/scripts/js/es6/common/api/nodes.js b/src/scripts/js/es6/common/api/nodes.js
new file mode 100644
index 00000000..053daf80
--- /dev/null
+++ b/src/scripts/js/es6/common/api/nodes.js
@@ -0,0 +1,8 @@
+function thenGetNodes(where, embedded={}) {
+ let encodedWhere = encodeURIComponent(JSON.stringify(where));
+ let encodedEmbedded = encodeURIComponent(JSON.stringify(embedded));
+
+ return $.get(`/api/nodes?where=${encodedWhere}&embedded=${encodedEmbedded}`);
+}
+
+export { thenGetNodes }
diff --git a/src/scripts/js/es6/common/api/projects.js b/src/scripts/js/es6/common/api/projects.js
new file mode 100644
index 00000000..2f813824
--- /dev/null
+++ b/src/scripts/js/es6/common/api/projects.js
@@ -0,0 +1,5 @@
+function thenGetProject(projectId) {
+ return $.get(`/api/projects/${projectId}`);
+}
+
+export { thenGetProject }
diff --git a/src/scripts/js/es6/common/events/Nodes.js b/src/scripts/js/es6/common/events/Nodes.js
new file mode 100644
index 00000000..2be3dfe6
--- /dev/null
+++ b/src/scripts/js/es6/common/events/Nodes.js
@@ -0,0 +1,92 @@
+class EventName {
+ static parentCreated(parentId, node_type) {
+ return `pillar:node:${parentId}:created-${node_type}`;
+ }
+
+ static globalCreated(node_type) {
+ return `pillar:node:created-${node_type}`;
+ }
+
+ static updated(nodeId) {
+ return `pillar:node:${nodeId}:updated`;
+ }
+
+ static deleted(nodeId) {
+ return `pillar:node:${nodeId}:deleted`;
+ }
+}
+
+class Nodes {
+ static triggerCreated(node) {
+ if (node.parent) {
+ $('body').trigger(
+ EventName.parentCreated(node.parent, node.node_type),
+ node);
+ }
+ $('body').trigger(
+ EventName.globalCreated(node.node_type),
+ node);
+ }
+
+ static onParentCreated(parentId, node_type, cb){
+ $('body').on(
+ EventName.parentCreated(parentId, node_type),
+ cb);
+ }
+
+ static offParentCreated(parentId, node_type, cb){
+ $('body').off(
+ EventName.parentCreated(parentId, node_type),
+ cb);
+ }
+
+ static onCreated(node_type, cb){
+ $('body').on(
+ EventName.globalCreated(node_type),
+ cb);
+ }
+
+ static offCreated(node_type, cb){
+ $('body').off(
+ EventName.globalCreated(node_type),
+ cb);
+ }
+
+ static triggerUpdated(node) {
+ $('body').trigger(
+ EventName.updated(node._id),
+ node);
+ }
+
+ static onUpdated(nodeId, cb) {
+ $('body').on(
+ EventName.updated(nodeId),
+ cb);
+ }
+
+ static offUpdated(nodeId, cb) {
+ $('body').off(
+ EventName.updated(nodeId),
+ cb);
+ }
+
+ static triggerDeleted(nodeId) {
+ $('body').trigger(
+ EventName.deleted(nodeId),
+ nodeId);
+ }
+
+ static onDeleted(nodeId, cb) {
+ $('body').on(
+ EventName.deleted(nodeId),
+ cb);
+ }
+
+ static offDeleted(nodeId, cb) {
+ $('body').off(
+ EventName.deleted(nodeId),
+ cb);
+ }
+}
+
+export { Nodes }
diff --git a/src/scripts/js/es6/common/events/init.js b/src/scripts/js/es6/common/events/init.js
new file mode 100644
index 00000000..36edc2ec
--- /dev/null
+++ b/src/scripts/js/es6/common/events/init.js
@@ -0,0 +1 @@
+export {Nodes} from './Nodes'
diff --git a/src/scripts/js/es6/common/templates/nodes/NodesBase.js b/src/scripts/js/es6/common/templates/nodes/NodesBase.js
index 76ad9c17..53789c12 100644
--- a/src/scripts/js/es6/common/templates/nodes/NodesBase.js
+++ b/src/scripts/js/es6/common/templates/nodes/NodesBase.js
@@ -1,5 +1,4 @@
import { prettyDate } from '../../utils/prettydate';
-import { thenLoadImage } from '../utils';
import { ComponentCreatorInterface } from '../component/ComponentCreatorInterface'
export class NodesBase extends ComponentCreatorInterface {
@@ -20,7 +19,7 @@ export class NodesBase extends ComponentCreatorInterface {
}
else {
$(window).trigger('pillar:workStart');
- thenLoadImage(node.picture)
+ pillar.utils.thenLoadImage(node.picture)
.fail(warnNoPicture)
.then((imgVariation) => {
let img = $('
')
diff --git a/src/scripts/js/es6/common/templates/utils.js b/src/scripts/js/es6/common/templates/utils.js
index 36e96d8c..a10abcf7 100644
--- a/src/scripts/js/es6/common/templates/utils.js
+++ b/src/scripts/js/es6/common/templates/utils.js
@@ -1,24 +1,5 @@
-function thenLoadImage(imgId, size = 'm') {
- return $.get('/api/files/' + imgId)
- .then((resp)=> {
- var show_variation = null;
- if (typeof resp.variations != 'undefined') {
- for (var variation of resp.variations) {
- if (variation.size != size) continue;
- show_variation = variation;
- break;
- }
- }
-
- if (show_variation == null) {
- throw 'Image not found: ' + imgId + ' size: ' + size;
- }
- return show_variation;
- })
-}
-
function thenLoadVideoProgress(nodeId) {
return $.get('/api/users/video/' + nodeId + '/progress')
}
-export { thenLoadImage, thenLoadVideoProgress };
\ No newline at end of file
+export { thenLoadVideoProgress };
diff --git a/src/scripts/js/es6/common/utils/files.js b/src/scripts/js/es6/common/utils/files.js
new file mode 100644
index 00000000..2b2fe64d
--- /dev/null
+++ b/src/scripts/js/es6/common/utils/files.js
@@ -0,0 +1,20 @@
+function thenLoadImage(imgId, size = 'm') {
+ return $.get('/api/files/' + imgId)
+ .then((resp)=> {
+ var show_variation = null;
+ if (typeof resp.variations != 'undefined') {
+ for (var variation of resp.variations) {
+ if (variation.size != size) continue;
+ show_variation = variation;
+ break;
+ }
+ }
+
+ if (show_variation == null) {
+ throw 'Image not found: ' + imgId + ' size: ' + size;
+ }
+ return show_variation;
+ })
+}
+
+export { thenLoadImage }
diff --git a/src/scripts/js/es6/common/utils/init.js b/src/scripts/js/es6/common/utils/init.js
index 2f5e7b5b..18ef5307 100644
--- a/src/scripts/js/es6/common/utils/init.js
+++ b/src/scripts/js/es6/common/utils/init.js
@@ -1,6 +1,7 @@
export { transformPlaceholder } from './placeholder'
export { prettyDate } from './prettydate'
export { getCurrentUser, initCurrentUser } from './currentuser'
+export { thenLoadImage } from './files'
export function debounced(fn, delay=1000) {
@@ -32,4 +33,4 @@ export function messageFromError(err){
// type xhr probably
return xhrErrorResponseMessage(err);
}
-}
\ No newline at end of file
+}
diff --git a/src/scripts/js/es6/common/utils/prettydate.js b/src/scripts/js/es6/common/utils/prettydate.js
index 61a5b441..8438089d 100644
--- a/src/scripts/js/es6/common/utils/prettydate.js
+++ b/src/scripts/js/es6/common/utils/prettydate.js
@@ -13,7 +13,7 @@ export function prettyDate(time, detail=false) {
let second_diff = Math.round((now - theDate) / 1000);
let day_diff = Math.round(second_diff / 86400); // seconds per day (60*60*24)
-
+
if ((day_diff < 0) && (theDate.getFullYear() !== now.getFullYear())) {
// "Jul 16, 2018"
pretty = theDate.toLocaleDateString('en-NL',{day: 'numeric', month: 'short', year: 'numeric'});
@@ -29,7 +29,7 @@ export function prettyDate(time, detail=false) {
else
pretty = "in " + week_count +" weeks";
}
- else if (day_diff < -1)
+ else if (day_diff < 0)
// "next Tuesday"
pretty = 'next ' + theDate.toLocaleDateString('en-NL',{weekday: 'long'});
else if (day_diff === 0) {
@@ -94,4 +94,4 @@ export function prettyDate(time, detail=false) {
}
return pretty;
-}
\ No newline at end of file
+}
diff --git a/src/scripts/js/es6/common/vuecomponents/customdirectives/click-outside.js b/src/scripts/js/es6/common/vuecomponents/customdirectives/click-outside.js
new file mode 100644
index 00000000..1bfa4bca
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/customdirectives/click-outside.js
@@ -0,0 +1,17 @@
+// Code from https://stackoverflow.com/a/42389266
+
+Vue.directive('click-outside', {
+ bind: function (el, binding, vnode) {
+ el.clickOutsideEvent = function (event) {
+ // here I check that click was outside the el and his childrens
+ if (!(el == event.target || el.contains(event.target))) {
+ // and if it did, call method provided in attribute value
+ vnode.context[binding.expression](event);
+ }
+ };
+ document.body.addEventListener('click', el.clickOutsideEvent)
+ },
+ unbind: function (el) {
+ document.body.removeEventListener('click', el.clickOutsideEvent)
+ },
+ });
diff --git a/src/scripts/js/es6/common/vuecomponents/init.js b/src/scripts/js/es6/common/vuecomponents/init.js
index 343dfa00..66bd1ca8 100644
--- a/src/scripts/js/es6/common/vuecomponents/init.js
+++ b/src/scripts/js/es6/common/vuecomponents/init.js
@@ -1 +1,38 @@
-import './comments/CommentTree'
\ No newline at end of file
+import './comments/CommentTree'
+import './customdirectives/click-outside'
+import { UnitOfWorkTracker } from './mixins/UnitOfWorkTracker'
+import { PillarTable } 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 } from './table/rows/RowObjectBase'
+import { RowFilter } from './table/filter/RowFilter'
+
+let mixins = {
+ UnitOfWorkTracker
+}
+
+let table = {
+ PillarTable,
+ columns: {
+ ColumnBase,
+ ColumnFactoryBase,
+ },
+ cells: {
+ renderer: {
+ CellDefault,
+ CellPrettyDate
+ }
+ },
+ rows: {
+ RowObjectsSourceBase,
+ RowBase
+ },
+ filter: {
+ RowFilter
+ }
+}
+
+export { mixins, table }
diff --git a/src/scripts/js/es6/common/vuecomponents/menu/DropDown.js b/src/scripts/js/es6/common/vuecomponents/menu/DropDown.js
new file mode 100644
index 00000000..e38d9e41
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/menu/DropDown.js
@@ -0,0 +1,42 @@
+const TEMPLATE =`
+
+`;
+
+let DropDown = Vue.component('pillar-dropdown', {
+ template: TEMPLATE,
+ data() {
+ return {
+ showMenu: false
+ }
+ },
+ computed: {
+ buttonClasses() {
+ return {'is-open': this.showMenu};
+ }
+ },
+ methods: {
+ toggleShowMenu(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ this.showMenu = !this.showMenu;
+ },
+ closeMenu(event) {
+ this.showMenu = false;
+ }
+ },
+});
+
+export { DropDown }
diff --git a/src/scripts/js/es6/common/vuecomponents/mixins/UnitOfWorkTracker.js b/src/scripts/js/es6/common/vuecomponents/mixins/UnitOfWorkTracker.js
index b1deeb4b..79743fdb 100644
--- a/src/scripts/js/es6/common/vuecomponents/mixins/UnitOfWorkTracker.js
+++ b/src/scripts/js/es6/common/vuecomponents/mixins/UnitOfWorkTracker.js
@@ -42,7 +42,15 @@ var UnitOfWorkTracker = {
methods: {
unitOfWork(promise) {
this.unitOfWorkBegin();
- return promise.always(this.unitOfWorkDone);
+ if (promise.always) {
+ // jQuery Promise
+ return promise.always(this.unitOfWorkDone);
+ }
+ if (promise.finally) {
+ // Native js Promise
+ return promise.finally(this.unitOfWorkDone);
+ }
+ throw Error('Unsupported promise type');
},
unitOfWorkBegin() {
this.unitOfWorkCounter++;
@@ -56,4 +64,4 @@ var UnitOfWorkTracker = {
}
}
-export { UnitOfWorkTracker }
\ No newline at end of file
+export { UnitOfWorkTracker }
diff --git a/src/scripts/js/es6/common/vuecomponents/table/Table.js b/src/scripts/js/es6/common/vuecomponents/table/Table.js
new file mode 100644
index 00000000..e97357d2
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/Table.js
@@ -0,0 +1,89 @@
+import './rows/renderer/Head'
+import './rows/renderer/Row'
+import './filter/ColumnFilter'
+import './filter/RowFilter'
+import {UnitOfWorkTracker} from '../mixins/UnitOfWorkTracker'
+
+const TEMPLATE =`
+
+`;
+
+let PillarTable = Vue.component('pillar-table-base', {
+ template: TEMPLATE,
+ mixins: [UnitOfWorkTracker],
+ // columnFactory,
+ // rowsSource,
+ props: {
+ projectId: String
+ },
+ data: function() {
+ return {
+ columns: [],
+ visibleColumns: [],
+ visibleRowObjects: [],
+ rowsSource: {}
+ }
+ },
+ computed: {
+ rowObjects() {
+ return this.rowsSource.rowObjects || [];
+ }
+ },
+ created() {
+ let columnFactory = new this.$options.columnFactory(this.projectId);
+ this.rowsSource = new this.$options.rowsSource(this.projectId);
+ this.unitOfWork(
+ Promise.all([
+ columnFactory.thenGetColumns(),
+ this.rowsSource.thenInit()
+ ])
+ .then((resp) => {
+ this.columns = resp[0];
+ })
+ );
+ },
+ methods: {
+ onVisibleColumnsChanged(visibleColumns) {
+ this.visibleColumns = visibleColumns;
+ },
+ onVisibleRowObjectsChanged(visibleRowObjects) {
+ this.visibleRowObjects = visibleRowObjects;
+ },
+ onSort(column, direction) {
+ function compareRows(r1, r2) {
+ return column.compareRows(r1, r2) * direction;
+ }
+ this.rowObjects.sort(compareRows);
+ },
+ }
+});
+
+export { PillarTable }
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
new file mode 100644
index 00000000..e3c433be
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellDefault.js
@@ -0,0 +1,21 @@
+const TEMPLATE =`
+
+ {{ cellValue }}
+
+`;
+
+let CellDefault = Vue.component('pillar-cell-default', {
+ template: TEMPLATE,
+ props: {
+ column: Object,
+ rowObject: Object,
+ rawCellValue: Object
+ },
+ computed: {
+ cellValue() {
+ return this.rawCellValue;
+ }
+ },
+});
+
+export { CellDefault }
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
new file mode 100644
index 00000000..f158d552
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellPrettyDate.js
@@ -0,0 +1,12 @@
+import { CellDefault } from './CellDefault'
+
+let CellPrettyDate = Vue.component('pillar-cell-pretty-date', {
+ extends: CellDefault,
+ computed: {
+ cellValue() {
+ return pillar.utils.prettyDate(this.rawCellValue);
+ }
+ }
+});
+
+export { CellPrettyDate }
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
new file mode 100644
index 00000000..51570895
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/CellProxy.js
@@ -0,0 +1,34 @@
+const TEMPLATE =`
+
+`;
+
+let CellProxy = Vue.component('pillar-cell-proxy', {
+ template: TEMPLATE,
+ props: {
+ column: Object,
+ rowObject: Object
+ },
+ computed: {
+ rawCellValue() {
+ return this.column.getRawCellValue(this.rowObject) || '';
+ },
+ cellRenderer() {
+ return this.column.getCellRenderer(this.rowObject);
+ },
+ cellClasses() {
+ return this.column.getCellClasses(this.rawCellValue, this.rowObject);
+ },
+ cellTitle() {
+ return this.column.getCellTitle(this.rawCellValue, this.rowObject);
+ }
+ },
+});
+
+export { CellProxy }
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
new file mode 100644
index 00000000..97facd37
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/cells/renderer/HeadCell.js
@@ -0,0 +1,43 @@
+const TEMPLATE =`
+
+`;
+
+Vue.component('pillar-head-cell', {
+ template: TEMPLATE,
+ props: {
+ column: Object
+ },
+ computed: {
+ cellClasses() {
+ return this.column.getHeaderCellClasses();
+ }
+ },
+ methods: {
+ onMouseEnter() {
+ this.column.highlightColumn(true);
+ },
+ onMouseLeave() {
+ this.column.highlightColumn(false);
+ },
+ },
+});
diff --git a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js
new file mode 100644
index 00000000..29cfa8f9
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnBase.js
@@ -0,0 +1,85 @@
+import { CellDefault } from '../cells/renderer/CellDefault'
+
+let nextColumnId = 0;
+export class ColumnBase {
+ constructor(displayName, columnType) {
+ this._id = nextColumnId++;
+ this.displayName = displayName;
+ this.columnType = columnType;
+ this.isMandatory = false;
+ this.isSortable = true;
+ this.isHighLighted = 0;
+ }
+
+ /**
+ *
+ * @param {*} rowObject
+ * @returns {String} Name of the Cell renderer component
+ */
+ getCellRenderer(rowObject) {
+ return CellDefault.options.name;
+ }
+
+ getRawCellValue(rowObject) {
+ // Should be overridden
+ throw Error('Not implemented');
+ }
+
+ /**
+ * Cell tooltip
+ * @param {Any} rawCellValue
+ * @param {RowObject} rowObject
+ * @returns {String}
+ */
+ getCellTitle(rawCellValue, rowObject) {
+ // Should be overridden
+ return '';
+ }
+
+ /**
+ * Object with css classes to use on the header cell
+ * @returns {Any} Object with css classes
+ */
+ getHeaderCellClasses() {
+ // Should be overridden
+ let classes = {}
+ classes[this.columnType] = true;
+ return classes;
+ }
+
+ /**
+ * Object with css classes to use on the cell
+ * @param {*} rawCellValue
+ * @param {*} rowObject
+ * @returns {Any} Object with css classes
+ */
+ getCellClasses(rawCellValue, rowObject) {
+ // Should be overridden
+ let classes = {}
+ classes[this.columnType] = true;
+ classes['highlight'] = !!this.isHighLighted;
+ return classes;
+ }
+
+ /**
+ * Compare two rows to sort them. Can be overridden for more complex situations.
+ *
+ * @param {RowObject} rowObject1
+ * @param {RowObject} rowObject2
+ * @returns {Number} -1, 0, 1
+ */
+ compareRows(rowObject1, rowObject2) {
+ let rawCellValue1 = this.getRawCellValue(rowObject1);
+ let rawCellValue2 = this.getRawCellValue(rowObject2);
+ if (rawCellValue1 === rawCellValue2) return 0;
+ return rawCellValue1 < rawCellValue2 ? -1 : 1;
+ }
+
+ /**
+ *
+ * @param {Boolean}
+ */
+ highlightColumn(value) {
+ this.isHighLighted += !!value ? 1 : -1;
+ }
+}
diff --git a/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js
new file mode 100644
index 00000000..18c1fa6a
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/columns/ColumnFactoryBase.js
@@ -0,0 +1,21 @@
+class ColumnFactoryBase{
+ constructor(projectId) {
+ this.projectId = projectId;
+ this.projectPromise;
+ }
+
+ // Override this
+ thenGetColumns() {
+ throw Error('Not implemented')
+ }
+
+ thenGetProject() {
+ if (this.projectPromise) {
+ return this.projectPromise;
+ }
+ this.projectPromise = pillar.api.thenGetProject(this.projectId);
+ return this.projectPromise;
+ }
+}
+
+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
new file mode 100644
index 00000000..d22392bc
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/columns/renderer/Column.js
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 00000000..222e9532
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/filter/ColumnFilter.js
@@ -0,0 +1,80 @@
+import '../../menu/DropDown'
+
+const TEMPLATE =`
+
+`;
+
+let Filter = Vue.component('pillar-table-column-filter', {
+ template: TEMPLATE,
+ props: {
+ columns: Array,
+ },
+ data() {
+ return {
+ columnStates: [],
+ }
+ },
+ 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({
+ _id: c._id,
+ displayName: c.displayName,
+ isVisible: 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
new file mode 100644
index 00000000..098627a2
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/filter/RowFilter.js
@@ -0,0 +1,45 @@
+const TEMPLATE =`
+
+
+
+`;
+
+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/RowObjectBase.js b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectBase.js
new file mode 100644
index 00000000..a8fde3ed
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectBase.js
@@ -0,0 +1,31 @@
+class RowBase {
+ constructor(underlyingObject) {
+ this.underlyingObject = underlyingObject;
+ this.isInitialized = false;
+ }
+
+ thenInit() {
+ this.isInitialized = true
+ return Promise.resolve();
+ }
+
+ getName() {
+ return this.underlyingObject.name;
+ }
+
+ getId() {
+ return this.underlyingObject._id;
+ }
+
+ getProperties() {
+ return this.underlyingObject.properties;
+ }
+
+ getRowClasses() {
+ return {
+ "is-busy": !this.isInitialized
+ }
+ }
+}
+
+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
new file mode 100644
index 00000000..6b88d22a
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/RowObjectsSourceBase.js
@@ -0,0 +1,13 @@
+class RowObjectsSourceBase {
+ constructor(projectId) {
+ this.projectId = projectId;
+ this.rowObjects = [];
+ }
+
+ // Override this
+ thenInit() {
+ throw Error('Not implemented');
+ }
+}
+
+export { RowObjectsSourceBase }
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
new file mode 100644
index 00000000..d71a88b2
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Head.js
@@ -0,0 +1,18 @@
+import '../../cells/renderer/HeadCell'
+const TEMPLATE =`
+
+
$emit('sort', column, direction)"
+ />
+
+`;
+
+Vue.component('pillar-table-head', {
+ template: TEMPLATE,
+ props: {
+ columns: Array
+ }
+});
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
new file mode 100644
index 00000000..b5581311
--- /dev/null
+++ b/src/scripts/js/es6/common/vuecomponents/table/rows/renderer/Row.js
@@ -0,0 +1,27 @@
+import '../../cells/renderer/CellProxy'
+
+const TEMPLATE =`
+
+`;
+
+Vue.component('pillar-table-row', {
+ template: TEMPLATE,
+ props: {
+ rowObject: Object,
+ columns: Array
+ },
+ computed: {
+ rowClasses() {
+ return this.rowObject.getRowClasses();
+ }
+ }
+});
diff --git a/src/styles/components/_base.sass b/src/styles/components/_base.sass
index e938f6e3..b0ddd7e3 100644
--- a/src/styles/components/_base.sass
+++ b/src/styles/components/_base.sass
@@ -11,6 +11,9 @@ body
max-width: 100%
min-width: auto
+.page-body
+ height: 100%
+
body.has-overlay
overflow: hidden
padding-right: 5px
@@ -24,6 +27,7 @@ body.has-overlay
.page-content
background-color: $white
+ height: 100%
.container-box
+container-box