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 * @typedef {{name: string,
10 * connecting: boolean,
11 * connectable: boolean,
12 * pairing: (string|undefined),
13 * passkey: (number|undefined),
14 * pincode: (string|undefined),
15 * entered: (number|undefined)}}
19 cr
.define('options.system.bluetooth', function() {
20 /** @const */ var ArrayDataModel
= cr
.ui
.ArrayDataModel
;
21 /** @const */ var DeletableItem
= options
.DeletableItem
;
22 /** @const */ var DeletableItemList
= options
.DeletableItemList
;
23 /** @const */ var ListSingleSelectionModel
= cr
.ui
.ListSingleSelectionModel
;
26 * Bluetooth settings constants.
28 function Constants() {}
31 * Creates a new bluetooth list item.
32 * @param {BluetoothDevice} device Description of the Bluetooth device.
34 * @extends {options.DeletableItem}
36 function BluetoothListItem(device
) {
37 var el
= cr
.doc
.createElement('div');
38 el
.__proto__
= BluetoothListItem
.prototype;
40 for (var key
in device
)
41 el
.data
[key
] = device
[key
];
43 // Only show the close button for paired devices, but not for connecting
45 el
.deletable
= device
.paired
&& !device
.connecting
;
49 BluetoothListItem
.prototype = {
50 __proto__
: DeletableItem
.prototype,
53 * Description of the Bluetooth device.
54 * @type {?BluetoothDevice}
59 decorate: function() {
60 DeletableItem
.prototype.decorate
.call(this);
61 var label
= this.ownerDocument
.createElement('div');
62 label
.className
= 'bluetooth-device-label';
63 this.classList
.add('bluetooth-device');
64 // There are four kinds of devices we want to distinguish:
65 // * Connecting devices: in bold with a "connecting" label,
66 // * Connected devices: in bold,
67 // * Paired, not connected but connectable devices: regular and
68 // * Paired, not connected and not connectable devices: grayed out.
69 this.connected
= this.data
.connecting
||
70 (this.data
.paired
&& this.data
.connected
);
71 this.notconnectable
= this.data
.paired
&& !this.data
.connecting
&&
72 !this.data
.connected
&& !this.data
.connectable
;
73 // "paired" devices are those that are remembered but not connected.
74 this.paired
= this.data
.paired
&& !this.data
.connected
&&
75 this.data
.connectable
;
77 var content
= this.data
.name
;
78 // Update the device's label according to its state. A "connecting" device
79 // can be in the process of connecting and pairing, so we check connecting
81 if (this.data
.connecting
) {
82 content
= loadTimeData
.getStringF('bluetoothDeviceConnecting',
85 label
.textContent
= content
;
86 this.contentElement
.appendChild(label
);
91 * Class for displaying a list of Bluetooth devices.
93 * @extends {options.DeletableItemList}
95 var BluetoothDeviceList
= cr
.ui
.define('list');
97 BluetoothDeviceList
.prototype = {
98 __proto__
: DeletableItemList
.prototype,
101 * Height of a list entry in px.
108 * Width of a list entry in px.
115 decorate: function() {
116 DeletableItemList
.prototype.decorate
.call(this);
117 // Force layout of all items even if not in the viewport to address
118 // errors in scroll positioning when the list is hidden during initial
119 // layout. The impact on performance should be minimal given that the
120 // list is not expected to grow very large. Fixed height items are also
121 // required to avoid caching incorrect sizes during layout of a hidden
123 this.autoExpands
= true;
124 this.fixedHeight
= true;
126 this.selectionModel
= new ListSingleSelectionModel();
130 * Adds a bluetooth device to the list of available devices. A check is
131 * made to see if the device is already in the list, in which case the
132 * existing device is updated.
133 * @param {{name: string,
136 * connected: boolean,
137 * connecting: boolean,
138 * connectable: boolean,
139 * pairing: (string|undefined),
140 * passkey: (number|undefined),
141 * pincode: (string|undefined),
142 * entered: (number|undefined)}} device
143 * Description of the bluetooth device.
144 * @return {boolean} True if the devies was successfully added or updated.
146 appendDevice: function(device
) {
147 var selectedDevice
= this.getSelectedDevice_();
148 var index
= this.find(device
.address
);
149 if (index
== undefined) {
150 this.dataModel
.push(device
);
153 this.dataModel
.splice(index
, 1, device
);
154 this.redrawItem(index
);
156 this.updateListVisibility_();
158 this.setSelectedDevice_(selectedDevice
);
163 * Forces a revailidation of the list content. Deleting a single item from
164 * the list results in a stale cache requiring an invalidation.
165 * @param {string=} opt_selection Optional address of device to select
166 * after refreshing the list.
168 refresh: function(opt_selection
) {
169 // TODO(kevers): Investigate if the stale cache issue can be fixed in
171 var selectedDevice
= opt_selection
? opt_selection
:
172 this.getSelectedDevice_();
176 this.setSelectedDevice_(selectedDevice
);
180 * Retrieves the address of the selected device, or null if no device is
182 * @return {(string|undefined)} Address of selected device or null.
185 getSelectedDevice_: function() {
186 var selection
= this.selectedItem
;
188 return selection
.address
;
193 * Selects the device with the matching address.
194 * @param {string} address The unique address of the device.
197 setSelectedDevice_: function(address
) {
198 var index
= this.find(address
);
199 if (index
!= undefined)
200 this.selectionModel
.selectRange(index
, index
);
204 * Perges all devices from the list.
207 this.dataModel
= new ArrayDataModel([]);
209 this.updateListVisibility_();
213 * Returns the index of the list entry with the matching address.
214 * @param {string} address Unique address of the Bluetooth device.
215 * @return {number|undefined} Index of the matching entry or
216 * undefined if no match found.
218 find: function(address
) {
219 var size
= this.dataModel
.length
;
220 for (var i
= 0; i
< size
; i
++) {
221 var entry
= this.dataModel
.item(i
);
222 if (entry
.address
== address
)
229 * @param {BluetoothDevice} entry
231 createItem: function(entry
) {
232 return new BluetoothListItem(entry
);
236 * Overrides the default implementation, which is used to compute the
237 * size of an element in the list. The default implementation relies
238 * on adding a placeholder item to the list and fetching its size and
239 * position. This strategy does not work if an item is added to the list
240 * while it is hidden, as the computed metrics will all be zero in that
242 * @return {{height: number, marginTop: number, marginBottom: number,
243 * width: number, marginLeft: number, marginRight: number}}
244 * The height and width of the item, taking margins into account,
245 * and the margins themselves.
247 measureItem: function() {
249 height
: this.itemHeight_
,
252 width
: this.itemWidth_
,
259 * Override the default implementation to return a predetermined size,
260 * which in turns allows proper layout of items even if the list is hidden.
261 * @return {{height: number, width: number}} Dimensions of a single item in
262 * the list of bluetooth device.
265 getDefaultItemSize_: function() {
267 height
: this.itemHeight_
,
268 width
: this.itemWidth_
273 * Override base implementation of handleClick, which unconditionally
274 * removes the item. In this case, removal of the element is deferred
275 * pending confirmation from the Bluetooth adapter.
276 * @param {Event} e The click event object.
279 handleClick: function(e
) {
283 var target
= /** @type {HTMLElement} */(e
.target
);
284 if (!target
.classList
.contains('row-delete-button'))
287 var item
= this.getListItemAncestor(target
);
288 var selected
= this.selectionModel
.selectedIndex
;
289 var index
= this.getIndexOfListItem(item
);
290 if (item
&& item
.deletable
) {
291 if (selected
!= index
)
292 this.setSelectedDevice_(item
.data
.address
);
293 // Device is busy until we hear back from the Bluetooth adapter.
294 // Prevent double removal request.
295 item
.deletable
= false;
296 // TODO(kevers): Provide visual feedback that the device is busy.
298 // Inform the bluetooth adapter that we are disconnecting or
299 // forgetting the device.
300 chrome
.send('updateBluetoothDevice',
301 [item
.data
.address
, item
.connected
? 'disconnect' : 'forget']);
303 chrome
.send('coreOptionsUserMetricsAction',
304 ['Options_BluetoothRemoveDevice']);
309 deleteItemAtIndex: function(index
) {
310 var selectedDevice
= this.getSelectedDevice_();
311 this.dataModel
.splice(index
, 1);
312 this.refresh(selectedDevice
);
313 this.updateListVisibility_();
317 * If the list has an associated empty list placholder then update the
318 * visibility of the list and placeholder.
321 updateListVisibility_: function() {
322 var empty
= this.dataModel
.length
== 0;
323 var listPlaceHolderID
= this.id
+ '-empty-placeholder';
324 if ($(listPlaceHolderID
)) {
325 if (this.hidden
!= empty
) {
327 $(listPlaceHolderID
).hidden
= !empty
;
334 cr
.defineProperty(BluetoothListItem
, 'connected', cr
.PropertyKind
.BOOL_ATTR
);
336 cr
.defineProperty(BluetoothListItem
, 'paired', cr
.PropertyKind
.BOOL_ATTR
);
338 cr
.defineProperty(BluetoothListItem
, 'connecting', cr
.PropertyKind
.BOOL_ATTR
);
340 cr
.defineProperty(BluetoothListItem
, 'notconnectable',
341 cr
.PropertyKind
.BOOL_ATTR
);
344 BluetoothListItem
: BluetoothListItem
,
345 BluetoothDeviceList
: BluetoothDeviceList
,