Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / ot-br-posix / repo / third_party / mdl / repo / src / mdlComponentHandler.js
1 /**
2  * @license
3  * Copyright 2015 Google Inc. All Rights Reserved.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 /**
19  * A component handler interface using the revealing module design pattern.
20  * More details on this design pattern here:
21  * https://github.com/jasonmayes/mdl-component-design-pattern
22  *
23  * @author Jason Mayes.
24  */
25 /* exported componentHandler */
26
27 // Pre-defining the componentHandler interface, for closure documentation and
28 // static verification.
29 var componentHandler = {
30   /**
31    * Searches existing DOM for elements of our component type and upgrades them
32    * if they have not already been upgraded.
33    *
34    * @param {string=} optJsClass the programatic name of the element class we
35    * need to create a new instance of.
36    * @param {string=} optCssClass the name of the CSS class elements of this
37    * type will have.
38    */
39   upgradeDom: function(optJsClass, optCssClass) {},
40   /**
41    * Upgrades a specific element rather than all in the DOM.
42    *
43    * @param {!Element} element The element we wish to upgrade.
44    * @param {string=} optJsClass Optional name of the class we want to upgrade
45    * the element to.
46    */
47   upgradeElement: function(element, optJsClass) {},
48   /**
49    * Upgrades a specific list of elements rather than all in the DOM.
50    *
51    * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
52    * The elements we wish to upgrade.
53    */
54   upgradeElements: function(elements) {},
55   /**
56    * Upgrades all registered components found in the current DOM. This is
57    * automatically called on window load.
58    */
59   upgradeAllRegistered: function() {},
60   /**
61    * Allows user to be alerted to any upgrades that are performed for a given
62    * component type
63    *
64    * @param {string} jsClass The class name of the MDL component we wish
65    * to hook into for any upgrades performed.
66    * @param {function(!HTMLElement)} callback The function to call upon an
67    * upgrade. This function should expect 1 parameter - the HTMLElement which
68    * got upgraded.
69    */
70   registerUpgradedCallback: function(jsClass, callback) {},
71   /**
72    * Registers a class for future use and attempts to upgrade existing DOM.
73    *
74    * @param {componentHandler.ComponentConfigPublic} config the registration configuration
75    */
76   register: function(config) {},
77   /**
78    * Downgrade either a given node, an array of nodes, or a NodeList.
79    *
80    * @param {!Node|!Array<!Node>|!NodeList} nodes
81    */
82   downgradeElements: function(nodes) {}
83 };
84
85 componentHandler = (function() {
86   'use strict';
87
88   /** @type {!Array<componentHandler.ComponentConfig>} */
89   var registeredComponents_ = [];
90
91   /** @type {!Array<componentHandler.Component>} */
92   var createdComponents_ = [];
93
94   var componentConfigProperty_ = 'mdlComponentConfigInternal_';
95
96   /**
97    * Searches registered components for a class we are interested in using.
98    * Optionally replaces a match with passed object if specified.
99    *
100    * @param {string} name The name of a class we want to use.
101    * @param {componentHandler.ComponentConfig=} optReplace Optional object to replace match with.
102    * @return {!Object|boolean}
103    * @private
104    */
105   function findRegisteredClass_(name, optReplace) {
106     for (var i = 0; i < registeredComponents_.length; i++) {
107       if (registeredComponents_[i].className === name) {
108         if (typeof optReplace !== 'undefined') {
109           registeredComponents_[i] = optReplace;
110         }
111         return registeredComponents_[i];
112       }
113     }
114     return false;
115   }
116
117   /**
118    * Returns an array of the classNames of the upgraded classes on the element.
119    *
120    * @param {!Element} element The element to fetch data from.
121    * @return {!Array<string>}
122    * @private
123    */
124   function getUpgradedListOfElement_(element) {
125     var dataUpgraded = element.getAttribute('data-upgraded');
126     // Use `['']` as default value to conform the `,name,name...` style.
127     return dataUpgraded === null ? [''] : dataUpgraded.split(',');
128   }
129
130   /**
131    * Returns true if the given element has already been upgraded for the given
132    * class.
133    *
134    * @param {!Element} element The element we want to check.
135    * @param {string} jsClass The class to check for.
136    * @returns {boolean}
137    * @private
138    */
139   function isElementUpgraded_(element, jsClass) {
140     var upgradedList = getUpgradedListOfElement_(element);
141     return upgradedList.indexOf(jsClass) !== -1;
142   }
143
144   /**
145    * Create an event object.
146    *
147    * @param {string} eventType The type name of the event.
148    * @param {boolean} bubbles Whether the event should bubble up the DOM.
149    * @param {boolean} cancelable Whether the event can be canceled.
150    * @returns {!Event}
151    */
152   function createEvent_(eventType, bubbles, cancelable) {
153     if ('CustomEvent' in window && typeof window.CustomEvent === 'function') {
154       return new CustomEvent(eventType, {
155         bubbles: bubbles,
156         cancelable: cancelable
157       });
158     } else {
159       var ev = document.createEvent('Events');
160       ev.initEvent(eventType, bubbles, cancelable);
161       return ev;
162     }
163   }
164
165   /**
166    * Searches existing DOM for elements of our component type and upgrades them
167    * if they have not already been upgraded.
168    *
169    * @param {string=} optJsClass the programatic name of the element class we
170    * need to create a new instance of.
171    * @param {string=} optCssClass the name of the CSS class elements of this
172    * type will have.
173    */
174   function upgradeDomInternal(optJsClass, optCssClass) {
175     if (typeof optJsClass === 'undefined' &&
176         typeof optCssClass === 'undefined') {
177       for (var i = 0; i < registeredComponents_.length; i++) {
178         upgradeDomInternal(registeredComponents_[i].className,
179             registeredComponents_[i].cssClass);
180       }
181     } else {
182       var jsClass = /** @type {string} */ (optJsClass);
183       if (typeof optCssClass === 'undefined') {
184         var registeredClass = findRegisteredClass_(jsClass);
185         if (registeredClass) {
186           optCssClass = registeredClass.cssClass;
187         }
188       }
189
190       var elements = document.querySelectorAll('.' + optCssClass);
191       for (var n = 0; n < elements.length; n++) {
192         upgradeElementInternal(elements[n], jsClass);
193       }
194     }
195   }
196
197   /**
198    * Upgrades a specific element rather than all in the DOM.
199    *
200    * @param {!Element} element The element we wish to upgrade.
201    * @param {string=} optJsClass Optional name of the class we want to upgrade
202    * the element to.
203    */
204   function upgradeElementInternal(element, optJsClass) {
205     // Verify argument type.
206     if (!(typeof element === 'object' && element instanceof Element)) {
207       throw new Error('Invalid argument provided to upgrade MDL element.');
208     }
209     // Allow upgrade to be canceled by canceling emitted event.
210     var upgradingEv = createEvent_('mdl-componentupgrading', true, true);
211     element.dispatchEvent(upgradingEv);
212     if (upgradingEv.defaultPrevented) {
213       return;
214     }
215
216     var upgradedList = getUpgradedListOfElement_(element);
217     var classesToUpgrade = [];
218     // If jsClass is not provided scan the registered components to find the
219     // ones matching the element's CSS classList.
220     if (!optJsClass) {
221       var classList = element.classList;
222       registeredComponents_.forEach(function(component) {
223         // Match CSS & Not to be upgraded & Not upgraded.
224         if (classList.contains(component.cssClass) &&
225             classesToUpgrade.indexOf(component) === -1 &&
226             !isElementUpgraded_(element, component.className)) {
227           classesToUpgrade.push(component);
228         }
229       });
230     } else if (!isElementUpgraded_(element, optJsClass)) {
231       classesToUpgrade.push(findRegisteredClass_(optJsClass));
232     }
233
234     // Upgrade the element for each classes.
235     for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) {
236       registeredClass = classesToUpgrade[i];
237       if (registeredClass) {
238         // Mark element as upgraded.
239         upgradedList.push(registeredClass.className);
240         element.setAttribute('data-upgraded', upgradedList.join(','));
241         var instance = new registeredClass.classConstructor(element);
242         instance[componentConfigProperty_] = registeredClass;
243         createdComponents_.push(instance);
244         // Call any callbacks the user has registered with this component type.
245         for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) {
246           registeredClass.callbacks[j](element);
247         }
248
249         if (registeredClass.widget) {
250           // Assign per element instance for control over API
251           element[registeredClass.className] = instance;
252         }
253       } else {
254         throw new Error(
255           'Unable to find a registered component for the given class.');
256       }
257
258       var upgradedEv = createEvent_('mdl-componentupgraded', true, false);
259       element.dispatchEvent(upgradedEv);
260     }
261   }
262
263   /**
264    * Upgrades a specific list of elements rather than all in the DOM.
265    *
266    * @param {!Element|!Array<!Element>|!NodeList|!HTMLCollection} elements
267    * The elements we wish to upgrade.
268    */
269   function upgradeElementsInternal(elements) {
270     if (!Array.isArray(elements)) {
271       if (elements instanceof Element) {
272         elements = [elements];
273       } else {
274         elements = Array.prototype.slice.call(elements);
275       }
276     }
277     for (var i = 0, n = elements.length, element; i < n; i++) {
278       element = elements[i];
279       if (element instanceof HTMLElement) {
280         upgradeElementInternal(element);
281         if (element.children.length > 0) {
282           upgradeElementsInternal(element.children);
283         }
284       }
285     }
286   }
287
288   /**
289    * Registers a class for future use and attempts to upgrade existing DOM.
290    *
291    * @param {componentHandler.ComponentConfigPublic} config
292    */
293   function registerInternal(config) {
294     // In order to support both Closure-compiled and uncompiled code accessing
295     // this method, we need to allow for both the dot and array syntax for
296     // property access. You'll therefore see the `foo.bar || foo['bar']`
297     // pattern repeated across this method.
298     var widgetMissing = (typeof config.widget === 'undefined' &&
299         typeof config['widget'] === 'undefined');
300     var widget = true;
301
302     if (!widgetMissing) {
303       widget = config.widget || config['widget'];
304     }
305
306     var newConfig = /** @type {componentHandler.ComponentConfig} */ ({
307       classConstructor: config.constructor || config['constructor'],
308       className: config.classAsString || config['classAsString'],
309       cssClass: config.cssClass || config['cssClass'],
310       widget: widget,
311       callbacks: []
312     });
313
314     registeredComponents_.forEach(function(item) {
315       if (item.cssClass === newConfig.cssClass) {
316         throw new Error('The provided cssClass has already been registered: ' + item.cssClass);
317       }
318       if (item.className === newConfig.className) {
319         throw new Error('The provided className has already been registered');
320       }
321     });
322
323     if (config.constructor.prototype
324         .hasOwnProperty(componentConfigProperty_)) {
325       throw new Error(
326           'MDL component classes must not have ' + componentConfigProperty_ +
327           ' defined as a property.');
328     }
329
330     var found = findRegisteredClass_(config.classAsString, newConfig);
331
332     if (!found) {
333       registeredComponents_.push(newConfig);
334     }
335   }
336
337   /**
338    * Allows user to be alerted to any upgrades that are performed for a given
339    * component type
340    *
341    * @param {string} jsClass The class name of the MDL component we wish
342    * to hook into for any upgrades performed.
343    * @param {function(!HTMLElement)} callback The function to call upon an
344    * upgrade. This function should expect 1 parameter - the HTMLElement which
345    * got upgraded.
346    */
347   function registerUpgradedCallbackInternal(jsClass, callback) {
348     var regClass = findRegisteredClass_(jsClass);
349     if (regClass) {
350       regClass.callbacks.push(callback);
351     }
352   }
353
354   /**
355    * Upgrades all registered components found in the current DOM. This is
356    * automatically called on window load.
357    */
358   function upgradeAllRegisteredInternal() {
359     for (var n = 0; n < registeredComponents_.length; n++) {
360       upgradeDomInternal(registeredComponents_[n].className);
361     }
362   }
363
364   /**
365    * Check the component for the downgrade method.
366    * Execute if found.
367    * Remove component from createdComponents list.
368    *
369    * @param {?componentHandler.Component} component
370    */
371   function deconstructComponentInternal(component) {
372     if (component) {
373       var componentIndex = createdComponents_.indexOf(component);
374       createdComponents_.splice(componentIndex, 1);
375
376       var upgrades = component.element_.getAttribute('data-upgraded').split(',');
377       var componentPlace = upgrades.indexOf(component[componentConfigProperty_].classAsString);
378       upgrades.splice(componentPlace, 1);
379       component.element_.setAttribute('data-upgraded', upgrades.join(','));
380
381       var ev = createEvent_('mdl-componentdowngraded', true, false);
382       component.element_.dispatchEvent(ev);
383     }
384   }
385
386   /**
387    * Downgrade either a given node, an array of nodes, or a NodeList.
388    *
389    * @param {!Node|!Array<!Node>|!NodeList} nodes
390    */
391   function downgradeNodesInternal(nodes) {
392     /**
393      * Auxiliary function to downgrade a single node.
394      * @param  {!Node} node the node to be downgraded
395      */
396     var downgradeNode = function(node) {
397       createdComponents_.filter(function(item) {
398         return item.element_ === node;
399       }).forEach(deconstructComponentInternal);
400     };
401     if (nodes instanceof Array || nodes instanceof NodeList) {
402       for (var n = 0; n < nodes.length; n++) {
403         downgradeNode(nodes[n]);
404       }
405     } else if (nodes instanceof Node) {
406       downgradeNode(nodes);
407     } else {
408       throw new Error('Invalid argument provided to downgrade MDL nodes.');
409     }
410   }
411
412   // Now return the functions that should be made public with their publicly
413   // facing names...
414   return {
415     upgradeDom: upgradeDomInternal,
416     upgradeElement: upgradeElementInternal,
417     upgradeElements: upgradeElementsInternal,
418     upgradeAllRegistered: upgradeAllRegisteredInternal,
419     registerUpgradedCallback: registerUpgradedCallbackInternal,
420     register: registerInternal,
421     downgradeElements: downgradeNodesInternal
422   };
423 })();
424
425 /**
426  * Describes the type of a registered component type managed by
427  * componentHandler. Provided for benefit of the Closure compiler.
428  *
429  * @typedef {{
430  *   constructor: Function,
431  *   classAsString: string,
432  *   cssClass: string,
433  *   widget: (string|boolean|undefined)
434  * }}
435  */
436 componentHandler.ComponentConfigPublic;  // jshint ignore:line
437
438 /**
439  * Describes the type of a registered component type managed by
440  * componentHandler. Provided for benefit of the Closure compiler.
441  *
442  * @typedef {{
443  *   constructor: !Function,
444  *   className: string,
445  *   cssClass: string,
446  *   widget: (string|boolean),
447  *   callbacks: !Array<function(!HTMLElement)>
448  * }}
449  */
450 componentHandler.ComponentConfig;  // jshint ignore:line
451
452 /**
453  * Created component (i.e., upgraded element) type as managed by
454  * componentHandler. Provided for benefit of the Closure compiler.
455  *
456  * @typedef {{
457  *   element_: !HTMLElement,
458  *   className: string,
459  *   classAsString: string,
460  *   cssClass: string,
461  *   widget: string
462  * }}
463  */
464 componentHandler.Component;  // jshint ignore:line
465
466 // Export all symbols, for the benefit of Closure compiler.
467 // No effect on uncompiled code.
468 componentHandler['upgradeDom'] = componentHandler.upgradeDom;
469 componentHandler['upgradeElement'] = componentHandler.upgradeElement;
470 componentHandler['upgradeElements'] = componentHandler.upgradeElements;
471 componentHandler['upgradeAllRegistered'] =
472     componentHandler.upgradeAllRegistered;
473 componentHandler['registerUpgradedCallback'] =
474     componentHandler.registerUpgradedCallback;
475 componentHandler['register'] = componentHandler.register;
476 componentHandler['downgradeElements'] = componentHandler.downgradeElements;
477 window.componentHandler = componentHandler;
478 window['componentHandler'] = componentHandler;
479
480 window.addEventListener('load', function() {
481   'use strict';
482
483   /**
484    * Performs a "Cutting the mustard" test. If the browser supports the features
485    * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL
486    * components requiring JavaScript.
487    */
488   if ('classList' in document.createElement('div') &&
489       'querySelector' in document &&
490       'addEventListener' in window && Array.prototype.forEach) {
491     document.documentElement.classList.add('mdl-js');
492     componentHandler.upgradeAllRegistered();
493   } else {
494     /**
495      * Dummy function to avoid JS errors.
496      */
497     componentHandler.upgradeElement = function() {};
498     /**
499      * Dummy function to avoid JS errors.
500      */
501     componentHandler.register = function() {};
502   }
503 });