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.
5 cr.define('cr.ui.dialogs', function() {
7 function BaseDialog(parentNode) {
8 this.parentNode_ = parentNode;
9 this.document_ = parentNode.ownerDocument;
11 // The DOM element from the dialog which should receive focus when the
12 // dialog is first displayed.
13 this.initialFocusElement_ = null;
15 // The DOM element from the parent which had focus before we were displayed,
16 // so we can restore it when we're hidden.
17 this.previousActiveElement_ = null;
23 * Default text for Ok and Cancel buttons.
25 * Clients should override these with localized labels.
27 BaseDialog.OK_LABEL = '[LOCALIZE ME] Ok';
28 BaseDialog.CANCEL_LABEL = '[LOCALIZE ME] Cancel';
31 * Number of miliseconds animation is expected to take, plus some margin for
34 BaseDialog.ANIMATE_STABLE_DURATION = 500;
36 BaseDialog.prototype.initDom_ = function() {
37 var doc = this.document_;
38 this.container_ = doc.createElement('div');
39 this.container_.className = 'cr-dialog-container';
40 this.container_.addEventListener('keydown',
41 this.onContainerKeyDown_.bind(this));
42 this.shield_ = doc.createElement('div');
43 this.shield_.className = 'cr-dialog-shield';
44 this.container_.appendChild(this.shield_);
45 this.container_.addEventListener('mousedown',
46 this.onContainerMouseDown_.bind(this));
48 this.frame_ = doc.createElement('div');
49 this.frame_.className = 'cr-dialog-frame';
50 // Elements that have negative tabIndex can be focused but are not traversed
52 this.frame_.tabIndex = -1;
53 this.container_.appendChild(this.frame_);
55 this.title_ = doc.createElement('div');
56 this.title_.className = 'cr-dialog-title';
57 this.frame_.appendChild(this.title_);
59 this.closeButton_ = doc.createElement('div');
60 this.closeButton_.className = 'cr-dialog-close';
61 this.closeButton_.addEventListener('click',
62 this.onCancelClick_.bind(this));
63 this.frame_.appendChild(this.closeButton_);
65 this.text_ = doc.createElement('div');
66 this.text_.className = 'cr-dialog-text';
67 this.frame_.appendChild(this.text_);
69 var buttons = doc.createElement('div');
70 buttons.className = 'cr-dialog-buttons';
71 this.frame_.appendChild(buttons);
73 this.okButton_ = doc.createElement('button');
74 this.okButton_.className = 'cr-dialog-ok';
75 this.okButton_.textContent = BaseDialog.OK_LABEL;
76 this.okButton_.addEventListener('click', this.onOkClick_.bind(this));
77 buttons.appendChild(this.okButton_);
79 this.cancelButton_ = doc.createElement('button');
80 this.cancelButton_.className = 'cr-dialog-cancel';
81 this.cancelButton_.textContent = BaseDialog.CANCEL_LABEL;
82 this.cancelButton_.addEventListener('click',
83 this.onCancelClick_.bind(this));
84 buttons.appendChild(this.cancelButton_);
86 this.initialFocusElement_ = this.okButton_;
89 BaseDialog.prototype.onOk_ = null;
90 BaseDialog.prototype.onCancel_ = null;
92 BaseDialog.prototype.onContainerKeyDown_ = function(event) {
94 if (event.keyCode == 27 && !this.cancelButton_.disabled) {
95 this.onCancelClick_(event);
96 event.stopPropagation();
97 // Prevent the event from being handled by the container of the dialog.
98 // e.g. Prevent the parent container from closing at the same time.
99 event.preventDefault();
103 BaseDialog.prototype.onContainerMouseDown_ = function(event) {
104 if (event.target == this.container_) {
105 var classList = this.frame_.classList;
106 // Start 'pulse' animation.
107 classList.remove('pulse');
108 setTimeout(classList.add.bind(classList, 'pulse'), 0);
109 event.preventDefault();
113 BaseDialog.prototype.onOkClick_ = function(event) {
119 BaseDialog.prototype.onCancelClick_ = function(event) {
125 BaseDialog.prototype.setOkLabel = function(label) {
126 this.okButton_.textContent = label;
129 BaseDialog.prototype.setCancelLabel = function(label) {
130 this.cancelButton_.textContent = label;
133 BaseDialog.prototype.setInitialFocusOnCancel = function() {
134 this.initialFocusElement_ = this.cancelButton_;
137 BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) {
138 this.showWithTitle(null, message, onOk, onCancel, onShow);
141 BaseDialog.prototype.showHtml = function(title, message,
142 onOk, onCancel, onShow) {
143 this.text_.innerHTML = message;
144 this.show_(title, onOk, onCancel, onShow);
147 BaseDialog.prototype.findFocusableElements_ = function(doc) {
148 var elements = Array.prototype.filter.call(
149 doc.querySelectorAll('*'),
150 function(n) { return n.tabIndex >= 0; });
152 var iframes = doc.querySelectorAll('iframe');
153 for (var i = 0; i < iframes.length; i++) {
154 // Some iframes have an undefined contentDocument for security reasons,
155 // such as chrome://terms (which is used in the chromeos OOBE screens).
156 var iframe = iframes[i];
159 contentDoc = iframe.contentDocument;
160 } catch(e) {} // ignore SecurityError
162 elements = elements.concat(this.findFocusableElements_(contentDoc));
167 BaseDialog.prototype.showWithTitle = function(title, message,
168 onOk, onCancel, onShow) {
169 this.text_.textContent = message;
170 this.show_(title, onOk, onCancel, onShow);
173 BaseDialog.prototype.show_ = function(title, onOk, onCancel, onShow) {
174 // Make all outside nodes unfocusable while the dialog is active.
175 this.deactivatedNodes_ = this.findFocusableElements_(this.document_);
176 this.tabIndexes_ = this.deactivatedNodes_.map(
177 function(n) { return n.getAttribute('tabindex'); });
178 this.deactivatedNodes_.forEach(
179 function(n) { n.tabIndex = -1; });
181 this.previousActiveElement_ = this.document_.activeElement;
182 this.parentNode_.appendChild(this.container_);
185 this.onCancel_ = onCancel;
188 this.title_.textContent = title;
189 this.title_.hidden = false;
191 this.title_.textContent = '';
192 this.title_.hidden = true;
196 setTimeout(function() {
197 // Note that we control the opacity of the *container*, but the top/left
199 self.container_.classList.add('shown');
200 self.initialFocusElement_.focus();
201 setTimeout(function() {
204 }, BaseDialog.ANIMATE_STABLE_DURATION);
208 BaseDialog.prototype.hide = function(onHide) {
209 // Restore focusability.
210 for (var i = 0; i < this.deactivatedNodes_.length; i++) {
211 var node = this.deactivatedNodes_[i];
212 if (this.tabIndexes_[i] === null)
213 node.removeAttribute('tabindex');
215 node.setAttribute('tabindex', this.tabIndexes_[i]);
217 this.deactivatedNodes_ = null;
218 this.tabIndexes_ = null;
220 // Note that we control the opacity of the *container*, but the top/left
222 this.container_.classList.remove('shown');
224 if (this.previousActiveElement_) {
225 this.previousActiveElement_.focus();
227 this.document_.body.focus();
229 this.frame_.classList.remove('pulse');
232 setTimeout(function() {
233 // Wait until the transition is done before removing the dialog.
234 self.parentNode_.removeChild(self.container_);
237 }, BaseDialog.ANIMATE_STABLE_DURATION);
241 * AlertDialog contains just a message and an ok button.
243 function AlertDialog(parentNode) {
244 BaseDialog.apply(this, [parentNode]);
245 this.cancelButton_.style.display = 'none';
248 AlertDialog.prototype = {__proto__: BaseDialog.prototype};
250 AlertDialog.prototype.show = function(message, onOk, onShow) {
251 return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]);
255 * ConfirmDialog contains a message, an ok button, and a cancel button.
257 function ConfirmDialog(parentNode) {
258 BaseDialog.apply(this, [parentNode]);
261 ConfirmDialog.prototype = {__proto__: BaseDialog.prototype};
264 * PromptDialog contains a message, a text input, an ok button, and a
267 function PromptDialog(parentNode) {
268 BaseDialog.apply(this, [parentNode]);
269 this.input_ = this.document_.createElement('input');
270 this.input_.setAttribute('type', 'text');
271 this.input_.addEventListener('focus', this.onInputFocus.bind(this));
272 this.input_.addEventListener('keydown', this.onKeyDown_.bind(this));
273 this.initialFocusElement_ = this.input_;
274 this.frame_.insertBefore(this.input_, this.text_.nextSibling);
277 PromptDialog.prototype = {__proto__: BaseDialog.prototype};
279 PromptDialog.prototype.onInputFocus = function(event) {
280 this.input_.select();
283 PromptDialog.prototype.onKeyDown_ = function(event) {
284 if (event.keyCode == 13) { // Enter
285 this.onOkClick_(event);
286 event.preventDefault();
290 PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel,
292 this.input_.value = defaultValue || '';
293 return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel,
297 PromptDialog.prototype.getValue = function() {
298 return this.input_.value;
301 PromptDialog.prototype.onOkClick_ = function(event) {
304 this.onOk_(this.getValue());
308 BaseDialog: BaseDialog,
309 AlertDialog: AlertDialog,
310 ConfirmDialog: ConfirmDialog,
311 PromptDialog: PromptDialog