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 * Creates a new container for the drop down menu items.
13 * @extends {HTMLDivElement}
15 var DropDownContainer = cr.ui.define('div');
17 DropDownContainer.prototype = {
18 __proto__: HTMLDivElement.prototype,
21 decorate: function() {
22 this.classList.add('dropdown-container');
23 // Selected item in the menu list.
24 this.selectedItem = null;
25 // First item which could be selected.
26 this.firstItem = null;
27 this.setAttribute('role', 'menu');
28 // Whether scroll has just happened.
29 this.scrollJustHappened = false;
33 * Gets scroll action to be done for the item.
34 * @param {!Object} item Menu item.
35 * @return {integer} -1 for scroll up; 0 for no action; 1 for scroll down.
37 scrollAction: function(item) {
38 var thisTop = this.scrollTop;
39 var thisBottom = thisTop + this.offsetHeight;
40 var itemTop = item.offsetTop;
41 var itemBottom = itemTop + item.offsetHeight;
42 if (itemTop <= thisTop) return -1;
43 if (itemBottom >= thisBottom) return 1;
49 * @param {!Object} selectedItem Item to be selected.
50 * @param {boolean} mouseOver Is mouseover event triggered?
52 selectItem: function(selectedItem, mouseOver) {
53 if (mouseOver && this.scrollJustHappened) {
54 this.scrollJustHappened = false;
57 if (this.selectedItem)
58 this.selectedItem.classList.remove('hover');
59 selectedItem.classList.add('hover');
60 this.selectedItem = selectedItem;
62 this.previousSibling.setAttribute(
63 'aria-activedescendant', selectedItem.id);
65 var action = this.scrollAction(selectedItem);
67 selectedItem.scrollIntoView(action < 0);
68 this.scrollJustHappened = true;
74 * Creates a new DropDown div.
76 * @extends {HTMLDivElement}
78 var DropDown = cr.ui.define('div');
80 DropDown.ITEM_DIVIDER_ID = -2;
82 DropDown.KEYCODE_DOWN = 40;
83 DropDown.KEYCODE_ENTER = 13;
84 DropDown.KEYCODE_ESC = 27;
85 DropDown.KEYCODE_SPACE = 32;
86 DropDown.KEYCODE_TAB = 9;
87 DropDown.KEYCODE_UP = 38;
89 DropDown.prototype = {
90 __proto__: HTMLDivElement.prototype,
93 decorate: function() {
94 this.appendChild(this.createOverlay_());
95 this.appendChild(this.title_ = this.createTitle_());
96 var container = new DropDownContainer();
97 container.id = this.id + '-dropdown-container';
98 this.appendChild(container);
100 this.addEventListener('keydown', this.keyDownHandler_);
102 this.title_.id = this.id + '-dropdown';
103 this.title_.setAttribute('role', 'button');
104 this.title_.setAttribute('aria-haspopup', 'true');
105 this.title_.setAttribute('aria-owns', container.id);
109 * Returns true if dropdown menu is shown.
110 * @type {bool} Whether menu element is shown.
113 return !this.container.hidden;
117 * Sets dropdown menu visibility.
118 * @param {bool} show New visibility state for dropdown menu.
121 this.firstElementChild.hidden = !show;
122 this.container.hidden = !show;
124 this.container.selectItem(this.container.firstItem, false);
126 this.title_.removeAttribute('aria-activedescendant');
131 * Returns container of the menu items.
134 return this.lastElementChild;
138 * Sets title and icon.
139 * @param {string} title Text on dropdown.
140 * @param {string} icon Icon in dataURL format.
142 setTitle: function(title, icon) {
143 this.title_.firstElementChild.src = icon;
144 this.title_.lastElementChild.textContent = title;
148 * Sets dropdown items.
149 * @param {Array} items Dropdown items array.
151 setItems: function(items) {
152 this.container.innerHTML = '';
153 this.container.firstItem = null;
154 this.container.selectedItem = null;
155 for (var i = 0; i < items.length; ++i) {
158 // Workaround for submenus, add items on top level.
159 // TODO(altimofeev): support submenus.
160 for (var j = 0; j < item.sub.length; ++j)
161 this.createItem_(this.container, item.sub[j]);
164 this.createItem_(this.container, item);
166 this.container.selectItem(this.container.firstItem, false);
170 * Id of the active drop-down element.
173 activeElementId_: '',
176 * Creates dropdown item element and adds into container.
177 * @param {HTMLElement} container Container where item is added.
178 * @param {!Object} item Item to be added.
181 createItem_: function(container, item) {
182 var itemContentElement;
183 var className = 'dropdown-item';
184 if (item.id == DropDown.ITEM_DIVIDER_ID) {
185 className = 'dropdown-divider';
186 itemContentElement = this.ownerDocument.createElement('hr');
188 var span = this.ownerDocument.createElement('span');
189 itemContentElement = span;
190 span.textContent = item.label;
191 if ('bold' in item && item.bold)
192 span.classList.add('bold');
193 var image = this.ownerDocument.createElement('img');
195 image.classList.add('dropdown-image');
197 image.src = item.icon;
200 var itemElement = this.ownerDocument.createElement('div');
201 itemElement.classList.add(className);
202 itemElement.appendChild(itemContentElement);
203 itemElement.iid = item.id;
204 itemElement.controller = this;
205 var enabled = 'enabled' in item && item.enabled;
207 itemElement.classList.add('disabled-item');
210 var wrapperDiv = this.ownerDocument.createElement('div');
211 wrapperDiv.setAttribute('role', 'menuitem');
212 wrapperDiv.id = this.id + item.id;
214 wrapperDiv.setAttribute('aria-disabled', 'true');
215 wrapperDiv.classList.add('dropdown-item-container');
216 var imageDiv = this.ownerDocument.createElement('div');
217 imageDiv.appendChild(image);
218 wrapperDiv.appendChild(imageDiv);
219 wrapperDiv.appendChild(itemElement);
220 wrapperDiv.addEventListener('click', function f(e) {
221 var item = this.lastElementChild;
222 if (item.iid < -1 || item.classList.contains('disabled-item'))
224 item.controller.isShown = false;
226 chrome.send('networkItemChosen', [item.iid]);
227 this.parentNode.parentNode.title_.focus();
229 wrapperDiv.addEventListener('mouseover', function f(e) {
230 this.parentNode.selectItem(this, true);
232 itemElement = wrapperDiv;
234 container.appendChild(itemElement);
235 if (!container.firstItem && item.id >= 0) {
236 container.firstItem = itemElement;
241 * Creates dropdown overlay element, which catches outside clicks.
242 * @type {HTMLElement}
245 createOverlay_: function() {
246 var overlay = this.ownerDocument.createElement('div');
247 overlay.classList.add('dropdown-overlay');
248 overlay.addEventListener('click', function() {
249 this.parentNode.title_.focus();
250 this.parentNode.isShown = false;
256 * Creates dropdown title element.
257 * @type {HTMLElement}
260 createTitle_: function() {
261 var image = this.ownerDocument.createElement('img');
263 image.classList.add('dropdown-image');
264 var text = this.ownerDocument.createElement('div');
266 var el = this.ownerDocument.createElement('div');
267 el.appendChild(image);
268 el.appendChild(text);
271 el.classList.add('dropdown-title');
273 el.controller = this;
277 el.addEventListener('click', function f(e) {
278 this.controller.isShown = !this.controller.isShown;
281 el.addEventListener('focus', function(e) {
285 el.addEventListener('blur', function(e) {
286 this.inFocus = false;
289 el.addEventListener('keydown', function f(e) {
290 if (this.inFocus && !this.controller.isShown &&
291 (e.keyCode == DropDown.KEYCODE_ENTER ||
292 e.keyCode == DropDown.KEYCODE_SPACE ||
293 e.keyCode == DropDown.KEYCODE_UP ||
294 e.keyCode == DropDown.KEYCODE_DOWN)) {
296 this.controller.isShown = true;
305 * Handles keydown event from the keyboard.
307 * @param {!Event} e Keydown event.
309 keyDownHandler_: function(e) {
312 var selected = this.container.selectedItem;
315 case DropDown.KEYCODE_UP: {
317 selected = selected.previousSibling;
319 selected = this.container.lastElementChild;
320 } while (selected.iid < 0);
321 this.container.selectItem(selected, false);
325 case DropDown.KEYCODE_DOWN: {
327 selected = selected.nextSibling;
329 selected = this.container.firstItem;
330 } while (selected.iid < 0);
331 this.container.selectItem(selected, false);
335 case DropDown.KEYCODE_ESC: {
336 this.isShown = false;
340 case DropDown.KEYCODE_TAB: {
341 this.isShown = false;
345 case DropDown.KEYCODE_ENTER: {
346 if (!this.title_.opening) {
348 this.isShown = false;
350 this.title_.controller.container.selectedItem.lastElementChild;
351 if (item.iid >= 0 && !item.classList.contains('disabled-item'))
352 chrome.send('networkItemChosen', [item.iid]);
362 this.title_.opening = false;
367 * Updates networks list with the new data.
368 * @param {!Object} data Networks list.
370 DropDown.updateNetworks = function(data) {
371 if (DropDown.activeElementId_)
372 $(DropDown.activeElementId_).setItems(data);
376 * Updates network title, which is shown by the drop-down.
377 * @param {string} title Title to be displayed.
378 * @param {!Object} icon Icon to be displayed.
380 DropDown.updateNetworkTitle = function(title, icon) {
381 if (DropDown.activeElementId_)
382 $(DropDown.activeElementId_).setTitle(title, icon);
386 * Activates network drop-down. Only one network drop-down
387 * can be active at the same time. So activating new drop-down deactivates
389 * @param {string} elementId Id of network drop-down element.
390 * @param {boolean} isOobe Whether drop-down is used by an Oobe screen.
392 DropDown.show = function(elementId, isOobe) {
393 $(elementId).isShown = false;
394 if (DropDown.activeElementId_ != elementId) {
395 DropDown.activeElementId_ = elementId;
396 chrome.send('networkDropdownShow', [elementId, isOobe]);
401 * Deactivates network drop-down. Deactivating inactive drop-down does
403 * @param {string} elementId Id of network drop-down element.
405 DropDown.hide = function(elementId) {
406 if (DropDown.activeElementId_ == elementId) {
407 DropDown.activeElementId_ = '';
408 chrome.send('networkDropdownHide');
413 * Refreshes network drop-down. Should be called on language change.
415 DropDown.refresh = function() {
416 chrome.send('networkDropdownRefresh');