| // 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 |
| }; |
| }); |