- add sources.
[platform/framework/web/crosswalk.git] / src / ui / webui / resources / js / cr / ui / dialogs.js
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
5 cr.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     // Elements that have negative tabIndex can be focused but are not traversed
51     // by Tab key.
52     this.frame_.tabIndex = -1;
53     this.container_.appendChild(this.frame_);
54
55     this.title_ = doc.createElement('div');
56     this.title_.className = 'cr-dialog-title';
57     this.frame_.appendChild(this.title_);
58
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_);
64
65     this.text_ = doc.createElement('div');
66     this.text_.className = 'cr-dialog-text';
67     this.frame_.appendChild(this.text_);
68
69     var buttons = doc.createElement('div');
70     buttons.className = 'cr-dialog-buttons';
71     this.frame_.appendChild(buttons);
72
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_);
78
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_);
85
86     this.initialFocusElement_ = this.okButton_;
87   };
88
89   BaseDialog.prototype.onOk_ = null;
90   BaseDialog.prototype.onCancel_ = null;
91
92   BaseDialog.prototype.onContainerKeyDown_ = function(event) {
93     // Handle Escape.
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();
100     }
101   };
102
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();
110     }
111   };
112
113   BaseDialog.prototype.onOkClick_ = function(event) {
114     this.hide();
115     if (this.onOk_)
116       this.onOk_();
117   };
118
119   BaseDialog.prototype.onCancelClick_ = function(event) {
120     this.hide();
121     if (this.onCancel_)
122       this.onCancel_();
123   };
124
125   BaseDialog.prototype.setOkLabel = function(label) {
126     this.okButton_.textContent = label;
127   };
128
129   BaseDialog.prototype.setCancelLabel = function(label) {
130     this.cancelButton_.textContent = label;
131   };
132
133   BaseDialog.prototype.setInitialFocusOnCancel = function() {
134     this.initialFocusElement_ = this.cancelButton_;
135   };
136
137   BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) {
138     this.showWithTitle(null, message, onOk, onCancel, onShow);
139   };
140
141   BaseDialog.prototype.showHtml = function(title, message,
142       onOk, onCancel, onShow) {
143     this.text_.innerHTML = message;
144     this.show_(title, onOk, onCancel, onShow);
145   };
146
147   BaseDialog.prototype.findFocusableElements_ = function(doc) {
148     var elements = Array.prototype.filter.call(
149         doc.querySelectorAll('*'),
150         function(n) { return n.tabIndex >= 0; });
151
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];
157       var contentDoc;
158       try {
159         contentDoc = iframe.contentDocument;
160       } catch(e) {} // ignore SecurityError
161       if (contentDoc)
162         elements = elements.concat(this.findFocusableElements_(contentDoc));
163     }
164     return elements;
165   };
166
167   BaseDialog.prototype.showWithTitle = function(title, message,
168       onOk, onCancel, onShow) {
169     this.text_.textContent = message;
170     this.show_(title, onOk, onCancel, onShow);
171   };
172
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; });
180
181     this.previousActiveElement_ = this.document_.activeElement;
182     this.parentNode_.appendChild(this.container_);
183
184     this.onOk_ = onOk;
185     this.onCancel_ = onCancel;
186
187     if (title) {
188       this.title_.textContent = title;
189       this.title_.hidden = false;
190     } else {
191       this.title_.textContent = '';
192       this.title_.hidden = true;
193     }
194
195     var self = this;
196     setTimeout(function() {
197       // Note that we control the opacity of the *container*, but the top/left
198       // of the *frame*.
199       self.container_.classList.add('shown');
200       self.initialFocusElement_.focus();
201       setTimeout(function() {
202         if (onShow)
203           onShow();
204       }, BaseDialog.ANIMATE_STABLE_DURATION);
205     }, 0);
206   };
207
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');
214       else
215         node.setAttribute('tabindex', this.tabIndexes_[i]);
216     }
217     this.deactivatedNodes_ = null;
218     this.tabIndexes_ = null;
219
220     // Note that we control the opacity of the *container*, but the top/left
221     // of the *frame*.
222     this.container_.classList.remove('shown');
223
224     if (this.previousActiveElement_) {
225       this.previousActiveElement_.focus();
226     } else {
227       this.document_.body.focus();
228     }
229     this.frame_.classList.remove('pulse');
230
231     var self = this;
232     setTimeout(function() {
233       // Wait until the transition is done before removing the dialog.
234       self.parentNode_.removeChild(self.container_);
235       if (onHide)
236         onHide();
237     }, BaseDialog.ANIMATE_STABLE_DURATION);
238   };
239
240   /**
241    * AlertDialog contains just a message and an ok button.
242    */
243   function AlertDialog(parentNode) {
244     BaseDialog.apply(this, [parentNode]);
245     this.cancelButton_.style.display = 'none';
246   }
247
248   AlertDialog.prototype = {__proto__: BaseDialog.prototype};
249
250   AlertDialog.prototype.show = function(message, onOk, onShow) {
251     return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]);
252   };
253
254   /**
255    * ConfirmDialog contains a message, an ok button, and a cancel button.
256    */
257   function ConfirmDialog(parentNode) {
258     BaseDialog.apply(this, [parentNode]);
259   }
260
261   ConfirmDialog.prototype = {__proto__: BaseDialog.prototype};
262
263   /**
264    * PromptDialog contains a message, a text input, an ok button, and a
265    * cancel button.
266    */
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);
275   }
276
277   PromptDialog.prototype = {__proto__: BaseDialog.prototype};
278
279   PromptDialog.prototype.onInputFocus = function(event) {
280     this.input_.select();
281   };
282
283   PromptDialog.prototype.onKeyDown_ = function(event) {
284     if (event.keyCode == 13) {  // Enter
285       this.onOkClick_(event);
286       event.preventDefault();
287     }
288   };
289
290   PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel,
291                                         onShow) {
292     this.input_.value = defaultValue || '';
293     return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel,
294                                                   onShow]);
295   };
296
297   PromptDialog.prototype.getValue = function() {
298     return this.input_.value;
299   };
300
301   PromptDialog.prototype.onOkClick_ = function(event) {
302     this.hide();
303     if (this.onOk_)
304       this.onOk_(this.getValue());
305   };
306
307   return {
308     BaseDialog: BaseDialog,
309     AlertDialog: AlertDialog,
310     ConfirmDialog: ConfirmDialog,
311     PromptDialog: PromptDialog
312   };
313 });