| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * @fileoverview |
| * Class representing an entry in the host-list portion of the home screen. |
| */ |
| |
| 'use strict'; |
| |
| /** @suppress {duplicate} */ |
| var remoting = remoting || {}; |
| |
| /** |
| * An entry in the host table. |
| * @param {remoting.Host} host The host, as obtained from Apiary. |
| * @param {number} webappMajorVersion The major version nmber of the web-app, |
| * used to identify out-of-date hosts. |
| * @param {function(remoting.HostTableEntry):void} onRename Callback for |
| * rename operations. |
| * @param {function(remoting.HostTableEntry):void=} opt_onDelete Callback for |
| * delete operations. |
| * @constructor |
| */ |
| remoting.HostTableEntry = function( |
| host, webappMajorVersion, onRename, opt_onDelete) { |
| /** @type {remoting.Host} */ |
| this.host = host; |
| /** @type {number} */ |
| this.webappMajorVersion_ = webappMajorVersion; |
| /** @type {function(remoting.HostTableEntry):void} @private */ |
| this.onRename_ = onRename; |
| /** @type {undefined|function(remoting.HostTableEntry):void} @private */ |
| this.onDelete_ = opt_onDelete; |
| |
| /** @type {HTMLElement} */ |
| this.tableRow = null; |
| /** @type {HTMLElement} @private */ |
| this.hostNameCell_ = null; |
| /** @type {HTMLElement} @private */ |
| this.warningOverlay_ = null; |
| // References to event handlers so that they can be removed. |
| /** @type {function():void} @private */ |
| this.onBlurReference_ = function() {}; |
| /** @type {function():void} @private */ |
| this.onConfirmDeleteReference_ = function() {}; |
| /** @type {function():void} @private */ |
| this.onCancelDeleteReference_ = function() {}; |
| /** @type {function():void?} @private */ |
| this.onConnectReference_ = null; |
| }; |
| |
| /** |
| * Create the HTML elements for this entry and set up event handlers. |
| * @return {void} Nothing. |
| */ |
| remoting.HostTableEntry.prototype.createDom = function() { |
| // Create the top-level <div> |
| var tableRow = /** @type {HTMLElement} */ document.createElement('div'); |
| tableRow.classList.add('section-row'); |
| // Create the host icon cell. |
| var hostIconDiv = /** @type {HTMLElement} */ document.createElement('div'); |
| hostIconDiv.classList.add('host-list-main-icon'); |
| var warningOverlay = |
| /** @type {HTMLElement} */ document.createElement('span'); |
| hostIconDiv.appendChild(warningOverlay); |
| var hostIcon = /** @type {HTMLElement} */ document.createElement('img'); |
| hostIcon.src = 'icon_host.webp'; |
| hostIconDiv.appendChild(hostIcon); |
| tableRow.appendChild(hostIconDiv); |
| // Create the host name cell. |
| var hostNameCell = /** @type {HTMLElement} */ document.createElement('div'); |
| hostNameCell.classList.add('box-spacer'); |
| hostNameCell.id = 'host_' + this.host.hostId; |
| tableRow.appendChild(hostNameCell); |
| // Create the host rename cell. |
| var editButton = /** @type {HTMLElement} */ document.createElement('span'); |
| var editButtonImg = /** @type {HTMLElement} */ document.createElement('img'); |
| editButtonImg.title = chrome.i18n.getMessage( |
| /*i18n-content*/'TOOLTIP_RENAME'); |
| editButtonImg.src = 'icon_pencil.webp'; |
| editButton.tabIndex = 0; |
| editButton.classList.add('clickable'); |
| editButton.classList.add('host-list-edit'); |
| editButtonImg.classList.add('host-list-rename-icon'); |
| editButton.appendChild(editButtonImg); |
| tableRow.appendChild(editButton); |
| // Create the host delete cell. |
| var deleteButton = /** @type {HTMLElement} */ document.createElement('span'); |
| var deleteButtonImg = |
| /** @type {HTMLElement} */ document.createElement('img'); |
| deleteButtonImg.title = |
| chrome.i18n.getMessage(/*i18n-content*/'TOOLTIP_DELETE'); |
| deleteButtonImg.src = 'icon_cross.webp'; |
| deleteButton.tabIndex = 0; |
| deleteButton.classList.add('clickable'); |
| deleteButton.classList.add('host-list-edit'); |
| deleteButtonImg.classList.add('host-list-remove-icon'); |
| deleteButton.appendChild(deleteButtonImg); |
| tableRow.appendChild(deleteButton); |
| |
| this.init(tableRow, warningOverlay, hostNameCell, editButton, deleteButton); |
| }; |
| |
| /** |
| * Associate the table row with the specified elements and callbacks, and set |
| * up event handlers. |
| * |
| * @param {HTMLElement} tableRow The top-level <div> for the table entry. |
| * @param {HTMLElement} warningOverlay The <span> element to render a warning |
| * icon on top of the host icon. |
| * @param {HTMLElement} hostNameCell The element containing the host name. |
| * @param {HTMLElement} editButton The <img> containing the pencil icon for |
| * editing the host name. |
| * @param {HTMLElement=} opt_deleteButton The <img> containing the cross icon |
| * for deleting the host, if present. |
| * @return {void} Nothing. |
| */ |
| remoting.HostTableEntry.prototype.init = function( |
| tableRow, warningOverlay, hostNameCell, editButton, opt_deleteButton) { |
| this.tableRow = tableRow; |
| this.warningOverlay_ = warningOverlay; |
| this.hostNameCell_ = hostNameCell; |
| this.setHostName_(); |
| |
| /** @type {remoting.HostTableEntry} */ |
| var that = this; |
| |
| /** @param {Event} event The click event. */ |
| var beginRename = function(event) { |
| that.beginRename_(); |
| event.stopPropagation(); |
| }; |
| /** @param {Event} event The keyup event. */ |
| var beginRenameKeyboard = function(event) { |
| if (event.which == 13 || event.which == 32) { |
| that.beginRename_(); |
| event.stopPropagation(); |
| } |
| }; |
| editButton.addEventListener('click', beginRename, true); |
| editButton.addEventListener('keyup', beginRenameKeyboard, true); |
| this.registerFocusHandlers_(editButton); |
| |
| if (opt_deleteButton) { |
| /** @param {Event} event The click event. */ |
| var confirmDelete = function(event) { |
| that.showDeleteConfirmation_(); |
| event.stopPropagation(); |
| }; |
| /** @param {Event} event The keyup event. */ |
| var confirmDeleteKeyboard = function(event) { |
| if (event.which == 13 || event.which == 32) { |
| that.showDeleteConfirmation_(); |
| } |
| }; |
| opt_deleteButton.addEventListener('click', confirmDelete, false); |
| opt_deleteButton.addEventListener('keyup', confirmDeleteKeyboard, false); |
| this.registerFocusHandlers_(opt_deleteButton); |
| } |
| this.updateStatus(); |
| }; |
| |
| /** |
| * Update the row to reflect the current status of the host (online/offline and |
| * clickable/unclickable). |
| * |
| * @param {boolean=} opt_forEdit True if the status is being updated in order |
| * to allow the host name to be edited. |
| * @return {void} Nothing. |
| */ |
| remoting.HostTableEntry.prototype.updateStatus = function(opt_forEdit) { |
| var clickToConnect = this.host.status == 'ONLINE' && !opt_forEdit; |
| if (clickToConnect) { |
| if (!this.onConnectReference_) { |
| /** @type {string} */ |
| var encodedHostId = encodeURIComponent(this.host.hostId); |
| this.onConnectReference_ = function() { |
| remoting.connectMe2Me(encodedHostId); |
| }; |
| this.tableRow.addEventListener('click', this.onConnectReference_, false); |
| } |
| this.tableRow.classList.add('clickable'); |
| this.tableRow.title = chrome.i18n.getMessage( |
| /*i18n-content*/'TOOLTIP_CONNECT', this.host.hostName); |
| } else { |
| if (this.onConnectReference_) { |
| this.tableRow.removeEventListener('click', this.onConnectReference_, |
| false); |
| this.onConnectReference_ = null; |
| } |
| this.tableRow.classList.remove('clickable'); |
| this.tableRow.title = ''; |
| } |
| var showOffline = this.host.status != 'ONLINE'; |
| if (showOffline) { |
| this.tableRow.classList.remove('host-online'); |
| this.tableRow.classList.add('host-offline'); |
| } else { |
| this.tableRow.classList.add('host-online'); |
| this.tableRow.classList.remove('host-offline'); |
| } |
| this.warningOverlay_.hidden = !remoting.Host.needsUpdate( |
| this.host, this.webappMajorVersion_); |
| }; |
| |
| /** |
| * Prepare the host for renaming by replacing its name with an edit box. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.beginRename_ = function() { |
| var editBox = /** @type {HTMLInputElement} */ document.createElement('input'); |
| editBox.type = 'text'; |
| editBox.value = this.host.hostName; |
| this.hostNameCell_.innerText = ''; |
| this.hostNameCell_.appendChild(editBox); |
| editBox.select(); |
| |
| this.onBlurReference_ = this.commitRename_.bind(this); |
| editBox.addEventListener('blur', this.onBlurReference_, false); |
| |
| editBox.addEventListener('keydown', this.onKeydown_.bind(this), false); |
| this.updateStatus(true); |
| }; |
| |
| /** |
| * Accept the hostname entered by the user. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.commitRename_ = function() { |
| var editBox = this.hostNameCell_.querySelector('input'); |
| if (editBox) { |
| if (this.host.hostName != editBox.value) { |
| this.host.hostName = editBox.value; |
| this.onRename_(this); |
| } |
| this.removeEditBox_(); |
| } |
| }; |
| |
| /** |
| * Prompt the user to confirm or cancel deletion of a host. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.showDeleteConfirmation_ = function() { |
| var message = document.getElementById('confirm-host-delete-message'); |
| l10n.localizeElement(message, this.host.hostName); |
| var confirm = document.getElementById('confirm-host-delete'); |
| var cancel = document.getElementById('cancel-host-delete'); |
| this.onConfirmDeleteReference_ = this.confirmDelete_.bind(this); |
| this.onCancelDeleteReference_ = this.cancelDelete_.bind(this); |
| confirm.addEventListener('click', this.onConfirmDeleteReference_, false); |
| cancel.addEventListener('click', this.onCancelDeleteReference_, false); |
| remoting.setMode(remoting.AppMode.CONFIRM_HOST_DELETE); |
| }; |
| |
| /** |
| * Confirm deletion of a host. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.confirmDelete_ = function() { |
| this.onDelete_(this); |
| this.cleanUpConfirmationEventListeners_(); |
| remoting.setMode(remoting.AppMode.HOME); |
| }; |
| |
| /** |
| * Cancel deletion of a host. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.cancelDelete_ = function() { |
| this.cleanUpConfirmationEventListeners_(); |
| remoting.setMode(remoting.AppMode.HOME); |
| }; |
| |
| /** |
| * Remove the confirm and cancel event handlers, which refer to this object. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.cleanUpConfirmationEventListeners_ = |
| function() { |
| var confirm = document.getElementById('confirm-host-delete'); |
| var cancel = document.getElementById('cancel-host-delete'); |
| confirm.removeEventListener('click', this.onConfirmDeleteReference_, false); |
| cancel.removeEventListener('click', this.onCancelDeleteReference_, false); |
| this.onCancelDeleteReference_ = function() {}; |
| this.onConfirmDeleteReference_ = function() {}; |
| }; |
| |
| /** |
| * Remove the edit box corresponding to the specified host, and reset its name. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.removeEditBox_ = function() { |
| var editBox = this.hostNameCell_.querySelector('input'); |
| if (editBox) { |
| // onblur will fire when the edit box is removed, so remove the hook. |
| editBox.removeEventListener('blur', this.onBlurReference_, false); |
| } |
| // Update the tool-top and event handler. |
| this.updateStatus(); |
| this.setHostName_(); |
| }; |
| |
| /** |
| * Create the DOM nodes and event handlers for the hostname cell. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.setHostName_ = function() { |
| var hostNameNode = /** @type {HTMLElement} */ document.createElement('a'); |
| if (this.host.status == 'ONLINE') { |
| if (remoting.Host.needsUpdate(this.host, this.webappMajorVersion_)) { |
| hostNameNode.innerText = chrome.i18n.getMessage( |
| /*i18n-content*/'UPDATE_REQUIRED', this.host.hostName); |
| } else { |
| hostNameNode.innerText = this.host.hostName; |
| } |
| hostNameNode.href = '#'; |
| this.registerFocusHandlers_(hostNameNode); |
| /** @type {remoting.HostTableEntry} */ |
| var that = this; |
| /** @param {Event} event */ |
| var onKeyDown = function(event) { |
| if (that.onConnectReference_ && |
| (event.which == 13 || event.which == 32)) { |
| that.onConnectReference_(); |
| } |
| }; |
| hostNameNode.addEventListener('keydown', onKeyDown, false); |
| } else { |
| if (this.host.updatedTime) { |
| var lastOnline = new Date(this.host.updatedTime); |
| var now = new Date(); |
| var displayString = ''; |
| if (now.getFullYear() == lastOnline.getFullYear() && |
| now.getMonth() == lastOnline.getMonth() && |
| now.getDate() == lastOnline.getDate()) { |
| displayString = lastOnline.toLocaleTimeString(); |
| } else { |
| displayString = lastOnline.toLocaleDateString(); |
| } |
| hostNameNode.innerText = chrome.i18n.getMessage( |
| /*i18n-content*/'LAST_ONLINE', [this.host.hostName, displayString]); |
| } else { |
| hostNameNode.innerText = chrome.i18n.getMessage( |
| /*i18n-content*/'OFFLINE', this.host.hostName); |
| } |
| } |
| hostNameNode.classList.add('host-list-label'); |
| this.hostNameCell_.innerText = ''; // Remove previous contents (if any). |
| this.hostNameCell_.appendChild(hostNameNode); |
| }; |
| |
| /** |
| * Handle a key event while the user is typing a host name |
| * @param {Event} event The keyboard event. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.onKeydown_ = function(event) { |
| if (event.which == 27) { // Escape |
| this.removeEditBox_(); |
| } else if (event.which == 13) { // Enter |
| this.commitRename_(); |
| } |
| }; |
| |
| /** |
| * Register focus and blur handlers to cause the parent node to be highlighted |
| * whenever a child link has keyboard focus. Note that this is only necessary |
| * because Chrome does not yet support the draft CSS Selectors 4 specification |
| * (http://www.w3.org/TR/selectors4/#subject), which provides a more elegant |
| * solution to this problem. |
| * |
| * @param {HTMLElement} e The element on which to register the event handlers. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.registerFocusHandlers_ = function(e) { |
| e.addEventListener('focus', this.onFocusChange_.bind(this), false); |
| e.addEventListener('blur', this.onFocusChange_.bind(this), false); |
| }; |
| |
| /** |
| * Handle a focus change event within this table row. |
| * @return {void} Nothing. |
| * @private |
| */ |
| remoting.HostTableEntry.prototype.onFocusChange_ = function() { |
| var element = document.activeElement; |
| while (element) { |
| if (element == this.tableRow) { |
| this.tableRow.classList.add('child-focused'); |
| return; |
| } |
| element = element.parentNode; |
| } |
| this.tableRow.classList.remove('child-focused'); |
| }; |