Roll src/third_party/WebKit 3529d49:06e8485 (svn 202554:202555)
[chromium-blink-merge.git] / remoting / webapp / base / js / modal_dialogs.js
blobe6b750d75f65c20afc9203890803148f23988bf3
1 // Copyright 2015 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 /** @suppress {duplicate} */
6 var remoting = remoting || {};
8 (function() {
10 'use strict';
12 /**
13  * A helper class for implementing dialogs with an input field using
14  * remoting.setMode().
15  *
16  * @param {remoting.AppMode} mode
17  * @param {HTMLElement} formElement
18  * @param {HTMLElement} inputField
19  * @param {HTMLElement} cancelButton
20  *
21  * @constructor
22  */
23 remoting.InputDialog = function(mode, formElement, inputField, cancelButton) {
24   /** @private */
25   this.appMode_ = mode;
26   /** @private */
27   this.formElement_ = formElement;
28   /** @private */
29   this.cancelButton_ = cancelButton;
30   /** @private */
31   this.inputField_ = inputField;
32   /** @private {base.Deferred} */
33   this.deferred_ = null;
34   /** @private {base.Disposables} */
35   this.eventHooks_ = null;
38 /**
39  * @return {Promise<string>}  Promise that resolves with the value of the
40  *    inputField or rejects with |remoting.Error.CANCELLED| if the user clicks
41  *    on the cancel button.
42  */
43 remoting.InputDialog.prototype.show = function() {
44   var onCancel = this.createFormEventHandler_(this.onCancel_.bind(this));
45   var onOk = this.createFormEventHandler_(this.onSubmit_.bind(this));
47   this.eventHooks_ = new base.Disposables(
48       new base.DomEventHook(this.formElement_, 'submit', onOk, false),
49       new base.DomEventHook(this.cancelButton_, 'click', onCancel, false));
50   console.assert(this.deferred_ === null, 'No deferred Promise found.');
51   this.deferred_ = new base.Deferred();
52   remoting.setMode(this.appMode_);
53   return this.deferred_.promise();
56 /** @return {HTMLElement} */
57 remoting.InputDialog.prototype.inputField = function() {
58   return this.inputField_;
61 /** @private */
62 remoting.InputDialog.prototype.onSubmit_ = function() {
63   this.deferred_.resolve(this.inputField_.value);
66 /** @private */
67 remoting.InputDialog.prototype.onCancel_ = function() {
68   this.deferred_.reject(new remoting.Error(remoting.Error.Tag.CANCELLED));
71 /**
72  * @param {function():void} handler
73  * @return {Function}
74  * @private
75  */
76 remoting.InputDialog.prototype.createFormEventHandler_ = function(handler) {
77   var that = this;
78   return function (/** Event */ e) {
79     // Prevents form submission from reloading the v1 app.
80     e.preventDefault();
82     // Set the focus away from the password field. This has to be done
83     // before the password field gets hidden, to work around a Blink
84     // clipboard-handling bug - http://crbug.com/281523.
85     that.cancelButton_.focus();
86     handler();
87     base.dispose(that.eventHooks_);
88     that.eventHooks_ = null;
89     that.deferred_ = null;
90   };
93 /**
94  * A helper class for implementing MessageDialog with a primary and
95  * and secondary button using remoting.setMode().
96  *
97  * @param {remoting.AppMode} mode
98  * @param {HTMLElement} primaryButton
99  * @param {HTMLElement=} opt_secondaryButton
101  * @constructor
102  * @implements {base.Disposable}
103  */
104 remoting.MessageDialog = function(mode, primaryButton, opt_secondaryButton) {
105   /** @private @const */
106   this.mode_ = mode;
107   /** @private @const */
108   this.primaryButton_ = primaryButton;
109   /** @private @const */
110   this.secondaryButton_ = opt_secondaryButton;
111   /** @private {base.Deferred} */
112   this.deferred_ = null;
113   /** @private {base.Disposables} */
114   this.eventHooks_ = null;
118  * @return {Promise<remoting.MessageDialog.Result>}  Promise that resolves with
119  * the button clicked.
120  */
121 remoting.MessageDialog.prototype.show = function() {
122   console.assert(this.eventHooks_ === null, 'Duplicate show() invocation.');
123   this.eventHooks_ = new base.Disposables(new base.DomEventHook(
124       this.primaryButton_, 'click',
125       this.onClicked_.bind(this, remoting.MessageDialog.Result.PRIMARY),
126       false));
128   if (this.secondaryButton_) {
129     this.eventHooks_.add(new base.DomEventHook(
130         this.secondaryButton_, 'click',
131         this.onClicked_.bind(this, remoting.MessageDialog.Result.SECONDARY),
132         false));
133   }
135   console.assert(this.deferred_ === null, 'No deferred Promise found.');
136   this.deferred_ = new base.Deferred();
137   remoting.setMode(this.mode_);
138   return this.deferred_.promise();
141 remoting.MessageDialog.prototype.dispose = function() {
142   base.dispose(this.eventHooks_);
143   this.eventHooks_ = null;
144   if (this.deferred_) {
145     this.deferred_.reject(new remoting.Error(remoting.Error.Tag.CANCELLED));
146   }
147   this.deferred_ = null;
151  * @param {remoting.MessageDialog.Result} result
152  * @return {Function}
153  * @private
154  */
155 remoting.MessageDialog.prototype.onClicked_ = function(result) {
156   this.deferred_.resolve(result);
157   this.deferred_ = null;
158   this.dispose();
164  * A promise-based dialog implementation using the <dialog> element.
166  * @param {remoting.Html5ModalDialog.Params} params
167  * @constructor
169  * @implements {remoting.WindowShape.ClientUI}
170  * @implements {base.Disposable}
171  */
172 remoting.Html5ModalDialog = function(params) {
173   /** @private */
174   this.dialog_ = params.dialog;
176   /** @private {base.Disposables} */
177   this.eventHooks_ = new base.Disposables(
178       new base.DomEventHook(
179           this.dialog_, 'cancel', this.onCancel_.bind(this), false),
180       new base.DomEventHook(
181           params.primaryButton, 'click',
182           this.close.bind(this, remoting.MessageDialog.Result.PRIMARY), false)
183   );
185   if (params.secondaryButton) {
186     this.eventHooks_.add(new base.DomEventHook(
187         params.secondaryButton, 'click',
188         this.close.bind(this, remoting.MessageDialog.Result.SECONDARY), false));
189   }
191   /** @private */
192   this.closeOnEscape_ = Boolean(params.closeOnEscape);
194   /** @private */
195   this.windowShape_ = params.windowShape;
197   /** @private {base.Deferred} */
198   this.deferred_ = null;
201 remoting.Html5ModalDialog.prototype.dispose = function() {
202   if (this.dialog_.open) {
203     this.close(remoting.MessageDialog.Result.CANCEL);
204   }
205   base.dispose(this.eventHooks_);
206   this.eventHookes_ = null;
210  * @return {Promise<remoting.MessageDialog.Result>}  Promise that resolves with
211  * the button clicked.
212  */
213 remoting.Html5ModalDialog.prototype.show = function() {
214   console.assert(this.deferred_ === null, 'No deferred Promise found.');
215   this.deferred_ = new base.Deferred();
216   this.dialog_.showModal();
217   if (this.windowShape_) {
218     this.windowShape_.registerClientUI(this);
219     this.windowShape_.centerToDesktop(this.dialog_);
220   }
221   return this.deferred_.promise();
224 /** @param {Event} e */
225 remoting.Html5ModalDialog.prototype.onCancel_ = function(e) {
226   e.preventDefault();
227   if (this.closeOnEscape_) {
228     this.close(remoting.MessageDialog.Result.CANCEL);
229   }
233  * @param {remoting.MessageDialog.Result} result
234  */
235 remoting.Html5ModalDialog.prototype.close = function(result) {
236   if (!this.dialog_.open) {
237     return;
238   }
239   this.dialog_.close();
240   this.deferred_.resolve(result);
241   this.deferred_ = null;
242   if (this.windowShape_) {
243     this.windowShape_.unregisterClientUI(this);
244   }
247 remoting.Html5ModalDialog.prototype.addToRegion = function(rects) {
248   var rect = /** @type {ClientRect} */(this.dialog_.getBoundingClientRect());
250   // If the dialog is repositioned by setting the left and top, it takes a while
251   // for getBoundingClientRect() to update the rectangle.
252   var left = this.dialog_.style.left;
253   var top = this.dialog_.style.top;
255   rects.push({
256     left: (left === '') ? rect.left : parseInt(left, 10),
257     top: (top === '') ? rect.top : parseInt(top, 10),
258     width: rect.width,
259     height: rect.height
260   });
265  * @param {Function} cancelCallback The callback to invoke when the user clicks
266  *     on the cancel button.
267  * @constructor
268  */
269 remoting.ConnectingDialog = function(cancelCallback) {
270   /** @private */
271   this.dialog_ = new remoting.MessageDialog(
272       remoting.AppMode.CLIENT_CONNECTING,
273       document.getElementById('cancel-connect-button'));
274   /** @private */
275   this.onCancel_ = cancelCallback;
278 remoting.ConnectingDialog.prototype.show = function() {
279   var that = this;
280   this.dialog_.show().then(function() {
281     remoting.setMode(remoting.AppMode.HOME);
282     that.onCancel_();
283   // The promise rejects when the dialog is hidden.  Don't report that as error.
284   }).catch(remoting.Error.handler(base.doNothing));
287 remoting.ConnectingDialog.prototype.hide = function() {
288   this.dialog_.dispose();
292  * A factory object for the modal dialogs.  The factory will be stubbed out in
293  * unit test to avoid UI dependencies on remoting.setMode().
295  * @constructor
296  */
297 remoting.ModalDialogFactory = function() {};
300  * @param {Function} cancelCallback
301  * @return {remoting.ConnectingDialog}
302  */
303 remoting.ModalDialogFactory.prototype.createConnectingDialog =
304     function(cancelCallback) {
305   return new remoting.ConnectingDialog(cancelCallback);
309  * @param {remoting.Html5ModalDialog.Params} params
310  * @return {remoting.Html5ModalDialog}
311  */
312 remoting.ModalDialogFactory.prototype.createHtml5ModalDialog =
313     function(params) {
314   return new remoting.Html5ModalDialog(params);
318  * @param {remoting.AppMode} mode
319  * @param {HTMLElement} primaryButton
320  * @param {HTMLElement=} opt_secondaryButton
321  * @return {remoting.MessageDialog}
322  */
323 remoting.ModalDialogFactory.prototype.createMessageDialog =
324     function(mode, primaryButton, opt_secondaryButton) {
325   return new remoting.MessageDialog(mode, primaryButton, opt_secondaryButton);
329  * @param {remoting.AppMode} mode
330  * @param {HTMLElement} formElement
331  * @param {HTMLElement} inputField
332  * @param {HTMLElement} cancelButton
333  * @return {remoting.InputDialog}
334  */
335 remoting.ModalDialogFactory.prototype.createInputDialog =
336     function(mode, formElement, inputField, cancelButton) {
337   return new remoting.InputDialog(mode, formElement, inputField, cancelButton);
340 /** @type {remoting.ModalDialogFactory} */
341 remoting.modalDialogFactory = new remoting.ModalDialogFactory();
343 })();
346  * Define the enum at the end of file as JSCompile doesn't understand enums that
347  * are defined within an IIFE (Immediately Invoked Function Expression).
348  * @enum {number}
349  */
350 remoting.MessageDialog.Result = {
351   PRIMARY: 0,
352   SECONDARY: 1,
353   CANCEL: 2 // User presses the escape button.
357  * Parameters for the remoting.Html5ModalDialog constructor.  Unless otherwise
358  * noted, all parameters are optional.
360  * dialog: (required) The HTML dialog element.
362  * primaryButton: (required) The HTML element of the primary button.
364  * secondaryButton: The HTML element of the secondary button.
366  * closeOnEscape: Whether the user can dismiss the dialog by pressing the escape
367  *     key. Default to false.
369  * @typedef {{
370  *   dialog: HTMLDialogElement,
371  *   primaryButton:HTMLElement,
372  *   secondaryButton:(HTMLElement|undefined),
373  *   closeOnEscape:(boolean|undefined),
374  *   windowShape:(remoting.WindowShape|undefined)
375  * }}
376  */
377 remoting.Html5ModalDialog.Params;