blob: 27e44ac7b043bfa3ce6e7912b67377230300d65c [file] [log] [blame]
// 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');
};