mframejs
Version:
simple framework
301 lines (236 loc) • 9.14 kB
text/typescript
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;
}
}