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;