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.
6 * @fileoverview Network drop-down implementation.
9 cr
.define('cr.ui', function() {
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.
14 var useKeyboardFlow
= false;
17 * Creates a new container for the drop down menu items.
19 * @extends {HTMLDivElement}
21 var DropDownContainer
= cr
.ui
.define('div');
23 DropDownContainer
.prototype = {
24 __proto__
: HTMLDivElement
.prototype,
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;
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.
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;
55 * @param {!Object} selectedItem Item to be selected.
56 * @param {boolean} mouseOver Is mouseover event triggered?
58 selectItem: function(selectedItem
, mouseOver
) {
59 if (mouseOver
&& this.scrollJustHappened
) {
60 this.scrollJustHappened
= false;
63 if (this.selectedItem
)
64 this.selectedItem
.classList
.remove('hover');
65 selectedItem
.classList
.add('hover');
66 this.selectedItem
= selectedItem
;
68 this.previousSibling
.setAttribute(
69 'aria-activedescendant', selectedItem
.id
);
71 var action
= this.scrollAction(selectedItem
);
73 selectedItem
.scrollIntoView(action
< 0);
74 this.scrollJustHappened
= true;
80 * Creates a new DropDown div.
82 * @extends {HTMLDivElement}
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,
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
);
115 * Returns true if dropdown menu is shown.
116 * @type {bool} Whether menu element is shown.
119 return !this.container
.hidden
;
123 * Sets dropdown menu visibility.
124 * @param {bool} show New visibility state for dropdown menu.
127 this.firstElementChild
.hidden
= !show
;
128 this.container
.hidden
= !show
;
130 this.container
.selectItem(this.container
.firstItem
, false);
132 this.title_
.removeAttribute('aria-activedescendant');
135 // Flag for keyboard flow util to forward the up/down keys.
136 this.title_
.classList
.toggle('needs-up-down-keys', show
);
140 * Returns container of the menu items.
143 return this.lastElementChild
;
147 * Sets title and icon.
148 * @param {string} title Text on dropdown.
149 * @param {string} icon Icon in dataURL format.
151 setTitle: function(title
, icon
) {
152 this.title_
.firstElementChild
.src
= icon
;
153 this.title_
.lastElementChild
.textContent
= title
;
157 * Sets dropdown items.
158 * @param {Array} items Dropdown items array.
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
) {
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
]);
173 this.createItem_(this.container
, item
);
175 this.container
.selectItem(this.container
.firstItem
, false);
177 var maxHeight
= cr
.ui
.LoginUITools
.getMaxHeightBeforeShelfOverlapping(
179 if (maxHeight
< this.container
.offsetHeight
)
180 this.container
.style
.maxHeight
= maxHeight
+ 'px';
184 * Id of the active drop-down element.
187 activeElementId_
: '',
190 * Creates dropdown item element and adds into container.
191 * @param {HTMLElement} container Container where item is added.
192 * @param {!Object} item Item to be added.
195 createItem_: function(container
, item
) {
196 var itemContentElement
;
197 var className
= 'dropdown-item';
198 if (item
.id
== DropDown
.ITEM_DIVIDER_ID
) {
199 className
= 'dropdown-divider';
200 itemContentElement
= this.ownerDocument
.createElement('hr');
202 var span
= this.ownerDocument
.createElement('span');
203 itemContentElement
= span
;
204 span
.textContent
= item
.label
;
205 if ('bold' in item
&& item
.bold
)
206 span
.classList
.add('bold');
207 var image
= this.ownerDocument
.createElement('img');
209 image
.classList
.add('dropdown-image');
211 image
.src
= item
.icon
;
214 var itemElement
= this.ownerDocument
.createElement('div');
215 itemElement
.classList
.add(className
);
216 itemElement
.appendChild(itemContentElement
);
217 itemElement
.iid
= item
.id
;
218 itemElement
.controller
= this;
219 var enabled
= 'enabled' in item
&& item
.enabled
;
221 itemElement
.classList
.add('disabled-item');
224 var wrapperDiv
= this.ownerDocument
.createElement('div');
225 wrapperDiv
.setAttribute('role', 'menuitem');
226 wrapperDiv
.id
= this.id
+ item
.id
;
228 wrapperDiv
.setAttribute('aria-disabled', 'true');
229 wrapperDiv
.classList
.add('dropdown-item-container');
230 var imageDiv
= this.ownerDocument
.createElement('div');
231 imageDiv
.appendChild(image
);
232 wrapperDiv
.appendChild(imageDiv
);
233 wrapperDiv
.appendChild(itemElement
);
234 wrapperDiv
.addEventListener('click', function f(e
) {
235 var item
= this.lastElementChild
;
236 if (item
.iid
< -1 || item
.classList
.contains('disabled-item'))
238 item
.controller
.isShown
= false;
240 chrome
.send('networkItemChosen', [item
.iid
]);
241 this.parentNode
.parentNode
.title_
.focus();
243 wrapperDiv
.addEventListener('mouseover', function f(e
) {
244 this.parentNode
.selectItem(this, true);
246 itemElement
= wrapperDiv
;
248 container
.appendChild(itemElement
);
249 if (!container
.firstItem
&& item
.id
>= 0) {
250 container
.firstItem
= itemElement
;
255 * Creates dropdown overlay element, which catches outside clicks.
256 * @type {HTMLElement}
259 createOverlay_: function() {
260 var overlay
= this.ownerDocument
.createElement('div');
261 overlay
.classList
.add('dropdown-overlay');
262 overlay
.addEventListener('click', function() {
263 this.parentNode
.title_
.focus();
264 this.parentNode
.isShown
= false;
270 * Creates dropdown title element.
271 * @type {HTMLElement}
274 createTitle_: function() {
275 var image
= this.ownerDocument
.createElement('img');
277 image
.classList
.add('dropdown-image');
278 var text
= this.ownerDocument
.createElement('div');
280 var el
= this.ownerDocument
.createElement('div');
281 el
.appendChild(image
);
282 el
.appendChild(text
);
285 el
.classList
.add('dropdown-title');
287 el
.controller
= this;
291 el
.addEventListener('click', function f(e
) {
292 this.controller
.isShown
= !this.controller
.isShown
;
295 el
.addEventListener('focus', function(e
) {
299 el
.addEventListener('blur', function(e
) {
300 this.inFocus
= false;
303 el
.addEventListener('keydown', function f(e
) {
304 if (this.inFocus
&& !this.controller
.isShown
&&
305 (e
.keyCode
== DropDown
.KEYCODE_ENTER
||
306 e
.keyCode
== DropDown
.KEYCODE_SPACE
||
307 (!useKeyboardFlow
&& (e
.keyCode
== DropDown
.KEYCODE_UP
||
308 e
.keyCode
== DropDown
.KEYCODE_DOWN
)))) {
310 this.controller
.isShown
= true;
319 * Handles keydown event from the keyboard.
321 * @param {!Event} e Keydown event.
323 keyDownHandler_: function(e
) {
326 var selected
= this.container
.selectedItem
;
329 case DropDown
.KEYCODE_UP
: {
331 selected
= selected
.previousSibling
;
333 selected
= this.container
.lastElementChild
;
334 } while (selected
.iid
< 0);
335 this.container
.selectItem(selected
, false);
339 case DropDown
.KEYCODE_DOWN
: {
341 selected
= selected
.nextSibling
;
343 selected
= this.container
.firstItem
;
344 } while (selected
.iid
< 0);
345 this.container
.selectItem(selected
, false);
349 case DropDown
.KEYCODE_ESC
: {
350 this.isShown
= false;
354 case DropDown
.KEYCODE_TAB
: {
355 this.isShown
= false;
359 case DropDown
.KEYCODE_ENTER
: {
360 if (!this.title_
.opening
) {
362 this.isShown
= false;
364 this.title_
.controller
.container
.selectedItem
.lastElementChild
;
365 if (item
.iid
>= 0 && !item
.classList
.contains('disabled-item'))
366 chrome
.send('networkItemChosen', [item
.iid
]);
376 this.title_
.opening
= false;
381 * Updates networks list with the new data.
382 * @param {!Object} data Networks list.
384 DropDown
.updateNetworks = function(data
) {
385 if (DropDown
.activeElementId_
)
386 $(DropDown
.activeElementId_
).setItems(data
);
390 * Updates network title, which is shown by the drop-down.
391 * @param {string} title Title to be displayed.
392 * @param {!Object} icon Icon to be displayed.
394 DropDown
.updateNetworkTitle = function(title
, icon
) {
395 if (DropDown
.activeElementId_
)
396 $(DropDown
.activeElementId_
).setTitle(title
, icon
);
400 * Activates network drop-down. Only one network drop-down
401 * can be active at the same time. So activating new drop-down deactivates
403 * @param {string} elementId Id of network drop-down element.
404 * @param {boolean} isOobe Whether drop-down is used by an Oobe screen.
406 DropDown
.show = function(elementId
, isOobe
) {
407 $(elementId
).isShown
= false;
408 if (DropDown
.activeElementId_
!= elementId
) {
409 DropDown
.activeElementId_
= elementId
;
410 chrome
.send('networkDropdownShow', [elementId
, isOobe
]);
415 * Deactivates network drop-down. Deactivating inactive drop-down does
417 * @param {string} elementId Id of network drop-down element.
419 DropDown
.hide = function(elementId
) {
420 if (DropDown
.activeElementId_
== elementId
) {
421 DropDown
.activeElementId_
= '';
422 chrome
.send('networkDropdownHide');
427 * Refreshes network drop-down. Should be called on language change.
429 DropDown
.refresh = function() {
430 chrome
.send('networkDropdownRefresh');
434 * Sets the keyboard flow flag.
436 DropDown
.enableKeyboardFlow = function() {
437 useKeyboardFlow
= true;