diff --git a/Message/Notification/App/AppNotification.js b/Message/Notification/App/AppNotification.js index 5774466..29cc54d 100644 --- a/Message/Notification/App/AppNotification.js +++ b/Message/Notification/App/AppNotification.js @@ -49,11 +49,11 @@ export class AppNotification return; } - let output = document.importNode(tpl.content, true); + const output = document.importNode(tpl.content, true); output.querySelector('.log-msg').classList.add('log-msg-status-' + msg.status); output.querySelector('.log-msg-content').innerHTML = msg.message; - output.querySelector('.close').addEventListeenr('click', function() { - this.parent.remove(); + output.querySelector('.close').addEventListener('click', function() { + this.parentNode.remove(); }); if (msg.title && msg.title !== '') { @@ -63,6 +63,9 @@ export class AppNotification } tpl.parentNode.appendChild(output); + + const logs = document.getElementsByClassName('log-msg'); + const lastElementAdded = logs[logs.length - 1]; window.navigator.vibrate(msg.vibrate ? 200 : 0); if (msg.isSticky) { @@ -71,7 +74,9 @@ export class AppNotification setTimeout(function () { - document.getElementsByClassName('log-msg')[0].remove(); + if (lastElementAdded !== null && lastElementAdded.parentNode !== null) { + lastElementAdded.parentNode.removeChild(lastElementAdded); + } }, 3000); }; }; \ No newline at end of file diff --git a/UI/Component/Form.js b/UI/Component/Form.js index 868f429..5a2f5b4 100644 --- a/UI/Component/Form.js +++ b/UI/Component/Form.js @@ -1487,6 +1487,34 @@ export class Form src.innerHTML = jsOMS.htmlspecialchars_encode(value); break; + case 'select': + const optionLength = src.options.length; + for (let i = 0; i < optionLength; ++i) { + if (src.options[i].value === value) { + src.options[i].selected = true; + + break; + } + } + + break; + case 'input': + if (src.type === 'radio') { + src.checked = false; + if (src.value === value) { + src.checked = true; + } + + break; + } else if (src.type === 'checkbox') { + src.checked = false; + const values = value.split(','); + if (values.includes(src.value)) { + src.checked = true; + } + + break; + } default: src.value = jsOMS.htmlspecialchars_decode(value); } @@ -1495,6 +1523,8 @@ export class Form setTextOfElement(src, value) { switch (src.tagName.toLowerCase()) { + case 'select': + break; case 'div': case 'span': case 'pre': @@ -1505,24 +1535,28 @@ export class Form case 'h1': src.innerHTML = jsOMS.htmlspecialchars_encode(value); break; - case 'textarea': - // textarea only has value data in it's content and nothing else! - break; default: - src.value = jsOMS.htmlspecialchars_decode(value); + if (src.value === '') { + src.value = jsOMS.htmlspecialchars_decode(value); + } } }; getValueFromDataSource(src) { + if (src.getAttribute('data-value') !== null) { + return src.getAttribute('data-value'); + } + switch (src.tagName.toLowerCase()) { + case 'td': case 'div': case 'span': case 'pre': case 'article': case 'section': case 'h1': - return src.innerHTML; + return src.innerText; default: return src.value; } @@ -1531,6 +1565,7 @@ export class Form getTextFromDataSource(src) { switch (src.tagName.toLowerCase()) { + case 'td': case 'div': case 'span': case 'pre': diff --git a/UI/Component/Table.js b/UI/Component/Table.js index 871eb41..00072c3 100644 --- a/UI/Component/Table.js +++ b/UI/Component/Table.js @@ -27,6 +27,10 @@ import { ResponseType } from '../../Message/Response/ResponseType.js'; * Data download * There is a small icon in the top right corner of tables which allows (not yet to be honest) to download the data in the table. * Whether the backend should be queried for this or only the frontend data should be collected (current situation) should depend on if the table has an api endpoint defined. + * + * @todo Allow column drag/drop ordering which is also saved in the front end + * + * @todo Save column visibility filter and apply that filter on page load */ export class Table { @@ -151,6 +155,9 @@ export class Table for (let i = 0; i < length; ++i) { this.bindCheckbox(checkboxes[i], id); } + + const header = this.tables[id].getHeader(); + this.bindColumnVisibility(header, id); }; /** @@ -183,6 +190,90 @@ export class Table }); }; + /** + * Bind column visibility + * + * @param {Element} header Header + * + * @return {void} + * + * @since 1.0.0 + */ + bindColumnVisibility(header) + { + header.addEventListener('contextmenu', function (event) { + jsOMS.preventAll(event); + + if (document.getElementById('table-context-menu') !== null) { + return; + } + + const tpl = document.getElementById('table-context-menu-tpl'); + + if (tpl === null) { + return; + } + + const output = document.importNode(tpl.content, true); + tpl.parentNode.appendChild(output); + const menu = document.getElementById('table-context-menu'); + + const columns = header.querySelectorAll('td'); + let length = columns.length; + + let baseMenuLine = menu.getElementsByClassName('context-line')[0].cloneNode(true); + + for (let i = 0; i < length; ++i) { + if (typeof columns[i].firstChild === 'undefined' + || typeof columns[i].firstChild.data === 'undefined' + || columns[i].firstChild.data.trim() === '' + ) { + continue; + } + + const menuLine = baseMenuLine.cloneNode(true); + const lineId = menuLine.firstElementChild.getAttribute('get') + i; + + menuLine.firstElementChild.setAttribute('for', lineId); + menuLine.firstElementChild.firstElementChild.setAttribute('id', lineId); + menuLine.firstElementChild.appendChild(document.createTextNode(columns[i].firstChild.data.trim())); + + menu.getElementsByTagName('ul')[0].appendChild(menuLine); + menu.querySelector('ul').lastElementChild.querySelector('input[type="checkbox"]').checked = columns[i].style.display !== 'none'; + + menu.querySelector('ul').lastElementChild.querySelector('input[type="checkbox"]').addEventListener('change', function () { + const rows = header.parentElement.getElementsByTagName('tr'); + const rowLength = rows.length; + + for (let j = 0; j < rowLength; ++j) { + const cols = rows[j].getElementsByTagName('td'); + + cols[i].style.display = cols[i].style.display === 'none' ? '' : 'none'; + } + }); + } + + menu.getElementsByTagName('ul')[0].removeChild(menu.getElementsByClassName('context-line')[0]); + + const rect = tpl.parentElement.getBoundingClientRect(); + menu.style.top = (event.clientY - rect.top) + "px"; + menu.style.left = (event.clientX - rect.left) + "px"; + + document.addEventListener('click', Table.hideMenuClickHandler); + }); + }; + + static hideMenuClickHandler(event) + { + const menu = document.getElementById('table-context-menu'); + const isClickedOutside = !menu.contains(event.target); + + if (isClickedOutside) { + menu.parentNode.removeChild(menu); + document.removeEventListener('click', Table.hideMenuClickHandler); + } + }; + /** * Swaps the row on click. * @@ -202,16 +293,14 @@ export class Table const table = document.getElementById(id), rows = table.getElementsByTagName('tbody')[0].rows, rowLength = rows.length, - rowId = this.closest('tr').rowIndex, + rowId = this.closest('tr').rowIndex - 1, orderType = jsOMS.hasClass(this, 'order-up') ? 1 : -1; - if (orderType === 1 && rowId > 1) { - rows[rowId].parentNode.insertBefore(rows[rowId - 2], rows[rowId]); + if (orderType === 1 && rowId > 0) { + rows[rowId].parentNode.insertBefore(rows[rowId], rows[rowId - 1]); } else if (orderType === -1 && rowId < rowLength) { - rows[rowId - 1].parentNode.insertBefore(rows[rowId], rows[rowId - 1]); + rows[rowId].parentNode.insertBefore(rows[rowId], rows[rowId + 2]); } - - // continue implementation }); }; diff --git a/UI/DragNDrop.js b/UI/DragNDrop.js index 58910dd..19485c9 100644 --- a/UI/DragNDrop.js +++ b/UI/DragNDrop.js @@ -53,9 +53,7 @@ export class DragNDrop length = !elements ? 0 : elements.length; for (let i = 0; i < length; ++i) { - if (typeof elements[i].getAttribute('id') !== 'undefined' && elements[i].getAttribute('id') !== null) { - this.bindElement(elements[i].getAttribute('id')); - } + this.bindElement(elements[i]); } } }; @@ -69,10 +67,9 @@ export class DragNDrop * * @since 1.0.0 */ - bindElement (id) + bindElement (element) { - const element = document.getElementById(id), - self = this; + const self = this; if (!element) { return; @@ -87,10 +84,31 @@ export class DragNDrop }, false); element.addEventListener('dragenter', function(e) { - /** - * @todo Orange-Management/jsOMS#??? [t:feature] [p:low] [d:medium] - * Highlight the drop area - */ + const rowIndex = Array.from(this.parentElement.children).indexOf(this); + const dragIndex = Array.from(self.dragging.parentElement.children).indexOf(self.dragging); + + const oldPlaceholder = this.parentNode.querySelector('.drag-placeholder'); + if (oldPlaceholder !== null) { + this.parentNode.removeChild(oldPlaceholder); + } + + const placeholder = document.createElement(self.dragging.tagName); + + if (self.dragging.tagName.toLowerCase() === 'tr') { + const placeholderTd = document.createElement('td'); + placeholderTd.setAttribute('colspan', 42); + placeholder.appendChild(placeholderTd); + } + + placeholder.setAttribute('draggable', 'true'); + + jsOMS.addClass(placeholder, 'drag-placeholder'); + + if (dragIndex < rowIndex) { + this.parentNode.insertBefore(placeholder, this.nextSibling); + } else { + this.parentNode.insertBefore(placeholder, this); + } }, false); element.addEventListener('dragover', function(e) { @@ -101,39 +119,25 @@ export class DragNDrop element.addEventListener('dragleave', function(e) { e.preventDefault(); - - /** - * @todo Orange-Management/jsOMS#??? [t:feature] [p:low] [d:medium] - * Stop highlighting the drop area - */ }, false); element.addEventListener('dragend', function(e) { e.preventDefault(); - /** - * @todo Orange-Management/jsOMS#??? [t:feature] [p:low] [d:medium] - * Reset all UI states - */ + const oldPlaceholder = this.parentNode.querySelector('.drag-placeholder'); + if (oldPlaceholder === null) { + return; + } + + this.parentNode.insertBefore(self.dragging, oldPlaceholder); + this.parentNode.removeChild(oldPlaceholder); + + self.dragging = null; }, false); element.addEventListener('drop', function(e) { e.stopPropagation(); e.preventDefault(); - - if (self.dragging === this) { - return; - } - - self.dragging.innerHTML = this.innerHTML; - this.innerHTML = e.dataTransfer.getData('text/html'); - - /** - * @todo Orange-Management/jsOMS#??? [t:feature] [p:low] [d:medium] - * Remove from old destination if UI element and add to new destination - */ - - self.dragging = null; }, false); } }; \ No newline at end of file diff --git a/Views/TableView.js b/Views/TableView.js index 895f28d..8cf4f0e 100644 --- a/Views/TableView.js +++ b/Views/TableView.js @@ -134,6 +134,20 @@ export class TableView ); }; + /** + * Get table header + * + * @return {Array} + * + * @since 1.0.0 + */ + getHeader() + { + return document.querySelector( + '#' + this.id + ' thead' + ); + }; + /** * Get table header elements which provide filter functionality *