// dtp-display-engine.js // Copyright (C) 2022,2023 DTP Technologies, LLC // All Rights Reserved 'use strict'; const DTP_COMPONENT_NAME = 'DtpDisplayEngine'; import DtpLog from './dtp-log.js'; const dtp = window.dtp = window.dtp || { }; export default class DtpDisplayEngine { constructor ( ) { this.processors = { }; this.log = new DtpLog(DTP_COMPONENT_NAME); } /** * Register a named Display List processor plugin. This lets client objects * define their own Display List processor classes and resiter them. Server * code can then execute actions on named processors in the client. * @param {*} name The name of the DL processor being registered. * @param {*} processor The custom DL processor being registered. */ registerProcessor (name, processor) { this.processors[name] = processor; } /** * Unregisters a named Display List processing plugin. * @param {String} name The name of the DL processor to be unregistered. */ unregisterProcessor (name) { if (!this.processors[name]) { return; } delete this.processors[name]; } /** * Executes a Display List to implement view changes requested by the server. * These can arrive from an HTTP request, via socket.io, or generated in code * and passed in. * @param {DtpDisplayList} displayList */ async executeDisplayList (displayList) { try { displayList.commands.forEach((command) => { const processor = command.processor ? this.processors[command.processor] : this; this.log.debug(command.action, 'action', command); try { processor[command.action](displayList, command); } catch (error) { this.log.error('executeDisplayList', 'failed to execute DisplayEngine command', { error }); } }); } catch (error) { this.log.error('executeDisplayList', 'failed to apply DisplayEngine updates', { error }); UIkit.modal.alert(`Failed to apply updates: ${error.message}`); } } /* * action: addElement * selector: Specifies the container element to insertAdjacentHTML * where: 'beforebegin', 'afterbegin', 'beforeend', 'afterend' * html: the HTML content to insert at the container as specified */ async addElement (displayList, command) { const container = document.querySelector(command.selector); if (!container) { this.log.debug('addElement', 'displayList.addElement has failed', { command }); return; } container.insertAdjacentHTML(command.params.where, command.params.html); } async setTextContent (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || (elements.length === 0)) { this.log.debug('setTextContent', 'failed to find target elements', { command }); return; } elements.forEach((element) => { element.textContent = command.params.text; }); } async setInputValue (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || (elements.length === 0)) { this.log.debug('setInputValue', 'failed to find target elements', { command }); return; } elements.forEach((element) => { element.value = command.params.value; }); } /* * action: replaceElement * selector: Specifies the element to be replaced * html: replaces the whole specified element */ async replaceElement (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || (elements.length === 0)) { this.log.debug('replaceElement', 'displayList.replaceElement has failed to find requested element', { command }); return; } for (const element of elements) { element.outerHTML = command.params.html; } } /* * action: removeElement * selector: Specifies the element(s) to be removed */ async removeElement (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { this.log.debug('removeElement', 'displayList.removeElement has failed', { command }); return; } elements.forEach((element) => { element.parentElement.removeChild(element); }); } /** * Removes all child elements from the selected element(s). * @param {DisplayList} displayList The display list being executed. * @param {Command} command The command being executed. */ async removeChildren (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { this.log.debug('removeChildren', 'displayList.removeChildren has failed', { command }); return; } for (const e of elements) { while (e.firstChild) { e.removeChild(e.firstChild); } } } /* * action: setAttribute * selector: Specifies the element(s) for which an attribute's value should be set * name: the name of the attribute to be set * value: the value to be set on the named attribute */ async setAttribute (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.setAttribute has failed', { command }); return; } elements.forEach((element) => { element.setAttribute(command.params.name, command.params.value); }); } /* * action: removeAttribute * selector: specifies the element(s) from which an attribute is to be removed * name: the name of the attribute to be removed */ async removeAttribute (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.removeAttribute has failed', { command }); return; } elements.forEach((element) => { element.removeAttribute(command.params.name); }); } /* * action: toggleAttribute * selector: specifies the element(s) from which an attribute is to be removed * name: the name of the attribute to be added or removed * force: true to force the addition of the attribute, false to force the * removal of the attribute, leave undefined to simply toggle. */ async toggleAttribute (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.toggleAttribute has failed', { command }); return; } elements.forEach((element) => { element.toggleAttribute(command.params.name, command.params.force); }); } /* * action: addClass * selector: Specifies the element(s) for which style class(es) should be added * name: the class name to add to the classList */ async addClass (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.addClass has failed', { command }); return; } elements.forEach((element) => { element.classList.add(command.params.add); }); } /* * action: removeClass * selector: Specifies the element(s) for which style class(es) should be removed * name: the class name to remove from the classList */ async removeClass (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.removeClass has failed', { command }); return; } elements.forEach((element) => { element.classList.remove(command.params.remove); }); } /* * action: replaceClass * selector: Specifies the elements for which class replacement should occur * remove: the class name to remove from the classList * add: the class name to add to the classList */ async replaceClass (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.replaceClass has failed', { command }); return; } elements.forEach((element) => { element.classList.remove(command.params.remove); element.classList.add(command.params.add); }); } async showNotification (displayList, command) { UIkit.notification(command.params); } async showModal (displayList, command) { UIkit.modal.dialog(command.params.html); } async playNotificationSound (displayList, command) { const { action } = command.params; if (!dtp.app || !dtp.app.audio) { return; } dtp.app.playNotificationSound(action); } async playSound (displayList, command) { const { soundId } = command.params; if (!dtp.app || !dtp.app.audio && !dtp.app.audio.hasSound(soundId)) { return; } dtp.app.audio.playSound(soundId); } async setValue (displayList, command) { const elements = document.querySelectorAll(command.selector); if (!elements || !elements.length) { console.debug('displayList.setValue (no elements found)', { command }); return; } elements.forEach((element) => { element.value = command.params.value; }); } async navigateTo (displayList, command) { window.location = command.params.href; } async navigateBack ( ) { if (window.history.length === 0) { window.location = '/'; return; } window.history.back(); } async reloadView ( ) { window.location.reload(); } }