Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / ot-br-posix / repo / third_party / mdl / repo / src / layout / layout.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 (function() {
19   'use strict';
20
21   /**
22    * Class constructor for Layout MDL component.
23    * Implements MDL component design pattern defined at:
24    * https://github.com/jasonmayes/mdl-component-design-pattern
25    *
26    * @constructor
27    * @param {HTMLElement} element The element that will be upgraded.
28    */
29   var MaterialLayout = function MaterialLayout(element) {
30     this.element_ = element;
31
32     // Initialize instance.
33     this.init();
34   };
35   window['MaterialLayout'] = MaterialLayout;
36
37   /**
38    * Store constants in one place so they can be updated easily.
39    *
40    * @enum {string | number}
41    * @private
42    */
43   MaterialLayout.prototype.Constant_ = {
44     MAX_WIDTH: '(max-width: 1024px)',
45     TAB_SCROLL_PIXELS: 100,
46     RESIZE_TIMEOUT: 100,
47
48     MENU_ICON: '',
49     CHEVRON_LEFT: 'chevron_left',
50     CHEVRON_RIGHT: 'chevron_right'
51   };
52
53   /**
54    * Keycodes, for code readability.
55    *
56    * @enum {number}
57    * @private
58    */
59   MaterialLayout.prototype.Keycodes_ = {
60     ENTER: 13,
61     ESCAPE: 27,
62     SPACE: 32
63   };
64
65   /**
66    * Modes.
67    *
68    * @enum {number}
69    * @private
70    */
71   MaterialLayout.prototype.Mode_ = {
72     STANDARD: 0,
73     SEAMED: 1,
74     WATERFALL: 2,
75     SCROLL: 3
76   };
77
78   /**
79    * Store strings for class names defined by this component that are used in
80    * JavaScript. This allows us to simply change it in one place should we
81    * decide to modify at a later date.
82    *
83    * @enum {string}
84    * @private
85    */
86   MaterialLayout.prototype.CssClasses_ = {
87     CONTAINER: 'mdl-layout__container',
88     HEADER: 'mdl-layout__header',
89     DRAWER: 'mdl-layout__drawer',
90     CONTENT: 'mdl-layout__content',
91     DRAWER_BTN: 'mdl-layout__drawer-button',
92
93     ICON: 'material-icons',
94
95     JS_RIPPLE_EFFECT: 'mdl-js-ripple-effect',
96     RIPPLE_CONTAINER: 'mdl-layout__tab-ripple-container',
97     RIPPLE: 'mdl-ripple',
98     RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
99
100     HEADER_SEAMED: 'mdl-layout__header--seamed',
101     HEADER_WATERFALL: 'mdl-layout__header--waterfall',
102     HEADER_SCROLL: 'mdl-layout__header--scroll',
103
104     FIXED_HEADER: 'mdl-layout--fixed-header',
105     OBFUSCATOR: 'mdl-layout__obfuscator',
106
107     TAB_BAR: 'mdl-layout__tab-bar',
108     TAB_CONTAINER: 'mdl-layout__tab-bar-container',
109     TAB: 'mdl-layout__tab',
110     TAB_BAR_BUTTON: 'mdl-layout__tab-bar-button',
111     TAB_BAR_LEFT_BUTTON: 'mdl-layout__tab-bar-left-button',
112     TAB_BAR_RIGHT_BUTTON: 'mdl-layout__tab-bar-right-button',
113     TAB_MANUAL_SWITCH: 'mdl-layout__tab-manual-switch',
114     PANEL: 'mdl-layout__tab-panel',
115
116     HAS_DRAWER: 'has-drawer',
117     HAS_TABS: 'has-tabs',
118     HAS_SCROLLING_HEADER: 'has-scrolling-header',
119     CASTING_SHADOW: 'is-casting-shadow',
120     IS_COMPACT: 'is-compact',
121     IS_SMALL_SCREEN: 'is-small-screen',
122     IS_DRAWER_OPEN: 'is-visible',
123     IS_ACTIVE: 'is-active',
124     IS_UPGRADED: 'is-upgraded',
125     IS_ANIMATING: 'is-animating',
126
127     ON_LARGE_SCREEN: 'mdl-layout--large-screen-only',
128     ON_SMALL_SCREEN: 'mdl-layout--small-screen-only'
129
130   };
131
132   /**
133    * Handles scrolling on the content.
134    *
135    * @private
136    */
137   MaterialLayout.prototype.contentScrollHandler_ = function() {
138     if (this.header_.classList.contains(this.CssClasses_.IS_ANIMATING)) {
139       return;
140     }
141
142     var headerVisible =
143         !this.element_.classList.contains(this.CssClasses_.IS_SMALL_SCREEN) ||
144         this.element_.classList.contains(this.CssClasses_.FIXED_HEADER);
145
146     if (this.content_.scrollTop > 0 &&
147         !this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
148       this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
149       this.header_.classList.add(this.CssClasses_.IS_COMPACT);
150       if (headerVisible) {
151         this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
152       }
153     } else if (this.content_.scrollTop <= 0 &&
154         this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
155       this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
156       this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
157       if (headerVisible) {
158         this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
159       }
160     }
161   };
162
163   /**
164    * Handles a keyboard event on the drawer.
165    *
166    * @param {Event} evt The event that fired.
167    * @private
168    */
169   MaterialLayout.prototype.keyboardEventHandler_ = function(evt) {
170     // Only react when the drawer is open.
171     if (evt.keyCode === this.Keycodes_.ESCAPE &&
172         this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
173       this.toggleDrawer();
174     }
175   };
176
177   /**
178    * Handles changes in screen size.
179    *
180    * @private
181    */
182   MaterialLayout.prototype.screenSizeHandler_ = function() {
183     if (this.screenSizeMediaQuery_.matches) {
184       this.element_.classList.add(this.CssClasses_.IS_SMALL_SCREEN);
185     } else {
186       this.element_.classList.remove(this.CssClasses_.IS_SMALL_SCREEN);
187       // Collapse drawer (if any) when moving to a large screen size.
188       if (this.drawer_) {
189         this.drawer_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
190         this.obfuscator_.classList.remove(this.CssClasses_.IS_DRAWER_OPEN);
191       }
192     }
193   };
194
195   /**
196    * Handles events of drawer button.
197    *
198    * @param {Event} evt The event that fired.
199    * @private
200    */
201   MaterialLayout.prototype.drawerToggleHandler_ = function(evt) {
202     if (evt && (evt.type === 'keydown')) {
203       if (evt.keyCode === this.Keycodes_.SPACE || evt.keyCode === this.Keycodes_.ENTER) {
204         // prevent scrolling in drawer nav
205         evt.preventDefault();
206       } else {
207         // prevent other keys
208         return;
209       }
210     }
211
212     this.toggleDrawer();
213   };
214
215   /**
216    * Handles (un)setting the `is-animating` class
217    *
218    * @private
219    */
220   MaterialLayout.prototype.headerTransitionEndHandler_ = function() {
221     this.header_.classList.remove(this.CssClasses_.IS_ANIMATING);
222   };
223
224   /**
225    * Handles expanding the header on click
226    *
227    * @private
228    */
229   MaterialLayout.prototype.headerClickHandler_ = function() {
230     if (this.header_.classList.contains(this.CssClasses_.IS_COMPACT)) {
231       this.header_.classList.remove(this.CssClasses_.IS_COMPACT);
232       this.header_.classList.add(this.CssClasses_.IS_ANIMATING);
233     }
234   };
235
236   /**
237    * Reset tab state, dropping active classes
238    *
239    * @private
240    */
241   MaterialLayout.prototype.resetTabState_ = function(tabBar) {
242     for (var k = 0; k < tabBar.length; k++) {
243       tabBar[k].classList.remove(this.CssClasses_.IS_ACTIVE);
244     }
245   };
246
247   /**
248    * Reset panel state, droping active classes
249    *
250    * @private
251    */
252   MaterialLayout.prototype.resetPanelState_ = function(panels) {
253     for (var j = 0; j < panels.length; j++) {
254       panels[j].classList.remove(this.CssClasses_.IS_ACTIVE);
255     }
256   };
257
258   /**
259   * Toggle drawer state
260   *
261   * @public
262   */
263   MaterialLayout.prototype.toggleDrawer = function() {
264     var drawerButton = this.element_.querySelector('.' + this.CssClasses_.DRAWER_BTN);
265     this.drawer_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
266     this.obfuscator_.classList.toggle(this.CssClasses_.IS_DRAWER_OPEN);
267
268     // Set accessibility properties.
269     if (this.drawer_.classList.contains(this.CssClasses_.IS_DRAWER_OPEN)) {
270       this.drawer_.setAttribute('aria-hidden', 'false');
271       drawerButton.setAttribute('aria-expanded', 'true');
272     } else {
273       this.drawer_.setAttribute('aria-hidden', 'true');
274       drawerButton.setAttribute('aria-expanded', 'false');
275     }
276   };
277   MaterialLayout.prototype['toggleDrawer'] =
278       MaterialLayout.prototype.toggleDrawer;
279
280   /**
281    * Initialize element.
282    */
283   MaterialLayout.prototype.init = function() {
284     if (this.element_) {
285       var container = document.createElement('div');
286       container.classList.add(this.CssClasses_.CONTAINER);
287
288       var focusedElement = this.element_.querySelector(':focus');
289
290       this.element_.parentElement.insertBefore(container, this.element_);
291       this.element_.parentElement.removeChild(this.element_);
292       container.appendChild(this.element_);
293
294       if (focusedElement) {
295         focusedElement.focus();
296       }
297
298       var directChildren = this.element_.childNodes;
299       var numChildren = directChildren.length;
300       for (var c = 0; c < numChildren; c++) {
301         var child = directChildren[c];
302         if (child.classList &&
303             child.classList.contains(this.CssClasses_.HEADER)) {
304           this.header_ = child;
305         }
306
307         if (child.classList &&
308             child.classList.contains(this.CssClasses_.DRAWER)) {
309           this.drawer_ = child;
310         }
311
312         if (child.classList &&
313             child.classList.contains(this.CssClasses_.CONTENT)) {
314           this.content_ = child;
315         }
316       }
317
318       window.addEventListener('pageshow', function(e) {
319         if (e.persisted) { // when page is loaded from back/forward cache
320           // trigger repaint to let layout scroll in safari
321           this.element_.style.overflowY = 'hidden';
322           requestAnimationFrame(function() {
323             this.element_.style.overflowY = '';
324           }.bind(this));
325         }
326       }.bind(this), false);
327
328       if (this.header_) {
329         this.tabBar_ = this.header_.querySelector('.' + this.CssClasses_.TAB_BAR);
330       }
331
332       var mode = this.Mode_.STANDARD;
333
334       if (this.header_) {
335         if (this.header_.classList.contains(this.CssClasses_.HEADER_SEAMED)) {
336           mode = this.Mode_.SEAMED;
337         } else if (this.header_.classList.contains(
338             this.CssClasses_.HEADER_WATERFALL)) {
339           mode = this.Mode_.WATERFALL;
340           this.header_.addEventListener('transitionend',
341             this.headerTransitionEndHandler_.bind(this));
342           this.header_.addEventListener('click',
343             this.headerClickHandler_.bind(this));
344         } else if (this.header_.classList.contains(
345             this.CssClasses_.HEADER_SCROLL)) {
346           mode = this.Mode_.SCROLL;
347           container.classList.add(this.CssClasses_.HAS_SCROLLING_HEADER);
348         }
349
350         if (mode === this.Mode_.STANDARD) {
351           this.header_.classList.add(this.CssClasses_.CASTING_SHADOW);
352           if (this.tabBar_) {
353             this.tabBar_.classList.add(this.CssClasses_.CASTING_SHADOW);
354           }
355         } else if (mode === this.Mode_.SEAMED || mode === this.Mode_.SCROLL) {
356           this.header_.classList.remove(this.CssClasses_.CASTING_SHADOW);
357           if (this.tabBar_) {
358             this.tabBar_.classList.remove(this.CssClasses_.CASTING_SHADOW);
359           }
360         } else if (mode === this.Mode_.WATERFALL) {
361           // Add and remove shadows depending on scroll position.
362           // Also add/remove auxiliary class for styling of the compact version of
363           // the header.
364           this.content_.addEventListener('scroll',
365               this.contentScrollHandler_.bind(this));
366           this.contentScrollHandler_();
367         }
368       }
369
370       // Add drawer toggling button to our layout, if we have an openable drawer.
371       if (this.drawer_) {
372         var drawerButton = this.element_.querySelector('.' +
373           this.CssClasses_.DRAWER_BTN);
374         if (!drawerButton) {
375           drawerButton = document.createElement('div');
376           drawerButton.setAttribute('aria-expanded', 'false');
377           drawerButton.setAttribute('role', 'button');
378           drawerButton.setAttribute('tabindex', '0');
379           drawerButton.classList.add(this.CssClasses_.DRAWER_BTN);
380
381           var drawerButtonIcon = document.createElement('i');
382           drawerButtonIcon.classList.add(this.CssClasses_.ICON);
383           drawerButtonIcon.innerHTML = this.Constant_.MENU_ICON;
384           drawerButton.appendChild(drawerButtonIcon);
385         }
386
387         if (this.drawer_.classList.contains(this.CssClasses_.ON_LARGE_SCREEN)) {
388           //If drawer has ON_LARGE_SCREEN class then add it to the drawer toggle button as well.
389           drawerButton.classList.add(this.CssClasses_.ON_LARGE_SCREEN);
390         } else if (this.drawer_.classList.contains(this.CssClasses_.ON_SMALL_SCREEN)) {
391           //If drawer has ON_SMALL_SCREEN class then add it to the drawer toggle button as well.
392           drawerButton.classList.add(this.CssClasses_.ON_SMALL_SCREEN);
393         }
394
395         drawerButton.addEventListener('click',
396             this.drawerToggleHandler_.bind(this));
397
398         drawerButton.addEventListener('keydown',
399             this.drawerToggleHandler_.bind(this));
400
401         // Add a class if the layout has a drawer, for altering the left padding.
402         // Adds the HAS_DRAWER to the elements since this.header_ may or may
403         // not be present.
404         this.element_.classList.add(this.CssClasses_.HAS_DRAWER);
405
406         // If we have a fixed header, add the button to the header rather than
407         // the layout.
408         if (this.element_.classList.contains(this.CssClasses_.FIXED_HEADER)) {
409           this.header_.insertBefore(drawerButton, this.header_.firstChild);
410         } else {
411           this.element_.insertBefore(drawerButton, this.content_);
412         }
413
414         var obfuscator = document.createElement('div');
415         obfuscator.classList.add(this.CssClasses_.OBFUSCATOR);
416         this.element_.appendChild(obfuscator);
417         obfuscator.addEventListener('click',
418             this.drawerToggleHandler_.bind(this));
419         this.obfuscator_ = obfuscator;
420
421         this.drawer_.addEventListener('keydown', this.keyboardEventHandler_.bind(this));
422         this.drawer_.setAttribute('aria-hidden', 'true');
423       }
424
425       // Keep an eye on screen size, and add/remove auxiliary class for styling
426       // of small screens.
427       this.screenSizeMediaQuery_ = window.matchMedia(
428           /** @type {string} */ (this.Constant_.MAX_WIDTH));
429       this.screenSizeMediaQuery_.addListener(this.screenSizeHandler_.bind(this));
430       this.screenSizeHandler_();
431
432       // Initialize tabs, if any.
433       if (this.header_ && this.tabBar_) {
434         this.element_.classList.add(this.CssClasses_.HAS_TABS);
435
436         var tabContainer = document.createElement('div');
437         tabContainer.classList.add(this.CssClasses_.TAB_CONTAINER);
438         this.header_.insertBefore(tabContainer, this.tabBar_);
439         this.header_.removeChild(this.tabBar_);
440
441         var leftButton = document.createElement('div');
442         leftButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
443         leftButton.classList.add(this.CssClasses_.TAB_BAR_LEFT_BUTTON);
444         var leftButtonIcon = document.createElement('i');
445         leftButtonIcon.classList.add(this.CssClasses_.ICON);
446         leftButtonIcon.textContent = this.Constant_.CHEVRON_LEFT;
447         leftButton.appendChild(leftButtonIcon);
448         leftButton.addEventListener('click', function() {
449           this.tabBar_.scrollLeft -= this.Constant_.TAB_SCROLL_PIXELS;
450         }.bind(this));
451
452         var rightButton = document.createElement('div');
453         rightButton.classList.add(this.CssClasses_.TAB_BAR_BUTTON);
454         rightButton.classList.add(this.CssClasses_.TAB_BAR_RIGHT_BUTTON);
455         var rightButtonIcon = document.createElement('i');
456         rightButtonIcon.classList.add(this.CssClasses_.ICON);
457         rightButtonIcon.textContent = this.Constant_.CHEVRON_RIGHT;
458         rightButton.appendChild(rightButtonIcon);
459         rightButton.addEventListener('click', function() {
460           this.tabBar_.scrollLeft += this.Constant_.TAB_SCROLL_PIXELS;
461         }.bind(this));
462
463         tabContainer.appendChild(leftButton);
464         tabContainer.appendChild(this.tabBar_);
465         tabContainer.appendChild(rightButton);
466
467         // Add and remove tab buttons depending on scroll position and total
468         // window size.
469         var tabUpdateHandler = function() {
470           if (this.tabBar_.scrollLeft > 0) {
471             leftButton.classList.add(this.CssClasses_.IS_ACTIVE);
472           } else {
473             leftButton.classList.remove(this.CssClasses_.IS_ACTIVE);
474           }
475
476           if (this.tabBar_.scrollLeft <
477               this.tabBar_.scrollWidth - this.tabBar_.offsetWidth) {
478             rightButton.classList.add(this.CssClasses_.IS_ACTIVE);
479           } else {
480             rightButton.classList.remove(this.CssClasses_.IS_ACTIVE);
481           }
482         }.bind(this);
483
484         this.tabBar_.addEventListener('scroll', tabUpdateHandler);
485         tabUpdateHandler();
486
487         // Update tabs when the window resizes.
488         var windowResizeHandler = function() {
489           // Use timeouts to make sure it doesn't happen too often.
490           if (this.resizeTimeoutId_) {
491             clearTimeout(this.resizeTimeoutId_);
492           }
493           this.resizeTimeoutId_ = setTimeout(function() {
494             tabUpdateHandler();
495             this.resizeTimeoutId_ = null;
496           }.bind(this), /** @type {number} */ (this.Constant_.RESIZE_TIMEOUT));
497         }.bind(this);
498
499         window.addEventListener('resize', windowResizeHandler);
500
501         if (this.tabBar_.classList.contains(this.CssClasses_.JS_RIPPLE_EFFECT)) {
502           this.tabBar_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
503         }
504
505         // Select element tabs, document panels
506         var tabs = this.tabBar_.querySelectorAll('.' + this.CssClasses_.TAB);
507         var panels = this.content_.querySelectorAll('.' + this.CssClasses_.PANEL);
508
509         // Create new tabs for each tab element
510         for (var i = 0; i < tabs.length; i++) {
511           new MaterialLayoutTab(tabs[i], tabs, panels, this);
512         }
513       }
514
515       this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
516     }
517   };
518
519   /**
520    * Constructor for an individual tab.
521    *
522    * @constructor
523    * @param {HTMLElement} tab The HTML element for the tab.
524    * @param {!Array<HTMLElement>} tabs Array with HTML elements for all tabs.
525    * @param {!Array<HTMLElement>} panels Array with HTML elements for all panels.
526    * @param {MaterialLayout} layout The MaterialLayout object that owns the tab.
527    */
528   function MaterialLayoutTab(tab, tabs, panels, layout) {
529
530     /**
531      * Auxiliary method to programmatically select a tab in the UI.
532      */
533     function selectTab() {
534       var href = tab.href.split('#')[1];
535       var panel = layout.content_.querySelector('#' + href);
536       layout.resetTabState_(tabs);
537       layout.resetPanelState_(panels);
538       tab.classList.add(layout.CssClasses_.IS_ACTIVE);
539       panel.classList.add(layout.CssClasses_.IS_ACTIVE);
540     }
541
542     if (layout.tabBar_.classList.contains(
543         layout.CssClasses_.JS_RIPPLE_EFFECT)) {
544       var rippleContainer = document.createElement('span');
545       rippleContainer.classList.add(layout.CssClasses_.RIPPLE_CONTAINER);
546       rippleContainer.classList.add(layout.CssClasses_.JS_RIPPLE_EFFECT);
547       var ripple = document.createElement('span');
548       ripple.classList.add(layout.CssClasses_.RIPPLE);
549       rippleContainer.appendChild(ripple);
550       tab.appendChild(rippleContainer);
551     }
552
553     if (!layout.tabBar_.classList.contains(
554       layout.CssClasses_.TAB_MANUAL_SWITCH)) {
555       tab.addEventListener('click', function(e) {
556         if (tab.getAttribute('href').charAt(0) === '#') {
557           e.preventDefault();
558           selectTab();
559         }
560       });
561     }
562
563     tab.show = selectTab;
564   }
565   window['MaterialLayoutTab'] = MaterialLayoutTab;
566
567   // The component registers itself. It can assume componentHandler is available
568   // in the global scope.
569   componentHandler.register({
570     constructor: MaterialLayout,
571     classAsString: 'MaterialLayout',
572     cssClass: 'mdl-js-layout'
573   });
574 })();