Vue Attract: Sort/filterable table based on Vue
Initial commit implementing sortable and filterable tables for attract using Vue.
This commit is contained in:
@@ -1 +1,3 @@
|
||||
export { thenMarkdownToHtml } from './markdown'
|
||||
export { thenMarkdownToHtml } from './markdown'
|
||||
export { thenGetProject } from './projects'
|
||||
export { thenGetNodes } from './nodes'
|
||||
|
8
src/scripts/js/es6/common/api/nodes.js
Normal file
8
src/scripts/js/es6/common/api/nodes.js
Normal file
@@ -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 }
|
5
src/scripts/js/es6/common/api/projects.js
Normal file
5
src/scripts/js/es6/common/api/projects.js
Normal file
@@ -0,0 +1,5 @@
|
||||
function thenGetProject(projectId) {
|
||||
return $.get(`/api/projects/${projectId}`);
|
||||
}
|
||||
|
||||
export { thenGetProject }
|
92
src/scripts/js/es6/common/events/Nodes.js
Normal file
92
src/scripts/js/es6/common/events/Nodes.js
Normal file
@@ -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 }
|
1
src/scripts/js/es6/common/events/init.js
Normal file
1
src/scripts/js/es6/common/events/init.js
Normal file
@@ -0,0 +1 @@
|
||||
export {Nodes} from './Nodes'
|
@@ -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 = $('<img class="card-img-top">')
|
||||
|
@@ -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 };
|
||||
export { thenLoadVideoProgress };
|
||||
|
20
src/scripts/js/es6/common/utils/files.js
Normal file
20
src/scripts/js/es6/common/utils/files.js
Normal file
@@ -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 }
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
},
|
||||
});
|
@@ -1 +1,38 @@
|
||||
import './comments/CommentTree'
|
||||
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 }
|
||||
|
42
src/scripts/js/es6/common/vuecomponents/menu/DropDown.js
Normal file
42
src/scripts/js/es6/common/vuecomponents/menu/DropDown.js
Normal file
@@ -0,0 +1,42 @@
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-dropdown">
|
||||
<div class="pillar-dropdown-button"
|
||||
:class="buttonClasses"
|
||||
@click="toggleShowMenu"
|
||||
>
|
||||
<slot name="button"/>
|
||||
</div>
|
||||
<div class="pillar-dropdown-menu"
|
||||
v-show="showMenu"
|
||||
v-click-outside="closeMenu"
|
||||
>
|
||||
<slot name="menu"/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 }
|
@@ -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 }
|
||||
export { UnitOfWorkTracker }
|
||||
|
89
src/scripts/js/es6/common/vuecomponents/table/Table.js
Normal file
89
src/scripts/js/es6/common/vuecomponents/table/Table.js
Normal file
@@ -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 =`
|
||||
<div class="pillar-table-container"
|
||||
:class="$options.name"
|
||||
>
|
||||
<div class="pillar-table-menu">
|
||||
<pillar-table-row-filter
|
||||
:rowObjects="rowObjects"
|
||||
@visibleRowObjectsChanged="onVisibleRowObjectsChanged"
|
||||
/>
|
||||
<pillar-table-actions/>
|
||||
<pillar-table-column-filter
|
||||
:columns="columns"
|
||||
@visibleColumnsChanged="onVisibleColumnsChanged"
|
||||
/>
|
||||
</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()"
|
||||
/>
|
||||
</transition-group>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 }
|
@@ -0,0 +1,21 @@
|
||||
const TEMPLATE =`
|
||||
<div>
|
||||
{{ cellValue }}
|
||||
</div>
|
||||
`;
|
||||
|
||||
let CellDefault = Vue.component('pillar-cell-default', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
column: Object,
|
||||
rowObject: Object,
|
||||
rawCellValue: Object
|
||||
},
|
||||
computed: {
|
||||
cellValue() {
|
||||
return this.rawCellValue;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export { CellDefault }
|
@@ -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 }
|
@@ -0,0 +1,34 @@
|
||||
const TEMPLATE =`
|
||||
<component class="pillar-cell"
|
||||
:class="cellClasses"
|
||||
:title="cellTitle"
|
||||
:is="cellRenderer"
|
||||
:rowObject="rowObject"
|
||||
:column="column"
|
||||
:rawCellValue="rawCellValue"
|
||||
/>
|
||||
`;
|
||||
|
||||
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 }
|
@@ -0,0 +1,43 @@
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-cell header-cell"
|
||||
:class="cellClasses"
|
||||
@mouseenter="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<div class="cell-content">
|
||||
{{ column.displayName }}
|
||||
<div class="column-sort"
|
||||
v-if="column.isSortable"
|
||||
>
|
||||
<i class="sort-action pi-angle-up"
|
||||
title="Sort Ascending"
|
||||
@click="$emit('sort', column, 1)"
|
||||
/>
|
||||
<i class="sort-action pi-angle-down"
|
||||
title="Sort Descending"
|
||||
@click="$emit('sort', column, -1)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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);
|
||||
},
|
||||
},
|
||||
});
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -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 }
|
@@ -0,0 +1,10 @@
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-column"/>
|
||||
`;
|
||||
|
||||
Vue.component('pillar-table-column', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
column: Object
|
||||
},
|
||||
});
|
@@ -0,0 +1,80 @@
|
||||
import '../../menu/DropDown'
|
||||
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-column-filter">
|
||||
<pillar-dropdown>
|
||||
<i class="pi-cog"
|
||||
slot="button"
|
||||
title="Table Settings"/>
|
||||
|
||||
<ul class="settings-menu"
|
||||
slot="menu"
|
||||
>
|
||||
Columns:
|
||||
<li class="attract-column-select"
|
||||
v-for="c in columnStates"
|
||||
:key="c._id"
|
||||
>
|
||||
<input type="checkbox"
|
||||
v-model="c.isVisible"
|
||||
/>
|
||||
{{ c.displayName }}
|
||||
</li>
|
||||
</ul>
|
||||
</pillar-dropdown>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 }
|
@@ -0,0 +1,45 @@
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-row-filter">
|
||||
<input
|
||||
placeholder="Filter by name"
|
||||
v-model="nameQuery"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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 }
|
@@ -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 }
|
@@ -0,0 +1,13 @@
|
||||
class RowObjectsSourceBase {
|
||||
constructor(projectId) {
|
||||
this.projectId = projectId;
|
||||
this.rowObjects = [];
|
||||
}
|
||||
|
||||
// Override this
|
||||
thenInit() {
|
||||
throw Error('Not implemented');
|
||||
}
|
||||
}
|
||||
|
||||
export { RowObjectsSourceBase }
|
@@ -0,0 +1,18 @@
|
||||
import '../../cells/renderer/HeadCell'
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-head">
|
||||
<pillar-head-cell
|
||||
v-for="c in columns"
|
||||
:column="c"
|
||||
key="c._id"
|
||||
@sort="(column, direction) => $emit('sort', column, direction)"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Vue.component('pillar-table-head', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
columns: Array
|
||||
}
|
||||
});
|
@@ -0,0 +1,27 @@
|
||||
import '../../cells/renderer/CellProxy'
|
||||
|
||||
const TEMPLATE =`
|
||||
<div class="pillar-table-row"
|
||||
:class="rowClasses"
|
||||
>
|
||||
<pillar-cell-proxy
|
||||
v-for="c in columns"
|
||||
:rowObject="rowObject"
|
||||
:column="c"
|
||||
:key="c._id"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
Vue.component('pillar-table-row', {
|
||||
template: TEMPLATE,
|
||||
props: {
|
||||
rowObject: Object,
|
||||
columns: Array
|
||||
},
|
||||
computed: {
|
||||
rowClasses() {
|
||||
return this.rowObject.getRowClasses();
|
||||
}
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user