Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(68)

Side by Side Diff: chrome/browser/resources/options/chromeos/network_list.js

Issue 10907148: Implement popup bubbles for the controlled setting indicator (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Reimplemented on top of newly refactored BubbleBase class. Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('options.network', function() { 5 cr.define('options.network', function() {
6 6
7 var ArrayDataModel = cr.ui.ArrayDataModel; 7 var ArrayDataModel = cr.ui.ArrayDataModel;
8 var List = cr.ui.List; 8 var List = cr.ui.List;
9 var ListItem = cr.ui.ListItem; 9 var ListItem = cr.ui.ListItem;
10 var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
10 var Menu = cr.ui.Menu; 11 var Menu = cr.ui.Menu;
11 var MenuItem = cr.ui.MenuItem; 12 var MenuItem = cr.ui.MenuItem;
12 var ControlledSettingIndicator = options.ControlledSettingIndicator; 13 var ControlledSettingIndicator = options.ControlledSettingIndicator;
13 14
14 /** 15 /**
15 * Network settings constants. These enums usually match their C++ 16 * Network settings constants. These enums usually match their C++
16 * counterparts. 17 * counterparts.
17 */ 18 */
18 function Constants() {} 19 function Constants() {}
19 20
(...skipping 262 matching lines...) Expand 10 before | Expand all | Expand 10 after
282 this.subtitle = null; 283 this.subtitle = null;
283 if (this.data.iconType) 284 if (this.data.iconType)
284 this.iconType = this.data.iconType; 285 this.iconType = this.data.iconType;
285 this.addEventListener('click', function() { 286 this.addEventListener('click', function() {
286 this.showMenu(); 287 this.showMenu();
287 }); 288 });
288 }, 289 },
289 290
290 /** 291 /**
291 * Retrieves the ID for the menu. 292 * Retrieves the ID for the menu.
292 * @private
293 */ 293 */
294 getMenuName_: function() { 294 getMenuName: function() {
295 return this.data_.key + '-network-menu'; 295 return this.data_.key + '-network-menu';
296 }, 296 },
297 297
298 /** 298 /**
299 * Creates a popup menu for the control. 299 * Creates a popup menu for the control.
300 * @return {Element} The newly created menu. 300 * @return {Element} The newly created menu.
301 */ 301 */
302 createMenu: function() { 302 createMenu: function() {
303 if (this.data.menu) { 303 if (this.data.menu) {
304 var menu = this.ownerDocument.createElement('div'); 304 var menu = this.ownerDocument.createElement('div');
305 menu.id = this.getMenuName_(); 305 menu.id = this.getMenuName();
306 menu.className = 'network-menu'; 306 menu.className = 'network-menu';
307 menu.hidden = true; 307 menu.hidden = true;
308 Menu.decorate(menu); 308 Menu.decorate(menu);
309 for (var i = 0; i < this.data.menu.length; i++) { 309 for (var i = 0; i < this.data.menu.length; i++) {
310 var entry = this.data.menu[i]; 310 var entry = this.data.menu[i];
311 createCallback_(menu, null, entry.label, entry.command); 311 createCallback_(menu, null, entry.label, entry.command);
312 } 312 }
313 return menu; 313 return menu;
314 } 314 }
315 return null; 315 return null;
316 }, 316 },
317 317
318 canUpdateMenu: function() { 318 canUpdateMenu: function() {
319 return false; 319 return false;
320 }, 320 },
321 321
322 /** 322 /**
323 * Displays a popup menu. 323 * Displays a popup menu.
324 */ 324 */
325 showMenu: function() { 325 showMenu: function() {
326 var rebuild = false; 326 var rebuild = false;
327 // Force a rescan if opening the menu for WiFi networks to ensure the 327 // Force a rescan if opening the menu for WiFi networks to ensure the
328 // list is up to date. Networks are periodically rescanned, but depending 328 // list is up to date. Networks are periodically rescanned, but depending
329 // on timing, there could be an excessive delay before the first rescan 329 // on timing, there could be an excessive delay before the first rescan
330 // unless forced. 330 // unless forced.
331 var rescan = !activeMenu_ && this.data_.key == 'wifi'; 331 var rescan = !activeMenu_ && this.data_.key == 'wifi';
332 if (!this.menu_) { 332 if (!this.menu_) {
333 rebuild = true; 333 rebuild = true;
334 var existing = $(this.getMenuName_()); 334 var existing = $(this.getMenuName());
335 if (existing) { 335 if (existing) {
336 if (this.updateMenu()) 336 if (this.updateMenu())
337 return; 337 return;
338 closeMenu_(); 338 closeMenu_();
339 } 339 }
340 this.menu_ = this.createMenu(); 340 this.menu_ = this.createMenu();
341 this.menu_.addEventListener('mousedown', function(e) { 341 this.menu_.addEventListener('mousedown', function(e) {
342 // Prevent blurring of list, which would close the menu. 342 // Prevent blurring of list, which would close the menu.
343 e.preventDefault(); 343 e.preventDefault();
344 }, true); 344 });
345 var parent = $('network-menus'); 345 var parent = $('network-menus');
346 if (existing) 346 if (existing)
347 parent.replaceChild(this.menu_, existing); 347 parent.replaceChild(this.menu_, existing);
348 else 348 else
349 parent.appendChild(this.menu_); 349 parent.appendChild(this.menu_);
350 } 350 }
351 var top = this.offsetTop + this.clientHeight; 351 var top = this.offsetTop + this.clientHeight;
352 var menuId = this.getMenuName_(); 352 var menuId = this.getMenuName();
353 if (menuId != activeMenu_ || rebuild) { 353 if (menuId != activeMenu_ || rebuild) {
354 closeMenu_(); 354 closeMenu_();
355 activeMenu_ = menuId; 355 activeMenu_ = menuId;
356 this.menu_.style.setProperty('top', top + 'px'); 356 this.menu_.style.setProperty('top', top + 'px');
357 this.menu_.hidden = false; 357 this.menu_.hidden = false;
358 } 358 }
359 if (rescan) 359 if (rescan)
360 chrome.send('refreshNetworks'); 360 chrome.send('refreshNetworks');
361 }, 361 },
362 }; 362 };
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after
407 if (candidateURL) 407 if (candidateURL)
408 this.iconURL = candidateURL; 408 this.iconURL = candidateURL;
409 else 409 else
410 this.iconType = this.data.key; 410 this.iconType = this.data.key;
411 411
412 this.showSelector(); 412 this.showSelector();
413 413
414 if (policyManaged) 414 if (policyManaged)
415 this.showManagedNetworkIndicator(); 415 this.showManagedNetworkIndicator();
416 416
417 if (activeMenu_ == this.getMenuName_()) { 417 if (activeMenu_ == this.getMenuName()) {
418 // Menu is already showing and needs to be updated. Explicitly calling 418 // Menu is already showing and needs to be updated. Explicitly calling
419 // show menu will force the existing menu to be replaced. The call 419 // show menu will force the existing menu to be replaced. The call
420 // is deferred in order to ensure that position of this element has 420 // is deferred in order to ensure that position of this element has
421 // beem properly updated. 421 // beem properly updated.
422 var self = this; 422 var self = this;
423 setTimeout(function() {self.showMenu();}, 0); 423 setTimeout(function() {self.showMenu();}, 0);
424 } 424 }
425 }, 425 },
426 426
427 /** 427 /**
428 * Creates a menu for selecting, configuring or disconnecting from a 428 * Creates a menu for selecting, configuring or disconnecting from a
429 * network. 429 * network.
430 * @return {Element} The newly created menu. 430 * @return {Element} The newly created menu.
431 */ 431 */
432 createMenu: function() { 432 createMenu: function() {
433 var menu = this.ownerDocument.createElement('div'); 433 var menu = this.ownerDocument.createElement('div');
434 menu.id = this.getMenuName_(); 434 menu.id = this.getMenuName();
435 menu.className = 'network-menu'; 435 menu.className = 'network-menu';
436 menu.hidden = true; 436 menu.hidden = true;
437 Menu.decorate(menu); 437 Menu.decorate(menu);
438 var addendum = []; 438 var addendum = [];
439 if (this.data_.key == 'wifi') { 439 if (this.data_.key == 'wifi') {
440 addendum.push({label: loadTimeData.getString('joinOtherNetwork'), 440 addendum.push({label: loadTimeData.getString('joinOtherNetwork'),
441 command: 'add', 441 command: 'add',
442 data: {networkType: Constants.TYPE_WIFI, 442 data: {networkType: Constants.TYPE_WIFI,
443 servicePath: ''}}); 443 servicePath: ''}});
444 } else if (this.data_.key == 'cellular') { 444 } else if (this.data_.key == 'cellular') {
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
557 return menu; 557 return menu;
558 }, 558 },
559 559
560 /** 560 /**
561 * Determines if a menu can be updated on the fly. Menus that cannot be 561 * Determines if a menu can be updated on the fly. Menus that cannot be
562 * updated are fully regenerated using createMenu. The advantage of 562 * updated are fully regenerated using createMenu. The advantage of
563 * updating a menu is that it can preserve ordering of networks avoiding 563 * updating a menu is that it can preserve ordering of networks avoiding
564 * entries from jumping around after an update. 564 * entries from jumping around after an update.
565 */ 565 */
566 canUpdateMenu: function() { 566 canUpdateMenu: function() {
567 return this.data_.key == 'wifi' && activeMenu_ == this.getMenuName_(); 567 return this.data_.key == 'wifi' && activeMenu_ == this.getMenuName();
568 }, 568 },
569 569
570 /** 570 /**
571 * Updates an existing menu. Updated menus preserve ordering of prior 571 * Updates an existing menu. Updated menus preserve ordering of prior
572 * entries. During the update process, the ordering may differ from the 572 * entries. During the update process, the ordering may differ from the
573 * preferred ordering as determined by the network library. If the 573 * preferred ordering as determined by the network library. If the
574 * ordering becomes potentially out of sync, then the updated menu is 574 * ordering becomes potentially out of sync, then the updated menu is
575 * marked for disposal on close. Reopening the menu will force a 575 * marked for disposal on close. Reopening the menu will force a
576 * regeneration, which will in turn fix the ordering. 576 * regeneration, which will in turn fix the ordering.
577 * @return {boolean} True if successfully updated. 577 * @return {boolean} True if successfully updated.
578 */ 578 */
579 updateMenu: function() { 579 updateMenu: function() {
580 if (!this.canUpdateMenu()) 580 if (!this.canUpdateMenu())
581 return false; 581 return false;
582 var oldMenu = $(this.getMenuName_()); 582 var oldMenu = $(this.getMenuName());
583 var group = oldMenu.getElementsByClassName('network-menu-group')[0]; 583 var group = oldMenu.getElementsByClassName('network-menu-group')[0];
584 if (!group) 584 if (!group)
585 return false; 585 return false;
586 var newMenu = this.createMenu(); 586 var newMenu = this.createMenu();
587 var discardOnClose = false; 587 var discardOnClose = false;
588 var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu); 588 var oldNetworkButtons = this.extractNetworkConnectButtons_(oldMenu);
589 var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu); 589 var newNetworkButtons = this.extractNetworkConnectButtons_(newMenu);
590 for (var key in oldNetworkButtons) { 590 for (var key in oldNetworkButtons) {
591 if (newNetworkButtons[key]) { 591 if (newNetworkButtons[key]) {
592 group.replaceChild(newNetworkButtons[key].button, 592 group.replaceChild(newNetworkButtons[key].button,
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after
750 var NetworkList = cr.ui.define('list'); 750 var NetworkList = cr.ui.define('list');
751 751
752 NetworkList.prototype = { 752 NetworkList.prototype = {
753 __proto__: List.prototype, 753 __proto__: List.prototype,
754 754
755 /** @inheritDoc */ 755 /** @inheritDoc */
756 decorate: function() { 756 decorate: function() {
757 List.prototype.decorate.call(this); 757 List.prototype.decorate.call(this);
758 this.startBatchUpdates(); 758 this.startBatchUpdates();
759 this.autoExpands = true; 759 this.autoExpands = true;
760 this.addEventListener('blur', this.onBlur_);
761 this.dataModel = new ArrayDataModel([]); 760 this.dataModel = new ArrayDataModel([]);
761 this.selectionModel = new ListSingleSelectionModel();
762 this.addEventListener('blur', this.onBlur_.bind(this));
763 this.selectionModel.addEventListener('change',
764 this.onSelectionChange_.bind(this));
762 765
763 // Wi-Fi control is always visible. 766 // Wi-Fi control is always visible.
764 this.update({key: 'wifi', networkList: []}); 767 this.update({key: 'wifi', networkList: []});
765 768
766 if (airplaneModeAvailable_()) { 769 if (airplaneModeAvailable_()) {
767 this.update({key: 'airplaneMode', 770 this.update({key: 'airplaneMode',
768 subtitle: loadTimeData.getString('airplaneModeLabel'), 771 subtitle: loadTimeData.getString('airplaneModeLabel'),
769 command: function() { 772 command: function() {
770 chrome.send('toggleAirplaneMode'); 773 chrome.send('toggleAirplaneMode');
771 }}); 774 }});
(...skipping 24 matching lines...) Expand all
796 * When the list loses focus, unselect all items in the list and close the 799 * When the list loses focus, unselect all items in the list and close the
797 * active menu. 800 * active menu.
798 * @private 801 * @private
799 */ 802 */
800 onBlur_: function() { 803 onBlur_: function() {
801 this.selectionModel.unselectAll(); 804 this.selectionModel.unselectAll();
802 closeMenu_(); 805 closeMenu_();
803 }, 806 },
804 807
805 /** 808 /**
809 * Close bubble and menu when a different list item is selected.
810 * @param {Event} event Event detailing the selection change.
811 * @private
812 */
813 onSelectionChange_: function(event) {
814 OptionsPage.hideBubble();
815 // A list item may temporarily become unselected while it is constructing
816 // its menu. The menu should therefore only be closed if a different item
817 // is selected, not when the menu's owner item is deselected.
818 if (activeMenu_) {
819 for (var i = 0; i < event.changes.length; ++i) {
820 if (event.changes[i].selected) {
821 var item = this.dataModel.item(event.changes[i].index);
822 if (!item.getMenuName || item.getMenuName() != activeMenu_) {
823 closeMenu_();
824 return;
825 }
826 }
827 }
828 }
829 },
830
831 /**
806 * Finds the index of a network item within the data model based on 832 * Finds the index of a network item within the data model based on
807 * category. 833 * category.
808 * @param {string} key Unique key for the item in the list. 834 * @param {string} key Unique key for the item in the list.
809 * @return {number} The index of the network item, or |undefined| if it is 835 * @return {number} The index of the network item, or |undefined| if it is
810 * not found. 836 * not found.
811 */ 837 */
812 indexOf: function(key) { 838 indexOf: function(key) {
813 var size = this.dataModel.length; 839 var size = this.dataModel.length;
814 for (var i = 0; i < size; i++) { 840 for (var i = 0; i < size; i++) {
815 var entry = this.dataModel.item(i); 841 var entry = this.dataModel.item(i);
(...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after
984 networkList.deleteItem('vpn'); 1010 networkList.deleteItem('vpn');
985 networkList.updateToggleControl('airplaneMode', data.airplaneMode); 1011 networkList.updateToggleControl('airplaneMode', data.airplaneMode);
986 networkList.endBatchUpdates(); 1012 networkList.endBatchUpdates();
987 }; 1013 };
988 1014
989 /** 1015 /**
990 * Element for indicating a policy managed network. 1016 * Element for indicating a policy managed network.
991 * @constructor 1017 * @constructor
992 */ 1018 */
993 function ManagedNetworkIndicator() { 1019 function ManagedNetworkIndicator() {
994 var el = cr.doc.createElement('div'); 1020 var el = cr.doc.createElement('span');
995 el.__proto__ = ManagedNetworkIndicator.prototype; 1021 el.__proto__ = ManagedNetworkIndicator.prototype;
996 el.decorate(); 1022 el.decorate();
997 return el; 1023 return el;
998 } 1024 }
999 1025
1000 ManagedNetworkIndicator.prototype = { 1026 ManagedNetworkIndicator.prototype = {
1001 __proto__: ControlledSettingIndicator.prototype, 1027 __proto__: ControlledSettingIndicator.prototype,
1002 1028
1003 /** @inheritDoc */ 1029 /** @inheritDoc */
1004 decorate: function() { 1030 decorate: function() {
1005 ControlledSettingIndicator.prototype.decorate.call(this); 1031 ControlledSettingIndicator.prototype.decorate.call(this);
1006 this.controlledBy = 'policy'; 1032 this.controlledBy = 'policy';
1007 var policyLabel = loadTimeData.getString('managedNetwork'); 1033 var policyLabel = loadTimeData.getString('managedNetwork');
1008 this.setAttribute('textPolicy', policyLabel); 1034 this.setAttribute('textPolicy', policyLabel);
1009 this.className = 'controlled-setting-indicator'; 1035 this.className = 'controlled-setting-indicator';
1010 // The default popup clips to the bounds of the list of networks in the 1036 this.removeAttribute('tabindex');
1011 // drop-down because it has enforced size constraints with auto- 1037 },
1012 // scrolling. Use a tooltip in place of the bubble popup until the 1038
1013 // clipping issues are resolved. 1039 /** @inheritDoc */
1014 this.setAttribute('title', policyLabel); 1040 handleEvent: function(event) {
1015 this.addEventListener('click', function(e) { 1041 // Prevent focus blurring as that would close any currently open menu.
1016 e.preventDefault(); 1042 if (event.type == 'mousedown')
1017 e.stopPropagation(); 1043 return;
1018 }); 1044 ControlledSettingIndicator.prototype.handleEvent.call(this, event);
1019 } 1045 },
1046
1047 /**
1048 * Handle mouse events received by the bubble, preventing focus blurring as
1049 * that would close any currently open menu and propagation to any elements
1050 * located behind the bubble.
1051 * @param {Event} Mouse event.
1052 */
1053 stopEvent: function(event) {
1054 event.preventDefault();
1055 event.stopPropagation();
1056 },
1057
1058 /** @inheritDoc */
1059 toggleBubble_: function() {
1060 if (activeMenu_ && !$(activeMenu_).contains(this))
1061 closeMenu_();
1062 ControlledSettingIndicator.prototype.toggleBubble_.call(this);
1063 if (this.isShowingBubble) {
1064 var bubble = OptionsPage.getVisibleBubble();
1065 bubble.addEventListener('mousedown', this.stopEvent);
1066 bubble.addEventListener('click', this.stopEvent);
1067 }
1068 },
1020 }; 1069 };
1021 1070
1022 /** 1071 /**
1023 * Updates the list of available networks and their status, filtered by 1072 * Updates the list of available networks and their status, filtered by
1024 * network type. 1073 * network type.
1025 * @param {string} category The type of network. 1074 * @param {string} category The type of network.
1026 * @param {Array} available The list of available networks and their status. 1075 * @param {Array} available The list of available networks and their status.
1027 * @param {Array} remembered The list of remmebered networks. 1076 * @param {Array} remembered The list of remmebered networks.
1028 */ 1077 */
1029 function loadData_(category, available, remembered) { 1078 function loadData_(category, available, remembered) {
(...skipping 85 matching lines...) Expand 10 before | Expand all | Expand 10 after
1115 * Whether the Network list is disabled. Only used for display purpose. 1164 * Whether the Network list is disabled. Only used for display purpose.
1116 * @type {boolean} 1165 * @type {boolean}
1117 */ 1166 */
1118 cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR); 1167 cr.defineProperty(NetworkList, 'disabled', cr.PropertyKind.BOOL_ATTR);
1119 1168
1120 // Export 1169 // Export
1121 return { 1170 return {
1122 NetworkList: NetworkList 1171 NetworkList: NetworkList
1123 }; 1172 };
1124 }); 1173 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698