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.
7 * Class representing an entry in the host-list portion of the home screen.
10 /** @suppress {duplicate} */
11 var remoting
= remoting
|| {};
18 * An entry in the host table.
20 * @param {number} webappMajorVersion The major version nmber of the web-app,
21 * used to identify out-of-date hosts.
22 * @param {function(string):void} onConnect Callback for
24 * @param {function(remoting.HostTableEntry):void} onRename Callback for
26 * @param {function(remoting.HostTableEntry):void=} opt_onDelete Callback for
30 * @implements {base.Disposable}
32 remoting
.HostTableEntry = function(
33 webappMajorVersion
, onConnect
, onRename
, opt_onDelete
) {
34 /** @type {remoting.Host} */
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
;
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;
54 /** @private {base.Disposables} */
55 this.renameInputEventHooks_
= null;
56 /** @private {base.Disposables} */
57 this.disposables_
= new base
.Disposables();
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() {};
68 /** @param {remoting.Host} host */
69 remoting
.HostTableEntry
.prototype.setHost = function(host
) {
74 /** @return {HTMLElement} */
75 remoting
.HostTableEntry
.prototype.element = function() {
76 return this.rootElement_
;
79 remoting
.HostTableEntry
.prototype.dispose = function() {
80 base
.dispose(this.disposables_
);
81 this.disposables_
= null;
82 base
.dispose(this.renameInputEventHooks_
);
83 this.renameInputEventHooks_
= null;
86 /** @return {string} */
87 remoting
.HostTableEntry
.prototype.getHTML_ = function() {
89 '<div class="host-list-main-icon">' +
90 '<span class="warning-overlay"></span>' +
91 '<img src="icon_host.webp">' +
93 '<div class="box-spacer">' +
94 '<a class="host-name-label" href="#""></a>' +
95 '<input class="host-rename-input" type="text" hidden/>' +
97 '<span tabindex="0" class="clickable host-list-edit rename-button">' +
98 '<img src="icon_pencil.webp" class="host-list-rename-icon">' +
100 if (this.onDelete_
) {
102 '<span tabindex="0" class="clickable host-list-edit delete-button">' +
103 '<img src="icon_cross.webp" class="host-list-remove-icon">' +
106 return '<div class="section-row host-offline">' + html
+ '</div>';
110 * Create the HTML elements for this entry and set up event handlers.
111 * @return {void} Nothing.
113 remoting
.HostTableEntry
.prototype.createDom_ = function() {
114 var container
= /** @type {HTMLElement} */ (document
.createElement('div'));
115 container
.innerHTML
= this.getHTML_();
117 // Setup DOM references.
118 this.rootElement_
= /** @type {HTMLElement} */ (container
.firstElementChild
);
119 this.warningOverlay_
= container
.querySelector('.warning-overlay');
120 this.hostNameLabel_
= container
.querySelector('.host-name-label');
121 this.renameInputField_
= /** @type {HTMLInputElement} */ (
122 container
.querySelector('.host-rename-input'));
124 // Register event handlers and set tooltips.
125 var editButton
= container
.querySelector('.rename-button');
126 var deleteButton
= container
.querySelector('.delete-button');
127 editButton
.title
= chrome
.i18n
.getMessage(/*i18n-content*/'TOOLTIP_RENAME');
128 this.registerButton_(editButton
, this.beginRename_
.bind(this));
129 this.registerButton_(this.rootElement_
, this.onConnectButton_
.bind(this));
131 this.registerButton_(deleteButton
, this.showDeleteConfirmation_
.bind(this));
133 chrome
.i18n
.getMessage(/*i18n-content*/'TOOLTIP_DELETE');
137 /** @return {base.Disposable} @private */
138 remoting
.HostTableEntry
.prototype.registerButton_ = function(
139 /** HTMLElement */ button
, /** Function */ callback
) {
140 var onKeyDown = function(/** Event */ e
) {
141 if (e
.which
=== KeyCodes
.ENTER
|| e
.which
=== KeyCodes
.SPACEBAR
) {
146 var onClick = function(/** Event */ e
) {
150 var onFocusChanged
= this.onFocusChange_
.bind(this);
151 this.disposables_
.add(
152 new base
.DomEventHook(button
, 'click', onClick
, false),
153 new base
.DomEventHook(button
, 'keydown', onKeyDown
, false),
154 // Register focus and blur handlers to cause the parent node to be
155 // highlighted whenever a child link has keyboard focus. Note that this is
156 // only necessary because Chrome does not yet support the draft CSS
157 // Selectors 4 specification (http://www.w3.org/TR/selectors4/#subject),
158 // which provides a more elegant solution to this problem.
159 new base
.DomEventHook(button
, 'focus', onFocusChanged
, false),
160 new base
.DomEventHook(button
, 'blur', onFocusChanged
, false));
164 /** @return {void} @private */
165 remoting
.HostTableEntry
.prototype.onConnectButton_ = function() {
166 // Don't connect during renaming as this event fires if the user hits Enter
167 // after typing a new name.
168 if (!this.isRenaming_() && this.isOnline_()) {
169 var encodedHostId
= encodeURIComponent(this.host
.hostId
);
170 this.onConnect_(encodedHostId
);
174 /** @return {boolean} @private */
175 remoting
.HostTableEntry
.prototype.isOnline_ = function() {
176 return Boolean(this.host
) && this.host
.status
=== 'ONLINE';
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
);
186 return this.host
.hostName
;
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
]);
193 return chrome
.i18n
.getMessage(/*i18n-content*/ 'OFFLINE',
199 * Update the UI to reflect the current status of the host
201 * @return {void} Nothing.
204 remoting
.HostTableEntry
.prototype.updateUI_ = function() {
208 var clickToConnect
= this.isOnline_() && !this.isRenaming_();
209 var showOffline
= !this.isOnline_();
210 this.rootElement_
.querySelector('.box-spacer').id
=
211 'host_' + this.host
.hostId
;
212 var connectLabel
= chrome
.i18n
.getMessage(/*i18n-content*/'TOOLTIP_CONNECT',
214 this.rootElement_
.classList
.toggle('clickable', clickToConnect
);
215 this.rootElement_
.title
= (clickToConnect
) ? connectLabel
: '';
216 this.rootElement_
.classList
.toggle('host-online', !showOffline
);
217 this.rootElement_
.classList
.toggle('host-offline', showOffline
);
219 var hostReportedError
= this.host
.hostOfflineReason
!== '';
220 var hostNeedsUpdate
= remoting
.Host
.needsUpdate(
221 this.host
, this.webappMajorVersion_
);
222 this.warningOverlay_
.hidden
= !hostNeedsUpdate
&& !hostReportedError
;
223 this.hostNameLabel_
.innerText
= this.getHostDisplayName_();
224 this.hostNameLabel_
.title
=
225 formatHostOfflineReason(this.host
.hostOfflineReason
);
226 this.renameInputField_
.hidden
= !this.isRenaming_();
227 this.hostNameLabel_
.hidden
= this.isRenaming_();
231 * Prepares the host for renaming by showing an edit box.
232 * @return {void} Nothing.
235 remoting
.HostTableEntry
.prototype.beginRename_ = function() {
236 this.renameInputField_
.value
= this.host
.hostName
;
237 console
.assert(this.renameInputEventHooks_
=== null,
238 '|renameInputEventHooks_| already exists.');
239 base
.dispose(this.renameInputEventHooks_
);
240 this.renameInputEventHooks_
= new base
.Disposables(
241 new base
.DomEventHook(this.renameInputField_
, 'blur',
242 this.commitRename_
.bind(this), false),
243 new base
.DomEventHook(this.renameInputField_
, 'keydown',
244 this.onKeydown_
.bind(this), false));
246 this.renameInputField_
.focus();
249 /** @return {boolean} @private */
250 remoting
.HostTableEntry
.prototype.isRenaming_ = function() {
251 return Boolean(this.renameInputEventHooks_
);
254 /** @return {void} @private */
255 remoting
.HostTableEntry
.prototype.commitRename_ = function() {
256 if (this.host
.hostName
!= this.renameInputField_
.value
) {
257 this.host
.hostName
= this.renameInputField_
.value
;
258 this.onRename_(this);
263 /** @return {void} @private */
264 remoting
.HostTableEntry
.prototype.hideEditBox_ = function() {
265 // onblur will fire when the edit box is removed, so remove the hook.
266 base
.dispose(this.renameInputEventHooks_
);
267 this.renameInputEventHooks_
= null;
268 // Update the tool-tip and event handler.
273 * Handle a key event while the user is typing a host name
274 * @param {Event} event The keyboard event.
275 * @return {void} Nothing.
278 remoting
.HostTableEntry
.prototype.onKeydown_ = function(event
) {
279 if (event
.which
== KeyCodes
.ESCAPE
) {
281 } else if (event
.which
== KeyCodes
.ENTER
) {
282 this.commitRename_();
283 event
.stopPropagation();
288 * Prompt the user to confirm or cancel deletion of a host.
289 * @return {void} Nothing.
292 remoting
.HostTableEntry
.prototype.showDeleteConfirmation_ = function() {
293 var message
= document
.getElementById('confirm-host-delete-message');
294 l10n
.localizeElement(message
, this.host
.hostName
);
295 var confirm
= document
.getElementById('confirm-host-delete');
296 var cancel
= document
.getElementById('cancel-host-delete');
297 this.onConfirmDeleteReference_
= this.confirmDelete_
.bind(this);
298 this.onCancelDeleteReference_
= this.cancelDelete_
.bind(this);
299 confirm
.addEventListener('click', this.onConfirmDeleteReference_
, false);
300 cancel
.addEventListener('click', this.onCancelDeleteReference_
, false);
301 remoting
.setMode(remoting
.AppMode
.CONFIRM_HOST_DELETE
);
305 * Confirm deletion of a host.
306 * @return {void} Nothing.
309 remoting
.HostTableEntry
.prototype.confirmDelete_ = function() {
310 this.onDelete_(this);
311 this.cleanUpConfirmationEventListeners_();
312 remoting
.setMode(remoting
.AppMode
.HOME
);
316 * Cancel deletion of a host.
317 * @return {void} Nothing.
320 remoting
.HostTableEntry
.prototype.cancelDelete_ = function() {
321 this.cleanUpConfirmationEventListeners_();
322 remoting
.setMode(remoting
.AppMode
.HOME
);
326 * Remove the confirm and cancel event handlers, which refer to this object.
327 * @return {void} Nothing.
330 remoting
.HostTableEntry
.prototype.cleanUpConfirmationEventListeners_
=
332 var confirm
= document
.getElementById('confirm-host-delete');
333 var cancel
= document
.getElementById('cancel-host-delete');
334 confirm
.removeEventListener('click', this.onConfirmDeleteReference_
, false);
335 cancel
.removeEventListener('click', this.onCancelDeleteReference_
, false);
336 this.onCancelDeleteReference_ = function() {};
337 this.onConfirmDeleteReference_ = function() {};
341 * Handle a focus change event within this table row.
342 * @return {void} Nothing.
345 remoting
.HostTableEntry
.prototype.onFocusChange_ = function() {
346 var element
= document
.activeElement
;
348 if (element
== this.rootElement_
) {
349 this.rootElement_
.classList
.add('child-focused');
352 element
= element
.parentNode
;
354 this.rootElement_
.classList
.remove('child-focused');
358 * Formats host's updateTime value relative to current time (i.e. only
359 * displaying hours and minutes if updateTime is less than a day in the past).
360 * @param {string} updateTime RFC 3339 formatted date-time value.
361 * @return {string} Formatted value (i.e. 11/11/2014)
363 function formatUpdateTime(updateTime
) {
364 var lastOnline
= new Date(updateTime
);
365 var now
= new Date();
366 var displayString
= '';
367 if (now
.getFullYear() == lastOnline
.getFullYear() &&
368 now
.getMonth() == lastOnline
.getMonth() &&
369 now
.getDate() == lastOnline
.getDate()) {
370 return lastOnline
.toLocaleTimeString();
372 return lastOnline
.toLocaleDateString();
377 * Formats host's host-offline-reason value (i.e. 'INVALID_HOST_CONFIGURATION')
378 * to a human-readable description of the error.
379 * @param {string} hostOfflineReason
382 function formatHostOfflineReason(hostOfflineReason
) {
383 if (!hostOfflineReason
) {
386 var knownReasonTags
= [
387 /*i18n-content*/'OFFLINE_REASON_INITIALIZATION_FAILED',
388 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_CONFIGURATION',
389 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_DOMAIN',
390 /*i18n-content*/'OFFLINE_REASON_INVALID_HOST_ID',
391 /*i18n-content*/'OFFLINE_REASON_INVALID_OAUTH_CREDENTIALS',
392 /*i18n-content*/'OFFLINE_REASON_LOGIN_SCREEN_NOT_SUPPORTED',
393 /*i18n-content*/'OFFLINE_REASON_POLICY_CHANGE_REQUIRES_RESTART',
394 /*i18n-content*/'OFFLINE_REASON_POLICY_READ_ERROR',
395 /*i18n-content*/'OFFLINE_REASON_SUCCESS_EXIT',
396 /*i18n-content*/'OFFLINE_REASON_USERNAME_MISMATCH'
398 var offlineReasonTag
= 'OFFLINE_REASON_' + hostOfflineReason
;
399 if (knownReasonTags
.indexOf(offlineReasonTag
) != (-1)) {
400 return chrome
.i18n
.getMessage(offlineReasonTag
);
402 return chrome
.i18n
.getMessage(
403 /*i18n-content*/'OFFLINE_REASON_UNKNOWN', hostOfflineReason
);
407 /** @enum {number} */