Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(35)

Side by Side Diff: remoting/webapp/crd/js/host_table_entry.js

Issue 944183002: HostTableEntry refactor (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Reviewer's feedbacks with unit tests Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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-list-label" href="#""></a>' +
95 '<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('.box-spacer a');
66 /** @type {HTMLElement} */ (document.createElement('span')); 121 this.renameInputField_ = /** @type {HTMLInputElement} */ (
67 hostIconDiv.appendChild(warningOverlay); 122 container.querySelector('.box-spacer input'));
Jamie 2015/02/27 18:19:40 This should be accessed explicitly by class name,
kelvinp 2015/03/03 21:57:53 Done.
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) {
Jamie 2015/02/27 18:19:41 Don't you need @type to annotate parameters in thi
kelvinp 2015/03/03 21:57:53 No, this is magic of the new syntax that John show
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 if (!this.isRenaming_() && this.isOnline_()) {
Jamie 2015/02/27 18:19:40 Add a comment explaining why we don't connect if w
kelvinp 2015/03/03 21:57:53 That's exactly it :) Done.
165 }; 167 var encodedHostId = encodeURIComponent(this.host.hostId);
166 168 this.onConnect_(encodedHostId);
167 /** 169 }
168 * Update the row to reflect the current status of the host (online/offline and 170 };
169 * clickable/unclickable). 171
172 /** @return {boolean} @private */
173 remoting.HostTableEntry.prototype.isOnline_ = function() {
174 return Boolean(this.host) && this.host.status === 'ONLINE';
175 };
176
177 /** @return {string} @private */
178 remoting.HostTableEntry.prototype.getHostDisplayName_ = function() {
179 if (this.isOnline_()) {
180 if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) {
181 return chrome.i18n.getMessage(
182 /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName);
183 }
184 return this.host.hostName;
185 } else {
186 if (this.host.updatedTime) {
187 var formattedTime = formatUpdateTime(this.host.updatedTime);
188 return chrome.i18n.getMessage(/*i18n-content*/ 'LAST_ONLINE',
189 [this.host.hostName, formattedTime]);
190 }
191 return chrome.i18n.getMessage(/*i18n-content*/ 'OFFLINE',
192 this.host.hostName);
193 }
194 };
195
196 /**
197 * Update the UI to reflect the current status of the host
170 * 198 *
171 * @param {boolean=} opt_forEdit True if the status is being updated in order 199 * @return {void} Nothing.
172 * to allow the host name to be edited. 200 * @private
173 * @return {void} Nothing. 201 */
174 */ 202 remoting.HostTableEntry.prototype.updateUI_ = function() {
175 remoting.HostTableEntry.prototype.updateStatus = function(opt_forEdit) { 203 if (!this.host) {
176 var clickToConnect = this.host.status == 'ONLINE' && !opt_forEdit; 204 return;
177 if (clickToConnect) { 205 }
178 if (!this.onConnectReference_) { 206 var clickToConnect = this.isOnline_() && !this.isRenaming_();
179 /** @type {string} */ 207 var showOffline = !this.isOnline_();
180 var encodedHostId = encodeURIComponent(this.host.hostId); 208 var connectLabel = chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_CONNECT',
181 this.onConnectReference_ = function() { 209 this.host.hostName);
182 remoting.connectMe2Me(encodedHostId); 210 this.rootElement_.classList.toggle('clickable', clickToConnect);
183 }; 211 this.rootElement_.title = (clickToConnect) ? connectLabel : '';
184 this.tableRow.addEventListener('click', this.onConnectReference_, false); 212 this.rootElement_.classList.toggle('host-online', !showOffline);
185 } 213 this.rootElement_.classList.toggle('host-offline', showOffline);
186 this.tableRow.classList.add('clickable'); 214
187 this.tableRow.title = chrome.i18n.getMessage( 215 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( 216 var hostNeedsUpdate = remoting.Host.needsUpdate(
208 this.host, this.webappMajorVersion_); 217 this.host, this.webappMajorVersion_);
209 this.warningOverlay_.hidden = !hostNeedsUpdate && !hostReportedError; 218 this.warningOverlay_.hidden = !hostNeedsUpdate && !hostReportedError;
210 }; 219 this.hostNameLabel_.innerText = this.getHostDisplayName_();
211 220 this.hostNameLabel_.title =
212 /** 221 formatHostOfflineReason(this.host.hostOfflineReason);
213 * Prepare the host for renaming by replacing its name with an edit box. 222 this.renameInputField_.hidden = !this.isRenaming_();
223 this.hostNameLabel_.hidden = this.isRenaming_();
224 };
225
226 /**
227 * Prepares the host for renaming by showing an edit box.
214 * @return {void} Nothing. 228 * @return {void} Nothing.
215 * @private 229 * @private
216 */ 230 */
217 remoting.HostTableEntry.prototype.beginRename_ = function() { 231 remoting.HostTableEntry.prototype.beginRename_ = function() {
218 var editBox = 232 this.renameInputField_.value = this.host.hostName;
219 /** @type {HTMLInputElement} */ (document.createElement('input')); 233 base.dispose(this.renameInputEventHooks_);
Jamie 2015/02/27 18:19:41 You're using the existence of renameInputHooks_ el
kelvinp 2015/03/03 21:57:53 An assert may not work as it will fire if the user
Jamie 2015/03/04 00:09:25 I don't think that's true. Clicking a second renam
kelvinp 2015/03/04 02:02:28 Done.
220 editBox.type = 'text'; 234 this.renameInputEventHooks_ = new base.Disposables(
221 editBox.value = this.host.hostName; 235 new base.DomEventHook(this.renameInputField_, 'blur',
222 this.hostNameCell_.innerText = ''; 236 this.commitRename_.bind(this), false),
223 this.hostNameCell_.appendChild(editBox); 237 new base.DomEventHook(this.renameInputField_, 'keydown',
224 editBox.select(); 238 this.onKeydown_.bind(this), false));
225 239 this.updateUI_();
226 this.onBlurReference_ = this.commitRename_.bind(this); 240 this.renameInputField_.focus();
227 editBox.addEventListener('blur', this.onBlurReference_, false); 241 };
228 242
229 editBox.addEventListener('keydown', this.onKeydown_.bind(this), false); 243 /** @return {boolean} @private */
230 this.updateStatus(true); 244 remoting.HostTableEntry.prototype.isRenaming_ = function() {
231 }; 245 return Boolean(this.renameInputEventHooks_);
232 246 };
233 /** 247
234 * Accept the hostname entered by the user. 248 /** @return {void} @private */
249 remoting.HostTableEntry.prototype.commitRename_ = function() {
250 if (this.host.hostName != this.renameInputField_.value) {
251 this.host.hostName = this.renameInputField_.value;
252 this.onRename_(this);
253 }
254 this.hideEditBox_();
255 };
256
257 /** @return {void} @private */
258 remoting.HostTableEntry.prototype.hideEditBox_ = function() {
259 // onblur will fire when the edit box is removed, so remove the hook.
260 base.dispose(this.renameInputEventHooks_);
261 this.renameInputEventHooks_ = null;
262 // Update the tool-top and event handler.
Jamie 2015/02/27 18:19:40 s/top/top/
kelvinp 2015/03/03 21:57:53 Done.
263 this.updateUI_();
264 };
265
266 /**
267 * Handle a key event while the user is typing a host name
268 * @param {Event} event The keyboard event.
235 * @return {void} Nothing. 269 * @return {void} Nothing.
236 * @private 270 * @private
237 */ 271 */
238 remoting.HostTableEntry.prototype.commitRename_ = function() { 272 remoting.HostTableEntry.prototype.onKeydown_ = function(event) {
239 var editBox = this.hostNameCell_.querySelector('input'); 273 if (event.which == KeyCodes.ESCAPE) {
240 if (editBox) { 274 this.hideEditBox_();
241 if (this.host.hostName != editBox.value) { 275 } else if (event.which == KeyCodes.ENTER) {
242 this.host.hostName = editBox.value; 276 this.commitRename_();
243 this.onRename_(this); 277 event.stopPropagation();
244 } 278 }
245 this.removeEditBox_(); 279 };
246 } 280
247 };
248
249 /** 281 /**
250 * Prompt the user to confirm or cancel deletion of a host. 282 * Prompt the user to confirm or cancel deletion of a host.
251 * @return {void} Nothing. 283 * @return {void} Nothing.
252 * @private 284 * @private
253 */ 285 */
254 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() { 286 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() {
255 var message = document.getElementById('confirm-host-delete-message'); 287 var message = document.getElementById('confirm-host-delete-message');
256 l10n.localizeElement(message, this.host.hostName); 288 l10n.localizeElement(message, this.host.hostName);
257 var confirm = document.getElementById('confirm-host-delete'); 289 var confirm = document.getElementById('confirm-host-delete');
258 var cancel = document.getElementById('cancel-host-delete'); 290 var cancel = document.getElementById('cancel-host-delete');
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
293 function() { 325 function() {
294 var confirm = document.getElementById('confirm-host-delete'); 326 var confirm = document.getElementById('confirm-host-delete');
295 var cancel = document.getElementById('cancel-host-delete'); 327 var cancel = document.getElementById('cancel-host-delete');
296 confirm.removeEventListener('click', this.onConfirmDeleteReference_, false); 328 confirm.removeEventListener('click', this.onConfirmDeleteReference_, false);
297 cancel.removeEventListener('click', this.onCancelDeleteReference_, false); 329 cancel.removeEventListener('click', this.onCancelDeleteReference_, false);
298 this.onCancelDeleteReference_ = function() {}; 330 this.onCancelDeleteReference_ = function() {};
299 this.onConfirmDeleteReference_ = function() {}; 331 this.onConfirmDeleteReference_ = function() {};
300 }; 332 };
301 333
302 /** 334 /**
303 * Remove the edit box corresponding to the specified host, and reset its name. 335 * Handle a focus change event within this table row.
304 * @return {void} Nothing. 336 * @return {void} Nothing.
305 * @private 337 * @private
306 */ 338 */
307 remoting.HostTableEntry.prototype.removeEditBox_ = function() { 339 remoting.HostTableEntry.prototype.onFocusChange_ = function() {
308 var editBox = this.hostNameCell_.querySelector('input'); 340 var element = document.activeElement;
309 if (editBox) { 341 while (element) {
310 // onblur will fire when the edit box is removed, so remove the hook. 342 if (element == this.rootElement_) {
311 editBox.removeEventListener('blur', this.onBlurReference_, false); 343 this.rootElement_.classList.add('child-focused');
344 return;
345 }
346 element = element.parentNode;
312 } 347 }
313 // Update the tool-top and event handler. 348 this.rootElement_.classList.remove('child-focused');
314 this.updateStatus();
315 this.setHostName_();
316 }; 349 };
317 350
318 /** 351 /**
319 * Formats host's updateTime value relative to current time (i.e. only 352 * 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). 353 * displaying hours and minutes if updateTime is less than a day in the past).
321 * @param {string} updateTime RFC 3339 formatted date-time value. 354 * @param {string} updateTime RFC 3339 formatted date-time value.
322 * @return {string} Formatted value (i.e. 11/11/2014) 355 * @return {string} Formatted value (i.e. 11/11/2014)
323 */ 356 */
324 function formatUpdateTime(updateTime) { 357 function formatUpdateTime(updateTime) {
325 var lastOnline = new Date(updateTime); 358 var lastOnline = new Date(updateTime);
326 var now = new Date(); 359 var now = new Date();
327 var displayString = ''; 360 var displayString = '';
328 if (now.getFullYear() == lastOnline.getFullYear() && 361 if (now.getFullYear() == lastOnline.getFullYear() &&
329 now.getMonth() == lastOnline.getMonth() && 362 now.getMonth() == lastOnline.getMonth() &&
330 now.getDate() == lastOnline.getDate()) { 363 now.getDate() == lastOnline.getDate()) {
331 return lastOnline.toLocaleTimeString(); 364 return lastOnline.toLocaleTimeString();
332 } else { 365 } else {
333 return lastOnline.toLocaleDateString(); 366 return lastOnline.toLocaleDateString();
334 } 367 }
335 } 368 }
336 369
337 /** 370 /**
338 * Formats host's host-offline-reason value (i.e. 'INVALID_HOST_CONFIGURATION') 371 * Formats host's host-offline-reason value (i.e. 'INVALID_HOST_CONFIGURATION')
339 * to a human-readable description of the error. 372 * to a human-readable description of the error.
340 * @param {string} hostOfflineReason 373 * @param {string} hostOfflineReason
341 * @return {string} 374 * @return {string}
342 */ 375 */
343 function formatHostOfflineReason(hostOfflineReason) { 376 function formatHostOfflineReason(hostOfflineReason) {
377 if (!hostOfflineReason) {
378 return '';
379 }
344 var knownReasonTags = [ 380 var knownReasonTags = [
345 /*i18n-content*/ 'OFFLINE_REASON_INITIALIZATION_FAILED', 381 /*i18n-content*/'OFFLINE_REASON_INITIALIZATION_FAILED',
346 /*i18n-content*/ 'OFFLINE_REASON_INVALID_HOST_CONFIGURATION', 382 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_CONFIGURATION',
347 /*i18n-content*/ 'OFFLINE_REASON_INVALID_HOST_ID', 383 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_ID',
348 /*i18n-content*/ 'OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS', 384 /*i18n-content*/'OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS',
349 /*i18n-content*/ 'OFFLINE_REASON_INVALID_HOST_DOMAIN', 385 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_DOMAIN',
350 /*i18n-content*/ 'OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED', 386 /*i18n-content*/'OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED',
351 /*i18n-content*/ 'OFFLINE_REASON_USERNAME_MISMATCH' 387 /*i18n-content*/'OFFLINE_REASON_USERNAME_MISMATCH'
352 ]; 388 ];
353 var offlineReasonTag = 'OFFLINE_REASON_' + hostOfflineReason; 389 var offlineReasonTag = 'OFFLINE_REASON_' + hostOfflineReason;
354 if (knownReasonTags.indexOf(offlineReasonTag) != (-1)) { 390 if (knownReasonTags.indexOf(offlineReasonTag) != (-1)) {
355 return chrome.i18n.getMessage(offlineReasonTag); 391 return chrome.i18n.getMessage(offlineReasonTag);
356 } else { 392 } else {
357 return chrome.i18n.getMessage( 393 return chrome.i18n.getMessage(
358 /*i18n-content*/ 'OFFLINE_REASON_UNKNOWN', 394 /*i18n-content*/'OFFLINE_REASON_UNKNOWN', hostOfflineReason);
359 hostOfflineReason);
360 } 395 }
361 } 396 }
362 397
363 /** 398 /** @enum {number} */
364 * Create the DOM nodes and event handlers for the hostname cell. 399 var KeyCodes = {
365 * @return {void} Nothing. 400 ENTER: 13,
366 * @private 401 ESCAPE: 27,
367 */ 402 SPACEBAR: 32
368 remoting.HostTableEntry.prototype.setHostName_ = function() {
369 var hostNameNode = /** @type {HTMLElement} */ (document.createElement('a'));
370 if (this.host.status == 'ONLINE') {
371 if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) {
372 hostNameNode.innerText = chrome.i18n.getMessage(
373 /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName);
374 } else {
375 hostNameNode.innerText = this.host.hostName;
376 }
377 hostNameNode.href = '#';
378 this.registerFocusHandlers_(hostNameNode);
379 /** @type {remoting.HostTableEntry} */
380 var that = this;
381 /** @param {Event} event */
382 var onKeyDown = function(event) {
383 if (that.onConnectReference_ &&
384 (event.which == 13 || event.which == 32)) {
385 that.onConnectReference_();
386 }
387 };
388 hostNameNode.addEventListener('keydown', onKeyDown, false);
389 } else {
390 if (this.host.updatedTime) {
391 var formattedTime = formatUpdateTime(this.host.updatedTime);
392 hostNameNode.innerText = chrome.i18n.getMessage(
393 /*i18n-content*/'LAST_ONLINE', [this.host.hostName, formattedTime]);
394 if (this.host.hostOfflineReason) {
395 var detailsText = formatHostOfflineReason(this.host.hostOfflineReason);
396 // TODO(lukasza): Put detailsText into a hideable div (title/tooltip
397 // is not as discoverable + doesn't work well for touchscreens).
398 hostNameNode.title = detailsText;
399 }
400 } else {
401 hostNameNode.innerText = chrome.i18n.getMessage(
402 /*i18n-content*/'OFFLINE', this.host.hostName);
403 }
404 }
405 hostNameNode.classList.add('host-list-label');
406 this.hostNameCell_.innerText = ''; // Remove previous contents (if any).
407 this.hostNameCell_.appendChild(hostNameNode);
408 }; 403 };
409 404
410 /** 405 })();
411 * Handle a key event while the user is typing a host name
412 * @param {Event} event The keyboard event.
413 * @return {void} Nothing.
414 * @private
415 */
416 remoting.HostTableEntry.prototype.onKeydown_ = function(event) {
417 if (event.which == 27) { // Escape
418 this.removeEditBox_();
419 } else if (event.which == 13) { // Enter
420 this.commitRename_();
421 }
422 };
423
424 /**
425 * Register focus and blur handlers to cause the parent node to be highlighted
426 * whenever a child link has keyboard focus. Note that this is only necessary
427 * because Chrome does not yet support the draft CSS Selectors 4 specification
428 * (http://www.w3.org/TR/selectors4/#subject), which provides a more elegant
429 * solution to this problem.
430 *
431 * @param {HTMLElement} e The element on which to register the event handlers.
432 * @return {void} Nothing.
433 * @private
434 */
435 remoting.HostTableEntry.prototype.registerFocusHandlers_ = function(e) {
436 e.addEventListener('focus', this.onFocusChange_.bind(this), false);
437 e.addEventListener('blur', this.onFocusChange_.bind(this), false);
438 };
439
440 /**
441 * Handle a focus change event within this table row.
442 * @return {void} Nothing.
443 * @private
444 */
445 remoting.HostTableEntry.prototype.onFocusChange_ = function() {
446 var element = document.activeElement;
447 while (element) {
448 if (element == this.tableRow) {
449 this.tableRow.classList.add('child-focused');
450 return;
451 }
452 element = element.parentNode;
453 }
454 this.tableRow.classList.remove('child-focused');
455 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698