1 | import {NumberSequence, Utils as _} from "../utils";
|
2 | import {Context} from "../context/context";
|
3 | import {BeanStub} from "../context/beanStub";
|
4 | import {IComponent} from "../interfaces/iComponent";
|
5 | import {AgEvent} from "../events";
|
6 |
|
7 | let compIdSequence = new NumberSequence();
|
8 |
|
9 | export interface VisibleChangedEvent extends AgEvent {
|
10 | visible: boolean;
|
11 | }
|
12 |
|
13 | interface AttrLists {
|
14 | normal: NameValue [];
|
15 | events: NameValue [];
|
16 | bindings: NameValue [];
|
17 | }
|
18 |
|
19 | interface NameValue {
|
20 | name: string;
|
21 | value: string;
|
22 | }
|
23 |
|
24 | export class Component extends BeanStub implements IComponent<any> {
|
25 |
|
26 | public static EVENT_VISIBLE_CHANGED = 'visibleChanged';
|
27 |
|
28 | private eGui: HTMLElement;
|
29 |
|
30 | private childComponents: IComponent<any>[] = [];
|
31 |
|
32 | private annotatedEventListeners: any[] = [];
|
33 |
|
34 | private visible = true;
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | private compId = compIdSequence.next();
|
40 |
|
41 | constructor(template?: string) {
|
42 | super();
|
43 | if (template) {
|
44 | this.setTemplate(template);
|
45 | }
|
46 | }
|
47 |
|
48 | public getCompId(): number {
|
49 | return this.compId;
|
50 | }
|
51 |
|
52 | public instantiate(context: Context): void {
|
53 | this.instantiateRecurse(this.getGui(), context);
|
54 | }
|
55 |
|
56 | private instantiateRecurse(parentNode: Element, context: Context): void {
|
57 |
|
58 |
|
59 |
|
60 | let childNodeList: Node[] = _.copyNodeList(parentNode.childNodes);
|
61 |
|
62 | childNodeList.forEach(childNode => {
|
63 | let childComp = context.createComponent(<Element>childNode, (childComp)=> {
|
64 | let attrList = this.getAttrLists(<Element>childNode);
|
65 | this.copyAttributesFromNode(attrList, childComp.getGui());
|
66 | this.createChildAttributes(attrList, childComp);
|
67 | this.addEventListenersToComponent(attrList, childComp);
|
68 | });
|
69 | if (childComp) {
|
70 | this.swapComponentForNode(childComp, parentNode, childNode);
|
71 | } else {
|
72 | if (childNode.childNodes) {
|
73 | this.instantiateRecurse(<Element>childNode, context);
|
74 | }
|
75 | if (childNode instanceof HTMLElement) {
|
76 | let attrList = this.getAttrLists(<Element>childNode);
|
77 | this.addEventListenersToElement(attrList, <HTMLElement>childNode);
|
78 | }
|
79 | }
|
80 | });
|
81 | }
|
82 |
|
83 | private getAttrLists(child: Element): AttrLists {
|
84 | let res: AttrLists = {
|
85 | bindings: [],
|
86 | events: [],
|
87 | normal: []
|
88 | };
|
89 | _.iterateNamedNodeMap(child.attributes,
|
90 | (name: string, value: string) => {
|
91 | let firstCharacter = name.substr(0,1);
|
92 | if (firstCharacter==='(') {
|
93 | let eventName = name.replace('(', '').replace(')', '');
|
94 | res.events.push({
|
95 | name: eventName,
|
96 | value: value
|
97 | });
|
98 | } else if (firstCharacter==='[') {
|
99 | let bindingName = name.replace('[', '').replace(']', '');
|
100 | res.bindings.push({
|
101 | name: bindingName,
|
102 | value: value
|
103 | });
|
104 | } else {
|
105 | res.normal.push({
|
106 | name: name,
|
107 | value: value
|
108 | });
|
109 | }
|
110 | }
|
111 | );
|
112 | return res;
|
113 | }
|
114 |
|
115 | private addEventListenersToElement(attrLists: AttrLists, element: HTMLElement): void {
|
116 | this.addEventListenerCommon(attrLists, (eventName: string, listener: (event?: any)=>void )=> {
|
117 | this.addDestroyableEventListener(element, eventName, listener);
|
118 | });
|
119 | }
|
120 |
|
121 | private addEventListenersToComponent(attrLists: AttrLists, component: Component): void {
|
122 | this.addEventListenerCommon(attrLists, (eventName: string, listener: (event?: any)=>void )=> {
|
123 | this.addDestroyableEventListener(component, eventName, listener);
|
124 | });
|
125 | }
|
126 |
|
127 | private addEventListenerCommon(attrLists: AttrLists,
|
128 | callback: (eventName: string, listener: (event?: any)=>void)=>void): void {
|
129 | let methodAliases = this.getAgComponentMetaData('methods');
|
130 |
|
131 | attrLists.events.forEach( nameValue => {
|
132 | let methodName = nameValue.value;
|
133 | let methodAlias = _.find(methodAliases, 'alias', methodName);
|
134 |
|
135 | let methodNameToUse = _.exists(methodAlias) ? methodAlias.methodName : methodName;
|
136 |
|
137 | let listener = (<any>this)[methodNameToUse];
|
138 | if (typeof listener !== 'function') {
|
139 | console.warn('ag-Grid: count not find callback ' + methodName);
|
140 | return;
|
141 | }
|
142 |
|
143 | let eventCamelCase = _.hyphenToCamelCase(nameValue.name);
|
144 |
|
145 | callback(eventCamelCase, listener.bind(this));
|
146 | });
|
147 | }
|
148 |
|
149 | private createChildAttributes(attrLists: AttrLists, child: any): void {
|
150 |
|
151 | let childAttributes: any = {};
|
152 |
|
153 | attrLists.normal.forEach( nameValue => {
|
154 | let nameCamelCase = _.hyphenToCamelCase(nameValue.name);
|
155 | childAttributes[nameCamelCase] = nameValue.value;
|
156 | });
|
157 |
|
158 | attrLists.bindings.forEach( nameValue => {
|
159 | let nameCamelCase = _.hyphenToCamelCase(nameValue.name);
|
160 | childAttributes[nameCamelCase] = (<any>this)[nameValue.value];
|
161 | });
|
162 |
|
163 | child.props = childAttributes;
|
164 | }
|
165 |
|
166 | private copyAttributesFromNode(attrLists: AttrLists, childNode: Element): void {
|
167 | attrLists.normal.forEach( nameValue => {
|
168 | childNode.setAttribute(nameValue.name, nameValue.value);
|
169 | });
|
170 | }
|
171 |
|
172 | private swapComponentForNode(newComponent: Component, parentNode: Element, childNode: Node): void {
|
173 | let eComponent = newComponent.getGui();
|
174 | parentNode.replaceChild(eComponent, childNode);
|
175 | parentNode.insertBefore(document.createComment(childNode.nodeName), eComponent);
|
176 | this.childComponents.push(newComponent);
|
177 | this.swapInComponentForQuerySelectors(newComponent, childNode);
|
178 | }
|
179 |
|
180 | private swapInComponentForQuerySelectors(newComponent: Component, childNode: Node): void {
|
181 |
|
182 | let thisProto: any = Object.getPrototypeOf(this);
|
183 |
|
184 | let thisNoType = <any> this;
|
185 | while (thisProto != null) {
|
186 | let metaData = thisProto.__agComponentMetaData;
|
187 | let currentProtoName = (thisProto.constructor).name;
|
188 |
|
189 | if (metaData && metaData[currentProtoName] && metaData[currentProtoName].querySelectors) {
|
190 | metaData[currentProtoName].querySelectors.forEach((querySelector: any) => {
|
191 | if (thisNoType[querySelector.attributeName] === childNode) {
|
192 | thisNoType[querySelector.attributeName] = newComponent;
|
193 | }
|
194 | });
|
195 | }
|
196 |
|
197 | thisProto = Object.getPrototypeOf(thisProto);
|
198 | }
|
199 | }
|
200 |
|
201 | public setTemplate(template: string): void {
|
202 | let eGui = _.loadTemplate(<string>template);
|
203 | this.setTemplateFromElement(eGui);
|
204 | }
|
205 |
|
206 | public setTemplateFromElement(element: HTMLElement): void {
|
207 | this.eGui = element;
|
208 | (<any>this.eGui).__agComponent = this;
|
209 | this.addAnnotatedEventListeners();
|
210 | this.wireQuerySelectors();
|
211 | }
|
212 |
|
213 | protected wireQuerySelectors(): void {
|
214 | if (!this.eGui) {
|
215 | return;
|
216 | }
|
217 |
|
218 | let thisProto: any = Object.getPrototypeOf(this);
|
219 |
|
220 | while (thisProto != null) {
|
221 | let metaData = thisProto.__agComponentMetaData;
|
222 | let currentProtoName = (thisProto.constructor).name;
|
223 |
|
224 | if (metaData && metaData[currentProtoName] && metaData[currentProtoName].querySelectors) {
|
225 | let thisNoType = <any> this;
|
226 | metaData[currentProtoName].querySelectors.forEach((querySelector: any) => {
|
227 | let resultOfQuery = this.eGui.querySelector(querySelector.querySelector);
|
228 | if (resultOfQuery) {
|
229 | let backingComponent = (<any>resultOfQuery).__agComponent;
|
230 | if (backingComponent) {
|
231 | thisNoType[querySelector.attributeName] = backingComponent;
|
232 | } else {
|
233 | thisNoType[querySelector.attributeName] = resultOfQuery;
|
234 | }
|
235 | } else {
|
236 |
|
237 | }
|
238 | });
|
239 | }
|
240 |
|
241 | thisProto = Object.getPrototypeOf(thisProto);
|
242 | }
|
243 | }
|
244 |
|
245 | private addAnnotatedEventListeners(): void {
|
246 | this.removeAnnotatedEventListeners();
|
247 | if (!this.eGui) {
|
248 | return;
|
249 | }
|
250 |
|
251 | let listenerMethods = this.getAgComponentMetaData('listenerMethods');
|
252 |
|
253 | if (_.missingOrEmpty(listenerMethods)) { return; }
|
254 |
|
255 | if (!this.annotatedEventListeners) {
|
256 | this.annotatedEventListeners = [];
|
257 | }
|
258 |
|
259 | listenerMethods.forEach((eventListener: any) => {
|
260 | let listener = (<any>this)[eventListener.methodName].bind(this);
|
261 | this.eGui.addEventListener(eventListener.eventName, listener);
|
262 | this.annotatedEventListeners.push({eventName: eventListener.eventName, listener: listener});
|
263 | });
|
264 | }
|
265 |
|
266 | private getAgComponentMetaData(key: string): any[] {
|
267 | let res: any[] = [];
|
268 |
|
269 | let thisProto: any = Object.getPrototypeOf(this);
|
270 |
|
271 | while (thisProto != null) {
|
272 | let metaData = thisProto.__agComponentMetaData;
|
273 | let currentProtoName = (thisProto.constructor).name;
|
274 |
|
275 | if (metaData && metaData[currentProtoName] && metaData[currentProtoName][key]) {
|
276 | res = res.concat(metaData[currentProtoName][key]);
|
277 | }
|
278 |
|
279 | thisProto = Object.getPrototypeOf(thisProto);
|
280 | }
|
281 |
|
282 | return res;
|
283 | }
|
284 |
|
285 | private removeAnnotatedEventListeners(): void {
|
286 | if (!this.annotatedEventListeners) {
|
287 | return;
|
288 | }
|
289 | if (!this.eGui) {
|
290 | return;
|
291 | }
|
292 | this.annotatedEventListeners.forEach((eventListener: any) => {
|
293 | this.eGui.removeEventListener(eventListener.eventName, eventListener.listener);
|
294 | });
|
295 | this.annotatedEventListeners = null;
|
296 | }
|
297 |
|
298 | public getGui(): HTMLElement {
|
299 | return this.eGui;
|
300 | }
|
301 |
|
302 |
|
303 |
|
304 | protected setGui(eGui: HTMLElement): void {
|
305 | this.eGui = eGui;
|
306 | }
|
307 |
|
308 | protected queryForHtmlElement(cssSelector: string): HTMLElement {
|
309 | return <HTMLElement> this.eGui.querySelector(cssSelector);
|
310 | }
|
311 |
|
312 | protected queryForHtmlInputElement(cssSelector: string): HTMLInputElement {
|
313 | return <HTMLInputElement> this.eGui.querySelector(cssSelector);
|
314 | }
|
315 |
|
316 | public appendChild(newChild: Node | IComponent<any>): void {
|
317 | if (_.isNodeOrElement(newChild)) {
|
318 | this.eGui.appendChild(<Node>newChild);
|
319 | } else {
|
320 | let childComponent = <IComponent<any>>newChild;
|
321 | this.eGui.appendChild(childComponent.getGui());
|
322 | this.childComponents.push(childComponent);
|
323 | }
|
324 | }
|
325 |
|
326 | public addFeature(context: Context, feature: BeanStub): void {
|
327 | context.wireBean(feature);
|
328 | if (feature.destroy) {
|
329 | this.addDestroyFunc(feature.destroy.bind(feature));
|
330 | }
|
331 | }
|
332 |
|
333 | public isVisible(): boolean {
|
334 | return this.visible;
|
335 | }
|
336 |
|
337 | public setVisible(visible: boolean): void {
|
338 | if (visible !== this.visible) {
|
339 | this.visible = visible;
|
340 | _.addOrRemoveCssClass(this.eGui, 'ag-hidden', !visible);
|
341 | let event: VisibleChangedEvent = {
|
342 | type: Component.EVENT_VISIBLE_CHANGED,
|
343 | visible: this.visible
|
344 | };
|
345 | this.dispatchEvent(event);
|
346 | }
|
347 | }
|
348 |
|
349 | public addOrRemoveCssClass(className: string, addOrRemove: boolean): void {
|
350 | _.addOrRemoveCssClass(this.eGui, className, addOrRemove);
|
351 | }
|
352 |
|
353 | public destroy(): void {
|
354 | super.destroy();
|
355 | this.childComponents.forEach(childComponent => childComponent.destroy());
|
356 | this.childComponents.length = 0;
|
357 |
|
358 | this.removeAnnotatedEventListeners();
|
359 | }
|
360 |
|
361 | public addGuiEventListener(event: string, listener: (event: any) => void): void {
|
362 | this.getGui().addEventListener(event, listener);
|
363 | this.addDestroyFunc(() => this.getGui().removeEventListener(event, listener));
|
364 | }
|
365 |
|
366 | public addCssClass(className: string): void {
|
367 | _.addCssClass(this.getGui(), className);
|
368 | }
|
369 |
|
370 | public removeCssClass(className: string): void {
|
371 | _.removeCssClass(this.getGui(), className);
|
372 | }
|
373 |
|
374 | public getAttribute(key: string): string {
|
375 | let eGui = this.getGui();
|
376 | if (eGui) {
|
377 | return eGui.getAttribute(key);
|
378 | } else {
|
379 | return null;
|
380 | }
|
381 | }
|
382 |
|
383 | public getRefElement(refName: string): HTMLElement {
|
384 | return this.queryForHtmlElement('[ref="' + refName + '"]');
|
385 | }
|
386 |
|
387 | } |
\ | No newline at end of file |