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) { if (this.ui) {
/** this.writeUi(message, context, level);
* @todo Orange-Management/jsOMS#67
* Implement UI logging
* Create a dom element with inline css for UI logging.
*/
} }
if (this.remote) { 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 * Create local log message
* *

View File

@ -60,9 +60,9 @@ export class BrowserNotification
{ {
/** global: Notification */ /** global: Notification */
if (Notification.permission === "granted") { 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 * @todo Orange-Management/jsOMS#26
* Sync/Async events * 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. * It would be better to make it sync/async depending on a option flag.
* *
* @todo Orange-Management/jsOMS#35 * @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()); console.log(exports.serialize());
/** /**
* @todo Orange-Management/jsOMS#90 * @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. * 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, ... * 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. * If no endpoint is specified or reachable the client side should create a json or csv export.
@ -495,7 +495,7 @@ export class Table
table.appendChild(newRow); 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 * Bind element
* *
* @param {Object} [id] DOM element * @param {Object} [element] DOM element
* *
* @return {void} * @return {void}
* *
* @since 1.0.0 * @since 1.0.0
*/ */
bind (id) bind (element)
{ {
if (typeof id !== 'undefined') { if (typeof element !== 'undefined') {
this.bindElement(id); this.bindElement(element);
} else { } else {
const elements = document.querySelectorAll('[draggable]'), const elements = document.querySelectorAll('[draggable]'),
length = !elements ? 0 : elements.length; length = !elements ? 0 : elements.length;

View File

@ -50,7 +50,7 @@ export class GeneralUI
/** /**
* Bind & rebind UI element. * Bind & rebind UI element.
* *
* @param {Object} [e] Element id * @param {Object} [e] Element
* *
* @return {void} * @return {void}
* *
@ -176,4 +176,121 @@ export class GeneralUI
new AdvancedInput(e[i], this.app.eventManager, this.app.uiManager.getDOMObserver()); 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. * Remove the speech recognition wrapper once it is obsolete and supported by the major browsers.
*/ */
/** global: webkitSpeechRecognition */ /** 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. * Remove the speech recognition wrapper once it is obsolete and supported by the major browsers.
*/ */
/** global: webkitSpeechRecognition */ /** global: webkitSpeechRecognition */

View File

@ -126,7 +126,6 @@ export class UriFactory
*/ */
static unique (url) 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. // The fragment is ignored in such a case.
const parsed = HttpUri.parseUrl(url); const parsed = HttpUri.parseUrl(url);
const pars = []; const pars = [];
@ -139,6 +138,16 @@ export class UriFactory
spl = null, spl = null,
length = parts.length; 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) { for (let i = 0; i < length; ++i) {
spl = parts[i].split('='); spl = parts[i].split('=');
comps[spl[0]] = spl.length < 2 ? '' : spl[1]; comps[spl[0]] = spl.length < 2 ? '' : spl[1];
@ -188,6 +197,14 @@ export class UriFactory
static build (uri, toMatch) static build (uri, toMatch)
{ {
const current = HttpUri.parseUrl(window.location.href); const current = HttpUri.parseUrl(window.location.href);
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) { let parsed = uri.replace(new RegExp('\{[\/#\?%@\.\$\!][a-zA-Z0-9\-\#\.]*\}', 'g'), function (match) {
match = match.substr(1, match.length - 2); match = match.substr(1, match.length - 2);
@ -272,15 +289,6 @@ export class UriFactory
UriFactory.setQuery('/scheme', uri.getScheme()); UriFactory.setQuery('/scheme', uri.getScheme());
UriFactory.setQuery('/host', uri.getHost()); UriFactory.setQuery('/host', uri.getHost());
UriFactory.setQuery('/base', jsOMS.rtrim(uri.getBase(), '/')); 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) jsOMS.triggerEvent = function (element, eventName)
{ {
if (document.createEvent) { if (document.createEvent) {
event = document.createEvent('HTMLEvents'); const event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, true); event.initEvent(eventName, true, true);
event.eventName = eventName; event.eventName = eventName;
element.dispatchEvent(event); element.dispatchEvent(event);
} else { } else {
event = document.createEventObject(); const event = document.createEventObject();
event.eventName = eventName; event.eventName = eventName;
event.eventType = eventName; event.eventType = eventName;
element.fireEvent(event.eventType, event); element.fireEvent(event.eventType, event);
@ -264,14 +264,4 @@
e[i].addEventListener(event, callback); 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 || {})); }(window.jsOMS = window.jsOMS || {}));

View File

@ -27,12 +27,12 @@
jsOMS.triggerEvent = function (element, eventName) jsOMS.triggerEvent = function (element, eventName)
{ {
if (document.createEvent) { if (document.createEvent) {
event = document.createEvent('HTMLEvents'); const event = document.createEvent('HTMLEvents');
event.initEvent(eventName, true, true); event.initEvent(eventName, true, true);
event.eventName = eventName; event.eventName = eventName;
element.dispatchEvent(event); element.dispatchEvent(event);
} else { } else {
event = document.createEventObject(); const event = document.createEventObject();
event.eventName = eventName; event.eventName = eventName;
event.eventType = eventName; event.eventType = eventName;
element.fireEvent(event.eventType, event); element.fireEvent(event.eventType, event);
@ -278,12 +278,12 @@
jsOMS.removeClass = function (ele, cls) jsOMS.removeClass = function (ele, cls)
{ {
if (jsOMS.hasClass(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') { 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') { } 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 * @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], ' '#' + this.id + ' input[type=submit], '
+ 'button[form=' + this.id + '][type=submit], ' + 'button[form=' + this.id + '][type=submit], '
+ '#' + this.id + ' button[type=submit], ' + '#' + this.id + ' button[type=submit], '
+ '.submit[data-form=' + this.id + '], ' + '.submit[data-form=' + this.id + '], '
+ '#' + this.id + ' .submit' + '#' + this.id + ' .submit'
+ (e !== null ? ' .submit' : '')
); );
}; };
@ -144,12 +147,15 @@ export class FormView
* *
* @since 1.0.0 * @since 1.0.0
*/ */
getUpdate () getUpdate (e = null)
{ {
return document.querySelectorAll( const parent = e === null ? document : e;
'button[form=' + this.id + '].update, '
+ '.update[data-form=' + this.id + '], ' return parent.querySelectorAll(
+ '#' + this.id + ' .update' '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 * @since 1.0.0
*/ */
getSave () getSave (e = null)
{ {
return document.querySelectorAll( const parent = e === null ? document : e;
'button[form=' + this.id + '].save, '
+ '.save[data-form=' + this.id + '], ' return parent.querySelectorAll(
+ '#' + this.id + ' .save' '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 * @since 1.0.0
*/ */
getCancel () getCancel (e = null)
{ {
return document.querySelectorAll( const parent = e === null ? document : e;
'button[form=' + this.id + '].cancel, '
+ '.cancel[data-form=' + this.id + '], ' return parent.querySelectorAll(
+ '#' + this.id + ' .cancel' '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 * @since 1.0.0
*/ */
getRemove () getRemove (e = null)
{ {
return document.querySelectorAll( const parent = e === null ? document : e;
'button[form=' + this.id + '].remove, '
+ '.remove[data-form=' + this.id + '], ' return parent.querySelectorAll(
+ '#' + this.id + ' .remove' '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 * @since 1.0.0
*/ */
getAdd () getAdd (e = null)
{ {
return document.querySelectorAll( const parent = e === null ? document : e;
'button[form=' + this.id + '].add, '
+ '.add[data-form=' + this.id + '], ' return parent.querySelectorAll(
+ '#' + this.id + ' .add' '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; }); ).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 * Get unique form elements
* *
@ -463,6 +494,30 @@ export class FormView
return formData; 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 * Get form id
* *
@ -493,6 +548,10 @@ export class FormView
|| (typeof elements[i].pattern !== 'undefined' || (typeof elements[i].pattern !== 'undefined'
&& elements[i].pattern !== '' && elements[i].pattern !== ''
&& !(new RegExp(elements[i].pattern)).test(elements[i].value)) && !(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; return false;
} }
@ -504,6 +563,34 @@ export class FormView
return true; 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 * Get form element
* *