Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / chromeos / login / network_dropdown.js
blob3a5a3f5fe79dc2c7362c89a2ee7d4d659b948a4f
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 /**
6  * @fileoverview Network drop-down implementation.
7  */
9 cr.define('cr.ui', function() {
10   /**
11    * Whether keyboard flow is in use. When setting to true, up/down arrow key
12    * will be used to move focus instead of opening the drop down.
13    */
14   var useKeyboardFlow = false;
16   /**
17    * Creates a new container for the drop down menu items.
18    * @constructor
19    * @extends {HTMLDivElement}
20    */
21   var DropDownContainer = cr.ui.define('div');
23   DropDownContainer.prototype = {
24     __proto__: HTMLDivElement.prototype,
26     /** @override */
27     decorate: function() {
28       this.classList.add('dropdown-container');
29       // Selected item in the menu list.
30       this.selectedItem = null;
31       // First item which could be selected.
32       this.firstItem = null;
33       this.setAttribute('role', 'menu');
34       // Whether scroll has just happened.
35       this.scrollJustHappened = false;
36     },
38     /**
39      * Gets scroll action to be done for the item.
40      * @param {!Object} item Menu item.
41      * @return {integer} -1 for scroll up; 0 for no action; 1 for scroll down.
42      */
43     scrollAction: function(item) {
44       var thisTop = this.scrollTop;
45       var thisBottom = thisTop + this.offsetHeight;
46       var itemTop = item.offsetTop;
47       var itemBottom = itemTop + item.offsetHeight;
48       if (itemTop <= thisTop) return -1;
49       if (itemBottom >= thisBottom) return 1;
50       return 0;
51     },
53     /**
54      * Selects new item.
55      * @param {!Object} selectedItem Item to be selected.
56      * @param {boolean} mouseOver Is mouseover event triggered?
57      */
58     selectItem: function(selectedItem, mouseOver) {
59       if (mouseOver && this.scrollJustHappened) {
60         this.scrollJustHappened = false;
61         return;
62       }
63       if (this.selectedItem)
64         this.selectedItem.classList.remove('hover');
65       selectedItem.classList.add('hover');
66       this.selectedItem = selectedItem;
67       if (!this.hidden) {
68         this.previousSibling.setAttribute(
69             'aria-activedescendant', selectedItem.id);
70       }
71       var action = this.scrollAction(selectedItem);
72       if (action != 0) {
73         selectedItem.scrollIntoView(action < 0);
74         this.scrollJustHappened = true;
75       }
76     }
77   };
79   /**
80    * Creates a new DropDown div.
81    * @constructor
82    * @extends {HTMLDivElement}
83    */
84   var DropDown = cr.ui.define('div');
86   DropDown.ITEM_DIVIDER_ID = -2;
88   DropDown.KEYCODE_DOWN = 40;
89   DropDown.KEYCODE_ENTER = 13;
90   DropDown.KEYCODE_ESC = 27;
91   DropDown.KEYCODE_SPACE = 32;
92   DropDown.KEYCODE_TAB = 9;
93   DropDown.KEYCODE_UP = 38;
95   DropDown.prototype = {
96     __proto__: HTMLDivElement.prototype,
98     /** @override */
99     decorate: function() {
100       this.appendChild(this.createOverlay_());
101       this.appendChild(this.title_ = this.createTitle_());
102       var container = new DropDownContainer();
103       container.id = this.id + '-dropdown-container';
104       this.appendChild(container);
106       this.addEventListener('keydown', this.keyDownHandler_);
108       this.title_.id = this.id + '-dropdown';
109       this.title_.setAttribute('role', 'button');
110       this.title_.setAttribute('aria-haspopup', 'true');
111       this.title_.setAttribute('aria-owns', container.id);
112     },
114     /**
115      * Returns true if dropdown menu is shown.
116      * @type {bool} Whether menu element is shown.
117      */
118     get isShown() {
119       return !this.container.hidden;
120     },
122     /**
123      * Sets dropdown menu visibility.
124      * @param {bool} show New visibility state for dropdown menu.
125      */
126     set isShown(show) {
127       this.firstElementChild.hidden = !show;
128       this.container.hidden = !show;
129       if (show) {
130         this.container.selectItem(this.container.firstItem, false);
131       } else {
132         this.title_.removeAttribute('aria-activedescendant');
133       }
135       // Flag for keyboard flow util to forward the up/down keys.
136       this.title_.classList.toggle('needs-up-down-keys', show);
137     },
139     /**
140      * Returns container of the menu items.
141      */
142     get container() {
143       return this.lastElementChild;
144     },
146     /**
147      * Sets title and icon.
148      * @param {string} title Text on dropdown.
149      * @param {string} icon Icon in dataURL format.
150      */
151     setTitle: function(title, icon) {
152       this.title_.firstElementChild.src = icon;
153       this.title_.lastElementChild.textContent = title;
154     },
156     /**
157      * Sets dropdown items.
158      * @param {Array} items Dropdown items array.
159      */
160     setItems: function(items) {
161       this.container.innerHTML = '';
162       this.container.firstItem = null;
163       this.container.selectedItem = null;
164       for (var i = 0; i < items.length; ++i) {
165         var item = items[i];
166         if ('sub' in item) {
167           // Workaround for submenus, add items on top level.
168           // TODO(altimofeev): support submenus.
169           for (var j = 0; j < item.sub.length; ++j)
170             this.createItem_(this.container, item.sub[j]);
171           continue;
172         }
173         this.createItem_(this.container, item);
174       }
175       this.container.selectItem(this.container.firstItem, false);
176     },
178     /**
179      * Id of the active drop-down element.
180      * @private
181      */
182     activeElementId_: '',
184     /**
185      * Creates dropdown item element and adds into container.
186      * @param {HTMLElement} container Container where item is added.
187      * @param {!Object} item Item to be added.
188      * @private
189      */
190     createItem_: function(container, item) {
191       var itemContentElement;
192       var className = 'dropdown-item';
193       if (item.id == DropDown.ITEM_DIVIDER_ID) {
194         className = 'dropdown-divider';
195         itemContentElement = this.ownerDocument.createElement('hr');
196       } else {
197         var span = this.ownerDocument.createElement('span');
198         itemContentElement = span;
199         span.textContent = item.label;
200         if ('bold' in item && item.bold)
201           span.classList.add('bold');
202         var image = this.ownerDocument.createElement('img');
203         image.alt = '';
204         image.classList.add('dropdown-image');
205         if (item.icon)
206           image.src = item.icon;
207       }
209       var itemElement = this.ownerDocument.createElement('div');
210       itemElement.classList.add(className);
211       itemElement.appendChild(itemContentElement);
212       itemElement.iid = item.id;
213       itemElement.controller = this;
214       var enabled = 'enabled' in item && item.enabled;
215       if (!enabled)
216         itemElement.classList.add('disabled-item');
218       if (item.id > 0) {
219         var wrapperDiv = this.ownerDocument.createElement('div');
220         wrapperDiv.setAttribute('role', 'menuitem');
221         wrapperDiv.id = this.id + item.id;
222         if (!enabled)
223           wrapperDiv.setAttribute('aria-disabled', 'true');
224         wrapperDiv.classList.add('dropdown-item-container');
225         var imageDiv = this.ownerDocument.createElement('div');
226         imageDiv.appendChild(image);
227         wrapperDiv.appendChild(imageDiv);
228         wrapperDiv.appendChild(itemElement);
229         wrapperDiv.addEventListener('click', function f(e) {
230           var item = this.lastElementChild;
231           if (item.iid < -1 || item.classList.contains('disabled-item'))
232             return;
233           item.controller.isShown = false;
234           if (item.iid >= 0)
235             chrome.send('networkItemChosen', [item.iid]);
236           this.parentNode.parentNode.title_.focus();
237         });
238         wrapperDiv.addEventListener('mouseover', function f(e) {
239           this.parentNode.selectItem(this, true);
240         });
241         itemElement = wrapperDiv;
242       }
243       container.appendChild(itemElement);
244       if (!container.firstItem && item.id >= 0) {
245         container.firstItem = itemElement;
246       }
247     },
249     /**
250      * Creates dropdown overlay element, which catches outside clicks.
251      * @type {HTMLElement}
252      * @private
253      */
254     createOverlay_: function() {
255       var overlay = this.ownerDocument.createElement('div');
256       overlay.classList.add('dropdown-overlay');
257       overlay.addEventListener('click', function() {
258         this.parentNode.title_.focus();
259         this.parentNode.isShown = false;
260       });
261       return overlay;
262     },
264     /**
265      * Creates dropdown title element.
266      * @type {HTMLElement}
267      * @private
268      */
269     createTitle_: function() {
270       var image = this.ownerDocument.createElement('img');
271       image.alt = '';
272       image.classList.add('dropdown-image');
273       var text = this.ownerDocument.createElement('div');
275       var el = this.ownerDocument.createElement('div');
276       el.appendChild(image);
277       el.appendChild(text);
279       el.tabIndex = 0;
280       el.classList.add('dropdown-title');
281       el.iid = -1;
282       el.controller = this;
283       el.inFocus = false;
284       el.opening = false;
286       el.addEventListener('click', function f(e) {
287         this.controller.isShown = !this.controller.isShown;
288       });
290       el.addEventListener('focus', function(e) {
291         this.inFocus = true;
292       });
294       el.addEventListener('blur', function(e) {
295         this.inFocus = false;
296       });
298       el.addEventListener('keydown', function f(e) {
299         if (this.inFocus && !this.controller.isShown &&
300             (e.keyCode == DropDown.KEYCODE_ENTER ||
301              e.keyCode == DropDown.KEYCODE_SPACE ||
302              (!useKeyboardFlow && (e.keyCode == DropDown.KEYCODE_UP ||
303                                    e.keyCode == DropDown.KEYCODE_DOWN)))) {
304           this.opening = true;
305           this.controller.isShown = true;
306           e.stopPropagation();
307           e.preventDefault();
308         }
309       });
310       return el;
311     },
313     /**
314      * Handles keydown event from the keyboard.
315      * @private
316      * @param {!Event} e Keydown event.
317      */
318     keyDownHandler_: function(e) {
319       if (!this.isShown)
320         return;
321       var selected = this.container.selectedItem;
322       var handled = false;
323       switch (e.keyCode) {
324         case DropDown.KEYCODE_UP: {
325           do {
326             selected = selected.previousSibling;
327             if (!selected)
328               selected = this.container.lastElementChild;
329           } while (selected.iid < 0);
330           this.container.selectItem(selected, false);
331           handled = true;
332           break;
333         }
334         case DropDown.KEYCODE_DOWN: {
335           do {
336             selected = selected.nextSibling;
337             if (!selected)
338               selected = this.container.firstItem;
339           } while (selected.iid < 0);
340           this.container.selectItem(selected, false);
341           handled = true;
342           break;
343         }
344         case DropDown.KEYCODE_ESC: {
345           this.isShown = false;
346           handled = true;
347           break;
348         }
349         case DropDown.KEYCODE_TAB: {
350           this.isShown = false;
351           handled = true;
352           break;
353         }
354         case DropDown.KEYCODE_ENTER: {
355           if (!this.title_.opening) {
356             this.title_.focus();
357             this.isShown = false;
358             var item =
359                 this.title_.controller.container.selectedItem.lastElementChild;
360             if (item.iid >= 0 && !item.classList.contains('disabled-item'))
361               chrome.send('networkItemChosen', [item.iid]);
362           }
363           handled = true;
364           break;
365         }
366       }
367       if (handled) {
368         e.stopPropagation();
369         e.preventDefault();
370       }
371       this.title_.opening = false;
372     }
373   };
375   /**
376    * Updates networks list with the new data.
377    * @param {!Object} data Networks list.
378    */
379   DropDown.updateNetworks = function(data) {
380     if (DropDown.activeElementId_)
381       $(DropDown.activeElementId_).setItems(data);
382   };
384   /**
385    * Updates network title, which is shown by the drop-down.
386    * @param {string} title Title to be displayed.
387    * @param {!Object} icon Icon to be displayed.
388    */
389   DropDown.updateNetworkTitle = function(title, icon) {
390     if (DropDown.activeElementId_)
391       $(DropDown.activeElementId_).setTitle(title, icon);
392   };
394   /**
395    * Activates network drop-down. Only one network drop-down
396    * can be active at the same time. So activating new drop-down deactivates
397    * the previous one.
398    * @param {string} elementId Id of network drop-down element.
399    * @param {boolean} isOobe Whether drop-down is used by an Oobe screen.
400    */
401   DropDown.show = function(elementId, isOobe) {
402     $(elementId).isShown = false;
403     if (DropDown.activeElementId_ != elementId) {
404       DropDown.activeElementId_ = elementId;
405       chrome.send('networkDropdownShow', [elementId, isOobe]);
406     }
407   };
409   /**
410    * Deactivates network drop-down. Deactivating inactive drop-down does
411    * nothing.
412    * @param {string} elementId Id of network drop-down element.
413    */
414   DropDown.hide = function(elementId) {
415     if (DropDown.activeElementId_ == elementId) {
416       DropDown.activeElementId_ = '';
417       chrome.send('networkDropdownHide');
418     }
419   };
421   /**
422    * Refreshes network drop-down. Should be called on language change.
423    */
424   DropDown.refresh = function() {
425     chrome.send('networkDropdownRefresh');
426   };
428   /**
429    * Sets the keyboard flow flag.
430    */
431   DropDown.enableKeyboardFlow = function() {
432     useKeyboardFlow = true;
433   };
435   return {
436     DropDown: DropDown
437   };