UNPKG

@purtuga/dom-data-bind

Version:

DOM Data Bind utility. Bind data to DOM

157 lines (137 loc) 4.77 kB
import Compose from "@purtuga/common/src/jsutils/Compose.js" import {logError, PRIVATE, removeAttribute} from "../utils.js" import {NodeHandler} from "./NodeHandler.js"; //=================================================================================== // FIXME: replace use of Compose.js /** * A DOM element directive. * NOte that any directive that removes the original DOM element from its parent * will prevent all subsequent directives from running. * * @class Directive * @extends Compose * * @param {String} directiveAttr * The directive html element attribute as found in the element. * @param {String} attrValue * The value of the attribute */ export class Directive extends Compose { /** * Checks a given element has an element attribute that matches the Directive. * If a match is found, the html Element's attribute that was matched must be * returned. * * @param {HTMLElement} ele * * @returns {String} */ static has(/*ele*/) { return ""; } /** * A boolean indicating whether this directive manages the element. If set to true, then * `DomDataBind` will not process any other directives after this one. */ static manages() { return false; } /** * The Class that will be used to initialize a new node handler for the directive * * @type NodeHandler * @constructor */ static NodeHandlerConstructor = NodeHandler; /** * Render the Directive with given data * * @param {NodeHandler} handler * @param {Node} node * @param {Object} data */ render(handler, node, data) { let state = PRIVATE.get(handler); if (!state) { /** * Directive Node Handler state * * @typedef {Object} NodeHandlerState * * @property {*} data * @property {*} value * @property {Boolean} isQueued * @property {Function} tracker * @property {Function} update */ state = { data: null, value: "", isQueued: false, tracker: () => this.render(handler, node, state.data) //update: () => {} --- should be defined by Directive subclass }; PRIVATE.set(handler, state); } if (state.data !== data) { // With use of observables, the tracker function get a `stopWatchingAll()` method // which removes this tracker from all property dependency lists. if (state.tracker.stopWatchingAll) { state.tracker.stopWatchingAll(); } state.data = data; } this.update(handler); } /** * Updates a node by generating a new value for the Directive, storing it * in the handler `state.value` and calling `handle.update` after it. * * @param {NodeHandler} handler */ update(handler) { // destruction happens 1ms after .destroy() (Componse#destroy()) is called, // so its possible that a handler might come in here having its `.isDestroyed` // flag set. if (handler.isDestroyed) { return; } const handlerState = PRIVATE.get(handler); if (handlerState) { let newValue = ""; try { newValue = this._tokenValueGetter(handlerState.data || {}); // Update node if (handler.update) { handler.update(newValue); } } catch(e) { logError(e); } if (handlerState.value !== newValue) { handlerState.value = newValue; } } } /** * Cleans the node by removing Directive specific attribute. By default, the `_attr` * that was given to this constructor will be removed from the node. * * @param {Node} node */ cleanNode(node) { if (this._attr && node.nodeType !== 8 /* don't process comment nodes */) { removeAttribute(node, this._attr); } } /** * Returns a node handler for this Directive on the provided Node * * @param {Node} node * @param {Array<Directive>} [directives] * * @return {NodeHandler} */ getNodeHandler(node, directives) { this.cleanNode(node); return new this.constructor.NodeHandlerConstructor(this, node, directives); } } export default Directive;