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() {
9 function BaseDialog(parentNode) {
10 this.parentNode_ = parentNode;
11 this.document_ = parentNode.ownerDocument;
13 // The DOM element from the dialog which should receive focus when the
14 // dialog is first displayed.
15 this.initialFocusElement_ = null;
17 // The DOM element from the parent which had focus before we were displayed,
18 // so we can restore it when we're hidden.
19 this.previousActiveElement_ = null;
25 * Default text for Ok and Cancel buttons.
27 * Clients should override these with localized labels.
29 BaseDialog.OK_LABEL = '[LOCALIZE ME] Ok';
30 BaseDialog.CANCEL_LABEL = '[LOCALIZE ME] Cancel';
33 * Number of miliseconds animation is expected to take, plus some margin for
36 BaseDialog.ANIMATE_STABLE_DURATION = 500;
38 BaseDialog.prototype.initDom_ = function() {
39 var doc = this.document_;
40 this.container_ = doc.createElement('div');
41 this.container_.className = 'cr-dialog-container';
42 this.container_.addEventListener('keydown',
43 this.onContainerKeyDown_.bind(this));
44 this.shield_ = doc.createElement('div');
45 this.shield_.className = 'cr-dialog-shield';
46 this.container_.appendChild(this.shield_);
47 this.container_.addEventListener('mousedown',
48 this.onContainerMouseDown_.bind(this));
50 this.frame_ = doc.createElement('div');
51 this.frame_.className = 'cr-dialog-frame';
52 // Elements that have negative tabIndex can be focused but are not traversed
54 this.frame_.tabIndex = -1;
55 this.container_.appendChild(this.frame_);
57 this.title_ = doc.createElement('div');
58 this.title_.className = 'cr-dialog-title';
59 this.frame_.appendChild(this.title_);
61 this.closeButton_ = doc.createElement('div');
62 this.closeButton_.className = 'cr-dialog-close';
63 this.closeButton_.addEventListener('click',
64 this.onCancelClick_.bind(this));
65 this.frame_.appendChild(this.closeButton_);
67 this.text_ = doc.createElement('div');
68 this.text_.className = 'cr-dialog-text';
69 this.frame_.appendChild(this.text_);
71 this.buttons = doc.createElement('div');
72 this.buttons.className = 'cr-dialog-buttons';
73 this.frame_.appendChild(this.buttons);
75 this.okButton_ = doc.createElement('button');
76 this.okButton_.className = 'cr-dialog-ok';
77 this.okButton_.textContent = BaseDialog.OK_LABEL;
78 this.okButton_.addEventListener('click', this.onOkClick_.bind(this));
79 this.buttons.appendChild(this.okButton_);
81 this.cancelButton_ = doc.createElement('button');
82 this.cancelButton_.className = 'cr-dialog-cancel';
83 this.cancelButton_.textContent = BaseDialog.CANCEL_LABEL;
84 this.cancelButton_.addEventListener('click',
85 this.onCancelClick_.bind(this));
86 this.buttons.appendChild(this.cancelButton_);
88 this.initialFocusElement_ = this.okButton_;
91 BaseDialog.prototype.onOk_ = null;
92 BaseDialog.prototype.onCancel_ = null;
94 BaseDialog.prototype.onContainerKeyDown_ = function(event) {
96 if (event.keyCode == 27 && !this.cancelButton_.disabled) {
97 this.onCancelClick_(event);
98 event.stopPropagation();
99 // Prevent the event from being handled by the container of the dialog.
100 // e.g. Prevent the parent container from closing at the same time.
101 event.preventDefault();
105 BaseDialog.prototype.onContainerMouseDown_ = function(event) {
106 if (event.target == this.container_) {
107 var classList = this.frame_.classList;
108 // Start 'pulse' animation.
109 classList.remove('pulse');
110 setTimeout(classList.add.bind(classList, 'pulse'), 0);
111 event.preventDefault();
115 BaseDialog.prototype.onOkClick_ = function(event) {
121 BaseDialog.prototype.onCancelClick_ = function(event) {
127 BaseDialog.prototype.setOkLabel = function(label) {
128 this.okButton_.textContent = label;
131 BaseDialog.prototype.setCancelLabel = function(label) {
132 this.cancelButton_.textContent = label;
135 BaseDialog.prototype.setInitialFocusOnCancel = function() {
136 this.initialFocusElement_ = this.cancelButton_;
139 BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) {
140 this.showWithTitle(null, message, onOk, onCancel, onShow);
143 BaseDialog.prototype.showHtml = function(title, message,
144 onOk, onCancel, onShow) {
145 this.text_.innerHTML = message;
146 this.show_(title, onOk, onCancel, onShow);
149 BaseDialog.prototype.findFocusableElements_ = function(doc) {
150 var elements = Array.prototype.filter.call(
151 doc.querySelectorAll('*'),
152 function(n) { return n.tabIndex >= 0; });
154 var iframes = doc.querySelectorAll('iframe');
155 for (var i = 0; i < iframes.length; i++) {
156 // Some iframes have an undefined contentDocument for security reasons,
157 // such as chrome://terms (which is used in the chromeos OOBE screens).
158 var iframe = iframes[i];
161 contentDoc = iframe.contentDocument;
162 } catch(e) {} // ignore SecurityError
164 elements = elements.concat(this.findFocusableElements_(contentDoc));
169 BaseDialog.prototype.showWithTitle = function(title, message,
170 onOk, onCancel, onShow) {
171 this.text_.textContent = message;
172 this.show_(title, onOk, onCancel, onShow);
175 BaseDialog.prototype.show_ = function(title, onOk, onCancel, onShow) {
176 // Make all outside nodes unfocusable while the dialog is active.
177 this.deactivatedNodes_ = this.findFocusableElements_(this.document_);
178 this.tabIndexes_ = this.deactivatedNodes_.map(
179 function(n) { return n.getAttribute('tabindex'); });
180 this.deactivatedNodes_.forEach(
181 function(n) { n.tabIndex = -1; });
183 this.previousActiveElement_ = this.document_.activeElement;
184 this.parentNode_.appendChild(this.container_);
187 this.onCancel_ = onCancel;
190 this.title_.textContent = title;
191 this.title_.hidden = false;
193 this.title_.textContent = '';
194 this.title_.hidden = true;
198 setTimeout(function() {
199 // Note that we control the opacity of the *container*, but the top/left
201 self.container_.classList.add('shown');
202 self.initialFocusElement_.focus();
203 setTimeout(function() {
206 }, BaseDialog.ANIMATE_STABLE_DURATION);
211 * @param {Function=} opt_onHide
213 BaseDialog.prototype.hide = function(opt_onHide) {
214 // Restore focusability.
215 for (var i = 0; i < this.deactivatedNodes_.length; i++) {
216 var node = this.deactivatedNodes_[i];
217 if (this.tabIndexes_[i] === null)
218 node.removeAttribute('tabindex');
220 node.setAttribute('tabindex', this.tabIndexes_[i]);
222 this.deactivatedNodes_ = null;
223 this.tabIndexes_ = null;
225 // Note that we control the opacity of the *container*, but the top/left
227 this.container_.classList.remove('shown');
229 if (this.previousActiveElement_) {
230 this.previousActiveElement_.focus();
232 this.document_.body.focus();
234 this.frame_.classList.remove('pulse');
237 setTimeout(function() {
238 // Wait until the transition is done before removing the dialog.
239 self.parentNode_.removeChild(self.container_);
242 }, BaseDialog.ANIMATE_STABLE_DURATION);
246 * AlertDialog contains just a message and an ok button.
248 * @extends {cr.ui.dialogs.BaseDialog}
250 function AlertDialog(parentNode) {
251 BaseDialog.apply(this, [parentNode]);
252 this.cancelButton_.style.display = 'none';
255 AlertDialog.prototype = {__proto__: BaseDialog.prototype};
257 AlertDialog.prototype.show = function(message, onOk, onShow) {
258 return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]);
262 * ConfirmDialog contains a message, an ok button, and a cancel button.
264 * @extends {cr.ui.dialogs.BaseDialog}
266 function ConfirmDialog(parentNode) {
267 BaseDialog.apply(this, [parentNode]);
270 ConfirmDialog.prototype = {__proto__: BaseDialog.prototype};
273 * PromptDialog contains a message, a text input, an ok button, and a
276 * @extends {cr.ui.dialogs.BaseDialog}
278 function PromptDialog(parentNode) {
279 BaseDialog.apply(this, [parentNode]);
280 this.input_ = this.document_.createElement('input');
281 this.input_.setAttribute('type', 'text');
282 this.input_.addEventListener('focus', this.onInputFocus.bind(this));
283 this.input_.addEventListener('keydown', this.onKeyDown_.bind(this));
284 this.initialFocusElement_ = this.input_;
285 this.frame_.insertBefore(this.input_, this.text_.nextSibling);
288 PromptDialog.prototype = {__proto__: BaseDialog.prototype};
290 PromptDialog.prototype.onInputFocus = function(event) {
291 this.input_.select();
294 PromptDialog.prototype.onKeyDown_ = function(event) {
295 if (event.keyCode == 13) { // Enter
296 this.onOkClick_(event);
297 event.preventDefault();
302 * @suppress {checkTypes}
303 * TODO(fukino): remove suppression if there is a better way to avoid warning
304 * about overriding method with different signature.
306 PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel,
308 this.input_.value = defaultValue || '';
309 return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel,
313 PromptDialog.prototype.getValue = function() {
314 return this.input_.value;
317 PromptDialog.prototype.onOkClick_ = function(event) {
320 this.onOk_(this.getValue());
324 BaseDialog: BaseDialog,
325 AlertDialog: AlertDialog,
326 ConfirmDialog: ConfirmDialog,
327 PromptDialog: PromptDialog