OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Class representing the host-list portion of the home screen UI. | 7 * Class representing the host-list portion of the home screen UI. |
8 */ | 8 */ |
9 | 9 |
10 'use strict'; | 10 'use strict'; |
11 | 11 |
12 /** @suppress {duplicate} */ | 12 /** @suppress {duplicate} */ |
13 var remoting = remoting || {}; | 13 var remoting = remoting || {}; |
14 | 14 |
15 /** | 15 /** |
16 * The deserialized form of the chromoting host as returned by Apiary. | |
17 * Note that the object has more fields than are detailed below--these | |
18 * are just the ones that we refer to directly. | |
16 * @constructor | 19 * @constructor |
17 */ | 20 */ |
18 remoting.Host = function() { | 21 remoting.Host = function() { |
19 /** @type {string} */ | 22 /** @type {string} */ |
20 this.hostName = ''; | 23 this.hostName = ''; |
21 /** @type {string} */ | 24 /** @type {string} */ |
22 this.hostId = ''; | 25 this.hostId = ''; |
23 /** @type {string} */ | 26 /** @type {string} */ |
24 this.status = ''; | 27 this.status = ''; |
25 /** @type {string} */ | 28 /** @type {string} */ |
26 this.jabberId = ''; | 29 this.jabberId = ''; |
27 /** @type {string} */ | 30 /** @type {string} */ |
28 this.publicKey = ''; | 31 this.publicKey = ''; |
29 } | 32 }; |
30 | 33 |
34 /** | |
35 * An entry in the host table. | |
36 * @constructor | |
37 */ | |
38 remoting.HostTableEntry = function() { | |
39 /** @type {remoting.Host} */ | |
40 this.host = null; | |
41 /** @type {Element} */ | |
42 this.tableRow = null; | |
43 /** @type {Element} */ | |
44 this.hostNameCell = null; | |
45 }; | |
Jamie
2011/11/17 21:42:09
I separated out the raw object (Host) we get back
simonmorris
2011/11/17 23:59:48
Maybe HostModel and HostView would be more familia
Jamie
2011/11/18 22:54:57
It's not really MVC pattern though. I'd prefer to
| |
31 | 46 |
32 /** | 47 /** |
33 * @constructor | 48 * @constructor |
34 * @param {Element} table The HTML <table> to contain host-list. | 49 * @param {Element} table The HTML <table> to contain host-list. |
35 * @param {Element} errorDiv The HTML <div> to display error messages. | 50 * @param {Element} errorDiv The HTML <div> to display error messages. |
36 */ | 51 */ |
37 remoting.HostList = function(table, errorDiv) { | 52 remoting.HostList = function(table, errorDiv) { |
38 this.table = table; | 53 this.table = table; |
39 this.errorDiv = errorDiv; | 54 this.errorDiv = errorDiv; |
40 /** @type {Array.<remoting.Host>} */ | 55 /** |
41 this.hosts = null; | 56 * @type {Array.<remoting.HostTableEntry>} |
42 } | 57 * @private |
58 */ | |
59 this.hostTableEntries = null; | |
60 }; | |
61 | |
62 /** | |
63 * Search the host list for a host with the specified id. | |
64 * @param {string} hostId The unique id of the host. | |
65 * @return {remoting.HostTableEntry?} The host table entry, if any. | |
66 */ | |
67 remoting.HostList.prototype.getHostForId = function(hostId) { | |
68 for (var i = 0; i < this.hostTableEntries.length; ++i) { | |
69 if (this.hostTableEntries[i].host.hostId == hostId) { | |
70 return this.hostTableEntries[i]; | |
71 } | |
72 } | |
73 return null; | |
74 }; | |
43 | 75 |
44 /** | 76 /** |
45 * Refresh the host list with up-to-date details. | 77 * Refresh the host list with up-to-date details. |
46 * @param {Array.<remoting.Host>} hosts The new host list. | 78 * @param {Array.<remoting.Host>} hosts The new host list. |
47 * @return {void} Nothing. | 79 * @return {void} Nothing. |
48 */ | 80 */ |
49 remoting.HostList.prototype.update = function(hosts) { | 81 remoting.HostList.prototype.update = function(hosts) { |
50 this.table.innerHTML = ''; | 82 this.table.innerHTML = ''; |
51 this.showError(null); | 83 this.showError(null); |
52 this.hosts = hosts; | 84 this.hostTableEntries = []; |
53 | 85 |
54 for (var i = 0; i < hosts.length; ++i) { | 86 for (var i = 0; i < hosts.length; ++i) { |
55 var host = hosts[i]; | 87 var host = hosts[i]; |
88 // Validate the entry to make sure it has all the fields we expect. | |
56 if (!host.hostName || !host.hostId || !host.status || !host.jabberId || | 89 if (!host.hostName || !host.hostId || !host.status || !host.jabberId || |
57 !host.publicKey) | 90 !host.publicKey) |
58 continue; | 91 continue; |
59 | 92 |
60 var hostEntry = document.createElement('tr'); | 93 var hostTableEntry = new remoting.HostTableEntry(); |
61 addClass(hostEntry, 'host-list-row'); | 94 hostTableEntry.host = host; |
95 | |
96 hostTableEntry.tableRow = document.createElement('tr'); | |
97 addClass(hostTableEntry.tableRow, 'host-list-row'); | |
62 | 98 |
63 var hostIcon = document.createElement('td'); | 99 var hostIcon = document.createElement('td'); |
100 addClass(hostIcon, 'host-list-row-start'); | |
64 var hostIconImage = document.createElement('img'); | 101 var hostIconImage = document.createElement('img'); |
65 hostIconImage.src = 'icon_host.png'; | 102 hostIconImage.src = 'icon_host.png'; |
66 hostIcon.className = 'host-list-row-start'; | 103 addClass(hostIconImage, 'host-list-main-icon'); |
67 hostIcon.appendChild(hostIconImage); | 104 hostIcon.appendChild(hostIconImage); |
68 hostEntry.appendChild(hostIcon); | 105 hostTableEntry.tableRow.appendChild(hostIcon); |
69 | 106 |
70 var hostName = document.createElement('td'); | 107 hostTableEntry.hostNameCell = document.createElement('td'); |
71 hostName.setAttribute('class', 'mode-select-label'); | 108 hostTableEntry.hostNameCell.setAttribute('class', 'mode-select-label'); |
72 hostName.appendChild(document.createTextNode(host.hostName)); | 109 hostTableEntry.hostNameCell.appendChild( |
73 hostEntry.appendChild(hostName); | 110 document.createTextNode(host.hostName)); |
111 hostTableEntry.hostNameCell.setAttribute( | |
112 'ondblclick', | |
113 'remoting.hostList.beginRenameHost("' + | |
114 host.hostId + '"); return false;'); | |
115 hostTableEntry.tableRow.appendChild(hostTableEntry.hostNameCell); | |
74 | 116 |
75 var hostStatus = document.createElement('td'); | 117 var hostStatus = document.createElement('td'); |
76 if (host.status == 'ONLINE') { | 118 if (host.status == 'ONLINE') { |
77 var connectButton = document.createElement('button'); | 119 var connectButton = document.createElement('button'); |
78 connectButton.setAttribute('class', 'mode-select-button'); | 120 connectButton.setAttribute('class', 'mode-select-button'); |
79 connectButton.setAttribute('type', 'button'); | 121 connectButton.setAttribute('type', 'button'); |
80 connectButton.setAttribute('onclick', | 122 connectButton.setAttribute('onclick', |
81 'remoting.connectHost("'+host.hostId+'")'); | 123 'remoting.connectHost("' + host.hostId + '")'); |
82 connectButton.innerHTML = | 124 connectButton.innerHTML = |
83 chrome.i18n.getMessage(/*i18n-content*/'CONNECT_BUTTON'); | 125 chrome.i18n.getMessage(/*i18n-content*/'CONNECT_BUTTON'); |
84 hostStatus.appendChild(connectButton); | 126 hostStatus.appendChild(connectButton); |
85 } else { | 127 } else { |
86 addClass(hostEntry, 'host-offline'); | 128 addClass(hostTableEntry.tableRow, 'host-offline'); |
87 hostStatus.innerHTML = chrome.i18n.getMessage(/*i18n-content*/'OFFLINE'); | 129 hostStatus.innerHTML = chrome.i18n.getMessage(/*i18n-content*/'OFFLINE'); |
88 } | 130 } |
89 hostStatus.className = 'host-list-row-end'; | 131 hostStatus.className = 'host-list-row-end'; |
90 hostEntry.appendChild(hostStatus); | 132 hostTableEntry.tableRow.appendChild(hostStatus); |
91 | 133 |
92 this.table.appendChild(hostEntry); | 134 var editButton = document.createElement('td'); |
135 editButton.setAttribute('onclick', 'remoting.hostList.beginRenameHost("' + | |
136 host.hostId + '")'); | |
137 addClass(editButton, 'clickable'); | |
138 addClass(editButton, 'host-list-edit'); | |
139 var penImage = document.createElement('img'); | |
140 penImage.src = 'icon_pencil.png'; | |
141 addClass(penImage, 'host-list-rename-icon'); | |
142 editButton.appendChild(penImage); | |
143 hostTableEntry.tableRow.appendChild(editButton); | |
144 | |
145 var removeButton = document.createElement('td'); | |
146 removeButton.setAttribute('onclick', 'remoting.hostList.removeHost("' + | |
147 host.hostId + '")'); | |
148 addClass(removeButton, 'clickable'); | |
149 addClass(removeButton, 'host-list-edit'); | |
150 var crossImage = document.createElement('img'); | |
151 crossImage.src = 'icon_cross.png'; | |
152 addClass(crossImage, 'host-list-remove-icon'); | |
153 removeButton.appendChild(crossImage); | |
154 hostTableEntry.tableRow.appendChild(removeButton); | |
simonmorris
2011/11/17 23:59:48
Clearer to make this new code a method of HostTabl
Jamie
2011/11/18 22:54:57
Good point, thanks for spotting this. I've refacto
| |
155 | |
156 this.hostTableEntries[i] = hostTableEntry; | |
157 this.table.appendChild(hostTableEntry.tableRow); | |
93 } | 158 } |
94 | 159 |
95 this.showOrHide_(this.hosts.length != 0); | 160 this.showOrHide_(this.hostTableEntries.length != 0); |
96 } | 161 }; |
97 | 162 |
98 /** | 163 /** |
99 * Display a localized error message. | 164 * Display a localized error message. |
100 * @param {remoting.Error?} errorTag The error to display, or NULL to clear any | 165 * @param {remoting.Error?} errorTag The error to display, or NULL to clear any |
101 * previous error. | 166 * previous error. |
102 * @return {void} Nothing. | 167 * @return {void} Nothing. |
103 */ | 168 */ |
104 remoting.HostList.prototype.showError = function(errorTag) { | 169 remoting.HostList.prototype.showError = function(errorTag) { |
105 this.table.innerHTML = ''; | 170 this.table.innerHTML = ''; |
106 if (errorTag) { | 171 if (errorTag) { |
107 l10n.localizeElementFromTag(this.errorDiv, | 172 l10n.localizeElementFromTag(this.errorDiv, |
108 /** @type {string} */ (errorTag)); | 173 /** @type {string} */ (errorTag)); |
109 this.showOrHide_(true); | 174 this.showOrHide_(true); |
110 } else { | 175 } else { |
111 this.errorDiv.innerText = ''; | 176 this.errorDiv.innerText = ''; |
112 } | 177 } |
113 } | 178 }; |
114 | 179 |
115 /** | 180 /** |
116 * Show or hide the host-list UI. | 181 * Show or hide the host-list UI. |
117 * @param {boolean} show True to show the UI, or false to hide it. | 182 * @param {boolean} show True to show the UI, or false to hide it. |
118 * @return {void} Nothing. | 183 * @return {void} Nothing. |
119 * @private | 184 * @private |
120 */ | 185 */ |
121 remoting.HostList.prototype.showOrHide_ = function(show) { | 186 remoting.HostList.prototype.showOrHide_ = function(show) { |
122 var parent = /** @type {Element} */ (this.table.parentNode); | 187 var parent = /** @type {Element} */ (this.table.parentNode); |
123 parent.hidden = !show; | 188 parent.hidden = !show; |
124 if (show) { | 189 if (show) { |
125 parent.style.height = parent.scrollHeight + 'px'; | 190 parent.style.height = parent.scrollHeight + 'px'; |
126 removeClass(parent, remoting.HostList.COLLAPSED_); | 191 removeClass(parent, remoting.HostList.COLLAPSED_); |
127 } else { | 192 } else { |
128 addClass(parent, remoting.HostList.COLLAPSED_); | 193 addClass(parent, remoting.HostList.COLLAPSED_); |
129 } | 194 } |
130 } | 195 }; |
196 | |
197 /** | |
198 * Remove a host from the list, and deregister it. | |
simonmorris
2011/11/17 23:59:48
Won't the user expect this to have some effect on
Jamie
2011/11/18 22:54:57
I think the long-term plan is to have hosts shut d
| |
199 * @param {string} hostId The id of the host to be removed. | |
200 * @return {void} Nothing. | |
201 */ | |
202 remoting.HostList.prototype.removeHost = function(hostId) { | |
203 /** @type {remoting.HostTableEntry} */ | |
204 var hostTableEntry = this.getHostForId(hostId); | |
205 if (!hostTableEntry) { | |
206 console.error('No host registered for id ' + hostId); | |
207 return; | |
208 } | |
209 hostTableEntry.tableRow.parentElement.removeChild(hostTableEntry.tableRow); | |
210 var index = this.hostTableEntries.indexOf(hostTableEntry); | |
211 if (index != -1) { // Since we've just found it, index must be >= 0 | |
212 this.hostTableEntries.splice(index, 1); | |
213 } | |
214 | |
215 /** @param {string} token */ | |
216 var deleteHost = function(token) { | |
217 var headers = { 'Authorization': 'OAuth ' + token }; | |
218 remoting.xhr.remove( | |
219 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, | |
220 function() {}, '', headers); | |
Wez
2011/11/17 22:58:58
Why not refresh the host list when the XHR complet
Jamie
2011/11/18 22:54:57
I'm not sure about that. It's only going to make a
| |
221 } | |
222 remoting.oauth2.callWithToken(deleteHost); | |
223 | |
224 this.showOrHide_(this.hostTableEntries.length != 0); | |
225 }; | |
226 | |
227 /** | |
228 * Prepare a host for renaming by replacing its name with an edit box. | |
229 * @param {string} hostId The id of the host to be renamed. | |
230 * @return {void} Nothing. | |
231 */ | |
232 remoting.HostList.prototype.beginRenameHost = function(hostId) { | |
233 /** @type {remoting.HostTableEntry} */ | |
234 var hostTableEntry = this.getHostForId(hostId); | |
235 if (!hostTableEntry) { | |
236 console.error('No host registered for id ' + hostId); | |
237 return; | |
238 } | |
239 var editBox = /** @type {HTMLInputElement} */ document.createElement('input'); | |
240 editBox.type = 'text'; | |
241 editBox.value = hostTableEntry.host.hostName; | |
242 editBox.setAttribute('onblur', | |
243 'remoting.hostList.commitRenameHost("' + hostId + '");'); | |
244 /** @type {remoting.HostList} */ | |
245 var that = this; | |
246 /** @param {Event} event */ | |
247 var onKeydown = function(event) { | |
248 that.onKeydown(event, hostId); | |
249 } | |
250 editBox.onkeydown = onKeydown; | |
251 hostTableEntry.hostNameCell.innerHTML = ''; | |
252 hostTableEntry.hostNameCell.appendChild(editBox); | |
253 editBox.select(); | |
simonmorris
2011/11/17 23:59:48
This could be a method of HostView.
Jamie
2011/11/18 22:54:57
My refactoring addresses this.
| |
254 }; | |
255 | |
256 /** | |
257 * Remove the edit box corresponding to the specified host, and reset its name. | |
258 * @param {remoting.HostTableEntry} hostTableEntry The host being renamed. | |
259 * @return {void} Nothing. | |
260 */ | |
261 remoting.HostList.prototype.removeEditBox = function(hostTableEntry) { | |
262 var editBox = hostTableEntry.hostNameCell.querySelector('input'); | |
263 if (editBox) { | |
264 // onblur will fire when the edit box is removed, so remove the hook. | |
265 editBox.onblur = null; | |
266 } | |
267 hostTableEntry.hostNameCell.innerHTML = ''; | |
268 hostTableEntry.hostNameCell.appendChild( | |
269 document.createTextNode(hostTableEntry.host.hostName)); | |
270 }; | |
271 | |
272 /** | |
273 * Accept the host name entered by the user. | |
274 * @param {string} hostId The id of the host to be renamed. | |
275 * @return {void} Nothing. | |
276 */ | |
277 remoting.HostList.prototype.commitRenameHost = function(hostId) { | |
278 /** @type {remoting.HostTableEntry} */ | |
279 var hostTableEntry = this.getHostForId(hostId); | |
280 if (hostTableEntry) { | |
281 var editBox = hostTableEntry.hostNameCell.querySelector('input'); | |
282 if (editBox) { | |
283 if (hostTableEntry.host.hostName != editBox.value) { | |
284 hostTableEntry.host.hostName = editBox.value; | |
285 hostTableEntry.host.status = 'OFFLINE'; | |
simonmorris
2011/11/17 23:59:48
Why is the status set here?
Jamie
2011/11/18 22:54:57
Left-over debugging code. Removed.
| |
286 /** @param {string} token */ | |
287 var renameHost = function(token) { | |
288 var headers = { | |
289 'Authorization': 'OAuth ' + token, | |
290 'content-type' : 'application/json; charset=UTF-8' | |
Wez
2011/11/17 22:58:58
nit: content-type capitalization.
Jamie
2011/11/18 22:54:57
Done.
| |
291 }; | |
292 var newHostDetails = { data: hostTableEntry.host }; | |
293 remoting.xhr.put( | |
294 'https://www.googleapis.com/chromoting/v1/@me/hosts/' + hostId, | |
295 function(xhr) {}, | |
Wez
2011/11/17 22:58:58
Why not refresh when the XHR returns?
Jamie
2011/11/18 22:54:57
Same argument as above.
| |
296 JSON.stringify(newHostDetails), | |
297 headers); | |
298 } | |
299 remoting.oauth2.callWithToken(renameHost); | |
300 } | |
301 } | |
302 } | |
303 this.removeEditBox(hostTableEntry); | |
304 }; | |
305 | |
306 /** | |
307 * Revert the host name, ignoring changes made by the user. | |
308 * @param {string} hostId The id of the host to be renamed. | |
309 * @return {void} Nothing. | |
310 */ | |
311 remoting.HostList.prototype.cancelRenameHost = function(hostId) { | |
312 var host = this.getHostForId(hostId); | |
Wez
2011/11/17 22:58:58
How can the user have cancelled editing if the hos
Jamie
2011/11/18 22:54:57
It's just a sanity check. I'd make it a DCHECK if
| |
313 if (host) { | |
314 this.removeEditBox(host); | |
315 } | |
316 }; | |
317 | |
318 /** | |
319 * Handle a key event while the user is typing a host name | |
320 * @param {Event} event The keyboard event. | |
321 * @param {string} hostId The id of the host being renamed. | |
322 * @return {void} Nothing. | |
323 */ | |
324 remoting.HostList.prototype.onKeydown = function(event, hostId) { | |
325 if (event.which == 27) { // Escape | |
326 this.cancelRenameHost(hostId); | |
327 } else if (event.which == 13) { // Enter | |
328 this.commitRenameHost(hostId); | |
329 } | |
330 }; | |
131 | 331 |
132 /** | 332 /** |
133 * Class name for the host list when it is collapsed. | 333 * Class name for the host list when it is collapsed. |
134 * @private | 334 * @private |
135 */ | 335 */ |
136 remoting.HostList.COLLAPSED_ = 'collapsed'; | 336 remoting.HostList.COLLAPSED_ = 'collapsed'; |
137 | 337 |
138 /** @type {remoting.HostList} */ | 338 /** @type {remoting.HostList} */ |
139 remoting.hostList = null; | 339 remoting.hostList = null; |
Wez
2011/11/17 22:58:58
Newline change?
Jamie
2011/11/18 22:54:57
Yep. The original was missing a newline.
| |
OLD | NEW |