Core.UI = Core.UI || {};
Core.UI.Table = class extends Core.Singleton {
/**
* Get class name.
* @return {string}
*/
static getClassName() {
return 'Core.UI.Table';
}
/**
* Initialize table.
* @param {string} tableId
* @param {object} parameters
*/
init(tableId, parameters) {
this._parameters = parameters;
this._tableId = tableId;
this.$table = (parameters.$container || $('body')).find(`table.${tableId}:not(.dataTable)`);
if (this.$table.doesntExist()) {
console.error(`Table ${tableId} doesnt exist`);
return false;
} else if (this.$table.children('tbody').doesntExist()) {
console.error(`Table ${tableId} is empty`);
return false;
}
this.tableId = tableId;
this.instance = parameters.instance || this.instance || Page.activePage;
this.prepareTable();
this.loadedIds = [];
this.populateIds = [];
this.populatingCount = 0;
this.rowsBoundOn = null;
this.bindRequestedOn = null;
this.rowLockCounts = {};
this.$emptyTableRow = null;
this.queueSize = this.$table.data('queue-size') || 5;
let $parent = this.$table.parent();
let scrollY = this.$table.parent().innerHeight();
if (this.$table.parent().innerHeight() > window.innerHeight) {
scrollY = window.innerHeight - (this.$table.parent().innerHeight() - window.innerHeight);
}
if ($parent.parents('.ui.modal').exists()) {
$parent = $parent.parent();
scrollY = $parent.innerHeight() - this.$table.children('thead').outerHeight() - this.$table.children('tfoot').outerHeight() - (14 * 3.5);
}
let dataTableParameters = Object.assign(
{
scrollY: Math.min(
Math.max(
Math.abs(scrollY),
600
),
600
),
scrollCollapse: true,
scrollX: true,
paging: false,
searching: false,
search: {
smart: false
},
info: false,
deferRender: true,
destroy: true,
autoWidth: false,
dom: `<"toolbar ${tableId}">frtip`
},
this.$table.data('datatables-parameters')
);
if (dataTableParameters.paging) {
dataTableParameters.scrollY = null;
}
try {
this.table = this.$table.DataTable(dataTableParameters);
} catch (e) {
console.debug(e);
return false;
}
this.table.on('order.dt', () => this.bindRequestedOn = new Date());
this.table.on('draw.dt', () => this.bindRequestedOn = new Date());
this.table.on('page.dt', () => this.populate());
this.table.on('search.dt', () => this.populate());
this.updateBody();
this.hook();
if (dataTableParameters.hideHeader) {
this.getContainer().addClass('no-head');
}
if (!dataTableParameters.scrollY) {
this.getContainer().addClass('no-scroll');
}
this.$table.find('img').one(
'load',
() => this.table.columns.adjust()
);
this.table.draw();
this.table.columns.adjust();
if (dataTableParameters.paging === false) {
this.populate();
}
if (this.instance) {
setTimeout(
() => this.instance.bindTable(
$(this.table.table().container())
),
500
);
}
return true;
}
/**
* If the object becomes stale, re-initialize it.
* @param {string}
* @param {object} parameters
*/
reinit(tableId, parameters) {
if (this.$table.doesntExist() || this.$table.notVisible()) {
return this.init(tableId, parameters);
}
return true;
}
/**
* Monitor if rows need to be populated or bound.
*/
hook() {
setTimeout(
() => {
if (this.$table.doesntExist()) {
return;
}
if (this.populatingCount < this.queueSize && this.populateIds.length !== 0) {
for (let i = 0; i < (this.queueSize - this.populatingCount); i++) {
if (this.populateIds.length === 0) {
break;
}
this.populatingCount++;
this.loadRow(
this.populateIds.shift(),
() => this.populatingCount--
);
}
}
if (!this.rowsBoundOn || this.rowsBoundOn < this.bindRequestedOn) {
this.rowsBoundOn = new Date();
this.bindRows();
}
this.hook();
},
250
);
}
/**
* Get count of table columns.
*/
getColumnCount() {
return this.$table
.children('thead')
.children('tr')
.children('th')
.length;
}
/**
* Since the colspan attribute breaks DataTables, restructure
* any rows that use it.
*/
prepareTable() {
let columnCount = this.getColumnCount();
this.$table.find('tbody > tr').each(function () {
let $tr = $(this);
if ($tr.find('td').first().hasAttr('colspan')) {
let text = $tr.find('td').first().html();
$tr.find('td').remove();
let style = '';
for (let i = 0; i < columnCount; i++) {
$tr.append(`
${text}
|
`);
text = '';
style = ' style="display: none;"';
}
}
if ($tr.hasClass('empty-table-row')) {
$tr.children('td').attr('colspan', columnCount);
}
});
this.$table.find('tfoot > tr > *[colspan="999"]').each(function () {
$(this).attr('colspan', columnCount);
});
}
/**
* Bind all row events.
* @param {Array} ids
*/
bindRows(ids) {
let me = this;
Core.UI.Bind.get(this.$table, this.instance);
me.table.rows({ page: 'current' }).every(function () {
let $tr = $(this.node());
if ($tr.hasClass('preloaded')) {
return;
}
if ($tr.hasClass('empty-table-row')) {
return;
}
if (ids && !ids.includes($tr.data('id'))) {
return;
}
if (me.instance) {
me.instance.bindRow($tr, me.tableId);
}
});
}
/**
* Get footer element.
* @return {jQuery}
*/
getFooter() {
return $(this.table.table().footer());
}
/**
* Get an array with all the row ids.
*/
getRowIds() {
let ids = [];
this.table.rows().every(function () {
let id = $(this.node()).data('id');
if (!id) {
return;
}
ids.push(id);
});
return ids;
}
/**
* Select all rows.
*/
selectAll(withDataParameters) {
this.table.rows().every(function () {
let $tr = $(this.node());
$tr.find('td > .ui.selected.checkbox > input[type="checkbox"]:not(:checked)').each(function () {
if (withDataParameters) {
for (let name in withDataParameters) {
if ($(this).parent().data(name) != withDataParameters[name]) {
return;
}
}
}
$(this).trigger('click');
});
});
}
/**
* Clear selection.
*/
clearSelection() {
this.table.rows().every(function () {
let $tr = $(this.node());
$tr.find('td > .ui.selected.checkbox > input[type="checkbox"]:checked').each(function () {
$(this).trigger('click');
});
});
}
/**
* Iterate through table and get row ids that are selected.
*/
getSelectedIds() {
let ids = [];
this.table.rows().every(function () {
let $tr = $(this.node());
$tr.find('td > .ui.selected.checkbox > input[type="checkbox"]:checked').each(function () {
ids.push(
$(this).parents('tr[data-id]').data('id')
);
});
});
return ids;
}
/**
* Determine if this table is empty.
* @return {boolean}
*/
isEmpty() {
let $rows = this.table.rows('[data-id]').eq(0);
if (!$rows) {
return true;
}
return $rows.length === 0;
}
/**
* Determine if this table is not empty.
* @return {boolean}
*/
notEmpty() {
return this.isEmpty() === false;
}
/**
* Determine if the table is populated.
*/
populated() {
let preloadedRowCount = 0;
this.table.rows({ page: 'current' }).every(function () {
let $row = $(this.node());
if ($row.hasClass('preloaded')) {
preloadedRowCount++;
}
});
return preloadedRowCount === 0;
}
/**
* Get table toolbar.
* @return {JQuery}
*/
getToolbar() {
return $(this.table.table().container())
.children(`.${this.tableId}.toolbar`);
}
/**
* Show/hide empty row based on if there are rows in the table.
*/
updateBody() {
if (this.notEmpty() && !this.$emptyTableRow) {
this.$emptyTableRow = $(this.table.row('.empty-table-row').node());
this.table.row('.empty-table-row').remove();
this.table.draw(false);
} else if (this.isEmpty() && this.$emptyTableRow && !this.table.row('.empty-table-row').node()) {
this.table.row.add(this.$emptyTableRow);
this.$emptyTableRow = null;
this.table.draw(false);
}
if (!Boolean(this.$table.data('preload-page-only'))) {
let preloadedCount = 0;
let populatedCount = 0;
this.table.rows().every(function () {
let $tr = $(this.node());
if ($tr.data('id')) {
if ($tr.hasClass('preloaded')) {
preloadedCount++;
} else {
populatedCount++;
}
}
});
if (preloadedCount) {
if (!this.$table.data('populating')) {
this.$table.data('populating', 1);
if (Boolean(this.$table.data('preload-first'))) {
this.showDimmer();
}
if (this.getToolbar().find('.loading-table.progress').doesntExist()) {
this.getToolbar().append(`
`);
}
}
}
if ((preloadedCount + populatedCount) > 0) {
let $populatedProgressBar = this.getToolbar().find('.loading-table.progress');
if (preloadedCount > 0) {
$populatedProgressBar.progress(
'set percent',
populatedCount / (preloadedCount + populatedCount) * 100
);
} else if (this.$table.data('populating')) {
this.$table.data('populating', '');
this.hideDimmer();
this.getToolbar().find('.loading-table.progress').parent().remove();
this.table.draw(false);
this.table.columns.adjust();
if (this.instance) {
this.instance.bindTable(
$(this.table.table().container()),
this.tableId
);
}
}
}
}
if (!Boolean(this.$table.data('preload-first')) || this.populated()) {
this.table.draw(false);
}
}
/**
* Get container object.
* @return {jQuery}
*/
getContainer() {
return $(this.table.table().container())
.find('.dataTables_scroll');
}
/**
* Dim table.
*/
showDimmer() {
this.$table.data(
'populating-dimmer-id',
this.getContainer().fade('show')
);
}
/**
* Remove table dimmer.
*/
hideDimmer() {
this.getContainer()
.fade(
'hide',
this.$table.data('populating-dimmer-id')
);
this.$table.data(
'populating-dimmer-id',
''
);
}
/**
* Add a row to the table by id. Then update the table by row id.
* @param {string} rowId
*/
addRow(rowId, callback, uri) {
let html = ``;
for (let i = 0; i < this.table.columns()[0].length; i++) {
html += ' | ';
}
html += '
';
this.table.row.add($(html));
this.updateBody();
this.loadRow(rowId, callback, null, null, uri);
}
/**
* Delete row and update table. This is called recursively in case there
* are multiple rows with the same id.
* @param {string} rowId
* @param {Boolean} draw
*/
deleteRow(rowId, draw) {
let row = this.table.row(this.getRow(rowId));
if (!row.length) {
if (draw !== false) {
row.draw(false);
}
this.updateBody();
return;
}
row.remove();
this.deleteRow(rowId, draw);
}
/**
* Get row from id.
* @param {string} rowId
*/
getRow(rowId) {
let row = this.table.row(`[data-id="${rowId}"]`);
let node = row.node();
return $(node);
}
/**
* Redraw row.
* @param {string} rowId
*/
redrawRow(rowId) {
this.table.row(`[data-id="${rowId}"]`).draw(false);
}
/**
* Queue all rows that are only preloaded.
*/
populate() {
if (this.getToolbar().notVisible()) {
return;
}
let me = this;
let page = (Boolean(this.$table.data('preload-page-only')) ? 'current' : 'all');
this.table.rows({ page: page }).every(function () {
let $row = $(this.node());
if ($row.hasClass('preloaded') && !$row.hasClass('loading')) {
if (me.populateIds.includes($row.data('id')) === false) {
me.populateRow($row.data('id'));
}
}
});
}
/**
* Trigger row to be populated.
* @param {string} rowId
*/
populateRow(rowId) {
if (this.populateIds.includes(rowId)) {
return;
}
this.populateIds.push(rowId);
}
/**
* Load row.
* @param {string} rowId
* @param {function()} callback
* @param {boolean} dontLock
* @param {object} rowData
* @param {string} uri
*/
loadRow(rowId, callback, dontLock, rowData, uri) {
if (!this.instance) {
return;
}
let $row = this.getRow(rowId);
$row.addClass('loading');
if (!dontLock) {
this.lockRow(rowId);
}
Core.API.call({
url: uri ?? this.instance.getURI(),
data: {
func: 'apiGetTableRow',
table_id: this.tableId,
row_id: rowId,
row_data: rowData,
configuration: Page.getParameters()
},
callback: (data) => {
if (!data.row_id) {
data.row_id = rowId;
}
this.deleteRow(rowId, false);
if (data.html) {
this.table.row.add($(data.html));
this.unlockRow(rowId, true);
}
if ($.isFunction(callback)) {
callback();
}
this.updateBody();
},
fadeParameters: {
hide: true
}
});
}
/**
* Lock row cells.
* @param {string} rowId
*/
lockRow(rowId) {
let me = this;
if (!me.rowLockCounts.hasOwnProperty(rowId)) {
me.rowLockCounts[rowId] = 0;
}
me.rowLockCounts[rowId]++;
me.getRow(rowId).children('td').each(function () {
let $td = $(this);
$td.addClass('disabled');
// @todo what if readonly property was set before this?
$td.find('input').prop('readonly', 'readonly');
});
}
/**
* Unlock row cells.
* @param {string} rowId
* @param {Boolean} force
*/
unlockRow(rowId, force) {
let me = this;
me.rowLockCounts[rowId]--;
if (force) {
me.rowLockCounts[rowId] = 0;
}
if (me.rowLockCounts[rowId] > 0) {
return;
}
me.getRow(rowId).children('td').each(function () {
let $td = $(this);
$td.removeClass('disabled');
// @todo remove only if necessary
$td.find('input').removeProp('readonly');
});
if (this.populated()) {
this.table.draw(false);
}
}
/**
* Reload all table rows individually.
*/
reloadRows() {
let me = this;
this.table.rows({ page: 'all' }).every(function() {
let $row = $(this.node());
if (!$row.data('id')) {
return;
}
me.populateRow($row.data('id'));
});
}
/**
* Reload table.
* @param {object} data
*/
reload(data, callback) {
if (!data) {
data = {};
}
Core.API.call({
url: this.instance.getURI(),
data: {
func: 'apiGetTable',
table_id: this.tableId,
parameters: $.extend(Page.getParameters(), data)
},
callback: (data) => {
if (!data.html) {
return;
}
this.table.clear().destroy();
this.$table.replaceWith(data.html);
this.init(this._tableId, this._parameters);
this.table.columns.adjust();
if ($.isFunction(callback)) {
callback();
}
},
$context: this.$table,
fadeParameters: {
hide: true
}
});
}
};