Update To 11.40.268.0
[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    * @constructor
8    */
9   function BaseDialog(parentNode) {
10     this.parentNode_ = parentNode;
11     this.document_ = parentNode.ownerDocument;
12
13     // The DOM element from the dialog which should receive focus when the
14     // dialog is first displayed.
15     this.initialFocusElement_ = null;
16
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;
20
21     this.initDom_();
22   }
23
24   /**
25    * Default text for Ok and Cancel buttons.
26    *
27    * Clients should override these with localized labels.
28    */
29   BaseDialog.OK_LABEL = '[LOCALIZE ME] Ok';
30   BaseDialog.CANCEL_LABEL = '[LOCALIZE ME] Cancel';
31
32   /**
33    * Number of miliseconds animation is expected to take, plus some margin for
34    * error.
35    */
36   BaseDialog.ANIMATE_STABLE_DURATION = 500;
37
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));
49
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
53     // by Tab key.
54     this.frame_.tabIndex = -1;
55     this.container_.appendChild(this.frame_);
56
57     this.title_ = doc.createElement('div');
58     this.title_.className = 'cr-dialog-title';
59     this.frame_.appendChild(this.title_);
60
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_);
66
67     this.text_ = doc.createElement('div');
68     this.text_.className = 'cr-dialog-text';
69     this.frame_.appendChild(this.text_);
70
71     this.buttons = doc.createElement('div');
72     this.buttons.className = 'cr-dialog-buttons';
73     this.frame_.appendChild(this.buttons);
74
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_);
80
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_);
87
88     this.initialFocusElement_ = this.okButton_;
89   };
90
91   BaseDialog.prototype.onOk_ = null;
92   BaseDialog.prototype.onCancel_ = null;
93
94   BaseDialog.prototype.onContainerKeyDown_ = function(event) {
95     // Handle Escape.
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();
102     }
103   };
104
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();
112     }
113   };
114
115   BaseDialog.prototype.onOkClick_ = function(event) {
116     this.hide();
117     if (this.onOk_)
118       this.onOk_();
119   };
120
121   BaseDialog.prototype.onCancelClick_ = function(event) {
122     this.hide();
123     if (this.onCancel_)
124       this.onCancel_();
125   };
126
127   BaseDialog.prototype.setOkLabel = function(label) {
128     this.okButton_.textContent = label;
129   };
130
131   BaseDialog.prototype.setCancelLabel = function(label) {
132     this.cancelButton_.textContent = label;
133   };
134
135   BaseDialog.prototype.setInitialFocusOnCancel = function() {
136     this.initialFocusElement_ = this.cancelButton_;
137   };
138
139   BaseDialog.prototype.show = function(message, onOk, onCancel, onShow) {
140     this.showWithTitle(null, message, onOk, onCancel, onShow);
141   };
142
143   BaseDialog.prototype.showHtml = function(title, message,
144       onOk, onCancel, onShow) {
145     this.text_.innerHTML = message;
146     this.show_(title, onOk, onCancel, onShow);
147   };
148
149   BaseDialog.prototype.findFocusableElements_ = function(doc) {
150     var elements = Array.prototype.filter.call(
151         doc.querySelectorAll('*'),
152         function(n) { return n.tabIndex >= 0; });
153
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];
159       var contentDoc;
160       try {
161         contentDoc = iframe.contentDocument;
162       } catch(e) {} // ignore SecurityError
163       if (contentDoc)
164         elements = elements.concat(this.findFocusableElements_(contentDoc));
165     }
166     return elements;
167   };
168
169   BaseDialog.prototype.showWithTitle = function(title, message,
170       onOk, onCancel, onShow) {
171     this.text_.textContent = message;
172     this.show_(title, onOk, onCancel, onShow);
173   };
174
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; });
182
183     this.previousActiveElement_ = this.document_.activeElement;
184     this.parentNode_.appendChild(this.container_);
185
186     this.onOk_ = onOk;
187     this.onCancel_ = onCancel;
188
189     if (title) {
190       this.title_.textContent = title;
191       this.title_.hidden = false;
192     } else {
193       this.title_.textContent = '';
194       this.title_.hidden = true;
195     }
196
197     var self = this;
198     setTimeout(function() {
199       // Note that we control the opacity of the *container*, but the top/left
200       // of the *frame*.
201       self.container_.classList.add('shown');
202       self.initialFocusElement_.focus();
203       setTimeout(function() {
204         if (onShow)
205           onShow();
206       }, BaseDialog.ANIMATE_STABLE_DURATION);
207     }, 0);
208   };
209
210   /**
211    * @param {Function=} opt_onHide
212    */
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');
219       else
220         node.setAttribute('tabindex', this.tabIndexes_[i]);
221     }
222     this.deactivatedNodes_ = null;
223     this.tabIndexes_ = null;
224
225     // Note that we control the opacity of the *container*, but the top/left
226     // of the *frame*.
227     this.container_.classList.remove('shown');
228
229     if (this.previousActiveElement_) {
230       this.previousActiveElement_.focus();
231     } else {
232       this.document_.body.focus();
233     }
234     this.frame_.classList.remove('pulse');
235
236     var self = this;
237     setTimeout(function() {
238       // Wait until the transition is done before removing the dialog.
239       self.parentNode_.removeChild(self.container_);
240       if (opt_onHide)
241         opt_onHide();
242     }, BaseDialog.ANIMATE_STABLE_DURATION);
243   };
244
245   /**
246    * AlertDialog contains just a message and an ok button.
247    * @constructor
248    * @extends {cr.ui.dialogs.BaseDialog}
249    */
250   function AlertDialog(parentNode) {
251     BaseDialog.apply(this, [parentNode]);
252     this.cancelButton_.style.display = 'none';
253   }
254
255   AlertDialog.prototype = {__proto__: BaseDialog.prototype};
256
257   AlertDialog.prototype.show = function(message, onOk, onShow) {
258     return BaseDialog.prototype.show.apply(this, [message, onOk, onOk, onShow]);
259   };
260
261   /**
262    * ConfirmDialog contains a message, an ok button, and a cancel button.
263    * @constructor
264    * @extends {cr.ui.dialogs.BaseDialog}
265    */
266   function ConfirmDialog(parentNode) {
267     BaseDialog.apply(this, [parentNode]);
268   }
269
270   ConfirmDialog.prototype = {__proto__: BaseDialog.prototype};
271
272   /**
273    * PromptDialog contains a message, a text input, an ok button, and a
274    * cancel button.
275    * @constructor
276    * @extends {cr.ui.dialogs.BaseDialog}
277    */
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);
286   }
287
288   PromptDialog.prototype = {__proto__: BaseDialog.prototype};
289
290   PromptDialog.prototype.onInputFocus = function(event) {
291     this.input_.select();
292   };
293
294   PromptDialog.prototype.onKeyDown_ = function(event) {
295     if (event.keyCode == 13) {  // Enter
296       this.onOkClick_(event);
297       event.preventDefault();
298     }
299   };
300
301   /**
302    * @suppress {checkTypes}
303    * TODO(fukino): remove suppression if there is a better way to avoid warning
304    * about overriding method with different signature.
305    */
306   PromptDialog.prototype.show = function(message, defaultValue, onOk, onCancel,
307                                         onShow) {
308     this.input_.value = defaultValue || '';
309     return BaseDialog.prototype.show.apply(this, [message, onOk, onCancel,
310                                                   onShow]);
311   };
312
313   PromptDialog.prototype.getValue = function() {
314     return this.input_.value;
315   };
316
317   PromptDialog.prototype.onOkClick_ = function(event) {
318     this.hide();
319     if (this.onOk_)
320       this.onOk_(this.getValue());
321   };
322
323   return {
324     BaseDialog: BaseDialog,
325     AlertDialog: AlertDialog,
326     ConfirmDialog: ConfirmDialog,
327     PromptDialog: PromptDialog
328   };
329 });