Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / options / chromeos / bluetooth_device_list.js
blobede390fe4db5cc4e4fe0b35dd84d4e6cd1b7fdd6
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.
5 cr.define('options.system.bluetooth', function() {
6   /** @const */ var ArrayDataModel = cr.ui.ArrayDataModel;
7   /** @const */ var DeletableItem = options.DeletableItem;
8   /** @const */ var DeletableItemList = options.DeletableItemList;
9   /** @const */ var ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
11   /**
12    * Bluetooth settings constants.
13    */
14   function Constants() {}
16   /**
17    * Creates a new bluetooth list item.
18    * @param {{name: string,
19    *          address: string,
20    *          paired: boolean,
21    *          connected: boolean,
22    *          connecting: boolean,
23    *          connectable: boolean,
24    *          pairing: string|undefined,
25    *          passkey: number|undefined,
26    *          pincode: string|undefined,
27    *          entered: number|undefined}} device
28    *    Description of the Bluetooth device.
29    * @constructor
30    * @extends {options.DeletableItem}
31    */
32   function BluetoothListItem(device) {
33     var el = cr.doc.createElement('div');
34     el.__proto__ = BluetoothListItem.prototype;
35     el.data = {};
36     for (var key in device)
37       el.data[key] = device[key];
38     el.decorate();
39     // Only show the close button for paired devices, but not for connecting
40     // devices.
41     el.deletable = device.paired && !device.connecting;
42     return el;
43   }
45   BluetoothListItem.prototype = {
46     __proto__: DeletableItem.prototype,
48     /**
49      * Description of the Bluetooth device.
50      * @type {{name: string,
51      *         address: string,
52      *         paired: boolean,
53      *         connected: boolean,
54      *         connecting: boolean,
55      *         connectable: boolean,
56      *         pairing: string|undefined,
57      *         passkey: number|undefined,
58      *         pincode: string|undefined,
59      *         entered: number|undefined}}
60      */
61     data: null,
63     /** @override */
64     decorate: function() {
65       DeletableItem.prototype.decorate.call(this);
66       var label = this.ownerDocument.createElement('div');
67       label.className = 'bluetooth-device-label';
68       this.classList.add('bluetooth-device');
69       // There are four kinds of devices we want to distinguish:
70       //  * Connecting devices: in bold with a "connecting" label,
71       //  * Connected devices: in bold,
72       //  * Paired, not connected but connectable devices: regular and
73       //  * Paired, not connected and not connectable devices: grayed out.
74       this.connected = this.data.connecting ||
75           (this.data.paired && this.data.connected);
76       this.notconnectable = this.data.paired && !this.data.connecting &&
77           !this.data.connected && !this.data.connectable;
78       // "paired" devices are those that are remembered but not connected.
79       this.paired = this.data.paired && !this.data.connected &&
80           this.data.connectable;
82       var content = this.data.name;
83       // Update the device's label according to its state. A "connecting" device
84       // can be in the process of connecting and pairing, so we check connecting
85       // first.
86       if (this.data.connecting) {
87         content = loadTimeData.getStringF('bluetoothDeviceConnecting',
88             this.data.name);
89       }
90       label.textContent = content;
91       this.contentElement.appendChild(label);
92     },
93   };
95   /**
96    * Class for displaying a list of Bluetooth devices.
97    * @constructor
98    * @extends {options.DeletableItemList}
99    */
100   var BluetoothDeviceList = cr.ui.define('list');
102   BluetoothDeviceList.prototype = {
103     __proto__: DeletableItemList.prototype,
105     /**
106      * Height of a list entry in px.
107      * @type {number}
108      * @private
109      */
110     itemHeight_: 32,
112     /**
113      * Width of a list entry in px.
114      * @type {number}
115      * @private.
116      */
117     itemWidth_: 400,
119     /** @override */
120     decorate: function() {
121       DeletableItemList.prototype.decorate.call(this);
122       // Force layout of all items even if not in the viewport to address
123       // errors in scroll positioning when the list is hidden during initial
124       // layout. The impact on performance should be minimal given that the
125       // list is not expected to grow very large. Fixed height items are also
126       // required to avoid caching incorrect sizes during layout of a hidden
127       // list.
128       this.autoExpands = true;
129       this.fixedHeight = true;
130       this.clear();
131       this.selectionModel = new ListSingleSelectionModel();
132     },
134     /**
135      * Adds a bluetooth device to the list of available devices. A check is
136      * made to see if the device is already in the list, in which case the
137      * existing device is updated.
138      * @param {{name: string,
139      *          address: string,
140      *          paired: boolean,
141      *          connected: boolean,
142      *          connecting: boolean,
143      *          connectable: boolean,
144      *          pairing: string|undefined,
145      *          passkey: number|undefined,
146      *          pincode: string|undefined,
147      *          entered: number|undefined}} device
148      *     Description of the bluetooth device.
149      * @return {boolean} True if the devies was successfully added or updated.
150      */
151     appendDevice: function(device) {
152       var selectedDevice = this.getSelectedDevice_();
153       var index = this.find(device.address);
154       if (index == undefined) {
155         this.dataModel.push(device);
156         this.redraw();
157       } else {
158         this.dataModel.splice(index, 1, device);
159         this.redrawItem(index);
160       }
161       this.updateListVisibility_();
162       if (selectedDevice)
163         this.setSelectedDevice_(selectedDevice);
164       return true;
165     },
167     /**
168      * Forces a revailidation of the list content. Deleting a single item from
169      * the list results in a stale cache requiring an invalidation.
170      * @param {string=} opt_selection Optional address of device to select
171      *     after refreshing the list.
172      */
173     refresh: function(opt_selection) {
174       // TODO(kevers): Investigate if the stale cache issue can be fixed in
175       // cr.ui.list.
176       var selectedDevice = opt_selection ? opt_selection :
177           this.getSelectedDevice_();
178       this.invalidate();
179       this.redraw();
180       if (selectedDevice)
181         this.setSelectedDevice_(selectedDevice);
182     },
184     /**
185      * Retrieves the address of the selected device, or null if no device is
186      * selected.
187      * @return {?string} Address of selected device or null.
188      * @private
189      */
190     getSelectedDevice_: function() {
191       var selection = this.selectedItem;
192       if (selection)
193         return selection.address;
194       return null;
195     },
197     /**
198      * Selects the device with the matching address.
199      * @param {string} address The unique address of the device.
200      * @private
201      */
202     setSelectedDevice_: function(address) {
203       var index = this.find(address);
204       if (index != undefined)
205         this.selectionModel.selectRange(index, index);
206     },
208     /**
209      * Perges all devices from the list.
210      */
211     clear: function() {
212       this.dataModel = new ArrayDataModel([]);
213       this.redraw();
214       this.updateListVisibility_();
215     },
217     /**
218      * Returns the index of the list entry with the matching address.
219      * @param {string} address Unique address of the Bluetooth device.
220      * @return {number|undefined} Index of the matching entry or
221      * undefined if no match found.
222      */
223     find: function(address) {
224       var size = this.dataModel.length;
225       for (var i = 0; i < size; i++) {
226         var entry = this.dataModel.item(i);
227         if (entry.address == address)
228           return i;
229       }
230     },
232     /** @override */
233     createItem: function(entry) {
234       return new BluetoothListItem(entry);
235     },
237     /**
238      * Overrides the default implementation, which is used to compute the
239      * size of an element in the list.  The default implementation relies
240      * on adding a placeholder item to the list and fetching its size and
241      * position. This strategy does not work if an item is added to the list
242      * while it is hidden, as the computed metrics will all be zero in that
243      * case.
244      * @return {{height: number, marginTop: number, marginBottom: number,
245      *     width: number, marginLeft: number, marginRight: number}}
246      *     The height and width of the item, taking margins into account,
247      *     and the margins themselves.
248      */
249     measureItem: function() {
250       return {
251         height: this.itemHeight_,
252         marginTop: 0,
253         marginBotton: 0,
254         width: this.itemWidth_,
255         marginLeft: 0,
256         marginRight: 0
257       };
258     },
260     /**
261      * Override the default implementation to return a predetermined size,
262      * which in turns allows proper layout of items even if the list is hidden.
263      * @return {height: number, width: number} Dimensions of a single item in
264      *     the list of bluetooth device.
265      * @private.
266      */
267     getDefaultItemSize_: function() {
268       return {
269         height: this.itemHeight_,
270         width: this.itemWidth_
271       };
272     },
274     /**
275      * Override base implementation of handleClick_, which unconditionally
276      * removes the item.  In this case, removal of the element is deferred
277      * pending confirmation from the Bluetooth adapter.
278      * @param {Event} e The click event object.
279      * @private
280      */
281     handleClick_: function(e) {
282       if (this.disabled)
283         return;
285       var target = e.target;
286       if (!target.classList.contains('row-delete-button'))
287         return;
289       var item = this.getListItemAncestor(target);
290       var selected = this.selectionModel.selectedIndex;
291       var index = this.getIndexOfListItem(item);
292       if (item && item.deletable) {
293         if (selected != index)
294           this.setSelectedDevice_(item.data.address);
295         // Device is busy until we hear back from the Bluetooth adapter.
296         // Prevent double removal request.
297         item.deletable = false;
298         // TODO(kevers): Provide visual feedback that the device is busy.
300         // Inform the bluetooth adapter that we are disconnecting or
301         // forgetting the device.
302         chrome.send('updateBluetoothDevice',
303           [item.data.address, item.connected ? 'disconnect' : 'forget']);
304       }
305     },
307     /** @override */
308     deleteItemAtIndex: function(index) {
309       var selectedDevice = this.getSelectedDevice_();
310       this.dataModel.splice(index, 1);
311       this.refresh(selectedDevice);
312       this.updateListVisibility_();
313     },
315     /**
316      * If the list has an associated empty list placholder then update the
317      * visibility of the list and placeholder.
318      * @private
319      */
320     updateListVisibility_: function() {
321       var empty = this.dataModel.length == 0;
322       var listPlaceHolderID = this.id + '-empty-placeholder';
323       if ($(listPlaceHolderID)) {
324         if (this.hidden != empty) {
325           this.hidden = empty;
326           $(listPlaceHolderID).hidden = !empty;
327           this.refresh();
328         }
329       }
330     },
331   };
333   cr.defineProperty(BluetoothListItem, 'connected', cr.PropertyKind.BOOL_ATTR);
335   cr.defineProperty(BluetoothListItem, 'paired', cr.PropertyKind.BOOL_ATTR);
337   cr.defineProperty(BluetoothListItem, 'connecting', cr.PropertyKind.BOOL_ATTR);
339   cr.defineProperty(BluetoothListItem, 'notconnectable',
340       cr.PropertyKind.BOOL_ATTR);
342   return {
343     BluetoothListItem: BluetoothListItem,
344     BluetoothDeviceList: BluetoothDeviceList,
345     Constants: Constants
346   };