Vue Attract: Sort/filterable table based on Vue
Initial commit implementing sortable and filterable tables for attract using Vue.
This commit is contained in:
parent
a5bae513e1
commit
2f5f73843d
@ -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 { prettyDate } from '../../utils/prettydate';
|
||||||
import { thenLoadImage } from '../utils';
|
|
||||||
import { ComponentCreatorInterface } from '../component/ComponentCreatorInterface'
|
import { ComponentCreatorInterface } from '../component/ComponentCreatorInterface'
|
||||||
|
|
||||||
export class NodesBase extends ComponentCreatorInterface {
|
export class NodesBase extends ComponentCreatorInterface {
|
||||||
@ -20,7 +19,7 @@ export class NodesBase extends ComponentCreatorInterface {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$(window).trigger('pillar:workStart');
|
$(window).trigger('pillar:workStart');
|
||||||
thenLoadImage(node.picture)
|
pillar.utils.thenLoadImage(node.picture)
|
||||||
.fail(warnNoPicture)
|
.fail(warnNoPicture)
|
||||||
.then((imgVariation) => {
|
.then((imgVariation) => {
|
||||||
let img = $('<img class="card-img-top">')
|
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) {
|
function thenLoadVideoProgress(nodeId) {
|
||||||
return $.get('/api/users/video/' + nodeId + '/progress')
|
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 { transformPlaceholder } from './placeholder'
|
||||||
export { prettyDate } from './prettydate'
|
export { prettyDate } from './prettydate'
|
||||||
export { getCurrentUser, initCurrentUser } from './currentuser'
|
export { getCurrentUser, initCurrentUser } from './currentuser'
|
||||||
|
export { thenLoadImage } from './files'
|
||||||
|
|
||||||
|
|
||||||
export function debounced(fn, delay=1000) {
|
export function debounced(fn, delay=1000) {
|
||||||
@ -32,4 +33,4 @@ export function messageFromError(err){
|
|||||||
// type xhr probably
|
// type xhr probably
|
||||||
return xhrErrorResponseMessage(err);
|
return xhrErrorResponseMessage(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ export function prettyDate(time, detail=false) {
|
|||||||
let second_diff = Math.round((now - theDate) / 1000);
|
let second_diff = Math.round((now - theDate) / 1000);
|
||||||
|
|
||||||
let day_diff = Math.round(second_diff / 86400); // seconds per day (60*60*24)
|
let day_diff = Math.round(second_diff / 86400); // seconds per day (60*60*24)
|
||||||
|
|
||||||
if ((day_diff < 0) && (theDate.getFullYear() !== now.getFullYear())) {
|
if ((day_diff < 0) && (theDate.getFullYear() !== now.getFullYear())) {
|
||||||
// "Jul 16, 2018"
|
// "Jul 16, 2018"
|
||||||
pretty = theDate.toLocaleDateString('en-NL',{day: 'numeric', month: 'short', year: 'numeric'});
|
pretty = theDate.toLocaleDateString('en-NL',{day: 'numeric', month: 'short', year: 'numeric'});
|
||||||
@ -29,7 +29,7 @@ export function prettyDate(time, detail=false) {
|
|||||||
else
|
else
|
||||||
pretty = "in " + week_count +" weeks";
|
pretty = "in " + week_count +" weeks";
|
||||||
}
|
}
|
||||||
else if (day_diff < -1)
|
else if (day_diff < 0)
|
||||||
// "next Tuesday"
|
// "next Tuesday"
|
||||||
pretty = 'next ' + theDate.toLocaleDateString('en-NL',{weekday: 'long'});
|
pretty = 'next ' + theDate.toLocaleDateString('en-NL',{weekday: 'long'});
|
||||||
else if (day_diff === 0) {
|
else if (day_diff === 0) {
|
||||||
@ -94,4 +94,4 @@ export function prettyDate(time, detail=false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return pretty;
|
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: {
|
methods: {
|
||||||
unitOfWork(promise) {
|
unitOfWork(promise) {
|
||||||
this.unitOfWorkBegin();
|
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() {
|
unitOfWorkBegin() {
|
||||||
this.unitOfWorkCounter++;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -11,6 +11,9 @@ body
|
|||||||
max-width: 100%
|
max-width: 100%
|
||||||
min-width: auto
|
min-width: auto
|
||||||
|
|
||||||
|
.page-body
|
||||||
|
height: 100%
|
||||||
|
|
||||||
body.has-overlay
|
body.has-overlay
|
||||||
overflow: hidden
|
overflow: hidden
|
||||||
padding-right: 5px
|
padding-right: 5px
|
||||||
@ -24,6 +27,7 @@ body.has-overlay
|
|||||||
|
|
||||||
.page-content
|
.page-content
|
||||||
background-color: $white
|
background-color: $white
|
||||||
|
height: 100%
|
||||||
|
|
||||||
.container-box
|
.container-box
|
||||||
+container-box
|
+container-box
|
||||||
|
Loading…
x
Reference in New Issue
Block a user