UNPKG

ag-grid

Version:

Advanced Data Grid / Data Table supporting Javascript / React / AngularJS / Web Components

387 lines (313 loc) 13.5 kB
import {NumberSequence, Utils as _} from "../utils"; import {Context} from "../context/context"; import {BeanStub} from "../context/beanStub"; import {IComponent} from "../interfaces/iComponent"; import {AgEvent} from "../events"; let compIdSequence = new NumberSequence(); export interface VisibleChangedEvent extends AgEvent { visible: boolean; } interface AttrLists { normal: NameValue []; events: NameValue []; bindings: NameValue []; } interface NameValue { name: string; value: string; } export class Component extends BeanStub implements IComponent<any> { public static EVENT_VISIBLE_CHANGED = 'visibleChanged'; private eGui: HTMLElement; private childComponents: IComponent<any>[] = []; private annotatedEventListeners: any[] = []; private visible = true; // unique id for this row component. this is used for getting a reference to the HTML dom. // we cannot use the RowNode id as this is not unique (due to animation, old rows can be lying // around as we create a new rowComp instance for the same row node). private compId = compIdSequence.next(); constructor(template?: string) { super(); if (template) { this.setTemplate(template); } } public getCompId(): number { return this.compId; } public instantiate(context: Context): void { this.instantiateRecurse(this.getGui(), context); } private instantiateRecurse(parentNode: Element, context: Context): void { // we MUST take a copy of the list first, as the 'swapComponentForNode' adds comments into the DOM // which messes up the traversal order of the children. let childNodeList: Node[] = _.copyNodeList(parentNode.childNodes); childNodeList.forEach(childNode => { let childComp = context.createComponent(<Element>childNode, (childComp)=> { let attrList = this.getAttrLists(<Element>childNode); this.copyAttributesFromNode(attrList, childComp.getGui()); this.createChildAttributes(attrList, childComp); this.addEventListenersToComponent(attrList, childComp); }); if (childComp) { this.swapComponentForNode(childComp, parentNode, childNode); } else { if (childNode.childNodes) { this.instantiateRecurse(<Element>childNode, context); } if (childNode instanceof HTMLElement) { let attrList = this.getAttrLists(<Element>childNode); this.addEventListenersToElement(attrList, <HTMLElement>childNode); } } }); } private getAttrLists(child: Element): AttrLists { let res: AttrLists = { bindings: [], events: [], normal: [] }; _.iterateNamedNodeMap(child.attributes, (name: string, value: string) => { let firstCharacter = name.substr(0,1); if (firstCharacter==='(') { let eventName = name.replace('(', '').replace(')', ''); res.events.push({ name: eventName, value: value }); } else if (firstCharacter==='[') { let bindingName = name.replace('[', '').replace(']', ''); res.bindings.push({ name: bindingName, value: value }); } else { res.normal.push({ name: name, value: value }); } } ); return res; } private addEventListenersToElement(attrLists: AttrLists, element: HTMLElement): void { this.addEventListenerCommon(attrLists, (eventName: string, listener: (event?: any)=>void )=> { this.addDestroyableEventListener(element, eventName, listener); }); } private addEventListenersToComponent(attrLists: AttrLists, component: Component): void { this.addEventListenerCommon(attrLists, (eventName: string, listener: (event?: any)=>void )=> { this.addDestroyableEventListener(component, eventName, listener); }); } private addEventListenerCommon(attrLists: AttrLists, callback: (eventName: string, listener: (event?: any)=>void)=>void): void { let methodAliases = this.getAgComponentMetaData('methods'); attrLists.events.forEach( nameValue => { let methodName = nameValue.value; let methodAlias = _.find(methodAliases, 'alias', methodName); let methodNameToUse = _.exists(methodAlias) ? methodAlias.methodName : methodName; let listener = (<any>this)[methodNameToUse]; if (typeof listener !== 'function') { console.warn('ag-Grid: count not find callback ' + methodName); return; } let eventCamelCase = _.hyphenToCamelCase(nameValue.name); callback(eventCamelCase, listener.bind(this)); }); } private createChildAttributes(attrLists: AttrLists, child: any): void { let childAttributes: any = {}; attrLists.normal.forEach( nameValue => { let nameCamelCase = _.hyphenToCamelCase(nameValue.name); childAttributes[nameCamelCase] = nameValue.value; }); attrLists.bindings.forEach( nameValue => { let nameCamelCase = _.hyphenToCamelCase(nameValue.name); childAttributes[nameCamelCase] = (<any>this)[nameValue.value]; }); child.props = childAttributes; } private copyAttributesFromNode(attrLists: AttrLists, childNode: Element): void { attrLists.normal.forEach( nameValue => { childNode.setAttribute(nameValue.name, nameValue.value); }); } private swapComponentForNode(newComponent: Component, parentNode: Element, childNode: Node): void { let eComponent = newComponent.getGui(); parentNode.replaceChild(eComponent, childNode); parentNode.insertBefore(document.createComment(childNode.nodeName), eComponent); this.childComponents.push(newComponent); this.swapInComponentForQuerySelectors(newComponent, childNode); } private swapInComponentForQuerySelectors(newComponent: Component, childNode: Node): void { let thisProto: any = Object.getPrototypeOf(this); let thisNoType = <any> this; while (thisProto != null) { let metaData = thisProto.__agComponentMetaData; let currentProtoName = (thisProto.constructor).name; if (metaData && metaData[currentProtoName] && metaData[currentProtoName].querySelectors) { metaData[currentProtoName].querySelectors.forEach((querySelector: any) => { if (thisNoType[querySelector.attributeName] === childNode) { thisNoType[querySelector.attributeName] = newComponent; } }); } thisProto = Object.getPrototypeOf(thisProto); } } public setTemplate(template: string): void { let eGui = _.loadTemplate(<string>template); this.setTemplateFromElement(eGui); } public setTemplateFromElement(element: HTMLElement): void { this.eGui = element; (<any>this.eGui).__agComponent = this; this.addAnnotatedEventListeners(); this.wireQuerySelectors(); } protected wireQuerySelectors(): void { if (!this.eGui) { return; } let thisProto: any = Object.getPrototypeOf(this); while (thisProto != null) { let metaData = thisProto.__agComponentMetaData; let currentProtoName = (thisProto.constructor).name; if (metaData && metaData[currentProtoName] && metaData[currentProtoName].querySelectors) { let thisNoType = <any> this; metaData[currentProtoName].querySelectors.forEach((querySelector: any) => { let resultOfQuery = this.eGui.querySelector(querySelector.querySelector); if (resultOfQuery) { let backingComponent = (<any>resultOfQuery).__agComponent; if (backingComponent) { thisNoType[querySelector.attributeName] = backingComponent; } else { thisNoType[querySelector.attributeName] = resultOfQuery; } } else { // put debug msg in here if query selector fails??? } }); } thisProto = Object.getPrototypeOf(thisProto); } } private addAnnotatedEventListeners(): void { this.removeAnnotatedEventListeners(); if (!this.eGui) { return; } let listenerMethods = this.getAgComponentMetaData('listenerMethods'); if (_.missingOrEmpty(listenerMethods)) { return; } if (!this.annotatedEventListeners) { this.annotatedEventListeners = []; } listenerMethods.forEach((eventListener: any) => { let listener = (<any>this)[eventListener.methodName].bind(this); this.eGui.addEventListener(eventListener.eventName, listener); this.annotatedEventListeners.push({eventName: eventListener.eventName, listener: listener}); }); } private getAgComponentMetaData(key: string): any[] { let res: any[] = []; let thisProto: any = Object.getPrototypeOf(this); while (thisProto != null) { let metaData = thisProto.__agComponentMetaData; let currentProtoName = (thisProto.constructor).name; if (metaData && metaData[currentProtoName] && metaData[currentProtoName][key]) { res = res.concat(metaData[currentProtoName][key]); } thisProto = Object.getPrototypeOf(thisProto); } return res; } private removeAnnotatedEventListeners(): void { if (!this.annotatedEventListeners) { return; } if (!this.eGui) { return; } this.annotatedEventListeners.forEach((eventListener: any) => { this.eGui.removeEventListener(eventListener.eventName, eventListener.listener); }); this.annotatedEventListeners = null; } public getGui(): HTMLElement { return this.eGui; } // this method is for older code, that wants to provide the gui element, // it is not intended for this to be in ag-Stack protected setGui(eGui: HTMLElement): void { this.eGui = eGui; } protected queryForHtmlElement(cssSelector: string): HTMLElement { return <HTMLElement> this.eGui.querySelector(cssSelector); } protected queryForHtmlInputElement(cssSelector: string): HTMLInputElement { return <HTMLInputElement> this.eGui.querySelector(cssSelector); } public appendChild(newChild: Node | IComponent<any>): void { if (_.isNodeOrElement(newChild)) { this.eGui.appendChild(<Node>newChild); } else { let childComponent = <IComponent<any>>newChild; this.eGui.appendChild(childComponent.getGui()); this.childComponents.push(childComponent); } } public addFeature(context: Context, feature: BeanStub): void { context.wireBean(feature); if (feature.destroy) { this.addDestroyFunc(feature.destroy.bind(feature)); } } public isVisible(): boolean { return this.visible; } public setVisible(visible: boolean): void { if (visible !== this.visible) { this.visible = visible; _.addOrRemoveCssClass(this.eGui, 'ag-hidden', !visible); let event: VisibleChangedEvent = { type: Component.EVENT_VISIBLE_CHANGED, visible: this.visible }; this.dispatchEvent(event); } } public addOrRemoveCssClass(className: string, addOrRemove: boolean): void { _.addOrRemoveCssClass(this.eGui, className, addOrRemove); } public destroy(): void { super.destroy(); this.childComponents.forEach(childComponent => childComponent.destroy()); this.childComponents.length = 0; this.removeAnnotatedEventListeners(); } public addGuiEventListener(event: string, listener: (event: any) => void): void { this.getGui().addEventListener(event, listener); this.addDestroyFunc(() => this.getGui().removeEventListener(event, listener)); } public addCssClass(className: string): void { _.addCssClass(this.getGui(), className); } public removeCssClass(className: string): void { _.removeCssClass(this.getGui(), className); } public getAttribute(key: string): string { let eGui = this.getGui(); if (eGui) { return eGui.getAttribute(key); } else { return null; } } public getRefElement(refName: string): HTMLElement { return this.queryForHtmlElement('[ref="' + refName + '"]'); } }