• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4
5cr.define('cr.ui.dialogs', function() {
6
7  function BaseDialog(parentNode) {
8    this.parentNode_ = parentNode;
9    this.document_ = parentNode.ownerDocument;
10
11    // The DOM element from the dialog which should receive focus when the
12    // dialog is first displayed.
13    this.initialFocusElement_ = null;
14
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;
18
19    this.initDom_();
20  }
21
22  /**
23   * Default text for Ok and Cancel buttons.
24   *
25   * Clients should override these with localized labels.
26   */
27  BaseDialog.OK_LABEL = '[LOCALIZE ME] Ok';
28  BaseDialog.CANCEL_LABEL = '[LOCALIZE ME] Cancel';
29
30  /**
31   * Number of miliseconds animation is expected to take, plus some margin for
32   * error.
33   */
34  BaseDialog.ANIMATE_STABLE_DURATION = 500;
35
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));
47
48    this.frame_ = doc.createElement('div');
49    this.frame_.className = 'cr-dialog-frame';
50    this.frame_.tabIndex = 0;
51    this.container_.appendChild(this.frame_);
52
53    this.title_ = doc.createElement('div');
54    this.title_.className = 'cr-dialog-title';
55    this.frame_.appendChild(this.title_);
56
57    this.closeButton_ = doc.createElement('div');
58    this.closeButton_.className = 'cr-dialog-close';
59    this.closeButton_.addEventListener('click',
60                                        this.onCancelClick_.bind(this));
61    this.frame_.appendChild(this.closeButton_);
62
63    this.text_ = doc.createElement('div');
64    this.text_.className = 'cr-dialog-text';
65    this.frame_.appendChild(this.text_);
66
67    var buttons = doc.createElement('div');
68    buttons.className = 'cr-dialog-buttons';
69    this.frame_.appendChild(buttons);
70
71    this.okButton_ = doc.createElement('button');
72    this.okButton_.className = 'cr-dialog-ok';
73    this.okButton_.textContent = BaseDialog.OK_LABEL;
74    this.okButton_.addEventListener('click', this.onOkClick_.bind(this));
75    buttons.appendChild(this.okButton_);
76
77    this.cancelButton_ = doc.createElement('button');
78    this.cancelButton_.className = 'cr-dialog-cancel';
79    this.cancelButton_.textContent = BaseDialog.CANCEL_LABEL;
80    this.cancelButton_.addEventListener('click',
81                                        this.onCancelClick_.bind(this));
82    buttons.appendChild(this.cancelButton_);
83
84    this.initialFocusElement_ = this.okButton_;
85  };
86
87  BaseDialog.prototype.onOk_ = null;
88  BaseDialog.prototype.onCancel_ = null;
89
90  BaseDialog.prototype.onContainerKeyDown_ = function(event) {
91    // Handle Escape.
92    if (event.keyCode == 27 && !this.cancelButton_.disabled) {
93      this.onCancelClick_(event);
94      event.preventDefault();
95    }
96  };
97
98  BaseDialog.prototype.onContainerMouseDown_ = function(event) {
99    if (event.target == this.container_) {
100      var classList = this.frame_.classList;
101      // Start 'pulse' animation.
102      classList.remove('pulse');
103      setTimeout(classList.add.bind(classList, 'pulse'), 0);
104      event.preventDefault();
105    }
106  };
107
108  BaseDialog.prototype.onOkClick_ = function(event) {
109    this.hide();
110    if (this.onOk_)
111      this.onOk_();
112  };
113
114  BaseDialog.prototype.onCancelClick_ = function(event) {
115    this.hide();
116    if (this.onCancel_)
117      this.onCancel_();
118  };
119
120  BaseDialog.prototype.setOkLabel = function(label) {
121    this.okButton_.textContent = label;
122  };
123
124  BaseDialog.prototype.setCancelLabel = function(label) {
125    this.cancelButton_.textContent = label;
126  };
127
128  BaseDialog.prototype.setInitialFocusOnCancel = function() {
129    this.initialFocusElement_ = this.cancelButton_;
130  };
131
132  BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) {
133    this.showWithTitle(null, message, onOk, onCancel, onShow);
134  };
135
136  BaseDialog.prototype.showHtml = function(title, message,
137      onOk, onCancel, onShow) {
138    this.text_.innerHTML = message;
139    this.show_(title, onOk, onCancel, onShow);
140  };
141
142  BaseDialog.prototype.findFocusableElements_ = function(doc) {
143    var elements = Array.prototype.filter.call(
144        doc.querySelectorAll('*'),
145        function(n) { return n.tabIndex >= 0; });
146
147    var iframes = doc.querySelectorAll('iframe');
148    for (var i = 0; i < iframes.length; i++) {
149      // Some iframes have an undefined contentDocument for security reasons,
150      // such as chrome://terms (which is used in the chromeos OOBE screens).
151      var contentDoc = iframes[i].contentDocument;
152      if (contentDoc)
153        elements = elements.concat(this.findFocusableElements_(contentDoc));
154    }
155    return elements;
156  };
157
158  BaseDialog.prototype.showWithTitle = function(title, message,
159      onOk, onCancel, onShow) {
160    this.text_.textContent = message;
161    this.show_(title, onOk, onCancel, onShow);
162  };
163
164  BaseDialog.prototype.show_ = function(title, onOk, onCancel, onShow) {
165    // Make all outside nodes unfocusable while the dialog is active.
166    this.deactivatedNodes_ = this.findFocusableElements_(this.document_);
167    this.tabIndexes_ = this.deactivatedNodes_.map(
168        function(n) { return n.getAttribute('tabindex'); });
169    this.deactivatedNodes_.forEach(
170        function(n) { n.tabIndex = -1; });
171
172    this.previousActiveElement_ = this.document_.activeElement;
173    this.parentNode_.appendChild(this.container_);
174
175    this.onOk_ = onOk;
176    this.onCancel_ = onCancel;
177
178    if (title) {
179      this.title_.textContent = title;
180      this.title_.hidden = false;
181    } else {
182      this.title_.textContent = '';
183      this.title_.hidden = true;
184    }
185
186    var self = this;
187    setTimeout(function() {
188      // Note that we control the opacity of the *container*, but the top/left
189      // of the *frame*.
190      self.container_.classList.add('shown');
191      self.initialFocusElement_.focus();
192      setTimeout(function() {
193        if (onShow)
194          onShow();
195      }, BaseDialog.ANIMATE_STABLE_DURATION);
196    }, 0);
197  };
198
199  BaseDialog.prototype.hide = function(onHide) {
200    // Restore focusability.
201    for (var i = 0; i < this.deactivatedNodes_.length; i++) {
202      var node = this.deactivatedNodes_[i];
203      if (this.tabIndexes_[i] === null)
204        node.removeAttribute('tabidex');
205      else
206        node.setAttribute('tabindex', this.tabIndexes_[i]);
207    }
208    this.deactivatedNodes_ = null;
209    this.tabIndexes_ = null;
210
211    // Note that we control the opacity of the *container*, but the top/left
212    // of the *frame*.
213    this.container_.classList.remove('shown');
214
215    if (this.previousActiveElement_) {
216      this.previousActiveElement_.focus();
217    } else {
218      this.document_.body.focus();
219    }
220    this.frame_.classList.remove('pulse');
221
222    var self = this;
223    setTimeout(function() {
224      // Wait until the transition is done before removing the dialog.
225      self.parentNode_.removeChild(self.container_);
226      if (onHide)
227        onHide();
228    }, BaseDialog.ANIMATE_STABLE_DURATION);
229  };
230
231  /**
232   * AlertDialog contains just a message and an ok button.
233   */
234  function AlertDialog(parentNode) {
235    BaseDialog.apply(this, [parentNode]);
236    this.cancelButton_.style.display = 'none';
237  }
238
239  AlertDialog.prototype = {__proto__: BaseDialog.prototype};
240
241  AlertDialog.prototype.show = function(message, onOk, onShow) {
242    return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]);
243  };
244
245  /**
246   * ConfirmDialog contains a message, an ok button, and a cancel button.
247   */
248  function ConfirmDialog(parentNode) {
249    BaseDialog.apply(this, [parentNode]);
250  }
251
252  ConfirmDialog.prototype = {__proto__: BaseDialog.prototype};
253
254  /**
255   * PromptDialog contains a message, a text input, an ok button, and a
256   * cancel button.
257   */
258  function PromptDialog(parentNode) {
259    BaseDialog.apply(this, [parentNode]);
260    this.input_ = this.document_.createElement('input');
261    this.input_.setAttribute('type', 'text');
262    this.input_.addEventListener('focus', this.onInputFocus.bind(this));
263    this.input_.addEventListener('keydown', this.onKeyDown_.bind(this));
264    this.initialFocusElement_ = this.input_;
265    this.frame_.insertBefore(this.input_, this.text_.nextSibling);
266  }
267
268  PromptDialog.prototype = {__proto__: BaseDialog.prototype};
269
270  PromptDialog.prototype.onInputFocus = function(event) {
271    this.input_.select();
272  };
273
274  PromptDialog.prototype.onKeyDown_ = function(event) {
275    if (event.keyCode == 13)  // Enter
276      this.onOkClick_(event);
277  };
278
279  PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel,
280                                        onShow) {
281    this.input_.value = defaultValue || '';
282    return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel,
283                                                  onShow]);
284  };
285
286  PromptDialog.prototype.getValue = function() {
287    return this.input_.value;
288  };
289
290  PromptDialog.prototype.onOkClick_ = function(event) {
291    this.hide();
292    if (this.onOk_)
293      this.onOk_(this.getValue());
294  };
295
296  return {
297    BaseDialog: BaseDialog,
298    AlertDialog: AlertDialog,
299    ConfirmDialog: ConfirmDialog,
300    PromptDialog: PromptDialog
301  };
302});
303