UNPKG

mframejs

Version:
301 lines (236 loc) 9.14 kB
import { IElement, IControllerArray, CONSTANTS, IBindingContext } from '../interface/exported'; import { View } from './view'; import { ViewController } from './viewController'; import { ContainerClasses } from '../container/exported'; import { BindingEngine, createBindingContext } from '../binding/exported'; import { Logger } from '../utils/exported'; import { DOM } from '../utils/exported'; /** * Element controller adds view and controls the lifecycle of element * */ export class ElementController { public classInstance: IElement; private templateString: string; private template: Node; private _process = true; private logger: Logger; private internalAttached = false; private elements: Node[]; private anchorNode: Node; private viewController: ViewController; private shadowDom: boolean; /** * Creates instance of ElementController * @param bindingContext - Binding context to use * @param htmlNode - html node * @param classInstance - class instance, optional.. * @param elementName - name of custom element * @param templateString - optional template string * @param parentViewController - parent viewController */ constructor( private bindingContext: IBindingContext, private htmlNode: Node, classInstance: IElement | null, elementName: string, templateString: string | null, parentViewController: ViewController) { this.logger = Logger.getLogger((htmlNode as any).tagName, 'element'); this.templateString = templateString; if (!classInstance) { this.logger.log('constructor'); } else { this.logger.log('constructor - using supplied class'); } this.classInstance = classInstance || ContainerClasses.get(elementName); // every element is a controller in it self, so create a new view this.viewController = new ViewController(htmlNode, parentViewController); this.viewController.addElement(this); if ((this.classInstance as any).__proto__[CONSTANTS.CONTAINER_FREE]) { this.anchorNode = DOM.document.createComment('containerless-anchor'); this.elements = []; } if ((this.classInstance as any).__proto__[CONSTANTS.SHADOW_DOM]) { this.shadowDom = true; } } /** * Get class instance from parent * @param _customElement custom element class you want to try and search for */ public searchForInstance<T>(_customElement: T): T | null { return this.viewController.searchForInstance(_customElement); } /** * get $view * */ public getView(): ViewController { return this.viewController; } /** * start element life cycle * */ public init() { this.classInstance.$element = this.htmlNode; this.classInstance.$bindingContext = this.bindingContext; this.classInstance.$attributes = (this.htmlNode as any).attributes; this.classInstance.$controller = this; if (this.shadowDom) { if ((this.classInstance as any).__proto__[CONSTANTS.CONTAINER_FREE]) { console.warn('containerfree can not be used with showdow dom'); } else { const mode = (this.classInstance as any).__proto__[CONSTANTS.SHADOW_DOM].mode; try { this.htmlNode = this.classInstance.$shadowdom; this.classInstance.$shadowdom = (this.htmlNode as any).attachShadow({ mode: mode }); } catch (e) { console.warn('error adding showdow dom:', e); } } } BindingEngine.subscribeClassMetaBinding(this.classInstance); const result = this.loadTemplate(); Promise.resolve(result).then((template: string) => { this.templateString = this.templateString || template || '<template></template>'; let arr: IControllerArray = []; this.template = View.createTemplate(this.templateString); this.create(); this.processContent(); if (this._process) { arr = View.parseTemplate(this.template, createBindingContext(this.classInstance), this.viewController); } // task is started, lets look children and append them, if we have process content if (this._process) { while (this.template.childNodes.length) { if (this.anchorNode) { this.htmlNode.parentNode.insertBefore(this.anchorNode, this.htmlNode.nextSibling); this.elements.push(this.template.firstChild); this.anchorNode.parentNode.insertBefore(this.template.firstChild, this.anchorNode.nextSibling); } else { this.htmlNode.appendChild(this.template.firstChild); } } if (this.anchorNode) { this.htmlNode.parentNode.removeChild(this.htmlNode); } } arr.reverse(); this.contentProcessed(arr); arr.forEach((a) => { if (a.attached) { a.attached(); } }); this.internalAttached = true; this.attached(); }); return this; } /** * calls loadtemplate if stemplate string isnt set * @internal * */ public loadTemplate(): string | Promise<string> { this.logger.log('loadTemplate'); if (this.templateString) { return this.templateString; } else { return this.classInstance.loadTemplate && this.classInstance.loadTemplate(); } } /** * sets vars and calls create on element * @internal * */ public create(): void { this.logger.log('create'); if (this.classInstance.created) { this.classInstance.created(); } } /** * calls elements processContent() if it exist * here someone can modify the template * @internal * */ public processContent(): void { this.logger.log('processContent'); if (this.classInstance.processContent) { this._process = this.classInstance.processContent(this.template); } else { while (this.htmlNode.childNodes[0]) { this.template.appendChild(this.htmlNode.childNodes[0]); } } } /** * calls elements contentProcessed() if it exist * is called after template is processed and added to element * @internal * */ public contentProcessed(controllers: IControllerArray): void { this.logger.log('contentProcessed'); if (this.classInstance.contentProcessed && this._process) { this.classInstance.contentProcessed(controllers); } } /** * calls elements attached() if it exist * when attached to parent * @public * */ public attached(): void { if (this.internalAttached) { if (!this.classInstance.$element.parentNode) { this.logger.log('attached', 'missing parent node'); } else { this.logger.log('attached'); } } if (this.classInstance.attached && this.internalAttached) { this.internalAttached = false; this.classInstance.attached(); } } /** * calls elements detached() if it exist and clears it * when about to be detached * @public * */ public detached(): void { this.logger.log('detached'); if (this.classInstance.detached) { this.classInstance.detached(); } BindingEngine.unSubscribeClassMetaBinding(this.classInstance); if (this.anchorNode) { this.elements.forEach((element: any) => { if (element.parentNode) { // if repeat or if.bind is here it might just be a dummy ref element.parentNode.removeChild(element); } }); if (this.anchorNode.parentNode) { this.anchorNode.parentNode.removeChild(this.anchorNode); } } this.anchorNode = null; this.elements = null; this.bindingContext = null; this.htmlNode = null; this.classInstance.$element = null; this.classInstance.$bindingContext = null; this.classInstance.$attributes = null; this.classInstance.$controller = null; this.classInstance = null; this.viewController = null; } }