Fix table CRUD

This commit is contained in:
Dennis Eichhorn 2019-03-31 20:28:13 +02:00
parent b9b339d39a
commit b40c6670d4
5 changed files with 408 additions and 10 deletions

View File

@ -49,7 +49,7 @@
*/
send (msg)
{
const tpl = document.getElementById('app-message');
const tpl = document.getElementById('app-message-tpl');
if (tpl === null) {
return;

View File

@ -39,6 +39,10 @@
*/
get (id)
{
if (!this.forms.hasOwnProperty(id)) {
this.bind(id);
}
return this.forms[id];
};
@ -281,6 +285,10 @@
});
request.send();
if (form.getFinally() !== null) {
form.getFinally()();
}
};
/**

View File

@ -117,6 +117,21 @@
for (let i = 0; i < length; ++i) {
this.bindRemovable(removable[i], id);
}
const createForm = this.tables[id].getForm();
const createButton = document.getElementById(id).getAttribute('data-table-add');
if (createButton !== null) {
this.bindCreateInline(createButton, id);
} else if (createForm !== null) {
this.bindCreateForm(createForm, id);
}
const update = this.tables[id].getUpdatable();
length = update.length;
for (let i = 0; i < length; ++i) {
this.bindUpdatable(update[i], id);
}
};
/**
@ -155,14 +170,17 @@
*
* @since 1.0.0
*/
bindRemovable(table)
bindRemovable(remove, id)
{
remove.addEventListener('click', function (event)
{
jsOMS.preventAll(event);
document.getElementById(id).deleteRow(this.closest('tr').rowIndex);
});
if (remove.closest('form').getAttribute('method') !== 'NONE') {
this.app.uiManager.getFormManager().get(remove.closest('form').id).setSuccess(function () {
document.getElementById(id).deleteRow(remove.closest('tr').rowIndex);
});
} else {
this.app.uiManager.getFormManager().get(remove.closest('form').id).setFinally(function () {
document.getElementById(id).deleteRow(remove.closest('tr').rowIndex);
});
}
};
/**
@ -187,11 +205,14 @@
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
});
};
@ -221,6 +242,15 @@
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) {
jsOMS.UI.Component.Table.getRemoteData(table);
return;
}
do {
order = false;
@ -270,7 +300,329 @@
filtering.addEventListener('click', function (event)
{
jsOMS.preventAll(event);
// filter algorithm here
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-table', this.closest('table').id);
output.firstElementChild.setAttribute('data-table-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-table'));
table.querySelectorAll('thead td')[
document.getElementById('table-filter').getAttribute('data-table-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) {
jsOMS.UI.Component.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);
});
};
/**
* Create the table row
*
* @param {string} createForm Create form
* @param {Object} id Table id
*
* @return {void}
*
* @since 1.0.0
*/
bindCreateForm(createForm, id)
{
this.app.uiManager.getFormManager().get(createForm).injectSubmit(function () {
const table = document.getElementById(id).getElementsByTagName('tbody')[0];
const newRow = table.getElementsByTagName('template')[0].content.cloneNode(true);
// set internal value
let fields = newRow.querySelectorAll('[data-tpl-value]');
let fieldLength = fields.length;
let uuid = '';
let value = '';
for (let j = 0; j < fieldLength; ++j) {
value = document.querySelectorAll(
'#' + createForm + ' [data-tpl-value="' + fields[j].getAttribute('data-tpl-value') + '"], [data-form="' + createForm + '"][data-tpl-value="' + fields[j].getAttribute('data-tpl-value') + '"]')[0]
.getAttribute('data-value');
// todo: we need to check what kind of tag the selector above returns in order to get the correct value. currently this only makes sense for input elements but for selection, checkboxes etc. this doesn't make sense there we need .innerHtml or [data-text=]
fields[j].setAttribute('data-value', value);
uuid += value;
}
// don't allow duplicate
if (table.querySelectorAll('[data-tpl-uuid="' + uuid + '"').length !== 0) {
return;
}
newRow.firstElementChild.setAttribute('data-tpl-uuid', uuid);
// set readable text
fields = newRow.querySelectorAll('[data-tpl-text]');
fieldLength = fields.length;
for (let j = 0; j < fieldLength; ++j) {
fields[j].appendChild(
document.createTextNode(
document.querySelectorAll('#' + createForm + ' [data-tpl-text="' + fields[j].getAttribute('data-tpl-text') + '"], [data-form="' + createForm + '"][data-tpl-text="' + fields[j].getAttribute('data-tpl-text') + '"]')[0].value
)
);
// todo: we need to check what kind of tag the selector above returns in order to get the correct value. currently this only makes sense for input elements but for selection, checkboxes etc. this doesn't make sense there we need .innerHtml or [data-text=]
}
table.appendChild(newRow);
// todo: consider to do ui action as success inject to the backend request... maybe optional because sometimes there will be no backend call?
// todo: if a column has a form in the template the id of the form needs to be set unique somehow (e.g. remove button in form)
// todo: bind removable
// todo: bind edit
return true;
});
};
/**
* Create the table row
*
* @param {string} createForm Create form
* @param {Object} id Table id
*
* @return {void}
*
* @since 1.0.0
*/
bindCreateInline(createForm, id)
{
const self = this;
document.getElementById(createForm).addEventListener('click', function() {
const table = document.getElementById(id).getElementsByTagName('tbody')[0];
const newRow = table.getElementsByTagName('template')[1].content.cloneNode(true);
const rowId = 'f' + Math.random().toString(36).substring(7);
// todo: check if random id doesn't already exist
newRow.firstElementChild.id = rowId;
newRow.firstElementChild.getElementsByTagName('form')[0].id = rowId + '-form';
const fields = newRow.firstElementChild.querySelectorAll('[data-form="' + self.tables[id].getForm() + '"]');
const length = fields.length;
for (let i = 0; i < length; ++i) {
fields[i].setAttribute('data-form', rowId + '-form');
}
table.appendChild(newRow.firstElementChild);
self.bindCreateForm(rowId + '-form', id);
self.app.uiManager.getFormManager().get(rowId + '-form').injectSubmit(function () {
document.getElementById(id).getElementsByTagName('tbody')[0].removeChild(
document.getElementById(rowId)
);
});
// todo: bind removable
// todo: bind edit
});
// todo: this is polluting the form manager because it should be emptied afterwards (form is deleted but not from form manager)
// todo: consider to do ui action as success inject to the backend request... maybe optional because sometimes there will be no backend call?
// todo: if a column has a form in the template the id of the form needs to be set unique somehow (e.g. remove button in form)
};
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 jsOMS.Message.Request.Request();
request.setData(data);
request.setType(jsOMS.Message.Response.ResponseType.JSON);
request.setUri(table.getAttribute('data-src'));
request.setMethod('GET');
request.setRequestHeader('Content-Type', 'application/json');
request.setSuccess(function (xhr) {
jsOMS.UI.Component.Table.emptyTable(table.getElementsByTagName('tbody')[0]);
jsOMS.UI.Component.Table.addToTable(table.getElementsByTagName('tbody')[0], JSON.parse(xhr.response));
});
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)
}
};
bindUpdatable (update, id)
{
const self = this;
update.addEventListener('click', function() {
// handle external form
// handle internal form
const formId = document.getElementById(id).getAttribute('data-table-form');
const values = this.closest('tr').querySelectorAll('[data-tpl-value]');
const text = this.closest('tr').querySelectorAll('[data-tpl-text]');
const table = document.getElementById(id).getElementsByTagName('tbody')[0];
if (document.getElementById(formId) === null) {
this.closest('tr').style = "display: none"; // todo: replace with class instead of inline style
const newRow = table.getElementsByTagName('template')[1].content.cloneNode(true);
const rowId = 'f' + Math.random().toString(36).substring(7);
// todo: don't use random ide use actual row id for data which needs to be updated
newRow.firstElementChild.id = rowId;
newRow.firstElementChild.getElementsByTagName('form')[0].id = rowId + '-form';
const fields = newRow.firstElementChild.querySelectorAll('[data-form="' + self.tables[id].getForm() + '"]');
let length = fields.length;
for (let i = 0; i < length; ++i) {
fields[i].setAttribute('data-form', rowId + '-form');
}
// insert row values data into form
length = values.length;
for (let i = 0; i < length; ++i) {
newRow.firstElementChild.querySelectorAll('[data-tpl-value="' + values[i].getAttribute('data-tpl-value') + '"')[0].value = values[i].value;
// todo: handle different input types
}
// insert row text data into form
length = text.length;
for (let i = 0; i < length; ++i) {
newRow.firstElementChild.querySelectorAll('[data-tpl-text="' + text[i].getAttribute('data-tpl-text') + '"')[0].value = text[i].innerText;
console.log(text[i].innerText);
// todo: handle different input types
}
table.insertBefore(newRow.firstElementChild, this.closest('tr'));
self.bindCreateForm(rowId + '-form', id);
self.app.uiManager.getFormManager().get(rowId + '-form').injectSubmit(function () {
document.getElementById(id).getElementsByTagName('tbody')[0].removeChild(
document.getElementById(rowId)
);
});
// todo: replace add button with save button and add cancel button
// todo: on save button click insert data into hidden row and show hidden row again, delete form row
} else {
const fields = document.getElementById(formId).querySelectorAll('[data-form="' + self.tables[id].getForm() + '"]');
let length = fields.length;
for (let i = 0; i < length; ++i) {
fields[i].setAttribute('data-form', rowId + '-form');
}
// insert row values data into form
length = values.length;
for (let i = 0; i < length; ++i) {
document.getElementById(formId).querySelectorAll('[data-tpl-value="' + values[i].getAttribute('data-tpl-value') + '"')[0].value = values[i].value;
// todo: handle different input types
}
// insert row text data into form
length = text.length;
for (let i = 0; i < length; ++i) {
document.getElementById(formId).querySelectorAll('[data-tpl-text="' + text[i].getAttribute('data-tpl-text') + '"')[0].value = text[i].innerText;
console.log(text[i].innerText);
// todo: handle different input types
}
// todo: replace add button with save button and add cancel button
// todo: on save button click insert data into hidden row and show hidden row again, delete form row
// todo: consider to highlight column during edit
}
});
};
}

View File

@ -32,6 +32,7 @@
this.bind();
this.success = null;
this.finally = null;
this.lastSubmit = 0;
};
@ -111,7 +112,8 @@
return document.querySelectorAll(
'#' + this.id + ' input[type=submit], '
+ 'button[form=' + this.id + '][type=submit], '
+ '#' + this.id + ' button[type=submit]'
+ '#' + this.id + ' button[type=submit], '
+ '#' + this.id + ' .submit'
);
};
@ -141,6 +143,30 @@
this.success = callback;
};
/**
* Get finally callback
*
* @return {callback}
*
* @since 1.0.0
*/
getFinally() {
return this.finally;
};
/**
* Set finally callback
*
* @param {callback} callback Callback
*
* @return {void}
*
* @since 1.0.0
*/
setFinally(callback) {
this.finally = callback;
};
/**
* Inject submit with post callback
*

View File

@ -146,5 +146,17 @@
'#' + this.id + ' tbody .remove'
);
};
getForm()
{
return document.getElementById(this.id).getAttribute('data-table-form');
};
getUpdatable()
{
return document.querySelectorAll(
'#' + this.id + ' tbody .update'
);
};
}
}(window.jsOMS = window.jsOMS || {}));