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);
179 * Id of the active drop-down element.
182 activeElementId_: '',
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.
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');
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');
204 image.classList.add('dropdown-image');
206 image.src = item.icon;
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;
216 itemElement.classList.add('disabled-item');
219 var wrapperDiv = this.ownerDocument.createElement('div');
220 wrapperDiv.setAttribute('role', 'menuitem');
221 wrapperDiv.id = this.id + item.id;
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'))
233 item.controller.isShown = false;
235 chrome.send('networkItemChosen', [item.iid]);
236 this.parentNode.parentNode.title_.focus();
238 wrapperDiv.addEventListener('mouseover', function f(e) {
239 this.parentNode.selectItem(this, true);
241 itemElement = wrapperDiv;
243 container.appendChild(itemElement);
244 if (!container.firstItem && item.id >= 0) {
245 container.firstItem = itemElement;
250 * Creates dropdown overlay element, which catches outside clicks.
251 * @type {HTMLElement}
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;
265 * Creates dropdown title element.
266 * @type {HTMLElement}
269 createTitle_: function() {
270 var image = this.ownerDocument.createElement('img');
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);
280 el.classList.add('dropdown-title');
282 el.controller = this;
286 el.addEventListener('click', function f(e) {
287 this.controller.isShown = !this.controller.isShown;
290 el.addEventListener('focus', function(e) {
294 el.addEventListener('blur', function(e) {
295 this.inFocus = false;
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)))) {
305 this.controller.isShown = true;
314 * Handles keydown event from the keyboard.
316 * @param {!Event} e Keydown event.
318 keyDownHandler_: function(e) {
321 var selected = this.container.selectedItem;
324 case DropDown.KEYCODE_UP: {
326 selected = selected.previousSibling;
328 selected = this.container.lastElementChild;
329 } while (selected.iid < 0);
330 this.container.selectItem(selected, false);
334 case DropDown.KEYCODE_DOWN: {
336 selected = selected.nextSibling;
338 selected = this.container.firstItem;
339 } while (selected.iid < 0);
340 this.container.selectItem(selected, false);
344 case DropDown.KEYCODE_ESC: {
345 this.isShown = false;
349 case DropDown.KEYCODE_TAB: {
350 this.isShown = false;
354 case DropDown.KEYCODE_ENTER: {
355 if (!this.title_.opening) {
357 this.isShown = false;
359 this.title_.controller.container.selectedItem.lastElementChild;
360 if (item.iid >= 0 && !item.classList.contains('disabled-item'))
361 chrome.send('networkItemChosen', [item.iid]);
371 this.title_.opening = false;
376 * Updates networks list with the new data.
377 * @param {!Object} data Networks list.
379 DropDown.updateNetworks = function(data) {
380 if (DropDown.activeElementId_)
381 $(DropDown.activeElementId_).setItems(data);
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.
389 DropDown.updateNetworkTitle = function(title, icon) {
390 if (DropDown.activeElementId_)
391 $(DropDown.activeElementId_).setTitle(title, icon);
395 * Activates network drop-down. Only one network drop-down
396 * can be active at the same time. So activating new drop-down deactivates
398 * @param {string} elementId Id of network drop-down element.
399 * @param {boolean} isOobe Whether drop-down is used by an Oobe screen.
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]);
410 * Deactivates network drop-down. Deactivating inactive drop-down does
412 * @param {string} elementId Id of network drop-down element.
414 DropDown.hide = function(elementId) {
415 if (DropDown.activeElementId_ == elementId) {
416 DropDown.activeElementId_ = '';
417 chrome.send('networkDropdownHide');
422 * Refreshes network drop-down. Should be called on language change.
424 DropDown.refresh = function() {
425 chrome.send('networkDropdownRefresh');
429 * Sets the keyboard flow flag.
431 DropDown.enableKeyboardFlow = function() {
432 useKeyboardFlow = true;