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} */