pragma-views2
Version:
146 lines (131 loc) • 4.72 kB
JavaScript
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;
});
}
});
}
}