mirror of
https://github.com/Karaka-Management/jsOMS.git
synced 2026-01-11 09:58:39 +00:00
385 lines
13 KiB
JavaScript
385 lines
13 KiB
JavaScript
import { TableView } from '../../Views/TableView.js';
|
|
import { Request } from '../../Message/Request/Request.js';
|
|
import { ResponseType } from '../../Message/Response/ResponseType.js';
|
|
/**
|
|
* Table manager class.
|
|
*
|
|
* @copyright Dennis Eichhorn
|
|
* @license OMS License 1.0
|
|
* @version 1.0.0
|
|
* @since 1.0.0
|
|
*/
|
|
export class Table {
|
|
/**
|
|
* @constructor
|
|
*
|
|
* @param {Object} app Application
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
constructor(app)
|
|
{
|
|
this.app = app;
|
|
|
|
/** @var {Object <string, TableView>} */
|
|
this.tables = {};
|
|
this.ignore = {};
|
|
};
|
|
|
|
/**
|
|
* Bind & rebind UI elements.
|
|
*
|
|
* @param {string} [id] Element id
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bind (id)
|
|
{
|
|
if (typeof id !== 'undefined' && typeof this.ignore[id] === 'undefined') {
|
|
this.bindTable(id);
|
|
} else {
|
|
const tables = document.getElementsByTagName('table'),
|
|
length = !tables ? 0 : tables.length;
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
let tableId = tables[i].getAttribute('id');
|
|
if (typeof tableId !== 'undefined' && tableId !== null && typeof this.ignore[tableId] === 'undefined') {
|
|
this.bindTable(tableId);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Unbind table
|
|
*
|
|
* @param {string} id Table Id
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
unbind (id)
|
|
{
|
|
|
|
};
|
|
|
|
/**
|
|
* Bind & rebind UI element.
|
|
*
|
|
* @param {Object} [id] Element id
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindTable (id)
|
|
{
|
|
if (typeof id === 'undefined' || !id) {
|
|
jsOMS.Log.Logger.instance.info('A table doesn\'t have an ID.');
|
|
return;
|
|
}
|
|
|
|
this.tables[id] = new TableView(id);
|
|
|
|
this.unbind(id);
|
|
|
|
this.bindExport(this.tables[id]);
|
|
|
|
// todo: sorting: increasing / decreasing only if icons are available
|
|
// todo: filtering: equals (alphanumeric), greater, greater equals, lesser, lesser equals, contains, doesn't contain, excel like selection of elements. Amount of filtering options unlimited.
|
|
// cell value should be data-value="" and cell name should be data-name="" and cell content should be data-content="".
|
|
// if no value is defined than data-value = cell content. if no name is defined then data-name = cell content. if no content is defined then data-content = cell content.
|
|
|
|
const sorting = this.tables[id].getSorting();
|
|
let length = sorting.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
this.bindSorting(sorting[i], id);
|
|
}
|
|
|
|
const order = this.tables[id].getSortableRows();
|
|
length = order.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
this.bindReorder(order[i], id);
|
|
}
|
|
|
|
const filters = this.tables[id].getFilter();
|
|
length = filters.length;
|
|
for (let i = 0; i < length; ++i) {
|
|
this.bindFiltering(filters[i], id);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Export a table
|
|
*
|
|
* @param {Element} exports Export button
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindExport(exports)
|
|
{
|
|
const button = exports.getExport();
|
|
|
|
if (typeof button === 'undefined' || button === null) {
|
|
return;
|
|
}
|
|
|
|
button.addEventListener('click', function (event)
|
|
{
|
|
console.log(exports.serialize());
|
|
// todo: either create download in javascript from this data or make round trip to server who then sends the data
|
|
// - think about allowing different export formats (json, csv, excel)
|
|
// - maybe this should never be done from the ui, maybe a endpoint uri should be specified which then calls the api get function for this data
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Swaps the row on click.
|
|
*
|
|
* @param {Element} sorting Swap button
|
|
* @param {Object} id Element id
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindReorder(sorting, id)
|
|
{
|
|
sorting.addEventListener('click', function (event)
|
|
{
|
|
jsOMS.preventAll(event);
|
|
|
|
const table = document.getElementById(id),
|
|
rows = table.getElementsByTagName('tbody')[0].rows,
|
|
rowLength = rows.length,
|
|
rowId = this.closest('tr').rowIndex,
|
|
orderType = jsOMS.hasClass(this, 'order-up') ? 1 : -1;
|
|
|
|
|
|
if (orderType === 1 && rowId > 1) {
|
|
rows[rowId].parentNode.insertBefore(rows[rowId - 2], rows[rowId]);
|
|
} else if (orderType === -1 && rowId < rowLength) {
|
|
rows[rowId - 1].parentNode.insertBefore(rows[rowId], rows[rowId - 1]);
|
|
}
|
|
|
|
// todo: submit new order to remote
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Sorts the table.
|
|
*
|
|
* @param {Element} sorting Sort button
|
|
* @param {Object} id Table id
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindSorting(sorting, id)
|
|
{
|
|
sorting.addEventListener('click', function (event)
|
|
{
|
|
jsOMS.preventAll(event);
|
|
|
|
const table = document.getElementById(id),
|
|
rows = table.getElementsByTagName('tbody')[0].rows,
|
|
rowLength = rows.length,
|
|
cellId = this.closest('td').cellIndex,
|
|
sortType = jsOMS.hasClass(this, 'sort-asc') ? 1 : -1;
|
|
|
|
let j, i, row1, row2, content1, content2,
|
|
order = false,
|
|
shouldSwitch = false;
|
|
|
|
const columnName = this.closest('td').getAttribute('data-name');
|
|
|
|
table.setAttribute('data-sorting', (sortType > 0 ? '+' : '-') + (columnName !== null ? columnName : cellId));
|
|
|
|
if (table.getAttribute('data-src') !== null) {
|
|
Table.getRemoteData(table);
|
|
return;
|
|
}
|
|
|
|
do {
|
|
order = false;
|
|
|
|
for (j = 0; j < rowLength - 1; ++j) {
|
|
shouldSwitch = false;
|
|
row1 = rows[j].getElementsByTagName('td')[cellId];
|
|
content1 = row1.getAttribute('data-content') !== null ? row1.getAttribute('data-content') : row1.textContent;
|
|
|
|
for (i = j + 1; i < rowLength; ++i) {
|
|
row2 = rows[i].getElementsByTagName('td')[cellId];
|
|
content2 = row2.getAttribute('data-content') !== null ? row2.getAttribute('data-content') : row2.textContent;
|
|
|
|
if (sortType === 1 && content1 > content2) {
|
|
shouldSwitch = true;
|
|
} else if (sortType === -1 && content1 < content2) {
|
|
shouldSwitch = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldSwitch === true) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (shouldSwitch) {
|
|
rows[j].parentNode.insertBefore(rows[i - 1], rows[j]);
|
|
order = true;
|
|
}
|
|
} while (order);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Filters the table.
|
|
*
|
|
* @param {Element} filtering Filter button
|
|
* @param {Object} id Table id
|
|
*
|
|
* @return {void}
|
|
*
|
|
* @since 1.0.0
|
|
*/
|
|
bindFiltering(filtering, id)
|
|
{
|
|
filtering.addEventListener('click', function (event)
|
|
{
|
|
jsOMS.preventAll(event);
|
|
|
|
const tpl = document.getElementById('table-filter-tpl');
|
|
|
|
if (tpl === null || document.getElementById(tpl.content.firstElementChild.id) !== null) {
|
|
return;
|
|
}
|
|
|
|
const posY = event.pageY + document.body.scrollTop + document.documentElement.scrollTop + tpl.parentNode.scrollTop - tpl.parentNode.getBoundingClientRect().top;
|
|
const posX = event.pageX + document.body.scrollLeft + document.documentElement.scrollLeft + tpl.parentNode.scrollLeft - tpl.parentNode.getBoundingClientRect().left;
|
|
|
|
const output = document.importNode(tpl.content, true);
|
|
output.firstElementChild.setAttribute('style', 'position: absolute; top: ' + posY + 'px; left: ' + posX + 'px;')
|
|
output.firstElementChild.setAttribute('data-ui', this.closest('table').id);
|
|
output.firstElementChild.setAttribute('data-ui-column', this.closest('td').cellIndex);
|
|
|
|
// todo: set existing filtering option in ui here
|
|
|
|
// todo: do this as injection into a form instead? -this would require cleanup for the events every time
|
|
output.firstElementChild.querySelectorAll('button[type="submit"], input[type="submit"]')[0].addEventListener('click', function (event) {
|
|
jsOMS.preventAll(event);
|
|
|
|
const input = document.getElementById('table-filter').querySelectorAll('input, select');
|
|
const length = input.length;
|
|
|
|
let filter = [];
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
filter.push(input[i].value);
|
|
}
|
|
|
|
const table = document.getElementById(document.getElementById('table-filter').getAttribute('data-ui'));
|
|
|
|
table.querySelectorAll('thead td')[
|
|
document.getElementById('table-filter').getAttribute('data-ui-column')
|
|
].setAttribute('data-filter', JSON.stringify(filter));
|
|
|
|
// todo: if not empty highlight filter button for user indication that filter is active
|
|
// todo: filter locally and if src is available to remote filter maybe just create an apply function which calls the different functions?
|
|
|
|
document.getElementById('table-filter').parentNode.removeChild(document.getElementById('table-filter'));
|
|
|
|
if (table.getAttribute('data-src') !== null) {
|
|
Table.getRemoteData(table);
|
|
return;
|
|
}
|
|
|
|
// todo: implement local filtering if no data-src available
|
|
});
|
|
|
|
output.firstElementChild.querySelectorAll('button[type="reset"], input[type="reset"]')[0].addEventListener('click', function (event) {
|
|
document.getElementById('table-filter').parentNode.removeChild(document.getElementById('table-filter'));
|
|
});
|
|
|
|
tpl.parentNode.appendChild(output);
|
|
});
|
|
};
|
|
|
|
static getRemoteData (table)
|
|
{
|
|
const data = {
|
|
limit: table.getAttribute('data-limit'),
|
|
offset: table.getAttribute('data-offset'),
|
|
sorting: table.getAttribute('data-sorting'),
|
|
filter: table.getAttribute('data-filter')
|
|
};
|
|
|
|
const request = new Request();
|
|
request.setData(data);
|
|
request.setType(ResponseType.JSON);
|
|
request.setUri(table.getAttribute('data-src'));
|
|
request.setMethod('GET');
|
|
request.setRequestHeader('Content-Type', 'application/json');
|
|
request.setSuccess(function (xhr) {
|
|
Table.emptyTable(table.getElementsByTagName('tbody')[0]);
|
|
Table.addToTable(table.getElementsByTagName('tbody')[0], JSON.parse(xhr.response)[0]);
|
|
});
|
|
|
|
request.send();
|
|
};
|
|
|
|
static emptyTable(table)
|
|
{
|
|
const rows = table.getElementsByTagName('tr');
|
|
const length = rows.length;
|
|
|
|
for (let i = 0; i < length; ++i) {
|
|
rows[i].parentNode.removeChild(rows[i]);
|
|
}
|
|
};
|
|
|
|
static addToTable(table, data)
|
|
{
|
|
const dataLength = data.length;
|
|
|
|
console.table(data);
|
|
|
|
for (let i = 0; i < dataLength; ++i) {
|
|
// set readable value
|
|
const newRow = table.getElementsByTagName('template')[0].content.cloneNode(true);
|
|
let fields = newRow.querySelectorAll('[data-tpl-text]');
|
|
let fieldLength = fields.length;
|
|
|
|
for (let j = 0; j < fieldLength; ++j) {
|
|
fields[j].appendChild(
|
|
document.createTextNode(
|
|
jsOMS.getArray(fields[j].getAttribute('data-tpl-text'), data[i])
|
|
)
|
|
);
|
|
}
|
|
|
|
// set internal value
|
|
fields = newRow.querySelectorAll('[data-tpl-value]');
|
|
fieldLength = fields.length;
|
|
|
|
for (let j = 0; j < fieldLength; ++j) {
|
|
fields[j].setAttribute(
|
|
'data-value',
|
|
jsOMS.getArray(fields[j].getAttribute('data-tpl-value'), data[i])
|
|
);
|
|
}
|
|
|
|
table.appendChild(newRow);
|
|
|
|
// todo: bind buttons if required (e.g. remove, edit button)
|
|
}
|
|
};
|
|
};
|