700 lines
12 KiB
JavaScript
700 lines
12 KiB
JavaScript
import './table.css';
|
|
|
|
import { Ut } from '../../utils/Ut';
|
|
import { $, El, fillData } from '../../utils/dom';
|
|
import { FormValidator } from '../../validator/FormValidator';
|
|
import { Rc } from '../../core/Rc';
|
|
import { Render } from '../../core/Render';
|
|
|
|
export class Table {
|
|
|
|
static CLEAN = 0;
|
|
static APPEND = 1;
|
|
static PREPEND = 2;
|
|
static UPDATE = 3;
|
|
|
|
/**
|
|
* Table, Constructor
|
|
*/
|
|
constructor(target, caller, opts) {
|
|
|
|
this.target = target;
|
|
this.caller = caller;
|
|
|
|
this.vars = new FormData();
|
|
|
|
this.reqOpts = {};
|
|
|
|
this.vars.set('pg', 1);
|
|
|
|
this.opts = { ... {
|
|
layout : 'auto',
|
|
filter : false,
|
|
header : {},
|
|
label : '%rows results',
|
|
sortable : 0
|
|
}, ... opts, ... opts.children };
|
|
|
|
this.rows = 0;
|
|
this.setHeader = true;
|
|
this.data = {};
|
|
|
|
if (!this.opts.header.cell || this.opts.display == 'none') {
|
|
this.setHeader = false;
|
|
}
|
|
|
|
this.makeUiTable();
|
|
|
|
if (this.opts.filter) { // create filter form
|
|
this.makeFilterForm();
|
|
}
|
|
|
|
if (Ut.isSet(this.opts.toolbar_header)) { // create toolbar header
|
|
this.makeToolbarHeader();
|
|
}
|
|
|
|
if (this.opts.label) { // create info label
|
|
this.makeInfoLabel();
|
|
}
|
|
|
|
this.makeNewTable();
|
|
|
|
this.makePagination();
|
|
|
|
if (Ut.isSet(this.opts.toolbar_footer)) { // create toolbar footer
|
|
this.makeToolbarFooter();
|
|
}
|
|
|
|
this.setMode(Table.CLEAN);
|
|
|
|
if ( Ut.isFn(caller) ) { // Data Mode
|
|
this.makeRequest();
|
|
}
|
|
else {
|
|
this.setTableData(caller);
|
|
}
|
|
|
|
if (this.opts.sortable) {
|
|
this.setSortable();
|
|
}
|
|
|
|
this.setTableEvents();
|
|
|
|
Ut.trigger(this.opts.onInit, this);
|
|
}
|
|
|
|
setMode(mode) {
|
|
|
|
this.mode = mode;
|
|
|
|
if (mode == Table.CLEAN) {
|
|
this.id = 0;
|
|
}
|
|
}
|
|
|
|
setTableData(data) {
|
|
|
|
this.data = data;
|
|
this.rows = parseInt(this.data.count);
|
|
|
|
this.updateInfoRows();
|
|
|
|
if (this.mode == Table.CLEAN) {
|
|
|
|
this.table.dxHtml('');
|
|
|
|
if (this.rows) {
|
|
this.makeTableHead();
|
|
}
|
|
}
|
|
|
|
this.makeTableRows();
|
|
|
|
this.updatePagination();
|
|
|
|
Ut.trigger(this.opts.onUpdate, this);
|
|
}
|
|
|
|
makeInfoLabel() {
|
|
this.info = El('div', { className: 'sp-table-info' });
|
|
this.ui.append(this.info);
|
|
}
|
|
|
|
// Make UI container
|
|
|
|
makeUiTable() {
|
|
this.ui = El('div', { className: 'sp-table-ui' });
|
|
this.target.append(this.ui);
|
|
}
|
|
|
|
// Make filter form
|
|
|
|
makeFilterForm() {
|
|
|
|
let that = this;
|
|
|
|
this.filterForm = El('form', { novalidate: true, className: 'sp-form filter-main' });
|
|
|
|
Render.makeUiElements(Ut.objVal(this.opts.filter).children, this.filterForm);
|
|
|
|
this.ui.append(this.filterForm);
|
|
|
|
this.filterForm.dxOn('changed clear', 'input[type="text"]', () => that.filterForm.dxTrigger('submit.form'));
|
|
this.filterForm.dxOn('change', 'select, input[type="radio"], input[type="checkbox"]', () => that.filterForm.dxTrigger('submit.form'));
|
|
|
|
new FormValidator(this.filterForm, {
|
|
|
|
onInit: el => that.filterFormEl = el,
|
|
onSubmit: function(vars) {
|
|
|
|
that.vars = vars;
|
|
|
|
that.vars.set('pg', 1);
|
|
|
|
that.setMode(Table.CLEAN);
|
|
|
|
that.makeRequest();
|
|
}
|
|
});
|
|
|
|
fillData(that.filterFormEl, this.vars);
|
|
}
|
|
|
|
// Make header toolbar
|
|
|
|
makeToolbarHeader() {
|
|
|
|
this.toolbarHeader = El('div', { className: 'sp-toolbar toolbar-header' + (this.opts.toolbar_header.cls ? ' ' + this.opts.toolbar_header.cls : '') });
|
|
|
|
Render.makeUiElements(Ut.objVal(this.opts.toolbar_header).children, this.toolbarHeader);
|
|
|
|
this.ui.append( this.toolbarHeader );
|
|
|
|
Ut.trigger(this.opts.toolbarHeader, this);
|
|
}
|
|
|
|
// Make Pagination Area
|
|
|
|
makePagination() {
|
|
|
|
this.paginationEl = this.ui.appendChild(El('div', { className: 'sp-pagination' }));
|
|
|
|
this.paginationEl.dxOn('click', 'a', (e, t) => {
|
|
|
|
this.vars.set('pg', t.dataset.id);
|
|
|
|
this.setMode(Table.CLEAN);
|
|
|
|
this.makeRequest();
|
|
});
|
|
|
|
this.paginationEl.dxOn('click', 'a.btn-before', (_, t) => {
|
|
|
|
this.vars.set('before', this.data.before);
|
|
this.vars.set('after', '');
|
|
|
|
this.setMode(Table.CLEAN);
|
|
|
|
this.makeRequest();
|
|
});
|
|
|
|
this.paginationEl.dxOn('click', 'a.btn-after', function() {
|
|
|
|
this.vars.set('before', '');
|
|
this.vars.set('after', this.data.after);
|
|
|
|
this.setMode(Table.CLEAN);
|
|
|
|
this.makeRequest();
|
|
});
|
|
}
|
|
|
|
// Make footer toolbar
|
|
|
|
makeToolbarFooter() {
|
|
|
|
this.toolbarFooter = El('div', { className: 'sp-toolbar toolbar-footer' + (this.opts.toolbar_footer.cls ? ' ' + this.opts.toolbar_footer.cls : '') });
|
|
|
|
Render.makeUiElements(Ut.objVal(this.opts.toolbar_footer).children, this.toolbarFooter);
|
|
|
|
this.ui.append(this.toolbarFooter);
|
|
|
|
Ut.trigger(this.opts.toolbarFooter, this);
|
|
}
|
|
|
|
// Make new ui table
|
|
|
|
makeNewTable() {
|
|
this.table = El('div', { className: 'sp-table layout-' + this.opts.layout + (!this.setHeader ? ' no-header' : '') + (this.opts.cls ? ' ' + this.opts.cls : '')});
|
|
this.ui.append(this.table);
|
|
}
|
|
|
|
// Make table head
|
|
|
|
makeTableHead() {
|
|
|
|
if (!this.setHeader) {
|
|
return;
|
|
}
|
|
|
|
const header = this.opts.header;
|
|
|
|
const thead = El('div', { className: 'thead' + (header.cls ? ' ' + header.cls : '') });
|
|
|
|
let n = 1;
|
|
|
|
for (; n < this.data.items[0].length; n++) {
|
|
|
|
const cell = header.cell ? (header.cell[ n - 1 ] || {}) : {};
|
|
|
|
const th = El('div');
|
|
|
|
th.className = 'th';
|
|
|
|
if (cell.cls) {
|
|
th.className += ` ${cell.cls}`;
|
|
}
|
|
|
|
if (cell.sortable) {
|
|
|
|
const sortMode = this.vars.get('sort_mode');
|
|
|
|
if (this.vars.get('sort_by') === cell.sortable && (sortMode === 'asc' || sortMode === 'desc')) {
|
|
th.className += ` sort-${sortMode}`;
|
|
}
|
|
|
|
th.dataset.sortable = cell.sortable;
|
|
}
|
|
|
|
th.textContent = cell.text || '';
|
|
|
|
thead.append(th);
|
|
}
|
|
|
|
this.table.prepend(thead);
|
|
}
|
|
|
|
// Make table rows
|
|
|
|
makeTableRows() {
|
|
|
|
let that = this;
|
|
|
|
if (this.mode == Table.UPDATE) {
|
|
|
|
let row = this.getRow(this.id);
|
|
|
|
if (row) {
|
|
|
|
row.dxHtml('');
|
|
|
|
let data = this.data.items[0];
|
|
|
|
let n = 1;
|
|
|
|
for (; n < data.length; n++) {
|
|
|
|
let cell = that.opts.header.cell ? (that.opts.header.cell[ n - 1 ] || {}) : {};
|
|
|
|
let column = El('div', { className: 't-col' });
|
|
|
|
row.append( El('div', { className: 'td' + (cell.cls ? ' ' + cell.cls : ''), 'data-label': cell.text },
|
|
column
|
|
));
|
|
|
|
if (data[n]) {
|
|
column.dxHtml(data[n]);
|
|
}
|
|
|
|
if (n == 1 && that.opts.sortable) {
|
|
column.classList.add('flex', 'v-center');
|
|
column.prepend( El('i', { className: 'handle' }) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
|
|
this.data.items.forEach((data, i) => {
|
|
|
|
let tbody = El('div', {
|
|
className: 'tbody' + (that.opts.body && that.opts.body.cls ? ' ' + that.opts.body.cls : ''),
|
|
'data-id': data[0],
|
|
'data-pos': i
|
|
});
|
|
|
|
let n = 1;
|
|
|
|
for (; n < data.length; n++) {
|
|
|
|
let cell = that.opts.header.cell ? (that.opts.header.cell[ n - 1 ] || {}) : {};
|
|
|
|
let column = El('div', { className: 't-col' });
|
|
|
|
tbody.append( El('div', { className: 'td' + (cell.cls ? ' ' + cell.cls : ''), 'data-label': cell.text },
|
|
column
|
|
));
|
|
|
|
if (data[n]) {
|
|
column.dxHtml(data[n]);
|
|
}
|
|
|
|
if (n == 1 && that.opts.sortable) {
|
|
column.classList.add('flex', 'v-center');
|
|
column.prepend( El('i', { className: 'handle' }) );
|
|
}
|
|
|
|
if (that.mode == Table.PREPEND) {
|
|
that.table.prepend(tbody);
|
|
}
|
|
else {
|
|
that.table.append(tbody);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Set info rows
|
|
|
|
updateInfoRows() {
|
|
this.setInfo(this.opts.label.replace(/%rows/g, this.rows));
|
|
}
|
|
|
|
// XHR Request
|
|
|
|
makeRequest() {
|
|
|
|
let that = this;
|
|
|
|
let vars = {};
|
|
|
|
if (this.mode == Table.UPDATE) {
|
|
vars.id = this.id;
|
|
}
|
|
else {
|
|
vars = this.vars;
|
|
vars.set('id', 0);
|
|
}
|
|
|
|
this.reqOpts.vars = vars;
|
|
|
|
this.caller(this.reqOpts).then(jr => {
|
|
|
|
if (jr.code != Rc.DONE) {
|
|
that.setInfo('No Data');
|
|
return;
|
|
}
|
|
|
|
that.setTableData(jr.data);
|
|
|
|
}).catch(error => console.error(error));
|
|
}
|
|
|
|
// Set ui sortable
|
|
|
|
setSortable() {
|
|
|
|
let that = this,
|
|
isHandle = false,
|
|
toDrag;
|
|
|
|
let isBefore = function(a, b) {
|
|
if (a.parentNode == b.parentNode) {
|
|
for (let cur = a; cur; cur = cur.previousSibling) {
|
|
if (cur === b) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
this.table.dxOn('mousedown mouseup', 'i.handle', function(e) {
|
|
|
|
let parent = this.dxParents('.tbody');
|
|
|
|
if (e.type == 'mousedown') {
|
|
isHandle = true;
|
|
parent.setAttribute('draggable', true);
|
|
}
|
|
else {
|
|
isHandle = false;
|
|
parent.removeAttribute('draggable');
|
|
}
|
|
|
|
});
|
|
|
|
this.table.dxOn('dragenter', '.tbody', function(e) {
|
|
|
|
if (!isHandle) {
|
|
return;
|
|
}
|
|
|
|
let target = this.matches('.tbody') ? this : this.dxParents('.tbody');
|
|
|
|
if (isBefore( toDrag, target )) {
|
|
target.before(toDrag);
|
|
}
|
|
else {
|
|
|
|
if (target.nextElementSibling === null) {
|
|
$('.sp-table').append( toDrag );
|
|
}
|
|
else {
|
|
target.nextElementSibling.before(toDrag);
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
this.table.dxOn('dragstart', '.tbody', function(e) {
|
|
|
|
if (!isHandle) {
|
|
return;
|
|
}
|
|
|
|
toDrag = this;
|
|
|
|
e.dataTransfer.setData('text/plain', null);
|
|
e.dataTransfer.effectAllowed = 'move';
|
|
|
|
toDrag.style.opacity = 0.4;
|
|
});
|
|
|
|
this.table.dxOn('dragend', '.tbody', function(e) {
|
|
|
|
if (!isHandle) {
|
|
return;
|
|
}
|
|
|
|
isHandle = false;
|
|
|
|
let onSort = false;
|
|
|
|
toDrag.style.opacity = '';
|
|
toDrag.removeAttribute('draggable');
|
|
|
|
let data = {};
|
|
|
|
that.table.dxFind('.tbody', (el, i) => {
|
|
|
|
if (el.dataset.pos != i && !onSort) {
|
|
onSort = true;
|
|
}
|
|
|
|
if (el.dataset.pos != i) {
|
|
|
|
data[ el.dataset.id ] = i;
|
|
|
|
el.dataset.pos = i;
|
|
}
|
|
});
|
|
|
|
if (onSort) {
|
|
Ut.trigger(that.opts.onSort, that, data);
|
|
onSort = false;
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
// Set Table Events
|
|
|
|
setTableEvents() {
|
|
|
|
this.table.dxOn('click', '.th[data-sortable]', (_, t) => {
|
|
|
|
this.vars.set('sort_by', t.dataset.sortable);
|
|
|
|
if (!t.classList.contains('sort-asc')) {
|
|
|
|
t.className = 'th sort-asc';
|
|
|
|
this.vars.set('sort_mode', 'asc');
|
|
this.update();
|
|
|
|
return;
|
|
}
|
|
|
|
t.className = 'th sort-desc';
|
|
|
|
this.vars.set('sort_mode', 'desc');
|
|
this.update();
|
|
});;
|
|
}
|
|
|
|
// Update pagination area
|
|
|
|
updatePagination() {
|
|
|
|
this.paginationEl.dxHtml('');
|
|
|
|
if (this.data.pagination && this.data.pagination.count > 1) {
|
|
|
|
let pg_btns = [],
|
|
pg_btn,
|
|
j = 0;
|
|
|
|
if (this.data.pagination.start > 1) {
|
|
|
|
pg_btns[j] = [
|
|
1, '<<', false
|
|
];
|
|
|
|
j = 1;
|
|
}
|
|
|
|
for (let i = this.data.pagination.start; i <= this.data.pagination.end; i++) {
|
|
|
|
pg_btns[j] = [
|
|
i, i, (this.vars.get('pg') == i)
|
|
]
|
|
|
|
j++;
|
|
}
|
|
|
|
if (this.data.pagination.start > 1 && this.data.pagination.page != this.data.pagination.end) {
|
|
pg_btns[j] = [
|
|
this.data.pagination.count, '>>', false
|
|
];
|
|
}
|
|
|
|
pg_btns.forEach((dat) => {
|
|
|
|
pg_btn = El('a', {
|
|
'data-id': dat[0],
|
|
className: (dat[2] ? 'disabled' : '')}, dat[1]
|
|
);
|
|
|
|
this.paginationEl.append(pg_btn);
|
|
});
|
|
}
|
|
else if (this.data.before || this.data.after) { // update UI
|
|
|
|
if (this.data.before) {
|
|
this.paginationEl.append( El('a', { 'data-id': this.data.before, className: 'btn-before' }, '<') );
|
|
}
|
|
|
|
if (this.data.after) {
|
|
this.paginationEl.append( El('a', { 'data-id': this.data.after, className: 'btn-after' }, '>') );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get All Rows
|
|
|
|
getRows(id) {
|
|
return this.table.dxFind('.tbody');
|
|
}
|
|
|
|
// Get Row By ID
|
|
|
|
getRow(id) {
|
|
return this.table.dxFind('.tbody[data-id="'+ id +'"]');
|
|
}
|
|
|
|
// Update Row Data
|
|
|
|
updateRow(id, data) {
|
|
|
|
this.setMode(Table.UPDATE);
|
|
this.id = id;
|
|
|
|
if (data) {
|
|
this.setTableData(data);
|
|
}
|
|
else {
|
|
this.makeRequest();
|
|
}
|
|
}
|
|
|
|
// Update All Rows
|
|
|
|
update(data) {
|
|
|
|
this.setMode(Table.CLEAN);
|
|
|
|
if (data) {
|
|
this.setTableData(data);
|
|
}
|
|
else {
|
|
this.makeRequest();
|
|
}
|
|
}
|
|
|
|
// Delete Row
|
|
|
|
deleteRow(id) {
|
|
|
|
let row = this.getRow(id);
|
|
|
|
if (row) {
|
|
|
|
this.rows--;
|
|
this.updateInfoRows();
|
|
|
|
row.remove();
|
|
|
|
if (!this.rows) {
|
|
|
|
let pg = this.vars.get('pg');
|
|
|
|
if (pg > 1) {
|
|
this.vars.set('pg', --pg);
|
|
}
|
|
|
|
this.update();
|
|
}
|
|
|
|
Ut.trigger(this.opts.onUpdate, this);
|
|
}
|
|
}
|
|
|
|
// Add Row At The End of the table
|
|
|
|
appendRow(data) {
|
|
|
|
this.setMode(this.table.children.length ? Table.APPEND : Table.CLEAN);
|
|
|
|
if (data) {
|
|
this.setTableData(data);
|
|
}
|
|
else {
|
|
this.makeRequest();
|
|
}
|
|
}
|
|
|
|
// Add Row At Begining of the table
|
|
|
|
prependRow(data) {
|
|
|
|
this.setMode(this.table.children.length ? Table.APPEND : Table.CLEAN);
|
|
|
|
if (data) {
|
|
this.setTableData(data);
|
|
}
|
|
else {
|
|
this.makeRequest();
|
|
}
|
|
}
|
|
|
|
// Set Info Table
|
|
|
|
setInfo(text) {
|
|
this.info.textContent = text;
|
|
};
|
|
|
|
// Set Toolbar Header
|
|
|
|
setHeaderToolbar(content) {
|
|
this.toolbarHeader.dxHtml(content);
|
|
}
|
|
|
|
// Set Toolbar Footer
|
|
|
|
setFooterToolbar(content) {
|
|
this.toolbarFooter.dxHtml(content);
|
|
}
|
|
} |