Separate Simple Backend creation from initialization.
[chromium-blink-merge.git] / remoting / webapp / host_table_entry.js
blob3ba51896d79556a39b65338fb7b3ac04ca253c09
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.
5 /**
6  * @fileoverview
7  * Class representing an entry in the host-list portion of the home screen.
8  */
10 'use strict';
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
15 /**
16  * An entry in the host table.
17  * @param {remoting.Host} host The host, as obtained from Apiary.
18  * @param {number} webappMajorVersion The major version nmber of the web-app,
19  *     used to identify out-of-date hosts.
20  * @param {function(remoting.HostTableEntry):void} onRename Callback for
21  *     rename operations.
22  * @param {function(remoting.HostTableEntry):void=} opt_onDelete Callback for
23  *     delete operations.
24  * @constructor
25  */
26 remoting.HostTableEntry = function(
27     host, webappMajorVersion, onRename, opt_onDelete) {
28   /** @type {remoting.Host} */
29   this.host = host;
30   /** @type {number} */
31   this.webappMajorVersion_ = webappMajorVersion;
32   /** @type {function(remoting.HostTableEntry):void} @private */
33   this.onRename_ = onRename;
34   /** @type {undefined|function(remoting.HostTableEntry):void} @private */
35   this.onDelete_ = opt_onDelete;
37   /** @type {HTMLElement} */
38   this.tableRow = null;
39   /** @type {HTMLElement} @private */
40   this.hostNameCell_ = null;
41   /** @type {HTMLElement} @private */
42   this.warningOverlay_ = null;
43   // References to event handlers so that they can be removed.
44   /** @type {function():void} @private */
45   this.onBlurReference_ = function() {};
46   /** @type {function():void} @private */
47   this.onConfirmDeleteReference_ = function() {};
48   /** @type {function():void} @private */
49   this.onCancelDeleteReference_ = function() {};
50   /** @type {function():void?} @private */
51   this.onConnectReference_ = null;
54 /**
55  * Create the HTML elements for this entry and set up event handlers.
56  * @return {void} Nothing.
57  */
58 remoting.HostTableEntry.prototype.createDom = function() {
59   // Create the top-level <div>
60   var tableRow = /** @type {HTMLElement} */ document.createElement('div');
61   tableRow.classList.add('section-row');
62   // Create the host icon cell.
63   var hostIconDiv = /** @type {HTMLElement} */ document.createElement('div');
64   hostIconDiv.classList.add('host-list-main-icon');
65   var warningOverlay =
66       /** @type {HTMLElement} */ document.createElement('span');
67   hostIconDiv.appendChild(warningOverlay);
68   var hostIcon = /** @type {HTMLElement} */ document.createElement('img');
69   hostIcon.src = 'icon_host.webp';
70   hostIconDiv.appendChild(hostIcon);
71   tableRow.appendChild(hostIconDiv);
72   // Create the host name cell.
73   var hostNameCell = /** @type {HTMLElement} */ document.createElement('div');
74   hostNameCell.classList.add('box-spacer');
75   tableRow.appendChild(hostNameCell);
76   // Create the host rename cell.
77   var editButton = /** @type {HTMLElement} */ document.createElement('span');
78   var editButtonImg = /** @type {HTMLElement} */ document.createElement('img');
79   editButtonImg.title = chrome.i18n.getMessage(
80       /*i18n-content*/'TOOLTIP_RENAME');
81   editButtonImg.src = 'icon_pencil.webp';
82   editButton.tabIndex = 0;
83   editButton.classList.add('clickable');
84   editButton.classList.add('host-list-edit');
85   editButtonImg.classList.add('host-list-rename-icon');
86   editButton.appendChild(editButtonImg);
87   tableRow.appendChild(editButton);
88   // Create the host delete cell.
89   var deleteButton = /** @type {HTMLElement} */ document.createElement('span');
90   var deleteButtonImg =
91       /** @type {HTMLElement} */ document.createElement('img');
92   deleteButtonImg.title =
93       chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_DELETE');
94   deleteButtonImg.src = 'icon_cross.webp';
95   deleteButton.tabIndex = 0;
96   deleteButton.classList.add('clickable');
97   deleteButton.classList.add('host-list-edit');
98   deleteButtonImg.classList.add('host-list-remove-icon');
99   deleteButton.appendChild(deleteButtonImg);
100   tableRow.appendChild(deleteButton);
102   this.init(tableRow, warningOverlay, hostNameCell, editButton, deleteButton);
106  * Associate the table row with the specified elements and callbacks, and set
107  * up event handlers.
109  * @param {HTMLElement} tableRow The top-level <div> for the table entry.
110  * @param {HTMLElement} warningOverlay The <span> element to render a warning
111  *     icon on top of the host icon.
112  * @param {HTMLElement} hostNameCell The element containing the host name.
113  * @param {HTMLElement} editButton The <img> containing the pencil icon for
114  *     editing the host name.
115  * @param {HTMLElement=} opt_deleteButton The <img> containing the cross icon
116  *     for deleting the host, if present.
117  * @return {void} Nothing.
118  */
119 remoting.HostTableEntry.prototype.init = function(
120     tableRow, warningOverlay, hostNameCell, editButton, opt_deleteButton) {
121   this.tableRow = tableRow;
122   this.warningOverlay_ = warningOverlay;
123   this.hostNameCell_ = hostNameCell;
124   this.setHostName_();
126   /** @type {remoting.HostTableEntry} */
127   var that = this;
129   /** @param {Event} event The click event. */
130   var beginRename = function(event) {
131     that.beginRename_();
132     event.stopPropagation();
133   };
134   /** @param {Event} event The keyup event. */
135   var beginRenameKeyboard = function(event) {
136     if (event.which == 13 || event.which == 32) {
137       that.beginRename_();
138       event.stopPropagation();
139     }
140   };
141   editButton.addEventListener('click', beginRename, true);
142   editButton.addEventListener('keyup', beginRenameKeyboard, true);
143   this.registerFocusHandlers_(editButton);
145   if (opt_deleteButton) {
146     /** @param {Event} event The click event. */
147     var confirmDelete = function(event) {
148       that.showDeleteConfirmation_();
149       event.stopPropagation();
150     };
151     /** @param {Event} event The keyup event. */
152     var confirmDeleteKeyboard = function(event) {
153       if (event.which == 13 || event.which == 32) {
154         that.showDeleteConfirmation_();
155       }
156     };
157     opt_deleteButton.addEventListener('click', confirmDelete, false);
158     opt_deleteButton.addEventListener('keyup', confirmDeleteKeyboard, false);
159     this.registerFocusHandlers_(opt_deleteButton);
160   }
161   this.updateStatus();
165  * Update the row to reflect the current status of the host (online/offline and
166  * clickable/unclickable).
168  * @param {boolean=} opt_forEdit True if the status is being updated in order
169  *     to allow the host name to be edited.
170  * @return {void} Nothing.
171  */
172 remoting.HostTableEntry.prototype.updateStatus = function(opt_forEdit) {
173   var clickToConnect = this.host.status == 'ONLINE' && !opt_forEdit;
174   if (clickToConnect) {
175     if (!this.onConnectReference_) {
176       /** @type {string} */
177       var encodedHostId = encodeURIComponent(this.host.hostId)
178       this.onConnectReference_ = function() {
179         remoting.connectMe2Me(encodedHostId);
180       };
181       this.tableRow.addEventListener('click', this.onConnectReference_, false);
182     }
183     this.tableRow.classList.add('clickable');
184     this.tableRow.title = chrome.i18n.getMessage(
185         /*i18n-content*/'TOOLTIP_CONNECT', this.host.hostName);
186   } else {
187     if (this.onConnectReference_) {
188       this.tableRow.removeEventListener('click', this.onConnectReference_,
189                                         false);
190       this.onConnectReference_ = null;
191     }
192     this.tableRow.classList.remove('clickable');
193     this.tableRow.title = '';
194   }
195   var showOffline = this.host.status != 'ONLINE';
196   if (showOffline) {
197     this.tableRow.classList.remove('host-online');
198     this.tableRow.classList.add('host-offline');
199   } else {
200     this.tableRow.classList.add('host-online');
201     this.tableRow.classList.remove('host-offline');
202   }
203   this.warningOverlay_.hidden = !remoting.Host.needsUpdate(
204       this.host, this.webappMajorVersion_);
208  * Prepare the host for renaming by replacing its name with an edit box.
209  * @return {void} Nothing.
210  * @private
211  */
212 remoting.HostTableEntry.prototype.beginRename_ = function() {
213   var editBox = /** @type {HTMLInputElement} */ document.createElement('input');
214   editBox.type = 'text';
215   editBox.value = this.host.hostName;
216   this.hostNameCell_.innerText = '';
217   this.hostNameCell_.appendChild(editBox);
218   editBox.select();
220   this.onBlurReference_ = this.commitRename_.bind(this);
221   editBox.addEventListener('blur', this.onBlurReference_, false);
223   editBox.addEventListener('keydown', this.onKeydown_.bind(this), false);
224   this.updateStatus(true);
228  * Accept the hostname entered by the user.
229  * @return {void} Nothing.
230  * @private
231  */
232 remoting.HostTableEntry.prototype.commitRename_ = function() {
233   var editBox = this.hostNameCell_.querySelector('input');
234   if (editBox) {
235     if (this.host.hostName != editBox.value) {
236       this.host.hostName = editBox.value;
237       this.onRename_(this);
238     }
239     this.removeEditBox_();
240   }
244  * Prompt the user to confirm or cancel deletion of a host.
245  * @return {void} Nothing.
246  * @private
247  */
248 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() {
249   var message = document.getElementById('confirm-host-delete-message');
250   l10n.localizeElement(message, this.host.hostName);
251   var confirm = document.getElementById('confirm-host-delete');
252   var cancel = document.getElementById('cancel-host-delete');
253   this.onConfirmDeleteReference_ = this.confirmDelete_.bind(this);
254   this.onCancelDeleteReference_ = this.cancelDelete_.bind(this);
255   confirm.addEventListener('click', this.onConfirmDeleteReference_, false);
256   cancel.addEventListener('click', this.onCancelDeleteReference_, false);
257   remoting.setMode(remoting.AppMode.CONFIRM_HOST_DELETE);
261  * Confirm deletion of a host.
262  * @return {void} Nothing.
263  * @private
264  */
265 remoting.HostTableEntry.prototype.confirmDelete_ = function() {
266   this.onDelete_(this);
267   this.cleanUpConfirmationEventListeners_();
268   remoting.setMode(remoting.AppMode.HOME);
272  * Cancel deletion of a host.
273  * @return {void} Nothing.
274  * @private
275  */
276 remoting.HostTableEntry.prototype.cancelDelete_ = function() {
277   this.cleanUpConfirmationEventListeners_();
278   remoting.setMode(remoting.AppMode.HOME);
282  * Remove the confirm and cancel event handlers, which refer to this object.
283  * @return {void} Nothing.
284  * @private
285  */
286 remoting.HostTableEntry.prototype.cleanUpConfirmationEventListeners_ =
287     function() {
288   var confirm = document.getElementById('confirm-host-delete');
289   var cancel = document.getElementById('cancel-host-delete');
290   confirm.removeEventListener('click', this.onConfirmDeleteReference_, false);
291   cancel.removeEventListener('click', this.onCancelDeleteReference_, false);
292   this.onCancelDeleteReference_ = function() {};
293   this.onConfirmDeleteReference_ = function() {};
297  * Remove the edit box corresponding to the specified host, and reset its name.
298  * @return {void} Nothing.
299  * @private
300  */
301 remoting.HostTableEntry.prototype.removeEditBox_ = function() {
302   var editBox = this.hostNameCell_.querySelector('input');
303   if (editBox) {
304     // onblur will fire when the edit box is removed, so remove the hook.
305     editBox.removeEventListener('blur', this.onBlurReference_, false);
306   }
307   // Update the tool-top and event handler.
308   this.updateStatus();
309   this.setHostName_();
313  * Create the DOM nodes and event handlers for the hostname cell.
314  * @return {void} Nothing.
315  * @private
316  */
317 remoting.HostTableEntry.prototype.setHostName_ = function() {
318   var hostNameNode = /** @type {HTMLElement} */ document.createElement('a');
319   if (this.host.status == 'ONLINE') {
320     if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) {
321       hostNameNode.innerText = chrome.i18n.getMessage(
322           /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName);
323     } else {
324       hostNameNode.innerText = this.host.hostName;
325     }
326     hostNameNode.href = '#';
327     this.registerFocusHandlers_(hostNameNode);
328     /** @type {remoting.HostTableEntry} */
329     var that = this;
330     /** @param {Event} event */
331     var onKeyDown = function(event) {
332       if (that.onConnectReference_ &&
333           (event.which == 13 || event.which == 32)) {
334         that.onConnectReference_();
335       }
336     };
337     hostNameNode.addEventListener('keydown', onKeyDown, false);
338   } else {
339     hostNameNode.innerText = chrome.i18n.getMessage(/*i18n-content*/'OFFLINE',
340                                                     this.host.hostName);
341   }
342   hostNameNode.classList.add('host-list-label');
343   this.hostNameCell_.innerText = '';  // Remove previous contents (if any).
344   this.hostNameCell_.appendChild(hostNameNode);
348  * Handle a key event while the user is typing a host name
349  * @param {Event} event The keyboard event.
350  * @return {void} Nothing.
351  * @private
352  */
353 remoting.HostTableEntry.prototype.onKeydown_ = function(event) {
354   if (event.which == 27) {  // Escape
355     this.removeEditBox_();
356   } else if (event.which == 13) {  // Enter
357     this.commitRename_();
358   }
362  * Register focus and blur handlers to cause the parent node to be highlighted
363  * whenever a child link has keyboard focus. Note that this is only necessary
364  * because Chrome does not yet support the draft CSS Selectors 4 specification
365  * (http://www.w3.org/TR/selectors4/#subject), which provides a more elegant
366  * solution to this problem.
368  * @param {HTMLElement} e The element on which to register the event handlers.
369  * @return {void} Nothing.
370  * @private
371  */
372 remoting.HostTableEntry.prototype.registerFocusHandlers_ = function(e) {
373   e.addEventListener('focus', this.onFocusChange_.bind(this), false);
374   e.addEventListener('blur', this.onFocusChange_.bind(this), false);
378  * Handle a focus change event within this table row.
379  * @return {void} Nothing.
380  * @private
381  */
382 remoting.HostTableEntry.prototype.onFocusChange_ = function() {
383   var element = document.activeElement;
384   while (element) {
385     if (element == this.tableRow) {
386       this.tableRow.classList.add('child-focused');
387       return;
388     }
389     element = element.parentNode;
390   }
391   this.tableRow.classList.remove('child-focused');