- add sources.
[platform/framework/web/crosswalk.git] / src / remoting / webapp / host_table_entry.js
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.
4
5 /**
6  * @fileoverview
7  * Class representing an entry in the host-list portion of the home screen.
8  */
9
10 'use strict';
11
12 /** @suppress {duplicate} */
13 var remoting = remoting || {};
14
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;
36
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;
52 };
53
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   hostNameCell.id = 'host_' + this.host.hostId;
76   tableRow.appendChild(hostNameCell);
77   // Create the host rename cell.
78   var editButton = /** @type {HTMLElement} */ document.createElement('span');
79   var editButtonImg = /** @type {HTMLElement} */ document.createElement('img');
80   editButtonImg.title = chrome.i18n.getMessage(
81       /*i18n-content*/'TOOLTIP_RENAME');
82   editButtonImg.src = 'icon_pencil.webp';
83   editButton.tabIndex = 0;
84   editButton.classList.add('clickable');
85   editButton.classList.add('host-list-edit');
86   editButtonImg.classList.add('host-list-rename-icon');
87   editButton.appendChild(editButtonImg);
88   tableRow.appendChild(editButton);
89   // Create the host delete cell.
90   var deleteButton = /** @type {HTMLElement} */ document.createElement('span');
91   var deleteButtonImg =
92       /** @type {HTMLElement} */ document.createElement('img');
93   deleteButtonImg.title =
94       chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_DELETE');
95   deleteButtonImg.src = 'icon_cross.webp';
96   deleteButton.tabIndex = 0;
97   deleteButton.classList.add('clickable');
98   deleteButton.classList.add('host-list-edit');
99   deleteButtonImg.classList.add('host-list-remove-icon');
100   deleteButton.appendChild(deleteButtonImg);
101   tableRow.appendChild(deleteButton);
102
103   this.init(tableRow, warningOverlay, hostNameCell, editButton, deleteButton);
104 };
105
106 /**
107  * Associate the table row with the specified elements and callbacks, and set
108  * up event handlers.
109  *
110  * @param {HTMLElement} tableRow The top-level <div> for the table entry.
111  * @param {HTMLElement} warningOverlay The <span> element to render a warning
112  *     icon on top of the host icon.
113  * @param {HTMLElement} hostNameCell The element containing the host name.
114  * @param {HTMLElement} editButton The <img> containing the pencil icon for
115  *     editing the host name.
116  * @param {HTMLElement=} opt_deleteButton The <img> containing the cross icon
117  *     for deleting the host, if present.
118  * @return {void} Nothing.
119  */
120 remoting.HostTableEntry.prototype.init = function(
121     tableRow, warningOverlay, hostNameCell, editButton, opt_deleteButton) {
122   this.tableRow = tableRow;
123   this.warningOverlay_ = warningOverlay;
124   this.hostNameCell_ = hostNameCell;
125   this.setHostName_();
126
127   /** @type {remoting.HostTableEntry} */
128   var that = this;
129
130   /** @param {Event} event The click event. */
131   var beginRename = function(event) {
132     that.beginRename_();
133     event.stopPropagation();
134   };
135   /** @param {Event} event The keyup event. */
136   var beginRenameKeyboard = function(event) {
137     if (event.which == 13 || event.which == 32) {
138       that.beginRename_();
139       event.stopPropagation();
140     }
141   };
142   editButton.addEventListener('click', beginRename, true);
143   editButton.addEventListener('keyup', beginRenameKeyboard, true);
144   this.registerFocusHandlers_(editButton);
145
146   if (opt_deleteButton) {
147     /** @param {Event} event The click event. */
148     var confirmDelete = function(event) {
149       that.showDeleteConfirmation_();
150       event.stopPropagation();
151     };
152     /** @param {Event} event The keyup event. */
153     var confirmDeleteKeyboard = function(event) {
154       if (event.which == 13 || event.which == 32) {
155         that.showDeleteConfirmation_();
156       }
157     };
158     opt_deleteButton.addEventListener('click', confirmDelete, false);
159     opt_deleteButton.addEventListener('keyup', confirmDeleteKeyboard, false);
160     this.registerFocusHandlers_(opt_deleteButton);
161   }
162   this.updateStatus();
163 };
164
165 /**
166  * Update the row to reflect the current status of the host (online/offline and
167  * clickable/unclickable).
168  *
169  * @param {boolean=} opt_forEdit True if the status is being updated in order
170  *     to allow the host name to be edited.
171  * @return {void} Nothing.
172  */
173 remoting.HostTableEntry.prototype.updateStatus = function(opt_forEdit) {
174   var clickToConnect = this.host.status == 'ONLINE' && !opt_forEdit;
175   if (clickToConnect) {
176     if (!this.onConnectReference_) {
177       /** @type {string} */
178       var encodedHostId = encodeURIComponent(this.host.hostId)
179       this.onConnectReference_ = function() {
180         remoting.connectMe2Me(encodedHostId);
181       };
182       this.tableRow.addEventListener('click', this.onConnectReference_, false);
183     }
184     this.tableRow.classList.add('clickable');
185     this.tableRow.title = chrome.i18n.getMessage(
186         /*i18n-content*/'TOOLTIP_CONNECT', this.host.hostName);
187   } else {
188     if (this.onConnectReference_) {
189       this.tableRow.removeEventListener('click', this.onConnectReference_,
190                                         false);
191       this.onConnectReference_ = null;
192     }
193     this.tableRow.classList.remove('clickable');
194     this.tableRow.title = '';
195   }
196   var showOffline = this.host.status != 'ONLINE';
197   if (showOffline) {
198     this.tableRow.classList.remove('host-online');
199     this.tableRow.classList.add('host-offline');
200   } else {
201     this.tableRow.classList.add('host-online');
202     this.tableRow.classList.remove('host-offline');
203   }
204   this.warningOverlay_.hidden = !remoting.Host.needsUpdate(
205       this.host, this.webappMajorVersion_);
206 };
207
208 /**
209  * Prepare the host for renaming by replacing its name with an edit box.
210  * @return {void} Nothing.
211  * @private
212  */
213 remoting.HostTableEntry.prototype.beginRename_ = function() {
214   var editBox = /** @type {HTMLInputElement} */ document.createElement('input');
215   editBox.type = 'text';
216   editBox.value = this.host.hostName;
217   this.hostNameCell_.innerText = '';
218   this.hostNameCell_.appendChild(editBox);
219   editBox.select();
220
221   this.onBlurReference_ = this.commitRename_.bind(this);
222   editBox.addEventListener('blur', this.onBlurReference_, false);
223
224   editBox.addEventListener('keydown', this.onKeydown_.bind(this), false);
225   this.updateStatus(true);
226 };
227
228 /**
229  * Accept the hostname entered by the user.
230  * @return {void} Nothing.
231  * @private
232  */
233 remoting.HostTableEntry.prototype.commitRename_ = function() {
234   var editBox = this.hostNameCell_.querySelector('input');
235   if (editBox) {
236     if (this.host.hostName != editBox.value) {
237       this.host.hostName = editBox.value;
238       this.onRename_(this);
239     }
240     this.removeEditBox_();
241   }
242 };
243
244 /**
245  * Prompt the user to confirm or cancel deletion of a host.
246  * @return {void} Nothing.
247  * @private
248  */
249 remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() {
250   var message = document.getElementById('confirm-host-delete-message');
251   l10n.localizeElement(message, this.host.hostName);
252   var confirm = document.getElementById('confirm-host-delete');
253   var cancel = document.getElementById('cancel-host-delete');
254   this.onConfirmDeleteReference_ = this.confirmDelete_.bind(this);
255   this.onCancelDeleteReference_ = this.cancelDelete_.bind(this);
256   confirm.addEventListener('click', this.onConfirmDeleteReference_, false);
257   cancel.addEventListener('click', this.onCancelDeleteReference_, false);
258   remoting.setMode(remoting.AppMode.CONFIRM_HOST_DELETE);
259 };
260
261 /**
262  * Confirm deletion of a host.
263  * @return {void} Nothing.
264  * @private
265  */
266 remoting.HostTableEntry.prototype.confirmDelete_ = function() {
267   this.onDelete_(this);
268   this.cleanUpConfirmationEventListeners_();
269   remoting.setMode(remoting.AppMode.HOME);
270 };
271
272 /**
273  * Cancel deletion of a host.
274  * @return {void} Nothing.
275  * @private
276  */
277 remoting.HostTableEntry.prototype.cancelDelete_ = function() {
278   this.cleanUpConfirmationEventListeners_();
279   remoting.setMode(remoting.AppMode.HOME);
280 };
281
282 /**
283  * Remove the confirm and cancel event handlers, which refer to this object.
284  * @return {void} Nothing.
285  * @private
286  */
287 remoting.HostTableEntry.prototype.cleanUpConfirmationEventListeners_ =
288     function() {
289   var confirm = document.getElementById('confirm-host-delete');
290   var cancel = document.getElementById('cancel-host-delete');
291   confirm.removeEventListener('click', this.onConfirmDeleteReference_, false);
292   cancel.removeEventListener('click', this.onCancelDeleteReference_, false);
293   this.onCancelDeleteReference_ = function() {};
294   this.onConfirmDeleteReference_ = function() {};
295 };
296
297 /**
298  * Remove the edit box corresponding to the specified host, and reset its name.
299  * @return {void} Nothing.
300  * @private
301  */
302 remoting.HostTableEntry.prototype.removeEditBox_ = function() {
303   var editBox = this.hostNameCell_.querySelector('input');
304   if (editBox) {
305     // onblur will fire when the edit box is removed, so remove the hook.
306     editBox.removeEventListener('blur', this.onBlurReference_, false);
307   }
308   // Update the tool-top and event handler.
309   this.updateStatus();
310   this.setHostName_();
311 };
312
313 /**
314  * Create the DOM nodes and event handlers for the hostname cell.
315  * @return {void} Nothing.
316  * @private
317  */
318 remoting.HostTableEntry.prototype.setHostName_ = function() {
319   var hostNameNode = /** @type {HTMLElement} */ document.createElement('a');
320   if (this.host.status == 'ONLINE') {
321     if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) {
322       hostNameNode.innerText = chrome.i18n.getMessage(
323           /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName);
324     } else {
325       hostNameNode.innerText = this.host.hostName;
326     }
327     hostNameNode.href = '#';
328     this.registerFocusHandlers_(hostNameNode);
329     /** @type {remoting.HostTableEntry} */
330     var that = this;
331     /** @param {Event} event */
332     var onKeyDown = function(event) {
333       if (that.onConnectReference_ &&
334           (event.which == 13 || event.which == 32)) {
335         that.onConnectReference_();
336       }
337     };
338     hostNameNode.addEventListener('keydown', onKeyDown, false);
339   } else {
340     if (this.host.updatedTime) {
341       var lastOnline = new Date(this.host.updatedTime);
342       var now = new Date();
343       var displayString = '';
344       if (now.getFullYear() == lastOnline.getFullYear() &&
345           now.getMonth() == lastOnline.getMonth() &&
346           now.getDate() == lastOnline.getDate()) {
347         displayString = lastOnline.toLocaleTimeString();
348       } else {
349         displayString = lastOnline.toLocaleDateString();
350       }
351       hostNameNode.innerText = chrome.i18n.getMessage(
352           /*i18n-content*/'LAST_ONLINE', [this.host.hostName, displayString]);
353     } else {
354       hostNameNode.innerText = chrome.i18n.getMessage(
355           /*i18n-content*/'OFFLINE', this.host.hostName);
356     }
357   }
358   hostNameNode.classList.add('host-list-label');
359   this.hostNameCell_.innerText = '';  // Remove previous contents (if any).
360   this.hostNameCell_.appendChild(hostNameNode);
361 };
362
363 /**
364  * Handle a key event while the user is typing a host name
365  * @param {Event} event The keyboard event.
366  * @return {void} Nothing.
367  * @private
368  */
369 remoting.HostTableEntry.prototype.onKeydown_ = function(event) {
370   if (event.which == 27) {  // Escape
371     this.removeEditBox_();
372   } else if (event.which == 13) {  // Enter
373     this.commitRename_();
374   }
375 };
376
377 /**
378  * Register focus and blur handlers to cause the parent node to be highlighted
379  * whenever a child link has keyboard focus. Note that this is only necessary
380  * because Chrome does not yet support the draft CSS Selectors 4 specification
381  * (http://www.w3.org/TR/selectors4/#subject), which provides a more elegant
382  * solution to this problem.
383  *
384  * @param {HTMLElement} e The element on which to register the event handlers.
385  * @return {void} Nothing.
386  * @private
387  */
388 remoting.HostTableEntry.prototype.registerFocusHandlers_ = function(e) {
389   e.addEventListener('focus', this.onFocusChange_.bind(this), false);
390   e.addEventListener('blur', this.onFocusChange_.bind(this), false);
391 };
392
393 /**
394  * Handle a focus change event within this table row.
395  * @return {void} Nothing.
396  * @private
397  */
398 remoting.HostTableEntry.prototype.onFocusChange_ = function() {
399   var element = document.activeElement;
400   while (element) {
401     if (element == this.tableRow) {
402       this.tableRow.classList.add('child-focused');
403       return;
404     }
405     element = element.parentNode;
406   }
407   this.tableRow.classList.remove('child-focused');
408 };