improve ui form handling

This commit is contained in:
Dennis Eichhorn 2022-01-22 21:53:59 +01:00
parent c8164a9715
commit cb42c44618
15 changed files with 651 additions and 631 deletions

View File

@ -115,11 +115,7 @@ export class Logger
}
if (this.ui) {
/**
* @todo Orange-Management/jsOMS#67
* Implement UI logging
* Create a dom element with inline css for UI logging.
*/
this.writeUi(message, context, level);
}
if (this.remote) {
@ -127,6 +123,17 @@ export class Logger
}
};
writeUi (message, context, level)
{
if (Notification.permission !== 'granted' && Notification.permission !== 'denied') {
Notification.requestPermission().then(function(permission) { });
}
/** global: Notification */
const notification = new Notification('Logger', {body: this.interpolate(message, context, level)});
setTimeout(notification.close.bind(notification), 4000);
};
/**
* Create local log message
*

View File

@ -64,7 +64,7 @@ export class AppNotification
tpl.parentNode.appendChild(output);
const logs = document.getElementsByClassName('log-msg');
const logs = document.getElementsByClassName('log-msg');
const lastElementAdded = logs[logs.length - 1];
window.navigator.vibrate(msg.vibrate ? 200 : 0);

View File

@ -60,9 +60,9 @@ export class BrowserNotification
{
/** global: Notification */
if (Notification.permission === "granted") {
let notification = new Notification(msg.title, { body: msg.message, vibrate: [msg.vibrate ? 200 : 0] });
const notification = new Notification(msg.title, { body: msg.message, vibrate: [msg.vibrate ? 200 : 0] });
setTimeout(notification.close.bind(notification), 4000);
setTimeout(notification.close.bind(notification), 5000);
}
};
};

View File

@ -10,7 +10,7 @@ import { Logger } from '../Log/Logger.js';
*
* @todo Orange-Management/jsOMS#26
* Sync/Async events
* Events so fare can be created sync and async depending on the implementation.
* Events so far can be created sync and async depending on the implementation.
* It would be better to make it sync/async depending on a option flag.
*
* @todo Orange-Management/jsOMS#35

File diff suppressed because it is too large Load Diff

View File

@ -182,7 +182,7 @@ export class Table
console.log(exports.serialize());
/**
* @todo Orange-Management/jsOMS#90
* mplement export
* Implement export
* Either create download in javascript from this data or make round trip to server who then sends the data.
* The export should be possible (if available) in json, csv, excel, word, pdf, ...
* If no endpoint is specified or reachable the client side should create a json or csv export.
@ -219,7 +219,7 @@ export class Table
const menu = document.getElementById('table-context-menu');
const columns = header.querySelectorAll('td');
let length = columns.length;
let length = columns.length;
let baseMenuLine = menu.getElementsByClassName('context-line')[0].cloneNode(true);
@ -232,7 +232,7 @@ export class Table
}
const menuLine = baseMenuLine.cloneNode(true);
const lineId = menuLine.firstElementChild.getAttribute('get') + i;
const lineId = menuLine.firstElementChild.getAttribute('get') + i;
menuLine.firstElementChild.setAttribute('for', lineId);
menuLine.firstElementChild.firstElementChild.setAttribute('id', lineId);
@ -242,7 +242,7 @@ export class Table
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 rows = header.parentElement.getElementsByTagName('tr');
const rowLength = rows.length;
for (let j = 0; j < rowLength; ++j) {
@ -255,8 +255,8 @@ export class Table
menu.getElementsByTagName('ul')[0].removeChild(menu.getElementsByClassName('context-line')[0]);
const rect = tpl.parentElement.getBoundingClientRect();
menu.style.top = (event.clientY - rect.top) + "px";
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);
@ -265,7 +265,7 @@ export class Table
static hideMenuClickHandler(event)
{
const menu = document.getElementById('table-context-menu');
const menu = document.getElementById('table-context-menu');
const isClickedOutside = !menu.contains(event.target);
if (isClickedOutside) {
@ -495,7 +495,7 @@ export class Table
table.appendChild(newRow);
// todo: bind buttons if required (e.g. remove, edit button)
// @todo: bind buttons if required (e.g. remove, edit button)
}
};
};

View File

@ -38,16 +38,16 @@ export class DragNDrop
/**
* Bind element
*
* @param {Object} [id] DOM element
* @param {Object} [element] DOM element
*
* @return {void}
*
* @since 1.0.0
*/
bind (id)
bind (element)
{
if (typeof id !== 'undefined') {
this.bindElement(id);
if (typeof element !== 'undefined') {
this.bindElement(element);
} else {
const elements = document.querySelectorAll('[draggable]'),
length = !elements ? 0 : elements.length;
@ -84,7 +84,7 @@ export class DragNDrop
}, false);
element.addEventListener('dragenter', function(e) {
const rowIndex = Array.from(this.parentElement.children).indexOf(this);
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');

View File

@ -50,7 +50,7 @@ export class GeneralUI
/**
* Bind & rebind UI element.
*
* @param {Object} [e] Element id
* @param {Object} [e] Element
*
* @return {void}
*
@ -176,4 +176,121 @@ export class GeneralUI
new AdvancedInput(e[i], this.app.eventManager, this.app.uiManager.getDOMObserver());
}
};
static setValueOfElement(src, value)
{
if (src.hasAttribute('data-value')) {
src.setAttribute('data-value', value);
}
switch (src.tagName.toLowerCase()) {
case 'div':
case 'span':
case 'pre':
case 'article':
case 'section':
case 'h1':
if (src.hasAttribute('data-tpl-text')) {
break; // prevent overwriting setTextOfElement
}
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);
}
};
static setTextOfElement(src, value)
{
switch (src.tagName.toLowerCase()) {
case 'select':
break;
case 'div':
case 'td':
case 'span':
case 'pre':
case 'article':
case 'section':
src.innerHTML = value;
break;
case 'h1':
src.innerHTML = jsOMS.htmlspecialchars_encode(value);
break;
default:
if (src.value === '') {
src.value = jsOMS.htmlspecialchars_decode(value);
}
}
};
static 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.innerText.trim(' ');
default:
return src.value;
}
};
static getTextFromDataSource(src)
{
switch (src.tagName.toLowerCase()) {
case 'td':
case 'div':
case 'span':
case 'pre':
case 'article':
case 'section':
case 'h1':
return src.innerHTML.trim(' ');
case 'select':
return src.options[src.selectedIndex].text;
case 'input':
if (src.getAttribute('type') === 'checkbox' || src.getAttribute('type') === 'radio') {
return document.querySelector('label[for="' + src.id + '"]').innerText.trim(' ');
}
default:
return src.value;
}
};
};

View File

@ -106,7 +106,7 @@ export class ReadManager
};
/**
* @todo Orange-Management/jsOMS#66
* @todo SpeechRecognition polyfill
* Remove the speech recognition wrapper once it is obsolete and supported by the major browsers.
*/
/** global: webkitSpeechRecognition */

View File

@ -165,7 +165,7 @@ export class VoiceManager
};
/**
* @todo Orange-Management/jsOMS#66
* @todo SpeechRecognition polyfill
* Remove the speech recognition wrapper once it is obsolete and supported by the major browsers.
*/
/** global: webkitSpeechRecognition */

View File

@ -168,7 +168,7 @@ export class HttpUri
this.queryString = typeof parsed['query'] !== 'undefined' ? parsed['query'] : [];
if (this.queryString !== null) {
this.query =HttpUri.getAllUriQueryParameters(this.queryString);
this.query = HttpUri.getAllUriQueryParameters(this.queryString);
}
this.fragment = typeof parsed['fragment'] !== 'undefined' ? parsed['fragment'] : '';

View File

@ -126,7 +126,6 @@ export class UriFactory
*/
static unique (url)
{
// @todo: there is a bug for uris which have a parameter without a value and a fragment e.g. ?debug#something.
// The fragment is ignored in such a case.
const parsed = HttpUri.parseUrl(url);
const pars = [];
@ -139,6 +138,16 @@ export class UriFactory
spl = null,
length = parts.length;
// fix bug for queries such as https://127.0.0.1/test?something#frag where no value is specified for a query parameter
if ((typeof parsed.fragment === 'undefined' || parsed.fragment === null)
&& parts[length - 1].includes('#')
) {
const lastQuery = parts[length - 1].split('#')[1];;
parsed.fragment = lastQuery[1];
parts[length - 1] = lastQuery[0];
}
for (let i = 0; i < length; ++i) {
spl = parts[i].split('=');
comps[spl[0]] = spl.length < 2 ? '' : spl[1];
@ -188,7 +197,15 @@ export class UriFactory
static build (uri, toMatch)
{
const current = HttpUri.parseUrl(window.location.href);
let parsed = uri.replace(new RegExp('\{[\/#\?%@\.\$\!][a-zA-Z0-9\-\#\.]*\}', 'g'), function (match) {
const query = HttpUri.getAllUriQueryParameters(typeof current.query === 'undefined' ? {} : current.query);
for (const key in query) {
if (query.hasOwnProperty(key)) {
UriFactory.setQuery('?' + key, query[key]);
}
}
let parsed = uri.replace(new RegExp('\{[\/#\?%@\.\$\!][a-zA-Z0-9\-\#\.]*\}', 'g'), function (match) {
match = match.substr(1, match.length - 2);
if (typeof toMatch !== 'undefined' && toMatch.hasOwnProperty(match)) {
@ -272,15 +289,6 @@ export class UriFactory
UriFactory.setQuery('/scheme', uri.getScheme());
UriFactory.setQuery('/host', uri.getHost());
UriFactory.setQuery('/base', jsOMS.rtrim(uri.getBase(), '/'));
const query = uri.getQuery();
// @todo consider to move this to the build function like all the other components. The reason for this is that JS may change the query values on the fly on the frontend and therefore these values will not be the current values!
for (const key in query) {
if (query.hasOwnProperty(key)) {
UriFactory.setQuery('?' + key, query[key]);
}
}
};
};

View File

@ -47,12 +47,12 @@
jsOMS.triggerEvent = function (element, eventName)
{
if (document.createEvent) {
event = document.createEvent('HTMLEvents');
const event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, true);
event.eventName = eventName;
element.dispatchEvent(event);
} else {
event = document.createEventObject();
const event = document.createEventObject();
event.eventName = eventName;
event.eventType = eventName;
element.fireEvent(event.eventType, event);
@ -264,14 +264,4 @@
e[i].addEventListener(event, callback);
}
};
/**
* @todo Orange-Management/jsOMS#64
* Implement a function which returns the nearest dom element based on a selector.
* Nearest is defined as vertical and horizontal distance.
*/
jsOMS.nearest = function (e, selector)
{
};
}(window.jsOMS = window.jsOMS || {}));

View File

@ -27,12 +27,12 @@
jsOMS.triggerEvent = function (element, eventName)
{
if (document.createEvent) {
event = document.createEvent('HTMLEvents');
const event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, true);
event.eventName = eventName;
element.dispatchEvent(event);
} else {
event = document.createEventObject();
const event = document.createEventObject();
event.eventName = eventName;
event.eventType = eventName;
element.fireEvent(event.eventType, event);
@ -278,12 +278,12 @@
jsOMS.removeClass = function (ele, cls)
{
if (jsOMS.hasClass(ele, cls)) {
const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
const reg = new RegExp('(\\s|^)' + cls);
if (typeof ele.className === 'string') {
ele.className = ele.className.replace(reg, '');
ele.className = ele.className.replace(reg, '').trim();
} else if (typeof ele.className.baseVal === 'string') {
ele.className.baseVal = ele.className.baseVal.replace(reg, '');
ele.className.baseVal = ele.className.baseVal.replace(reg, '').trim();
}
}
};

View File

@ -113,14 +113,17 @@ export class FormView
*
* @since 1.0.0
*/
getSubmit ()
getSubmit (e = null)
{
return document.querySelectorAll(
const parent = e === null ? document : e;
return parent.querySelectorAll(
'#' + this.id + ' input[type=submit], '
+ 'button[form=' + this.id + '][type=submit], '
+ '#' + this.id + ' button[type=submit], '
+ '.submit[data-form=' + this.id + '], '
+ '#' + this.id + ' .submit'
+ (e !== null ? ' .submit' : '')
);
};
@ -144,12 +147,15 @@ export class FormView
*
* @since 1.0.0
*/
getUpdate ()
getUpdate (e = null)
{
return document.querySelectorAll(
'button[form=' + this.id + '].update, '
+ '.update[data-form=' + this.id + '], '
+ '#' + this.id + ' .update'
const parent = e === null ? document : e;
return parent.querySelectorAll(
'button[form=' + this.id + '].update-form, '
+ '.update-form[data-form=' + this.id + '], '
+ '#' + this.id + ' .update-form'
+ (e !== null ? ', .update-form' : '')
);
};
@ -160,12 +166,15 @@ export class FormView
*
* @since 1.0.0
*/
getSave ()
getSave (e = null)
{
return document.querySelectorAll(
'button[form=' + this.id + '].save, '
+ '.save[data-form=' + this.id + '], '
+ '#' + this.id + ' .save'
const parent = e === null ? document : e;
return parent.querySelectorAll(
'button[form=' + this.id + '].save-form, '
+ '.save-form[data-form=' + this.id + '], '
+ '#' + this.id + ' .save-form'
+ (e !== null ? ', .save-form' : '')
);
};
@ -176,12 +185,15 @@ export class FormView
*
* @since 1.0.0
*/
getCancel ()
getCancel (e = null)
{
return document.querySelectorAll(
'button[form=' + this.id + '].cancel, '
+ '.cancel[data-form=' + this.id + '], '
+ '#' + this.id + ' .cancel'
const parent = e === null ? document : e;
return parent.querySelectorAll(
'button[form=' + this.id + '].cancel-form, '
+ '.cancel-form[data-form=' + this.id + '], '
+ '#' + this.id + ' .cancel-form'
+ (e !== null ? ', .cancel-form' : '')
);
};
@ -192,12 +204,15 @@ export class FormView
*
* @since 1.0.0
*/
getRemove ()
getRemove (e = null)
{
return document.querySelectorAll(
'button[form=' + this.id + '].remove, '
+ '.remove[data-form=' + this.id + '], '
+ '#' + this.id + ' .remove'
const parent = e === null ? document : e;
return parent.querySelectorAll(
'button[form=' + this.id + '].remove-form, '
+ '.remove-form[data-form=' + this.id + '], '
+ '#' + this.id + ' .remove-form'
+ (e !== null ? ', .remove-form' : '')
);
};
@ -210,12 +225,15 @@ export class FormView
*
* @since 1.0.0
*/
getAdd ()
getAdd (e = null)
{
return document.querySelectorAll(
'button[form=' + this.id + '].add, '
+ '.add[data-form=' + this.id + '], '
+ '#' + this.id + ' .add'
const parent = e === null ? document : e;
return parent.querySelectorAll(
'button[form=' + this.id + '].add-form, '
+ '.add-form[data-form=' + this.id + '], '
+ '#' + this.id + ' .add-form'
+ (e !== null ? ', .add-form' : '')
);
};
@ -360,6 +378,19 @@ export class FormView
).filter(function(val) { return val; });
};
getFirstInputElement (e = null)
{
const parent = e === null ? document : e;
return parent.querySelector(
'#' + this.id + ' input, '
+ '#' + this.id + ' textarea, '
+ '#' + this.id + ' select, '
+ '[form=' + this.id + '], '
+ '[data-form=' + this.id + ']'
);
};
/**
* Get unique form elements
*
@ -463,6 +494,30 @@ export class FormView
return formData;
};
resetValues ()
{
const elements = this.getFormElements(),
length = elements.length;
const form = document.getElementById(this.id);
form.reset();
for (let i = 0; i < length; ++i) {
const id = FormView.getElementId(elements[i]);
if (id === null) {
continue;
}
if (elements[i].tagName.toLowerCase() === 'canvas') {
elements[i].clearRect(0, 0, elements[i].width, elements[i].height);
}
if (elements[i].getAttribute('data-value') !== null) {
elements[i].setAttribute('data-value', '');
}
}
}
/**
* Get form id
*
@ -491,8 +546,12 @@ export class FormView
for (let i = 0; i < length; ++i) {
if ((elements[i].required && elements[i].value === '')
|| (typeof elements[i].pattern !== 'undefined'
&& elements[i].pattern !== ''
&& !(new RegExp(elements[i].pattern)).test(elements[i].value))
&& elements[i].pattern !== ''
&& !(new RegExp(elements[i].pattern)).test(elements[i].value))
|| (typeof elements[i].maxlength !== 'undefined' && elements[i].maxlength !== '' && elements[i].value.length > elements[i].maxlength)
|| (typeof elements[i].minlength !== 'undefined' && elements[i].minlength !== '' && elements[i].value.length < elements[i].minlength)
|| (typeof elements[i].max !== 'undefined' && elements[i].max !== '' && elements[i].value > elements[i].max)
|| (typeof elements[i].min !== 'undefined' && elements[i].min !== '' && elements[i].value < elements[i].min)
) {
return false;
}
@ -504,6 +563,34 @@ export class FormView
return true;
};
getInvalid (data)
{
const elements = typeof data === 'undefined' ? this.getFormElements() : data;
const length = elements.length;
const invalid = [];
try {
for (let i = 0; i < length; ++i) {
if ((elements[i].required && elements[i].value === '')
|| (typeof elements[i].pattern !== 'undefined'
&& elements[i].pattern !== ''
&& !(new RegExp(elements[i].pattern)).test(elements[i].value))
|| (typeof elements[i].maxlength !== 'undefined' && elements[i].maxlength !== '' && elements[i].value.length > elements[i].maxlength)
|| (typeof elements[i].minlength !== 'undefined' && elements[i].minlength !== '' && elements[i].value.length < elements[i].minlength)
|| (typeof elements[i].max !== 'undefined' && elements[i].max !== '' && elements[i].value > elements[i].max)
|| (typeof elements[i].min !== 'undefined' && elements[i].min !== '' && elements[i].value < elements[i].min)
) {
invalid.push(elements[i]);
}
}
} catch (e) {
jsOMS.Log.Logger.instance.error(e);
}
return invalid;
}
/**
* Get form element
*