// 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. cr.define('cr.ui.dialogs', function() { function BaseDialog(parentNode) { this.parentNode_ = parentNode; this.document_ = parentNode.ownerDocument; // The DOM element from the dialog which should receive focus when the // dialog is first displayed. this.initialFocusElement_ = null; // The DOM element from the parent which had focus before we were displayed, // so we can restore it when we're hidden. this.previousActiveElement_ = null; this.initDom_(); } /** * Default text for Ok and Cancel buttons. * * Clients should override these with localized labels. */ BaseDialog.OK_LABEL = '[LOCALIZE ME] Ok'; BaseDialog.CANCEL_LABEL = '[LOCALIZE ME] Cancel'; /** * Number of miliseconds animation is expected to take, plus some margin for * error. */ BaseDialog.ANIMATE_STABLE_DURATION = 500; BaseDialog.prototype.initDom_ = function() { var doc = this.document_; this.container_ = doc.createElement('div'); this.container_.className = 'cr-dialog-container'; this.container_.addEventListener('keydown', this.onContainerKeyDown_.bind(this)); this.shield_ = doc.createElement('div'); this.shield_.className = 'cr-dialog-shield'; this.container_.appendChild(this.shield_); this.container_.addEventListener('mousedown', this.onContainerMouseDown_.bind(this)); this.frame_ = doc.createElement('div'); this.frame_.className = 'cr-dialog-frame'; this.frame_.tabIndex = 0; this.container_.appendChild(this.frame_); this.title_ = doc.createElement('div'); this.title_.className = 'cr-dialog-title'; this.frame_.appendChild(this.title_); this.closeButton_ = doc.createElement('div'); this.closeButton_.className = 'cr-dialog-close'; this.closeButton_.addEventListener('click', this.onCancelClick_.bind(this)); this.frame_.appendChild(this.closeButton_); this.text_ = doc.createElement('div'); this.text_.className = 'cr-dialog-text'; this.frame_.appendChild(this.text_); var buttons = doc.createElement('div'); buttons.className = 'cr-dialog-buttons'; this.frame_.appendChild(buttons); this.okButton_ = doc.createElement('button'); this.okButton_.className = 'cr-dialog-ok'; this.okButton_.textContent = BaseDialog.OK_LABEL; this.okButton_.addEventListener('click', this.onOkClick_.bind(this)); buttons.appendChild(this.okButton_); this.cancelButton_ = doc.createElement('button'); this.cancelButton_.className = 'cr-dialog-cancel'; this.cancelButton_.textContent = BaseDialog.CANCEL_LABEL; this.cancelButton_.addEventListener('click', this.onCancelClick_.bind(this)); buttons.appendChild(this.cancelButton_); this.initialFocusElement_ = this.okButton_; }; BaseDialog.prototype.onOk_ = null; BaseDialog.prototype.onCancel_ = null; BaseDialog.prototype.onContainerKeyDown_ = function(event) { // Handle Escape. if (event.keyCode == 27 && !this.cancelButton_.disabled) { this.onCancelClick_(event); event.preventDefault(); } }; BaseDialog.prototype.onContainerMouseDown_ = function(event) { if (event.target == this.container_) { var classList = this.frame_.classList; // Start 'pulse' animation. classList.remove('pulse'); setTimeout(classList.add.bind(classList, 'pulse'), 0); event.preventDefault(); } }; BaseDialog.prototype.onOkClick_ = function(event) { this.hide(); if (this.onOk_) this.onOk_(); }; BaseDialog.prototype.onCancelClick_ = function(event) { this.hide(); if (this.onCancel_) this.onCancel_(); }; BaseDialog.prototype.setOkLabel = function(label) { this.okButton_.textContent = label; }; BaseDialog.prototype.setCancelLabel = function(label) { this.cancelButton_.textContent = label; }; BaseDialog.prototype.setInitialFocusOnCancel = function() { this.initialFocusElement_ = this.cancelButton_; }; BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) { this.showWithTitle(null, message, onOk, onCancel, onShow); }; BaseDialog.prototype.showHtml = function(title, message, onOk, onCancel, onShow) { this.text_.innerHTML = message; this.show_(title, onOk, onCancel, onShow); }; BaseDialog.prototype.findFocusableElements_ = function(doc) { var elements = Array.prototype.filter.call( doc.querySelectorAll('*'), function(n) { return n.tabIndex >= 0; }); var iframes = doc.querySelectorAll('iframe'); for (var i = 0; i < iframes.length; i++) { // Some iframes have an undefined contentDocument for security reasons, // such as chrome://terms (which is used in the chromeos OOBE screens). var contentDoc = iframes[i].contentDocument; if (contentDoc) elements = elements.concat(this.findFocusableElements_(contentDoc)); } return elements; }; BaseDialog.prototype.showWithTitle = function(title, message, onOk, onCancel, onShow) { this.text_.textContent = message; this.show_(title, onOk, onCancel, onShow); }; BaseDialog.prototype.show_ = function(title, onOk, onCancel, onShow) { // Make all outside nodes unfocusable while the dialog is active. this.deactivatedNodes_ = this.findFocusableElements_(this.document_); this.tabIndexes_ = this.deactivatedNodes_.map( function(n) { return n.getAttribute('tabindex'); }); this.deactivatedNodes_.forEach( function(n) { n.tabIndex = -1; }); this.previousActiveElement_ = this.document_.activeElement; this.parentNode_.appendChild(this.container_); this.onOk_ = onOk; this.onCancel_ = onCancel; if (title) { this.title_.textContent = title; this.title_.hidden = false; } else { this.title_.textContent = ''; this.title_.hidden = true; } var self = this; setTimeout(function() { // Note that we control the opacity of the *container*, but the top/left // of the *frame*. self.container_.classList.add('shown'); self.initialFocusElement_.focus(); setTimeout(function() { if (onShow) onShow(); }, BaseDialog.ANIMATE_STABLE_DURATION); }, 0); }; BaseDialog.prototype.hide = function(onHide) { // Restore focusability. for (var i = 0; i < this.deactivatedNodes_.length; i++) { var node = this.deactivatedNodes_[i]; if (this.tabIndexes_[i] === null) node.removeAttribute('tabidex'); else node.setAttribute('tabindex', this.tabIndexes_[i]); } this.deactivatedNodes_ = null; this.tabIndexes_ = null; // Note that we control the opacity of the *container*, but the top/left // of the *frame*. this.container_.classList.remove('shown'); if (this.previousActiveElement_) { this.previousActiveElement_.focus(); } else { this.document_.body.focus(); } this.frame_.classList.remove('pulse'); var self = this; setTimeout(function() { // Wait until the transition is done before removing the dialog. self.parentNode_.removeChild(self.container_); if (onHide) onHide(); }, BaseDialog.ANIMATE_STABLE_DURATION); }; /** * AlertDialog contains just a message and an ok button. */ function AlertDialog(parentNode) { BaseDialog.apply(this, [parentNode]); this.cancelButton_.style.display = 'none'; } AlertDialog.prototype = {__proto__: BaseDialog.prototype}; AlertDialog.prototype.show = function(message, onOk, onShow) { return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]); }; /** * ConfirmDialog contains a message, an ok button, and a cancel button. */ function ConfirmDialog(parentNode) { BaseDialog.apply(this, [parentNode]); } ConfirmDialog.prototype = {__proto__: BaseDialog.prototype}; /** * PromptDialog contains a message, a text input, an ok button, and a * cancel button. */ function PromptDialog(parentNode) { BaseDialog.apply(this, [parentNode]); this.input_ = this.document_.createElement('input'); this.input_.setAttribute('type', 'text'); this.input_.addEventListener('focus', this.onInputFocus.bind(this)); this.input_.addEventListener('keydown', this.onKeyDown_.bind(this)); this.initialFocusElement_ = this.input_; this.frame_.insertBefore(this.input_, this.text_.nextSibling); } PromptDialog.prototype = {__proto__: BaseDialog.prototype}; PromptDialog.prototype.onInputFocus = function(event) { this.input_.select(); }; PromptDialog.prototype.onKeyDown_ = function(event) { if (event.keyCode == 13) // Enter this.onOkClick_(event); }; PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel, onShow) { this.input_.value = defaultValue || ''; return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel, onShow]); }; PromptDialog.prototype.getValue = function() { return this.input_.value; }; PromptDialog.prototype.onOkClick_ = function(event) { this.hide(); if (this.onOk_) this.onOk_(this.getValue()); }; return { BaseDialog: BaseDialog, AlertDialog: AlertDialog, ConfirmDialog: ConfirmDialog, PromptDialog: PromptDialog }; });