OLD | NEW |
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 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * Class representing an entry in the host-list portion of the home screen. | 7 * Class representing an entry in the host-list portion of the home screen. |
8 */ | 8 */ |
9 | 9 |
10 'use strict'; | |
11 | |
12 /** @suppress {duplicate} */ | 10 /** @suppress {duplicate} */ |
13 var remoting = remoting || {}; | 11 var remoting = remoting || {}; |
14 | 12 |
| 13 (function() { |
| 14 |
| 15 'use strict'; |
| 16 |
15 /** | 17 /** |
16 * An entry in the host table. | 18 * An entry in the host table. |
17 * @param {remoting.Host} host The host, as obtained from Apiary. | 19 * |
18 * @param {number} webappMajorVersion The major version nmber of the web-app, | 20 * @param {number} webappMajorVersion The major version nmber of the web-app, |
19 * used to identify out-of-date hosts. | 21 * used to identify out-of-date hosts. |
| 22 * @param {function(string):void} onConnect Callback for |
| 23 * connect operations. |
20 * @param {function(remoting.HostTableEntry):void} onRename Callback for | 24 * @param {function(remoting.HostTableEntry):void} onRename Callback for |
21 * rename operations. | 25 * rename operations. |
22 * @param {function(remoting.HostTableEntry):void=} opt_onDelete Callback for | 26 * @param {function(remoting.HostTableEntry):void=} opt_onDelete Callback for |
23 * delete operations. | 27 * delete operations. |
| 28 * |
24 * @constructor | 29 * @constructor |
| 30 * @implements {base.Disposable} |
25 */ | 31 */ |
26 remoting.HostTableEntry = function( | 32 remoting.HostTableEntry = function( |
27 host, webappMajorVersion, onRename, opt_onDelete) { | 33 webappMajorVersion, onConnect, onRename, opt_onDelete) { |
28 /** @type {remoting.Host} */ | 34 /** @type {remoting.Host} */ |
| 35 this.host = null; |
| 36 /** @private {number} */ |
| 37 this.webappMajorVersion_ = webappMajorVersion; |
| 38 /** @private {function(remoting.HostTableEntry):void} */ |
| 39 this.onRename_ = onRename; |
| 40 /** @private {undefined|function(remoting.HostTableEntry):void} */ |
| 41 this.onDelete_ = opt_onDelete; |
| 42 /** @private {function(string):void} */ |
| 43 this.onConnect_ = onConnect; |
| 44 |
| 45 /** @private {HTMLElement} */ |
| 46 this.rootElement_ = null; |
| 47 /** @private {HTMLElement} */ |
| 48 this.hostNameLabel_ = null; |
| 49 /** @private {HTMLInputElement} */ |
| 50 this.renameInputField_ = null; |
| 51 /** @private {HTMLElement} */ |
| 52 this.warningOverlay_ = null; |
| 53 |
| 54 /** @private {base.Disposables} */ |
| 55 this.renameInputEventHooks_ = null; |
| 56 /** @private {base.Disposables} */ |
| 57 this.disposables_ = new base.Disposables(); |
| 58 |
| 59 // References to event handlers so that they can be removed. |
| 60 /** @private {function():void} */ |
| 61 this.onConfirmDeleteReference_ = function() {}; |
| 62 /** @private {function():void} */ |
| 63 this.onCancelDeleteReference_ = function() {}; |
| 64 |
| 65 this.createDom_(); |
| 66 }; |
| 67 |
| 68 /** @param {remoting.Host} host */ |
| 69 remoting.HostTableEntry.prototype.setHost = function(host) { |
29 this.host = host; | 70 this.host = host; |
30 /** @type {number} */ | 71 this.updateUI_(); |
31 this.webappMajorVersion_ = webappMajorVersion; | 72 }; |
32 /** @type {function(remoting.HostTableEntry):void} @private */ | 73 |
33 this.onRename_ = onRename; | 74 /** @return {HTMLElement} */ |
34 /** @type {undefined|function(remoting.HostTableEntry):void} @private */ | 75 remoting.HostTableEntry.prototype.element = function() { |
35 this.onDelete_ = opt_onDelete; | 76 return this.rootElement_; |
36 | 77 }; |
37 /** @type {HTMLElement} */ | 78 |
38 this.tableRow = null; | 79 remoting.HostTableEntry.prototype.dispose = function() { |
39 /** @type {HTMLElement} @private */ | 80 base.dispose(this.disposables_); |
40 this.hostNameCell_ = null; | 81 this.disposables_ = null; |
41 /** @type {HTMLElement} @private */ | 82 base.dispose(this.renameInputEventHooks_); |
42 this.warningOverlay_ = null; | 83 this.renameInputEventHooks_ = null; |
43 // References to event handlers so that they can be removed. | 84 }; |
44 /** @type {function():void} @private */ | 85 |
45 this.onBlurReference_ = function() {}; | 86 /** @return {string} */ |
46 /** @type {function():void} @private */ | 87 remoting.HostTableEntry.prototype.getHTML_ = function() { |
47 this.onConfirmDeleteReference_ = function() {}; | 88 var html = |
48 /** @type {function():void} @private */ | 89 '<div class="host-list-main-icon">' + |
49 this.onCancelDeleteReference_ = function() {}; | 90 '<span class="warning-overlay"></span>' + |
50 /** @type {function():void?} @private */ | 91 '<img src="icon_host.webp">' + |
51 this.onConnectReference_ = null; | 92 '</div>' + |
| 93 '<div class="box-spacer"">' + |
| 94 '<a class="host-name-label" href="#""></a>' + |
| 95 '<input class="host-rename-input" type="text" hidden/>' + |
| 96 '</div>' + |
| 97 '<span tabindex="0" class="clickable host-list-edit rename-button">' + |
| 98 '<img src="icon_pencil.webp" class="host-list-rename-icon">' + |
| 99 '</span>'; |
| 100 if (this.onDelete_) { |
| 101 html += |
| 102 '<span tabindex="0" class="clickable host-list-edit delete-button">' + |
| 103 '<img src="icon_cross.webp" class="host-list-remove-icon">' + |
| 104 '</span>'; |
| 105 } |
| 106 return '<div class="section-row host-offline">' + html + '</div>'; |
52 }; | 107 }; |
53 | 108 |
54 /** | 109 /** |
55 * Create the HTML elements for this entry and set up event handlers. | 110 * Create the HTML elements for this entry and set up event handlers. |
56 * @return {void} Nothing. | 111 * @return {void} Nothing. |
57 */ | 112 */ |
58 remoting.HostTableEntry.prototype.createDom = function() { | 113 remoting.HostTableEntry.prototype.createDom_ = function() { |
59 // Create the top-level <div> | 114 var container = /** @type {HTMLElement} */ (document.createElement('div')); |
60 var tableRow = /** @type {HTMLElement} */ (document.createElement('div')); | 115 container.innerHTML = this.getHTML_(); |
61 tableRow.classList.add('section-row'); | 116 |
62 // Create the host icon cell. | 117 // Setup DOM references. |
63 var hostIconDiv = /** @type {HTMLElement} */ (document.createElement('div')); | 118 this.rootElement_ = /** @type {HTMLElement} */ (container.firstElementChild); |
64 hostIconDiv.classList.add('host-list-main-icon'); | 119 this.warningOverlay_ = container.querySelector('.warning-overlay'); |
65 var warningOverlay = | 120 this.hostNameLabel_ = container.querySelector('.host-name-label'); |
66 /** @type {HTMLElement} */ (document.createElement('span')); | 121 this.renameInputField_ = /** @type {HTMLInputElement} */ ( |
67 hostIconDiv.appendChild(warningOverlay); | 122 container.querySelector('.host-rename-input')); |
68 var hostIcon = /** @type {HTMLElement} */ (document.createElement('img')); | 123 |
69 hostIcon.src = 'icon_host.webp'; | 124 // Register event handlers and set tooltips. |
70 hostIconDiv.appendChild(hostIcon); | 125 var editButton = container.querySelector('.rename-button'); |
71 tableRow.appendChild(hostIconDiv); | 126 var deleteButton = container.querySelector('.delete-button'); |
72 // Create the host name cell. | 127 editButton.title = chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_RENAME'); |
73 var hostNameCell = /** @type {HTMLElement} */ (document.createElement('div')); | 128 this.registerButton_(editButton, this.beginRename_.bind(this)); |
74 hostNameCell.classList.add('box-spacer'); | 129 this.registerButton_(this.rootElement_, this.onConnectButton_.bind(this)); |
75 hostNameCell.id = 'host_' + this.host.hostId; | 130 if (deleteButton) { |
76 tableRow.appendChild(hostNameCell); | 131 this.registerButton_(deleteButton, this.showDeleteConfirmation_.bind(this)); |
77 // Create the host rename cell. | 132 deleteButton.title = |
78 var editButton = /** @type {HTMLElement} */ (document.createElement('span')); | 133 chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_DELETE'); |
79 var editButtonImg = | 134 } |
80 /** @type {HTMLElement} */ (document.createElement('img')); | 135 }; |
81 editButtonImg.title = chrome.i18n.getMessage( | 136 |
82 /*i18n-content*/'TOOLTIP_RENAME'); | 137 /** @return {base.Disposable} @private */ |
83 editButtonImg.src = 'icon_pencil.webp'; | 138 remoting.HostTableEntry.prototype.registerButton_ = function( |
84 editButton.tabIndex = 0; | 139 /** HTMLElement */ button, /** Function */ callback) { |
85 editButton.classList.add('clickable'); | 140 var onKeyDown = function(/** Event */ e) { |
86 editButton.classList.add('host-list-edit'); | 141 if (e.which === KeyCodes.ENTER || e.which === KeyCodes.SPACEBAR) { |
87 editButtonImg.classList.add('host-list-rename-icon'); | 142 callback(); |
88 editButton.appendChild(editButtonImg); | 143 e.stopPropagation(); |
89 tableRow.appendChild(editButton); | |
90 // Create the host delete cell. | |
91 var deleteButton = | |
92 /** @type {HTMLElement} */ (document.createElement('span')); | |
93 var deleteButtonImg = | |
94 /** @type {HTMLElement} */ (document.createElement('img')); | |
95 deleteButtonImg.title = | |
96 chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_DELETE'); | |
97 deleteButtonImg.src = 'icon_cross.webp'; | |
98 deleteButton.tabIndex = 0; | |
99 deleteButton.classList.add('clickable'); | |
100 deleteButton.classList.add('host-list-edit'); | |
101 deleteButtonImg.classList.add('host-list-remove-icon'); | |
102 deleteButton.appendChild(deleteButtonImg); | |
103 tableRow.appendChild(deleteButton); | |
104 | |
105 this.init(tableRow, warningOverlay, hostNameCell, editButton, deleteButton); | |
106 }; | |
107 | |
108 /** | |
109 * Associate the table row with the specified elements and callbacks, and set | |
110 * up event handlers. | |
111 * | |
112 * @param {HTMLElement} tableRow The top-level <div> for the table entry. | |
113 * @param {HTMLElement} warningOverlay The <span> element to render a warning | |
114 * icon on top of the host icon. | |
115 * @param {HTMLElement} hostNameCell The element containing the host name. | |
116 * @param {HTMLElement} editButton The <img> containing the pencil icon for | |
117 * editing the host name. | |
118 * @param {HTMLElement=} opt_deleteButton The <img> containing the cross icon | |
119 * for deleting the host, if present. | |
120 * @return {void} Nothing. | |
121 */ | |
122 remoting.HostTableEntry.prototype.init = function( | |
123 tableRow, warningOverlay, hostNameCell, editButton, opt_deleteButton) { | |
124 this.tableRow = tableRow; | |
125 this.warningOverlay_ = warningOverlay; | |
126 this.hostNameCell_ = hostNameCell; | |
127 this.setHostName_(); | |
128 | |
129 /** @type {remoting.HostTableEntry} */ | |
130 var that = this; | |
131 | |
132 /** @param {Event} event The click event. */ | |
133 var beginRename = function(event) { | |
134 that.beginRename_(); | |
135 event.stopPropagation(); | |
136 }; | |
137 /** @param {Event} event The keyup event. */ | |
138 var beginRenameKeyboard = function(event) { | |
139 if (event.which == 13 || event.which == 32) { | |
140 that.beginRename_(); | |
141 event.stopPropagation(); | |
142 } | 144 } |
143 }; | 145 }; |
144 editButton.addEventListener('click', beginRename, true); | 146 var onClick = function(/** Event */ e) { |
145 editButton.addEventListener('keyup', beginRenameKeyboard, true); | 147 callback(); |
146 this.registerFocusHandlers_(editButton); | 148 e.stopPropagation(); |
147 | 149 }; |
148 if (opt_deleteButton) { | 150 var onFocusChanged = this.onFocusChange_.bind(this); |
149 /** @param {Event} event The click event. */ | 151 this.disposables_.add( |
150 var confirmDelete = function(event) { | 152 new base.DomEventHook(button, 'click', onClick, false), |
151 that.showDeleteConfirmation_(); | 153 new base.DomEventHook(button, 'keydown', onKeyDown, false), |
152 event.stopPropagation(); | 154 // Register focus and blur handlers to cause the parent node to be |
153 }; | 155 // highlighted whenever a child link has keyboard focus. Note that this is |
154 /** @param {Event} event The keyup event. */ | 156 // only necessary because Chrome does not yet support the draft CSS |
155 var confirmDeleteKeyboard = function(event) { | 157 // Selectors 4 specification (http://www.w3.org/TR/selectors4/#subject), |
156 if (event.which == 13 || event.which == 32) { | 158 // which provides a more elegant solution to this problem. |
157 that.showDeleteConfirmation_(); | 159 new base.DomEventHook(button, 'focus', onFocusChanged, false), |
158 } | 160 new base.DomEventHook(button, 'blur', onFocusChanged, false)); |
159 }; | 161 }; |
160 opt_deleteButton.addEventListener('click', confirmDelete, false); | 162 |
161 opt_deleteButton.addEventListener('keyup', confirmDeleteKeyboard, false); | 163 |
162 this.registerFocusHandlers_(opt_deleteButton); | 164 /** @return {void} @private */ |
163 } | 165 remoting.HostTableEntry.prototype.onConnectButton_ = function() { |
164 this.updateStatus(); | 166 // Don't connect during renaming as this event fires if the user hits Enter |
165 }; | 167 // after typing a new name. |
166 | 168 if (!this.isRenaming_() && this.isOnline_()) { |
167 /** | 169 var encodedHostId = encodeURIComponent(this.host.hostId); |
168 * Update the row to reflect the current status of the host (online/offline and | 170 this.onConnect_(encodedHostId); |
169 * clickable/unclickable). | 171 } |
| 172 }; |
| 173 |
| 174 /** @return {boolean} @private */ |
| 175 remoting.HostTableEntry.prototype.isOnline_ = function() { |
| 176 return Boolean(this.host) && this.host.status === 'ONLINE'; |
| 177 }; |
| 178 |
| 179 /** @return {string} @private */ |
| 180 remoting.HostTableEntry.prototype.getHostDisplayName_ = function() { |
| 181 if (this.isOnline_()) { |
| 182 if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) { |
| 183 return chrome.i18n.getMessage( |
| 184 /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName); |
| 185 } |
| 186 return this.host.hostName; |
| 187 } else { |
| 188 if (this.host.updatedTime) { |
| 189 var formattedTime = formatUpdateTime(this.host.updatedTime); |
| 190 return chrome.i18n.getMessage(/*i18n-content*/ 'LAST_ONLINE', |
| 191 [this.host.hostName, formattedTime]); |
| 192 } |
| 193 return chrome.i18n.getMessage(/*i18n-content*/ 'OFFLINE', |
| 194 this.host.hostName); |
| 195 } |
| 196 }; |
| 197 |
| 198 /** |
| 199 * Update the UI to reflect the current status of the host |
170 * | 200 * |
171 * @param {boolean=} opt_forEdit True if the status is being updated in order | 201 * @return {void} Nothing. |
172 * to allow the host name to be edited. | 202 * @private |
173 * @return {void} Nothing. | 203 */ |
174 */ | 204 remoting.HostTableEntry.prototype.updateUI_ = function() { |
175 remoting.HostTableEntry.prototype.updateStatus = function(opt_forEdit) { | 205 if (!this.host) { |
176 var clickToConnect = this.host.status == 'ONLINE' && !opt_forEdit; | 206 return; |
177 if (clickToConnect) { | 207 } |
178 if (!this.onConnectReference_) { | 208 var clickToConnect = this.isOnline_() && !this.isRenaming_(); |
179 /** @type {string} */ | 209 var showOffline = !this.isOnline_(); |
180 var encodedHostId = encodeURIComponent(this.host.hostId); | 210 var connectLabel = chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_CONNECT', |
181 this.onConnectReference_ = function() { | 211 this.host.hostName); |
182 remoting.connectMe2Me(encodedHostId); | 212 this.rootElement_.classList.toggle('clickable', clickToConnect); |
183 }; | 213 this.rootElement_.title = (clickToConnect) ? connectLabel : ''; |
184 this.tableRow.addEventListener('click', this.onConnectReference_, false); | 214 this.rootElement_.classList.toggle('host-online', !showOffline); |
185 } | 215 this.rootElement_.classList.toggle('host-offline', showOffline); |
186 this.tableRow.classList.add('clickable'); | 216 |
187 this.tableRow.title = chrome.i18n.getMessage( | 217 var hostReportedError = this.host.hostOfflineReason !== ''; |
188 /*i18n-content*/'TOOLTIP_CONNECT', this.host.hostName); | |
189 } else { | |
190 if (this.onConnectReference_) { | |
191 this.tableRow.removeEventListener('click', this.onConnectReference_, | |
192 false); | |
193 this.onConnectReference_ = null; | |
194 } | |
195 this.tableRow.classList.remove('clickable'); | |
196 this.tableRow.title = ''; | |
197 } | |
198 var showOffline = this.host.status != 'ONLINE'; | |
199 if (showOffline) { | |
200 this.tableRow.classList.remove('host-online'); | |
201 this.tableRow.classList.add('host-offline'); | |
202 } else { | |
203 this.tableRow.classList.add('host-online'); | |
204 this.tableRow.classList.remove('host-offline'); | |
205 } | |
206 var hostReportedError = this.host.hostOfflineReason != ''; | |
207 var hostNeedsUpdate = remoting.Host.needsUpdate( | 218 var hostNeedsUpdate = remoting.Host.needsUpdate( |
208 this.host, this.webappMajorVersion_); | 219 this.host, this.webappMajorVersion_); |
209 this.warningOverlay_.hidden = !hostNeedsUpdate && !hostReportedError; | 220 this.warningOverlay_.hidden = !hostNeedsUpdate && !hostReportedError; |
210 }; | 221 this.hostNameLabel_.innerText = this.getHostDisplayName_(); |
211 | 222 this.hostNameLabel_.title = |
212 /** | 223 formatHostOfflineReason(this.host.hostOfflineReason); |
213 * Prepare the host for renaming by replacing its name with an edit box. | 224 this.renameInputField_.hidden = !this.isRenaming_(); |
| 225 this.hostNameLabel_.hidden = this.isRenaming_(); |
| 226 }; |
| 227 |
| 228 /** |
| 229 * Prepares the host for renaming by showing an edit box. |
214 * @return {void} Nothing. | 230 * @return {void} Nothing. |
215 * @private | 231 * @private |
216 */ | 232 */ |
217 remoting.HostTableEntry.prototype.beginRename_ = function() { | 233 remoting.HostTableEntry.prototype.beginRename_ = function() { |
218 var editBox = | 234 this.renameInputField_.value = this.host.hostName; |
219 /** @type {HTMLInputElement} */ (document.createElement('input')); | 235 base.debug.assert(this.renameInputEventHooks_ === null); |
220 editBox.type = 'text'; | 236 base.dispose(this.renameInputEventHooks_); |
221 editBox.value = this.host.hostName; | 237 this.renameInputEventHooks_ = new base.Disposables( |
222 this.hostNameCell_.innerText = ''; | 238 new base.DomEventHook(this.renameInputField_, 'blur', |
223 this.hostNameCell_.appendChild(editBox); | 239 this.commitRename_.bind(this), false), |
224 editBox.select(); | 240 new base.DomEventHook(this.renameInputField_, 'keydown', |
225 | 241 this.onKeydown_.bind(this), false)); |
226 this.onBlurReference_ = this.commitRename_.bind(this); | 242 this.updateUI_(); |
227 editBox.addEventListener('blur', this.onBlurReference_, false); | 243 this.renameInputField_.focus(); |
228 | 244 }; |
229 editBox.addEventListener('keydown', this.onKeydown_.bind(this), false); | 245 |
230 this.updateStatus(true); | 246 /** @return {boolean} @private */ |
231 }; | 247 remoting.HostTableEntry.prototype.isRenaming_ = function() { |
232 | 248 return Boolean(this.renameInputEventHooks_); |
233 /** | 249 }; |
234 * Accept the hostname entered by the user. | 250 |
| 251 /** @return {void} @private */ |
| 252 remoting.HostTableEntry.prototype.commitRename_ = function() { |
| 253 if (this.host.hostName != this.renameInputField_.value) { |
| 254 this.host.hostName = this.renameInputField_.value; |
| 255 this.onRename_(this); |
| 256 } |
| 257 this.hideEditBox_(); |
| 258 }; |
| 259 |
| 260 /** @return {void} @private */ |
| 261 remoting.HostTableEntry.prototype.hideEditBox_ = function() { |
| 262 // onblur will fire when the edit box is removed, so remove the hook. |
| 263 base.dispose(this.renameInputEventHooks_); |
| 264 this.renameInputEventHooks_ = null; |
| 265 // Update the tool-tip and event handler. |
| 266 this.updateUI_(); |
| 267 }; |
| 268 |
| 269 /** |
| 270 * Handle a key event while the user is typing a host name |
| 271 * @param {Event} event The keyboard event. |
235 * @return {void} Nothing. | 272 * @return {void} Nothing. |
236 * @private | 273 * @private |
237 */ | 274 */ |
238 remoting.HostTableEntry.prototype.commitRename_ = function() { | 275 remoting.HostTableEntry.prototype.onKeydown_ = function(event) { |
239 var editBox = this.hostNameCell_.querySelector('input'); | 276 if (event.which == KeyCodes.ESCAPE) { |
240 if (editBox) { | 277 this.hideEditBox_(); |
241 if (this.host.hostName != editBox.value) { | 278 } else if (event.which == KeyCodes.ENTER) { |
242 this.host.hostName = editBox.value; | 279 this.commitRename_(); |
243 this.onRename_(this); | 280 event.stopPropagation(); |
244 } | 281 } |
245 this.removeEditBox_(); | 282 }; |
246 } | 283 |
247 }; | |
248 | |
249 /** | 284 /** |
250 * Prompt the user to confirm or cancel deletion of a host. | 285 * Prompt the user to confirm or cancel deletion of a host. |
251 * @return {void} Nothing. | 286 * @return {void} Nothing. |
252 * @private | 287 * @private |
253 */ | 288 */ |
254 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() { | 289 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() { |
255 var message = document.getElementById('confirm-host-delete-message'); | 290 var message = document.getElementById('confirm-host-delete-message'); |
256 l10n.localizeElement(message, this.host.hostName); | 291 l10n.localizeElement(message, this.host.hostName); |
257 var confirm = document.getElementById('confirm-host-delete'); | 292 var confirm = document.getElementById('confirm-host-delete'); |
258 var cancel = document.getElementById('cancel-host-delete'); | 293 var cancel = document.getElementById('cancel-host-delete'); |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
293 function() { | 328 function() { |
294 var confirm = document.getElementById('confirm-host-delete'); | 329 var confirm = document.getElementById('confirm-host-delete'); |
295 var cancel = document.getElementById('cancel-host-delete'); | 330 var cancel = document.getElementById('cancel-host-delete'); |
296 confirm.removeEventListener('click', this.onConfirmDeleteReference_, false); | 331 confirm.removeEventListener('click', this.onConfirmDeleteReference_, false); |
297 cancel.removeEventListener('click', this.onCancelDeleteReference_, false); | 332 cancel.removeEventListener('click', this.onCancelDeleteReference_, false); |
298 this.onCancelDeleteReference_ = function() {}; | 333 this.onCancelDeleteReference_ = function() {}; |
299 this.onConfirmDeleteReference_ = function() {}; | 334 this.onConfirmDeleteReference_ = function() {}; |
300 }; | 335 }; |
301 | 336 |
302 /** | 337 /** |
303 * Remove the edit box corresponding to the specified host, and reset its name. | 338 * Handle a focus change event within this table row. |
304 * @return {void} Nothing. | 339 * @return {void} Nothing. |
305 * @private | 340 * @private |
306 */ | 341 */ |
307 remoting.HostTableEntry.prototype.removeEditBox_ = function() { | 342 remoting.HostTableEntry.prototype.onFocusChange_ = function() { |
308 var editBox = this.hostNameCell_.querySelector('input'); | 343 var element = document.activeElement; |
309 if (editBox) { | 344 while (element) { |
310 // onblur will fire when the edit box is removed, so remove the hook. | 345 if (element == this.rootElement_) { |
311 editBox.removeEventListener('blur', this.onBlurReference_, false); | 346 this.rootElement_.classList.add('child-focused'); |
| 347 return; |
| 348 } |
| 349 element = element.parentNode; |
312 } | 350 } |
313 // Update the tool-top and event handler. | 351 this.rootElement_.classList.remove('child-focused'); |
314 this.updateStatus(); | |
315 this.setHostName_(); | |
316 }; | 352 }; |
317 | 353 |
318 /** | 354 /** |
319 * Formats host's updateTime value relative to current time (i.e. only | 355 * Formats host's updateTime value relative to current time (i.e. only |
320 * displaying hours and minutes if updateTime is less than a day in the past). | 356 * displaying hours and minutes if updateTime is less than a day in the past). |
321 * @param {string} updateTime RFC 3339 formatted date-time value. | 357 * @param {string} updateTime RFC 3339 formatted date-time value. |
322 * @return {string} Formatted value (i.e. 11/11/2014) | 358 * @return {string} Formatted value (i.e. 11/11/2014) |
323 */ | 359 */ |
324 function formatUpdateTime(updateTime) { | 360 function formatUpdateTime(updateTime) { |
325 var lastOnline = new Date(updateTime); | 361 var lastOnline = new Date(updateTime); |
326 var now = new Date(); | 362 var now = new Date(); |
327 var displayString = ''; | 363 var displayString = ''; |
328 if (now.getFullYear() == lastOnline.getFullYear() && | 364 if (now.getFullYear() == lastOnline.getFullYear() && |
329 now.getMonth() == lastOnline.getMonth() && | 365 now.getMonth() == lastOnline.getMonth() && |
330 now.getDate() == lastOnline.getDate()) { | 366 now.getDate() == lastOnline.getDate()) { |
331 return lastOnline.toLocaleTimeString(); | 367 return lastOnline.toLocaleTimeString(); |
332 } else { | 368 } else { |
333 return lastOnline.toLocaleDateString(); | 369 return lastOnline.toLocaleDateString(); |
334 } | 370 } |
335 } | 371 } |
336 | 372 |
337 /** | 373 /** |
338 * Formats host's host-offline-reason value (i.e. 'INVALID_HOST_CONFIGURATION') | 374 * Formats host's host-offline-reason value (i.e. 'INVALID_HOST_CONFIGURATION') |
339 * to a human-readable description of the error. | 375 * to a human-readable description of the error. |
340 * @param {string} hostOfflineReason | 376 * @param {string} hostOfflineReason |
341 * @return {string} | 377 * @return {string} |
342 */ | 378 */ |
343 function formatHostOfflineReason(hostOfflineReason) { | 379 function formatHostOfflineReason(hostOfflineReason) { |
| 380 if (!hostOfflineReason) { |
| 381 return ''; |
| 382 } |
344 var knownReasonTags = [ | 383 var knownReasonTags = [ |
345 /*i18n-content*/ 'OFFLINE_REASON_INITIALIZATION_FAILED', | 384 /*i18n-content*/'OFFLINE_REASON_INITIALIZATION_FAILED', |
346 /*i18n-content*/ 'OFFLINE_REASON_INVALID_HOST_CONFIGURATION', | 385 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_CONFIGURATION', |
347 /*i18n-content*/ 'OFFLINE_REASON_INVALID_HOST_DOMAIN', | 386 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_DOMAIN', |
348 /*i18n-content*/ 'OFFLINE_REASON_INVALID_HOST_ID', | 387 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_ID', |
349 /*i18n-content*/ 'OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS', | 388 /*i18n-content*/'OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS', |
350 /*i18n-content*/ 'OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED', | 389 /*i18n-content*/'OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED', |
351 /*i18n-content*/ 'OFFLINE_REASON_POLICY_CHANGE_REQUIRES_RESTART', | 390 /*i18n-content*/'OFFLINE_REASON_POLICY_CHANGE_REQUIRES_RESTART', |
352 /*i18n-content*/ 'OFFLINE_REASON_POLICY_READ_ERROR', | 391 /*i18n-content*/'OFFLINE_REASON_POLICY_READ_ERROR', |
353 /*i18n-content*/ 'OFFLINE_REASON_SUCCESS_EXIT', | 392 /*i18n-content*/'OFFLINE_REASON_SUCCESS_EXIT', |
354 /*i18n-content*/ 'OFFLINE_REASON_USERNAME_MISMATCH' | 393 /*i18n-content*/'OFFLINE_REASON_USERNAME_MISMATCH' |
355 ]; | 394 ]; |
356 var offlineReasonTag = 'OFFLINE_REASON_' + hostOfflineReason; | 395 var offlineReasonTag = 'OFFLINE_REASON_' + hostOfflineReason; |
357 if (knownReasonTags.indexOf(offlineReasonTag) != (-1)) { | 396 if (knownReasonTags.indexOf(offlineReasonTag) != (-1)) { |
358 return chrome.i18n.getMessage(offlineReasonTag); | 397 return chrome.i18n.getMessage(offlineReasonTag); |
359 } else { | 398 } else { |
360 return chrome.i18n.getMessage( | 399 return chrome.i18n.getMessage( |
361 /*i18n-content*/ 'OFFLINE_REASON_UNKNOWN', | 400 /*i18n-content*/'OFFLINE_REASON_UNKNOWN', hostOfflineReason); |
362 hostOfflineReason); | |
363 } | 401 } |
364 } | 402 } |
365 | 403 |
366 /** | 404 /** @enum {number} */ |
367 * Create the DOM nodes and event handlers for the hostname cell. | 405 var KeyCodes = { |
368 * @return {void} Nothing. | 406 ENTER: 13, |
369 * @private | 407 ESCAPE: 27, |
370 */ | 408 SPACEBAR: 32 |
371 remoting.HostTableEntry.prototype.setHostName_ = function() { | |
372 var hostNameNode = /** @type {HTMLElement} */ (document.createElement('a')); | |
373 if (this.host.status == 'ONLINE') { | |
374 if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) { | |
375 hostNameNode.innerText = chrome.i18n.getMessage( | |
376 /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName); | |
377 } else { | |
378 hostNameNode.innerText = this.host.hostName; | |
379 } | |
380 hostNameNode.href = '#'; | |
381 this.registerFocusHandlers_(hostNameNode); | |
382 /** @type {remoting.HostTableEntry} */ | |
383 var that = this; | |
384 /** @param {Event} event */ | |
385 var onKeyDown = function(event) { | |
386 if (that.onConnectReference_ && | |
387 (event.which == 13 || event.which == 32)) { | |
388 that.onConnectReference_(); | |
389 } | |
390 }; | |
391 hostNameNode.addEventListener('keydown', onKeyDown, false); | |
392 } else { | |
393 if (this.host.updatedTime) { | |
394 var formattedTime = formatUpdateTime(this.host.updatedTime); | |
395 hostNameNode.innerText = chrome.i18n.getMessage( | |
396 /*i18n-content*/'LAST_ONLINE', [this.host.hostName, formattedTime]); | |
397 if (this.host.hostOfflineReason) { | |
398 var detailsText = formatHostOfflineReason(this.host.hostOfflineReason); | |
399 // TODO(lukasza): Put detailsText into a hideable div (title/tooltip | |
400 // is not as discoverable + doesn't work well for touchscreens). | |
401 hostNameNode.title = detailsText; | |
402 } | |
403 } else { | |
404 hostNameNode.innerText = chrome.i18n.getMessage( | |
405 /*i18n-content*/'OFFLINE', this.host.hostName); | |
406 } | |
407 } | |
408 hostNameNode.classList.add('host-list-label'); | |
409 this.hostNameCell_.innerText = ''; // Remove previous contents (if any). | |
410 this.hostNameCell_.appendChild(hostNameNode); | |
411 }; | 409 }; |
412 | 410 |
413 /** | 411 })(); |
414 * Handle a key event while the user is typing a host name | |
415 * @param {Event} event The keyboard event. | |
416 * @return {void} Nothing. | |
417 * @private | |
418 */ | |
419 remoting.HostTableEntry.prototype.onKeydown_ = function(event) { | |
420 if (event.which == 27) { // Escape | |
421 this.removeEditBox_(); | |
422 } else if (event.which == 13) { // Enter | |
423 this.commitRename_(); | |
424 } | |
425 }; | |
426 | |
427 /** | |
428 * Register focus and blur handlers to cause the parent node to be highlighted | |
429 * whenever a child link has keyboard focus. Note that this is only necessary | |
430 * because Chrome does not yet support the draft CSS Selectors 4 specification | |
431 * (http://www.w3.org/TR/selectors4/#subject), which provides a more elegant | |
432 * solution to this problem. | |
433 * | |
434 * @param {HTMLElement} e The element on which to register the event handlers. | |
435 * @return {void} Nothing. | |
436 * @private | |
437 */ | |
438 remoting.HostTableEntry.prototype.registerFocusHandlers_ = function(e) { | |
439 e.addEventListener('focus', this.onFocusChange_.bind(this), false); | |
440 e.addEventListener('blur', this.onFocusChange_.bind(this), false); | |
441 }; | |
442 | |
443 /** | |
444 * Handle a focus change event within this table row. | |
445 * @return {void} Nothing. | |
446 * @private | |
447 */ | |
448 remoting.HostTableEntry.prototype.onFocusChange_ = function() { | |
449 var element = document.activeElement; | |
450 while (element) { | |
451 if (element == this.tableRow) { | |
452 this.tableRow.classList.add('child-focused'); | |
453 return; | |
454 } | |
455 element = element.parentNode; | |
456 } | |
457 this.tableRow.classList.remove('child-focused'); | |
458 }; | |
OLD | NEW |