1 // Copyright (c) 2013 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.
5 cr
.define('network.status', function() {
6 var ArrayDataModel
= cr
.ui
.ArrayDataModel
;
8 var ListItem
= cr
.ui
.ListItem
;
9 var ListSingleSelectionModel
= cr
.ui
.ListSingleSelectionModel
;
12 * Returns the entries of |dataModel| as an array.
13 * @param {ArrayDataModel} dataModel .
16 function dataModelToArray(dataModel
) {
18 for (var i
= 0; i
< dataModel
.length
; i
++) {
19 array
.push(dataModel
.item(i
));
25 * Calculates both set difference of |a| and |b| and returns them in an array:
27 * @param {Array<T>} a .
28 * @param {Array<T>} b .
29 * @param {function(T): K} toKey .
30 * @return {Array<Array<T>>} .
32 function differenceBy(a
, b
, toKey
) {
34 a
.forEach(function(elA
) {
35 inA
[toKey(elA
)] = elA
;
38 b
.forEach(function(elB
) {
39 var keyB
= toKey(elB
);
46 for (var keyA
in inA
) {
47 aMinusB
.push(inA
[keyA
]);
49 return [aMinusB
, bMinusA
];
53 * Updates the data model of |uiList| to |newList|. Ensures that unchanged
54 * entries are not touched. Doesn't preserve the order of |newList|.
55 * @param {List} uiList .
56 * @param {Array} newList .
58 function updateDataModel(uiList
, newList
) {
59 uiList
.startBatchUpdates();
60 var dataModel
= uiList
.dataModel
;
61 var diff
= differenceBy(dataModelToArray(dataModel
), newList
,
62 function(e
) { return e
[0]; });
63 var toRemove
= diff
[0];
65 toRemove
.forEach(function(element
) {
66 dataModel
.splice(dataModel
.indexOf(element
), 1);
68 dataModel
.splice
.apply(dataModel
, [dataModel
.length
, 0].concat(toAdd
));
69 uiList
.endBatchUpdates();
73 * Creates a map of the entries of |array|. Each entry is associated to the
75 * @param {Array<T>} array .
76 * @param {function(T): K} getKey .
77 * @return {Object<K, T>} .
79 function createMapFromList(array
, getKey
) {
81 array
.forEach(function(entry
) {
82 result
[getKey(entry
)] = entry
;
88 * Wraps each entry in |array| into an array. The result contains rows with
90 * @param {Array<T>} array .
91 * @return {Array<Array<T>>} .
93 function arrayToTable(array
) {
94 return array
.map(function(e
) {
100 * The NetworkStatusList contains various button types according to the
101 * following hierarchy. Note: this graph doesn't depict inheritance but
102 * ownership of instances of these types.
105 * +-- TechnologyButton
106 * +-- NestedStatusButton
108 * Inheritance hierarchy:
111 * +-- UnfoldingButton
112 * | +-- TechnologyButton
113 * +-- NestedStatusButton
117 * Base class for all buttons in the NetworkStatusList. |key| is used to
118 * identify this button.
119 * After construction, update() has to be called!
120 * @param {NetworkStatusList} networkStatus .
121 * @param {string} key .
124 function Button(networkStatus
, key
) {
125 var el
= cr
.doc
.createElement('li');
126 el
.__proto__
= Button
.prototype;
127 el
.decorate(networkStatus
, key
);
132 __proto__
: ListItem
.prototype,
137 decorate: function(networkStatus
, key
) {
138 ListItem
.prototype.decorate
.call(this);
139 this.networkStatus_
= networkStatus
;
141 this.className
= 'network-status-button';
143 var textContent
= this.ownerDocument
.createElement('div');
144 textContent
.className
= 'network-status-button-labels';
146 var title
= this.ownerDocument
.createElement('div');
147 title
.className
= 'nested-status-button-title';
148 textContent
.appendChild(title
);
151 var subTitle
= this.ownerDocument
.createElement('div');
152 subTitle
.className
= 'nested-status-button-subtitle';
153 textContent
.appendChild(subTitle
);
154 this.subTitle_
= subTitle
;
156 this.appendChild(textContent
);
167 * Should be called if the data presented by this button
168 * changed. E.g. updates the button's title, icon and nested buttons.
169 * To be overriden by subclasses.
172 this.title_
.textContent
= this.key
;
177 * A button that shows the status of one particular network.
178 * @param {NetworkStatusList} networkStatus .
179 * @param {string} networkID .
183 function NestedStatusButton(networkStatus
, networkID
) {
184 var el
= new Button(networkStatus
, networkID
);
185 el
.__proto__
= NestedStatusButton
.prototype;
189 NestedStatusButton
.prototype = {
190 __proto__
: Button
.prototype,
196 var network
= this.networkStatus_
.getNetworkByID(this.networkID
);
197 this.title_
.textContent
= network
.Name
;
198 this.subTitle_
.textContent
= network
.ConnectionState
;
207 * A button (toplevel in the NetworkStatusList) that unfolds a list of nested
208 * buttons when clicked. Only one button will be unfolded at a time.
209 * @param {NetworkStatusList} networkStatus .
210 * @param {string} key .
214 function UnfoldingButton(networkStatus
, key
) {
215 var el
= new Button(networkStatus
, key
);
216 el
.__proto__
= UnfoldingButton
.prototype;
221 UnfoldingButton
.prototype = {
222 __proto__
: Button
.prototype,
227 decorate: function() {
228 this.dropdown_
= null;
229 this.addEventListener('click', this.toggleDropdown_
.bind(this));
233 * Returns the list of identifiers for which nested buttons will be created.
234 * To be overridden by subclasses.
235 * @return {Array<string>} .
237 getEntries: function() {
242 * Creates a nested button for |entry| of the current |getEntries|.
243 * To be overridden by subclasses.
244 * @param {string} entry .
245 * @return {ListItem} .
247 createNestedButton: function(entry
) {
248 return new ListItem(entry
);
252 * Creates the dropdown list containing the nested buttons.
253 * To be overridden by subclasses.
256 createDropdown: function() {
257 var list
= new List();
259 list
.createItem = function(row
) {
260 return self
.createNestedButton(row
[0]);
262 list
.autoExpands
= true;
263 list
.dataModel
= new ArrayDataModel(arrayToTable(this.getEntries()));
264 list
.selectionModel
= new ListSingleSelectionModel();
272 Button
.prototype.update
.call(this);
275 updateDataModel(this.dropdown_
, arrayToTable(this.getEntries()));
278 openDropdown_: function() {
279 var dropdown
= this.createDropdown();
280 dropdown
.className
= 'network-dropdown';
281 this.appendChild(dropdown
);
282 this.dropdown_
= dropdown
;
283 this.networkStatus_
.openDropdown
= this;
286 closeDropdown_: function() {
287 this.removeChild(this.dropdown_
);
288 this.dropdown_
= null;
291 toggleDropdown_: function() {
292 // TODO(pneubeck): request rescan
293 if (this.networkStatus_
.openDropdown
=== this) {
294 this.closeDropdown_();
295 this.networkStatus_
.openDropdown
= null;
296 } else if (this.networkStatus_
.openDropdown
) {
297 this.networkStatus_
.openDropdown
.closeDropdown_();
298 this.openDropdown_();
300 this.openDropdown_();
306 * A button (toplevel in the NetworkStatusList) that represents one network
307 * technology (like WiFi or Ethernet) and unfolds a list of nested buttons
309 * @param {NetworkStatusList} networkStatus .
310 * @param {string} technology .
311 * @extends {UnfoldingButton}
314 function TechnologyButton(networkStatus
, technology
) {
315 var el
= new UnfoldingButton(networkStatus
, technology
);
316 el
.__proto__
= TechnologyButton
.prototype;
317 el
.decorate(technology
);
321 TechnologyButton
.prototype = {
322 __proto__
: UnfoldingButton
.prototype,
325 * @param {string} technology .
328 decorate: function(technology
) {
329 this.technology_
= technology
;
335 getEntries: function() {
336 return this.networkStatus_
.getNetworkIDsOfType(this.technology_
);
342 createNestedButton: function(id
) {
344 var button
= new NestedStatusButton(this.networkStatus_
, id
);
345 button
.onclick = function(e
) {
347 self
.networkStatus_
.handleUserAction({
348 command
: 'openConfiguration',
359 createDropdown: function() {
360 var list
= UnfoldingButton
.prototype.createDropdown
.call(this);
361 var getIndex
= this.networkStatus_
.getIndexOfNetworkID
.bind(
362 this.networkStatus_
);
363 list
.dataModel
.setCompareFunction(0, function(a
, b
) {
364 return ArrayDataModel
.prototype.defaultValuesCompareFunction(
368 list
.dataModel
.sort(0, 'asc');
376 return this.technology_
;
383 UnfoldingButton
.prototype.update
.call(this);
386 this.dropdown_
.items
.forEach(function(button
) {
393 * The order of the toplevel buttons.
404 * A map from button key to index according to |BUTTON_ORDER|.
406 var BUTTON_POSITION
= {};
407 BUTTON_ORDER
.forEach(function(entry
, index
) {
408 BUTTON_POSITION
[entry
] = index
;
412 * Groups networks by type.
413 * @param {Object<Object>} networkByID A map from network ID to network
415 * @return {Object<Array<string>>} A map from network type to the list of IDs
416 * of networks of that type.
418 function createNetworkIDsByType(networkByID
) {
420 for (var id
in networkByID
) {
421 var network
= networkByID
[id
];
422 var group
= byType
[network
.Type
];
423 if (group
=== undefined) {
425 byType
[network
.Type
] = group
;
427 group
.push(network
.GUID
);
433 * A list-like control showing the available networks and controls to
434 * dis-/connect to networks and to open dialogs to create, modify and remove
435 * network configurations.
438 var NetworkStatusList
= cr
.ui
.define('list');
440 NetworkStatusList
.prototype = {
441 __proto__
: List
.prototype,
446 decorate: function() {
447 List
.prototype.decorate
.call(this);
450 * The currently open unfolding button.
451 * @type {UnfoldingButton}
453 this.openDropdown
= null;
456 * The set of technologies shown to the user.
457 * @type {Object<boolean>}
459 this.technologies_
= {};
462 * A map from network type to the array of IDs of network of that type.
463 * @type {Object<Array<string>>}
465 this.networkIDsByType_
= {};
468 * A map from network ID to the network's properties.
469 * @type {Object<Object>}
471 this.networkByID_
= {};
474 * A map from network ID to the network's position in the last received
476 * @type {Object<number>}
478 this.networkIndexByID_
= {};
481 * A function that handles the various user actions.
482 * See |setUserActionHandler|.
483 * @type {function({command: string, networkID: string})}
485 this.userActionHandler_ = function() {};
487 this.autoExpands
= true;
488 this.dataModel
= new ArrayDataModel([]);
489 this.dataModel
.setCompareFunction(0, function(a
, b
) {
490 return ArrayDataModel
.prototype.defaultValuesCompareFunction(
491 BUTTON_POSITION
[a
], BUTTON_POSITION
[b
]);
493 this.dataModel
.sort(0, 'asc');
494 this.selectionModel
= new ListSingleSelectionModel();
496 this.updateDataStructuresAndButtons_();
497 this.registerStatusListener_();
503 createItem: function(row
) {
505 if (key
in this.technologies_
) {
506 var button
= new TechnologyButton(
512 return new Button(this, key
);
517 * See |setUserActionHandler| for the possible commands.
518 * @param {{command: string, networkID: string}} action .
520 handleUserAction: function(action
) {
521 this.userActionHandler_(action
);
525 * A function that handles the various user actions.
526 * |command| will be one of
527 * - openConfiguration
528 * @param {function({command: string, networkID: string})} handler .
530 setUserActionHandler: function(handler
) {
531 this.userActionHandler_
= handler
;
535 * @param {string} technology .
536 * @return {Array<string>} Array of network IDs.
538 getNetworkIDsOfType: function(technology
) {
539 var networkIDs
= this.networkIDsByType_
[technology
];
546 * @param {string} networkID .
547 * @return {number} The index of network with |networkID| in the last
548 * received network list.
550 getIndexOfNetworkID: function(networkID
) {
551 return this.networkIndexByID_
[networkID
];
555 * @param {string} networkID .
556 * @return {Object} The last received properties of network with
559 getNetworkByID: function(networkID
) {
560 return this.networkByID_
[networkID
];
564 * @param {string} networkType .
565 * @return {?TechnologyButton} .
567 getTechnologyButtonForType_: function(networkType
) {
568 var buttons
= this.items
;
569 for (var i
= 0; i
< buttons
.length
; i
++) {
570 var button
= buttons
[i
];
571 if (button
instanceof TechnologyButton
&&
572 button
.technology
=== networkType
) {
576 console
.log('TechnologyButton for type ' + networkType
+
577 ' requested but not found.');
581 updateTechnologiesFromNetworks_: function() {
582 var newTechnologies
= {};
583 Object
.keys(this.networkIDsByType_
).forEach(function(technology
) {
584 newTechnologies
[technology
] = true;
586 this.technologies_
= newTechnologies
;
589 updateDataStructuresAndButtons_: function() {
590 this.networkIDsByType_
= createNetworkIDsByType(this.networkByID_
);
591 this.updateTechnologiesFromNetworks_();
592 var keys
= Object
.keys(this.technologies_
);
593 // Add keys of always visible toplevel buttons.
594 keys
.push('addConnection');
595 updateDataModel(this, arrayToTable(keys
));
596 this.items
.forEach(function(button
) {
602 * @param {Array<string>} networkIDs .
604 updateIndexes_: function(networkIDs
) {
605 var newNetworkIndexByID
= {};
606 networkIDs
.forEach(function(id
, index
) {
607 newNetworkIndexByID
[id
] = index
;
609 this.networkIndexByID_
= newNetworkIndexByID
;
613 * @param {Array<string>} networkIDs .
615 onNetworkListChanged_: function(networkIDs
) {
616 var diff
= differenceBy(Object
.keys(this.networkByID_
),
618 function(e
) { return e
; });
619 var toRemove
= diff
[0];
622 var addCallback
= this.addNetworkCallback_
.bind(this);
623 toAdd
.forEach(function(id
) {
624 console
.log('NetworkStatus: Network ' + id
+ ' added.');
625 chrome
.networkingPrivate
.getProperties(id
, addCallback
);
628 toRemove
.forEach(function(id
) {
629 console
.log('NetworkStatus: Network ' + id
+ ' removed.');
630 delete this.networkByID_
[id
];
633 this.updateIndexes_(networkIDs
);
634 this.updateDataStructuresAndButtons_();
638 * @param {Array<string>} networkIDs .
640 onNetworksChanged_: function(networkIDs
) {
641 var updateCallback
= this.updateNetworkCallback_
.bind(this);
642 networkIDs
.forEach(function(id
) {
643 console
.log('NetworkStatus: Network ' + id
+ ' changed.');
644 chrome
.networkingPrivate
.getProperties(id
, updateCallback
);
649 * @param {Object} network .
651 updateNetworkCallback_: function(network
) {
652 this.networkByID_
[network
.GUID
] = network
;
653 this.getTechnologyButtonForType_(network
.Type
).update();
657 * @param {Object} network .
659 addNetworkCallback_: function(network
) {
660 this.networkByID_
[network
.GUID
] = network
;
661 this.updateDataStructuresAndButtons_();
665 * @param {Array<Object>} networks .
667 setVisibleNetworks: function(networks
) {
668 this.networkByID_
= createMapFromList(
673 this.updateIndexes_(networks
.map(function(network
) {
676 this.updateDataStructuresAndButtons_();
680 * Registers |this| at the networkingPrivate extension API and requests an
681 * initial list of all networks.
683 registerStatusListener_: function() {
684 chrome
.networkingPrivate
.onNetworkListChanged
.addListener(
685 this.onNetworkListChanged_
.bind(this));
686 chrome
.networkingPrivate
.onNetworksChanged
.addListener(
687 this.onNetworksChanged_
.bind(this));
688 chrome
.networkingPrivate
.getNetworks(
689 { 'networkType': 'All', 'visible': true },
690 this.setVisibleNetworks
.bind(this));
695 NetworkStatusList
: NetworkStatusList