UNPKG

pragma-views2

Version:

146 lines (131 loc) 4.72 kB
import {createBehavioursFromString} from "./binding-helpers.js"; import {RepeatBehaviour} from "../behaviours/repeat-behaviour.js"; import {PropertyBehaviour} from '../behaviours/property-behaviour.js'; import {parseElement, parseElements} from "./dom-parsing.js"; import {releaseElement} from "./binding-helpers.js"; /** * This is the primary binding class to process an element and it's children and set up bindings */ export class ViewBinding { /** * @constructor * @param context */ constructor(context) { this.context = context; this._viewMutatedHandler = this._viewMutated.bind(this); } /** * @destructor */ dispose() { this.pauseDomObservations(); this.mutationObserver = null; this._viewMutatedHandler = null; delete this._view; delete this.context; } /** * Start observing mutations on the dom, watching for elements being added and removed. * This is required specially when elements are removed so that cleanup can be done */ observeDomChanged() { if (this.mutationObserver == undefined) { this.mutationObserver = new MutationObserver(this._viewMutatedHandler); } this.mutationObserver.observe(this._view, {childList: true, subtree: true}); } /** * For performance reasons you don't want to be notified when you know you are adding large batches of items. * Use this to pause mutation observations during inserts of large batches */ pauseDomObservations() { if (this.mutationObserver != undefined) { this.mutationObserver.disconnect(); } } /** * Parse a given element in order to set up bindings * @param element * @returns {Promise<void>} */ async parse(element, children) { this._view = element; if (children == undefined) { await parseElement(element, this, this.context); } else { await parseElements(children, this, this.context); } } /** * If an attribute has a .delegate attribute on a event, set up the event to the context. * click.delegate will for example create a event on the click handler. * @param event * @param attribute * @returns {Promise<void>} */ async delegate(event, attribute) { const expression = attribute.value; if (attribute.value.indexOf("(") == -1) { this.context.registerEvent(attribute.ownerElement, event, this.context[attribute.value].bind(this.context)); } else { const fn = window.compiler.add(expression, false); this.context.registerEvent(attribute.ownerElement, event, () => {fn(this.context)}); } } /** * Handle attributes that has the .bind attribute setting either propery or attribute values from the model item it is bound on. * @param property * @param attr * @returns {Promise<any>} */ async bind(property, attr) { return new Promise(async resolve => { PropertyBehaviour.create(attr, this.context, property); resolve(); }).catch(error => console.error(error)); } /** * Handle repeat.for instances to render collections * @param property * @param attr * @returns {Promise<any>} */ async repeat(property, attr) { return new Promise(async resolve => { RepeatBehaviour.create(attr, this.context); resolve(); }).catch(error => console.error(error)); } async behaviours(bindingString, attr) { if ((bindingString||"").length == 1) { return; } return createBehavioursFromString(bindingString, attr.ownerElement); } /** * When the element being monitored changes, this is the callback that will fire * @param mutationList: List of changes * @returns {Promise<void>} * @private */ async _viewMutated(mutationList) { mutationList.forEach(item => { if (item.addedNodes != undefined) { item.addedNodes.forEach(element => { if (element.nodeName != "#text") { this.parse(element); } }) } if (item.removedNodes != undefined) { item.removedNodes.forEach(element => { releaseElement(element); element = null; }); } }); } }