1 | /*!
|
---|
2 | * AngularJS Material Design
|
---|
3 | * https://github.com/angular/material
|
---|
4 | * @license MIT
|
---|
5 | * v1.2.3
|
---|
6 | */
|
---|
7 | goog.provide('ngmaterial.components.dialog');
|
---|
8 | goog.require('ngmaterial.components.backdrop');
|
---|
9 | goog.require('ngmaterial.core');
|
---|
10 | /**
|
---|
11 | * @ngdoc module
|
---|
12 | * @name material.components.dialog
|
---|
13 | */
|
---|
14 | MdDialogDirective['$inject'] = ["$$rAF", "$mdTheming", "$mdDialog"];
|
---|
15 | MdDialogProvider['$inject'] = ["$$interimElementProvider"];
|
---|
16 | angular
|
---|
17 | .module('material.components.dialog', [
|
---|
18 | 'material.core',
|
---|
19 | 'material.components.backdrop'
|
---|
20 | ])
|
---|
21 | .directive('mdDialog', MdDialogDirective)
|
---|
22 | .provider('$mdDialog', MdDialogProvider);
|
---|
23 |
|
---|
24 | /**
|
---|
25 | * @ngdoc directive
|
---|
26 | * @name mdDialog
|
---|
27 | * @module material.components.dialog
|
---|
28 | *
|
---|
29 | * @restrict E
|
---|
30 | *
|
---|
31 | * @description
|
---|
32 | * `<md-dialog>` - The dialog's template must be inside this element.
|
---|
33 | *
|
---|
34 | * Inside, use an `<md-dialog-content>` element for the dialog's content, and use
|
---|
35 | * an `<md-dialog-actions>` element for the dialog's actions.
|
---|
36 | *
|
---|
37 | * ## CSS
|
---|
38 | * - `.md-dialog-content` - class that sets the padding on the content as the spec file
|
---|
39 | *
|
---|
40 | * ## Notes
|
---|
41 | * - If you specify an `id` for the `<md-dialog>`, the `<md-dialog-content>` will have the same `id`
|
---|
42 | * prefixed with `dialogContent_`.
|
---|
43 | *
|
---|
44 | * @usage
|
---|
45 | * ### Dialog template
|
---|
46 | * <hljs lang="html">
|
---|
47 | * <md-dialog aria-label="List dialog">
|
---|
48 | * <md-dialog-content>
|
---|
49 | * <md-list>
|
---|
50 | * <md-list-item ng-repeat="item in items">
|
---|
51 | * <p>Number {{item}}</p>
|
---|
52 | * </md-list-item>
|
---|
53 | * </md-list>
|
---|
54 | * </md-dialog-content>
|
---|
55 | * <md-dialog-actions>
|
---|
56 | * <md-button ng-click="closeDialog()" class="md-primary">Close Dialog</md-button>
|
---|
57 | * </md-dialog-actions>
|
---|
58 | * </md-dialog>
|
---|
59 | * </hljs>
|
---|
60 | */
|
---|
61 | function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
|
---|
62 | return {
|
---|
63 | restrict: 'E',
|
---|
64 | link: function(scope, element) {
|
---|
65 | element.addClass('_md'); // private md component indicator for styling
|
---|
66 |
|
---|
67 | $mdTheming(element);
|
---|
68 | $$rAF(function() {
|
---|
69 | var images;
|
---|
70 | var content = element[0].querySelector('md-dialog-content');
|
---|
71 |
|
---|
72 | if (content) {
|
---|
73 | images = content.getElementsByTagName('img');
|
---|
74 | addOverflowClass();
|
---|
75 | // delayed image loading may impact scroll height, check after images are loaded
|
---|
76 | angular.element(images).on('load', addOverflowClass);
|
---|
77 | }
|
---|
78 |
|
---|
79 | scope.$on('$destroy', function() {
|
---|
80 | $mdDialog.destroy(element);
|
---|
81 | });
|
---|
82 |
|
---|
83 | /**
|
---|
84 | *
|
---|
85 | */
|
---|
86 | function addOverflowClass() {
|
---|
87 | element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight);
|
---|
88 | }
|
---|
89 |
|
---|
90 |
|
---|
91 | });
|
---|
92 | }
|
---|
93 | };
|
---|
94 | }
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * @ngdoc service
|
---|
98 | * @name $mdDialog
|
---|
99 | * @module material.components.dialog
|
---|
100 | *
|
---|
101 | * @description
|
---|
102 | * `$mdDialog` opens a dialog over the app to inform users about critical information or require
|
---|
103 | * them to make decisions. There are two approaches for setup: a simple promise API
|
---|
104 | * and regular object syntax.
|
---|
105 | *
|
---|
106 | * ## Restrictions
|
---|
107 | *
|
---|
108 | * - The dialog is always given an isolate scope.
|
---|
109 | * - The dialog's template must have an outer `<md-dialog>` element.
|
---|
110 | * Inside, use an `<md-dialog-content>` element for the dialog's content, and use
|
---|
111 | * an `<md-dialog-actions>` element for the dialog's actions.
|
---|
112 | * - Dialogs must cover the entire application to keep interactions inside of them.
|
---|
113 | * Use the `parent` option to change where dialogs are appended.
|
---|
114 | *
|
---|
115 | * ## Sizing
|
---|
116 | * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`.
|
---|
117 | * - Default max-width is 80% of the `rootElement` or `parent`.
|
---|
118 | *
|
---|
119 | * ## CSS
|
---|
120 | * - `.md-dialog-content` - class that sets the padding on the content as the spec file
|
---|
121 | *
|
---|
122 | * @usage
|
---|
123 | * <hljs lang="html">
|
---|
124 | * <div ng-app="demoApp" ng-controller="AppController as ctrl">
|
---|
125 | * <div>
|
---|
126 | * <md-button ng-click="ctrl.showAlert()" class="md-raised md-warn">
|
---|
127 | * Basic Alert!
|
---|
128 | * </md-button>
|
---|
129 | * </div>
|
---|
130 | * <div>
|
---|
131 | * <md-button ng-click="ctrl.showDialog($event)" class="md-raised">
|
---|
132 | * Custom Dialog
|
---|
133 | * </md-button>
|
---|
134 | * </div>
|
---|
135 | * </div>
|
---|
136 | * </hljs>
|
---|
137 | *
|
---|
138 | * ### JavaScript: object syntax
|
---|
139 | * <hljs lang="js">
|
---|
140 | * (function(angular, undefined) {
|
---|
141 | * "use strict";
|
---|
142 | *
|
---|
143 | * angular
|
---|
144 | * .module('demoApp', ['ngMaterial'])
|
---|
145 | * .controller('AppCtrl', AppController);
|
---|
146 | *
|
---|
147 | * function AppController($mdDialog) {
|
---|
148 | * var alert;
|
---|
149 | * var ctrl = this;
|
---|
150 | * ctrl.showAlert = showAlert;
|
---|
151 | * ctrl.showDialog = showDialog;
|
---|
152 | * ctrl.items = [1, 2, 3];
|
---|
153 | *
|
---|
154 | * // Internal method
|
---|
155 | * function showAlert() {
|
---|
156 | * alert = $mdDialog.alert({
|
---|
157 | * title: 'Attention',
|
---|
158 | * textContent: 'This is an example of how simple dialogs can be!',
|
---|
159 | * ok: 'Close'
|
---|
160 | * });
|
---|
161 | *
|
---|
162 | * $mdDialog
|
---|
163 | * .show( alert )
|
---|
164 | * .finally(function() {
|
---|
165 | * alert = undefined;
|
---|
166 | * });
|
---|
167 | * }
|
---|
168 | *
|
---|
169 | * function showDialog($event) {
|
---|
170 | * var parentEl = angular.element(document.body);
|
---|
171 | * $mdDialog.show({
|
---|
172 | * parent: parentEl,
|
---|
173 | * targetEvent: $event,
|
---|
174 | * template:
|
---|
175 | * '<md-dialog aria-label="List dialog">' +
|
---|
176 | * ' <md-dialog-content>'+
|
---|
177 | * ' <md-list>'+
|
---|
178 | * ' <md-list-item ng-repeat="item in ctrl.items">'+
|
---|
179 | * ' <p>Number {{item}}</p>' +
|
---|
180 | * ' </md-item>'+
|
---|
181 | * ' </md-list>'+
|
---|
182 | * ' </md-dialog-content>' +
|
---|
183 | * ' <md-dialog-actions>' +
|
---|
184 | * ' <md-button ng-click="ctrl.closeDialog()" class="md-primary">' +
|
---|
185 | * ' Close Dialog' +
|
---|
186 | * ' </md-button>' +
|
---|
187 | * ' </md-dialog-actions>' +
|
---|
188 | * '</md-dialog>',
|
---|
189 | * locals: {
|
---|
190 | * items: ctrl.items
|
---|
191 | * },
|
---|
192 | * controller: DialogController
|
---|
193 | * controllerAs: 'ctrl'
|
---|
194 | * });
|
---|
195 | * function DialogController($mdDialog) {
|
---|
196 | * this.closeDialog = function() {
|
---|
197 | * $mdDialog.hide();
|
---|
198 | * }
|
---|
199 | * }
|
---|
200 | * }
|
---|
201 | * }
|
---|
202 | * })(angular);
|
---|
203 | * </hljs>
|
---|
204 | *
|
---|
205 | * ### Multiple Dialogs
|
---|
206 | * Using the `multiple` option for the `$mdDialog` service allows developers to show multiple
|
---|
207 | * dialogs at the same time.
|
---|
208 | *
|
---|
209 | * <hljs lang="js">
|
---|
210 | * // From plain options
|
---|
211 | * $mdDialog.show({
|
---|
212 | * multiple: true
|
---|
213 | * });
|
---|
214 | *
|
---|
215 | * // From a dialog preset
|
---|
216 | * $mdDialog.show(
|
---|
217 | * $mdDialog
|
---|
218 | * .alert()
|
---|
219 | * .multiple(true)
|
---|
220 | * );
|
---|
221 | *
|
---|
222 | * </hljs>
|
---|
223 | *
|
---|
224 | * ### Pre-Rendered Dialogs
|
---|
225 | * By using the `contentElement` option, it is possible to use an already existing element in the
|
---|
226 | * DOM.
|
---|
227 | *
|
---|
228 | * > Pre-rendered dialogs will be not linked to any scope and will not instantiate any new
|
---|
229 | * > controller.<br/>
|
---|
230 | * > You can manually link the elements to a scope or instantiate a controller from the template
|
---|
231 | * > (using `ng-controller`).
|
---|
232 | *
|
---|
233 | * <hljs lang="js">
|
---|
234 | * function showPrerenderedDialog() {
|
---|
235 | * $mdDialog.show({
|
---|
236 | * contentElement: '#myStaticDialog',
|
---|
237 | * parent: angular.element(document.body)
|
---|
238 | * });
|
---|
239 | * }
|
---|
240 | * </hljs>
|
---|
241 | *
|
---|
242 | * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS
|
---|
243 | * selector.
|
---|
244 | *
|
---|
245 | * <hljs lang="html">
|
---|
246 | * <div style="visibility: hidden">
|
---|
247 | * <div class="md-dialog-container" id="myStaticDialog">
|
---|
248 | * <md-dialog>
|
---|
249 | * This is a pre-rendered dialog.
|
---|
250 | * </md-dialog>
|
---|
251 | * </div>
|
---|
252 | * </div>
|
---|
253 | * </hljs>
|
---|
254 | *
|
---|
255 | * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise
|
---|
256 | * the dialog will not show up.
|
---|
257 | *
|
---|
258 | * It also possible to use a DOM element for the `contentElement` option.
|
---|
259 | * - `contentElement: document.querySelector('#myStaticDialog')`
|
---|
260 | * - `contentElement: angular.element(TEMPLATE)`
|
---|
261 | *
|
---|
262 | * When using a `template` as content element, it will be not compiled upon open.
|
---|
263 | * This allows you to compile the element yourself and use it each time the dialog opens.
|
---|
264 | *
|
---|
265 | * ### Custom Presets
|
---|
266 | * Developers are also able to create their own preset, which can be used without repeating
|
---|
267 | * their options each time.
|
---|
268 | *
|
---|
269 | * <hljs lang="js">
|
---|
270 | * $mdDialogProvider.addPreset('testPreset', {
|
---|
271 | * options: function() {
|
---|
272 | * return {
|
---|
273 | * template:
|
---|
274 | * '<md-dialog>' +
|
---|
275 | * 'This is a custom preset' +
|
---|
276 | * '</md-dialog>',
|
---|
277 | * controllerAs: 'dialog',
|
---|
278 | * bindToController: true,
|
---|
279 | * clickOutsideToClose: true,
|
---|
280 | * escapeToClose: true
|
---|
281 | * };
|
---|
282 | * }
|
---|
283 | * });
|
---|
284 | * </hljs>
|
---|
285 | *
|
---|
286 | * After creating your preset in the `config` phase, you can access it.
|
---|
287 | *
|
---|
288 | * <hljs lang="js">
|
---|
289 | * $mdDialog.show(
|
---|
290 | * $mdDialog.testPreset()
|
---|
291 | * );
|
---|
292 | * </hljs>
|
---|
293 | *
|
---|
294 | * ### JavaScript: promise API syntax, custom dialog template
|
---|
295 | *
|
---|
296 | * <hljs lang="js">
|
---|
297 | * (function(angular, undefined) {
|
---|
298 | * "use strict";
|
---|
299 | *
|
---|
300 | * angular
|
---|
301 | * .module('demoApp', ['ngMaterial'])
|
---|
302 | * .controller('EmployeeController', EmployeeController)
|
---|
303 | * .controller('GreetingController', GreetingController);
|
---|
304 | *
|
---|
305 | * // Fictitious Employee Editor to show how to use simple and complex dialogs.
|
---|
306 | *
|
---|
307 | * function EmployeeController($mdDialog) {
|
---|
308 | * var alert;
|
---|
309 | * var ctrl = this;
|
---|
310 | *
|
---|
311 | * ctrl.showAlert = showAlert;
|
---|
312 | * ctrl.showGreeting = showCustomGreeting;
|
---|
313 | *
|
---|
314 | * ctrl.hasAlert = function() { return !!alert };
|
---|
315 | * ctrl.userName = ctrl.userName || 'Bobby';
|
---|
316 | *
|
---|
317 | * // Dialog #1 - Show simple alert dialog and cache reference to dialog instance
|
---|
318 | *
|
---|
319 | * function showAlert() {
|
---|
320 | * alert = $mdDialog.alert()
|
---|
321 | * .title('Attention, ' + ctrl.userName)
|
---|
322 | * .textContent('This is an example of how simple dialogs can be!')
|
---|
323 | * .ok('Close');
|
---|
324 | *
|
---|
325 | * $mdDialog
|
---|
326 | * .show(alert)
|
---|
327 | * .finally(function() {
|
---|
328 | * alert = undefined;
|
---|
329 | * });
|
---|
330 | * }
|
---|
331 | *
|
---|
332 | * // Dialog #2 - Demonstrate more complex dialogs construction and popup.
|
---|
333 | *
|
---|
334 | * function showCustomGreeting($event) {
|
---|
335 | * $mdDialog.show({
|
---|
336 | * targetEvent: $event,
|
---|
337 | * template:
|
---|
338 | * '<md-dialog>' +
|
---|
339 | * ' <md-dialog-content>Hello {{ ctrl.employee }}!</md-dialog-content>' +
|
---|
340 | * ' <md-dialog-actions>' +
|
---|
341 | * ' <md-button ng-click="ctrl.closeDialog()" class="md-primary">' +
|
---|
342 | * ' Close Greeting' +
|
---|
343 | * ' </md-button>' +
|
---|
344 | * ' </md-dialog-actions>' +
|
---|
345 | * '</md-dialog>',
|
---|
346 | * controller: GreetingController,
|
---|
347 | * controllerAs: 'ctrl',
|
---|
348 | * onComplete: afterShowAnimation,
|
---|
349 | * locals: { employee: ctrl.userName }
|
---|
350 | * });
|
---|
351 | *
|
---|
352 | * // When the 'enter' animation finishes...
|
---|
353 | * function afterShowAnimation(scope, element, options) {
|
---|
354 | * // post-show code here: DOM element focus, etc.
|
---|
355 | * }
|
---|
356 | * }
|
---|
357 | * }
|
---|
358 | *
|
---|
359 | * // Greeting controller used with the 'showCustomGreeting()' custom dialog
|
---|
360 | * function GreetingController($mdDialog, $log) {
|
---|
361 | * var ctrl = this;
|
---|
362 | * this.$log = $log;
|
---|
363 | *
|
---|
364 | * ctrl.closeDialog = function() {
|
---|
365 | * // Hides the most recent dialog shown.
|
---|
366 | * // No specific dialog instance reference is needed.
|
---|
367 | * $mdDialog.hide();
|
---|
368 | * };
|
---|
369 | * }
|
---|
370 | *
|
---|
371 | * GreetingController.prototype.$onInit = function() {
|
---|
372 | * // Assigned from the locals options passed to $mdDialog.show.
|
---|
373 | * this.$log.log('Employee Name: ', ctrl.employee);
|
---|
374 | * };
|
---|
375 | *
|
---|
376 | * })(angular);
|
---|
377 | * </hljs>
|
---|
378 | */
|
---|
379 |
|
---|
380 | /**
|
---|
381 | * @ngdoc method
|
---|
382 | * @name $mdDialog#alert
|
---|
383 | *
|
---|
384 | * @description
|
---|
385 | * Builds a preconfigured dialog with the specified message.
|
---|
386 | *
|
---|
387 | * @returns {Object} a dialog preset with the chainable configuration methods:
|
---|
388 | *
|
---|
389 | * - `title(string)` - Sets the alert title.
|
---|
390 | * - `textContent(string)` - Sets the alert message.
|
---|
391 | * - `htmlContent(string)` - Sets the alert message as HTML. Requires the `ngSanitize`
|
---|
392 | * module to be loaded. HTML is not run through AngularJS' compiler.
|
---|
393 | * - `ok(string)` - Sets the alert "Okay" button text.
|
---|
394 | * - `theme(string)` - Sets the theme of the alert dialog.
|
---|
395 | * - `targetEvent(DOMClickEvent=)` - A click's event object. When passed in as an
|
---|
396 | * option, the location of the click will be used as the starting point for the opening
|
---|
397 | * animation of the the dialog.
|
---|
398 | */
|
---|
399 |
|
---|
400 | /**
|
---|
401 | * @ngdoc method
|
---|
402 | * @name $mdDialog#confirm
|
---|
403 | *
|
---|
404 | * @description
|
---|
405 | * Builds a preconfigured dialog with the specified message. You can call show and the promise
|
---|
406 | * returned will be resolved if the user clicks the confirm action on the dialog. The promise will
|
---|
407 | * be rejected if the user clicks the cancel action or dismisses the dialog.
|
---|
408 | *
|
---|
409 | * @returns {Object} a dialog preset with the chainable configuration methods:
|
---|
410 | *
|
---|
411 | * Additionally, it supports the following methods:
|
---|
412 | *
|
---|
413 | * - `title(string)` - Sets the confirm title.
|
---|
414 | * - `textContent(string)` - Sets the confirm message.
|
---|
415 | * - `htmlContent(string)` - Sets the confirm message as HTML. Requires the `ngSanitize`
|
---|
416 | * module to be loaded. HTML is not run through AngularJS' compiler.
|
---|
417 | * - `ok(string)` - Sets the confirm "Okay" button text.
|
---|
418 | * - `cancel(string)` - Sets the confirm "Cancel" button text.
|
---|
419 | * - `theme(string)` - Sets the theme of the confirm dialog.
|
---|
420 | * - `targetEvent(DOMClickEvent=)` - A click's event object. When passed in as an
|
---|
421 | * option, the location of the click will be used as the starting point for the opening
|
---|
422 | * animation of the the dialog.
|
---|
423 | */
|
---|
424 |
|
---|
425 | /**
|
---|
426 | * @ngdoc method
|
---|
427 | * @name $mdDialog#prompt
|
---|
428 | *
|
---|
429 | * @description
|
---|
430 | * Builds a preconfigured dialog with the specified message and input box. You can call show and the
|
---|
431 | * promise returned will be resolved, if the user clicks the prompt action on the dialog, passing
|
---|
432 | * the input value as the first argument. The promise will be rejected if the user clicks the cancel
|
---|
433 | * action or dismisses the dialog.
|
---|
434 | *
|
---|
435 | * @returns {Object} a dialog preset with the chainable configuration methods:
|
---|
436 | *
|
---|
437 | * Additionally, it supports the following methods:
|
---|
438 | *
|
---|
439 | * - `title(string)` - Sets the prompt title.
|
---|
440 | * - `textContent(string)` - Sets the prompt message.
|
---|
441 | * - `htmlContent(string)` - Sets the prompt message as HTML. Requires the `ngSanitize`
|
---|
442 | * module to be loaded. HTML is not run through AngularJS' compiler.
|
---|
443 | * - `placeholder(string)` - Sets the placeholder text for the input.
|
---|
444 | * - `required(boolean)` - Sets the input required value.
|
---|
445 | * - `initialValue(string)` - Sets the initial value for the prompt input.
|
---|
446 | * - `ok(string)` - Sets the prompt "Okay" button text.
|
---|
447 | * - `cancel(string)` - Sets the prompt "Cancel" button text.
|
---|
448 | * - `theme(string)` - Sets the theme of the prompt dialog.
|
---|
449 | * - `targetEvent(DOMClickEvent=)` - A click's event object. When passed in as an
|
---|
450 | * option, the location of the click will be used as the starting point for the opening
|
---|
451 | * animation of the the dialog.
|
---|
452 | */
|
---|
453 |
|
---|
454 | /**
|
---|
455 | * @ngdoc method
|
---|
456 | * @name $mdDialog#show
|
---|
457 | *
|
---|
458 | * @description
|
---|
459 | * Show a dialog with the specified options.
|
---|
460 | *
|
---|
461 | * @param {Object} optionsOrPreset Either provide a dialog preset returned from `alert()`,
|
---|
462 | * `prompt()`, or `confirm()`; or an options object with the following properties:
|
---|
463 | * - `templateUrl` - `{string=}`: The url of a template that will be used as the content
|
---|
464 | * of the dialog.
|
---|
465 | * - `template` - `{string=}`: HTML template to show in the dialog. This **must** be trusted HTML
|
---|
466 | * with respect to AngularJS' [$sce service](https://docs.angularjs.org/api/ng/service/$sce).
|
---|
467 | * This template should **never** be constructed with any kind of user input or user data.
|
---|
468 | * - `contentElement` - `{string|Element}`: Instead of using a template, which will be compiled
|
---|
469 | * each time a dialog opens, you can also use a DOM element.<br/>
|
---|
470 | * * When specifying an element, which is present on the DOM, `$mdDialog` will temporary fetch
|
---|
471 | * the element into the dialog and restores it at the old DOM position upon close.
|
---|
472 | * * When specifying a string, the string be used as a CSS selector, to lookup for the element
|
---|
473 | * in the DOM.
|
---|
474 | * - `autoWrap` - `{boolean=}`: Whether or not to automatically wrap the template with a
|
---|
475 | * `<md-dialog>` tag if one is not provided. Defaults to true. Can be disabled if you provide a
|
---|
476 | * custom dialog directive.
|
---|
477 | * - `targetEvent` - `{DOMClickEvent=}`: A click's event object. When passed in as an option,
|
---|
478 | * the location of the click will be used as the starting point for the opening animation
|
---|
479 | * of the the dialog.
|
---|
480 | * - `openFrom` - `{string|Element|Object}`: The query selector, DOM element or the Rect object
|
---|
481 | * that is used to determine the bounds (top, left, height, width) from which the Dialog will
|
---|
482 | * originate.
|
---|
483 | * - `closeTo` - `{string|Element|Object}`: The query selector, DOM element or the Rect object
|
---|
484 | * that is used to determine the bounds (top, left, height, width) to which the Dialog will
|
---|
485 | * target.
|
---|
486 | * - `scope` - `{Object=}`: the scope to link the template / controller to. If none is specified,
|
---|
487 | * it will create a new isolate scope.
|
---|
488 | * This scope will be destroyed when the dialog is removed unless `preserveScope` is set to
|
---|
489 | * true.
|
---|
490 | * - `preserveScope` - `{boolean=}`: whether to preserve the scope when the element is removed.
|
---|
491 | * Default is false
|
---|
492 | * - `disableParentScroll` - `{boolean=}`: Whether to disable scrolling while the dialog is open.
|
---|
493 | * Default true.
|
---|
494 | * - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
|
---|
495 | * Default true.
|
---|
496 | * - `clickOutsideToClose` - `{boolean=}`: Whether the user can click outside the dialog to
|
---|
497 | * close it. Default false.
|
---|
498 | * - `escapeToClose` - `{boolean=}`: Whether the user can press escape to close the dialog.
|
---|
499 | * Default true.
|
---|
500 | * - `focusOnOpen` - `{boolean=}`: An option to override focus behavior on open. Only disable if
|
---|
501 | * focusing some other way, as focus management is required for dialogs to be accessible.
|
---|
502 | * Defaults to true.
|
---|
503 | * - `controller` - `{Function|string=}`: The controller to associate with the dialog. The
|
---|
504 | * controller will be injected with the local `$mdDialog`, which passes along a scope for the
|
---|
505 | * dialog.
|
---|
506 | * - `locals` - `{Object=}`: An object containing key/value pairs. The keys will be used as names
|
---|
507 | * of values to inject into the controller. For example, `locals: {three: 3}` would inject
|
---|
508 | * `three` into the controller, with the value 3. If `bindToController` is true, they will be
|
---|
509 | * copied to the controller instead.
|
---|
510 | * - `bindToController` - `bool`: bind the locals to the controller, instead of passing them in.
|
---|
511 | * - `resolve` - `{Function=}`: Similar to locals, except it takes as values functions that return
|
---|
512 | * promises, and the dialog will not open until all of the promises resolve.
|
---|
513 | * - `controllerAs` - `{string=}`: An alias to assign the controller to on the scope.
|
---|
514 | * - `parent` - `{element=}`: The element to append the dialog to. Defaults to appending
|
---|
515 | * to the root element of the application.
|
---|
516 | * - `onShowing` - `Function(scope, element, options: Object=, controller: Object)=`: Callback
|
---|
517 | * function used to notify the show() animation is starting.
|
---|
518 | * - `onComplete` - `Function(scope, element, options: Object=)=`: Callback function used to
|
---|
519 | * notify when the show() animation is finished.
|
---|
520 | * - `onRemoving` - `Function(element, removePromise)`: Callback function used to announce the
|
---|
521 | * close/hide() action is starting. This allows developers to run custom animations
|
---|
522 | * in parallel with the close animations.
|
---|
523 | * - `fullscreen` `{boolean=}`: An option to toggle whether the dialog should show in fullscreen
|
---|
524 | * or not. Defaults to `false`.
|
---|
525 | * - `multiple` `{boolean=}`: An option to allow this dialog to display over one that's currently
|
---|
526 | * open.
|
---|
527 | * @returns {Promise} A promise that can be resolved with `$mdDialog.hide()` or
|
---|
528 | * rejected with `$mdDialog.cancel()`.
|
---|
529 | */
|
---|
530 |
|
---|
531 | /**
|
---|
532 | * @ngdoc method
|
---|
533 | * @name $mdDialog#hide
|
---|
534 | *
|
---|
535 | * @description
|
---|
536 | * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
|
---|
537 | *
|
---|
538 | * @param {*=} response An argument for the resolved promise.
|
---|
539 | *
|
---|
540 | * @returns {promise} A promise that is resolved when the dialog has been closed.
|
---|
541 | */
|
---|
542 |
|
---|
543 | /**
|
---|
544 | * @ngdoc method
|
---|
545 | * @name $mdDialog#cancel
|
---|
546 | *
|
---|
547 | * @description
|
---|
548 | * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
|
---|
549 | *
|
---|
550 | * @param {*=} response An argument for the rejected promise.
|
---|
551 | *
|
---|
552 | * @returns {promise} A promise that is resolved when the dialog has been closed.
|
---|
553 | */
|
---|
554 |
|
---|
555 | function MdDialogProvider($$interimElementProvider) {
|
---|
556 | // Elements to capture and redirect focus when the user presses tab at the dialog boundary.
|
---|
557 | MdDialogController['$inject'] = ["$mdDialog", "$mdConstant"];
|
---|
558 | dialogDefaultOptions['$inject'] = ["$mdDialog", "$mdAria", "$mdUtil", "$mdConstant", "$animate", "$document", "$window", "$rootElement", "$log", "$injector", "$mdTheming", "$interpolate", "$mdInteraction"];
|
---|
559 | var topFocusTrap, bottomFocusTrap;
|
---|
560 | var removeFocusTrap;
|
---|
561 |
|
---|
562 | return $$interimElementProvider('$mdDialog')
|
---|
563 | .setDefaults({
|
---|
564 | methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose',
|
---|
565 | 'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'multiple'],
|
---|
566 | options: dialogDefaultOptions
|
---|
567 | })
|
---|
568 | .addPreset('alert', {
|
---|
569 | methods: ['title', 'htmlContent', 'textContent', 'ariaLabel', 'ok', 'theme',
|
---|
570 | 'css'],
|
---|
571 | options: advancedDialogOptions
|
---|
572 | })
|
---|
573 | .addPreset('confirm', {
|
---|
574 | methods: ['title', 'htmlContent', 'textContent', 'ariaLabel', 'ok', 'cancel',
|
---|
575 | 'theme', 'css'],
|
---|
576 | options: advancedDialogOptions
|
---|
577 | })
|
---|
578 | .addPreset('prompt', {
|
---|
579 | methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'placeholder', 'ariaLabel',
|
---|
580 | 'ok', 'cancel', 'theme', 'css', 'required'],
|
---|
581 | options: advancedDialogOptions
|
---|
582 | });
|
---|
583 |
|
---|
584 | /* ngInject */
|
---|
585 | function advancedDialogOptions() {
|
---|
586 | return {
|
---|
587 | template: [
|
---|
588 | '<md-dialog md-theme="{{ dialog.theme || dialog.defaultTheme }}" aria-label="{{ dialog.ariaLabel }}" ng-class="dialog.css">',
|
---|
589 | ' <md-dialog-content class="md-dialog-content" role="document" tabIndex="-1">',
|
---|
590 | ' <h2 class="md-title">{{ dialog.title }}</h2>',
|
---|
591 | ' <div ng-if="::dialog.mdHtmlContent" class="md-dialog-content-body" ',
|
---|
592 | ' ng-bind-html="::dialog.mdHtmlContent"></div>',
|
---|
593 | ' <div ng-if="::!dialog.mdHtmlContent" class="md-dialog-content-body">',
|
---|
594 | ' <p>{{::dialog.mdTextContent}}</p>',
|
---|
595 | ' </div>',
|
---|
596 | ' <md-input-container md-no-float ng-if="::dialog.$type == \'prompt\'" class="md-prompt-input-container">',
|
---|
597 | ' <input ng-keypress="dialog.keypress($event)" md-autofocus ng-model="dialog.result" ' +
|
---|
598 | ' placeholder="{{::dialog.placeholder}}" ng-required="dialog.required">',
|
---|
599 | ' </md-input-container>',
|
---|
600 | ' </md-dialog-content>',
|
---|
601 | ' <md-dialog-actions>',
|
---|
602 | ' <md-button ng-if="dialog.$type === \'confirm\' || dialog.$type === \'prompt\'"' +
|
---|
603 | ' ng-click="dialog.abort()" class="md-primary md-cancel-button">',
|
---|
604 | ' {{ dialog.cancel }}',
|
---|
605 | ' </md-button>',
|
---|
606 | ' <md-button ng-click="dialog.hide()" class="md-primary md-confirm-button" md-autofocus="dialog.$type===\'alert\'"' +
|
---|
607 | ' ng-disabled="dialog.required && !dialog.result">',
|
---|
608 | ' {{ dialog.ok }}',
|
---|
609 | ' </md-button>',
|
---|
610 | ' </md-dialog-actions>',
|
---|
611 | '</md-dialog>'
|
---|
612 | ].join('').replace(/\s\s+/g, ''),
|
---|
613 | controller: MdDialogController,
|
---|
614 | controllerAs: 'dialog',
|
---|
615 | bindToController: true,
|
---|
616 | };
|
---|
617 | }
|
---|
618 |
|
---|
619 | /**
|
---|
620 | * Controller for the md-dialog interim elements
|
---|
621 | * ngInject
|
---|
622 | */
|
---|
623 | function MdDialogController($mdDialog, $mdConstant) {
|
---|
624 | // For compatibility with AngularJS 1.6+, we should always use the $onInit hook in
|
---|
625 | // interimElements. The $mdCompiler simulates the $onInit hook for all versions.
|
---|
626 | this.$onInit = function() {
|
---|
627 | var isPrompt = this.$type === 'prompt';
|
---|
628 |
|
---|
629 | if (isPrompt && this.initialValue) {
|
---|
630 | this.result = this.initialValue;
|
---|
631 | }
|
---|
632 |
|
---|
633 | this.hide = function() {
|
---|
634 | $mdDialog.hide(isPrompt ? this.result : true);
|
---|
635 | };
|
---|
636 | this.abort = function() {
|
---|
637 | $mdDialog.cancel();
|
---|
638 | };
|
---|
639 | this.keypress = function($event) {
|
---|
640 | var invalidPrompt = isPrompt && this.required && !angular.isDefined(this.result);
|
---|
641 |
|
---|
642 | if ($event.keyCode === $mdConstant.KEY_CODE.ENTER && !invalidPrompt) {
|
---|
643 | $mdDialog.hide(this.result);
|
---|
644 | }
|
---|
645 | };
|
---|
646 | };
|
---|
647 | }
|
---|
648 |
|
---|
649 | /* ngInject */
|
---|
650 | function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document,
|
---|
651 | $window, $rootElement, $log, $injector, $mdTheming, $interpolate,
|
---|
652 | $mdInteraction) {
|
---|
653 | return {
|
---|
654 | hasBackdrop: true,
|
---|
655 | isolateScope: true,
|
---|
656 | onCompiling: beforeCompile,
|
---|
657 | onShow: onShow,
|
---|
658 | onShowing: beforeShow,
|
---|
659 | onRemove: onRemove,
|
---|
660 | clickOutsideToClose: false,
|
---|
661 | escapeToClose: true,
|
---|
662 | targetEvent: null,
|
---|
663 | closeTo: null,
|
---|
664 | openFrom: null,
|
---|
665 | focusOnOpen: true,
|
---|
666 | disableParentScroll: true,
|
---|
667 | autoWrap: true,
|
---|
668 | fullscreen: false,
|
---|
669 | transformTemplate: function(template, options) {
|
---|
670 | // Make the dialog container focusable, because otherwise the focus will be always
|
---|
671 | // redirected to an element outside of the container, and the focus trap won't work.
|
---|
672 | // Also the tabindex is needed for the `escapeToClose` functionality, because
|
---|
673 | // the keyDown event can't be triggered when the focus is outside of the container.
|
---|
674 | var startSymbol = $interpolate.startSymbol();
|
---|
675 | var endSymbol = $interpolate.endSymbol();
|
---|
676 | var theme = startSymbol + (options.themeWatch ? '' : '::') + 'theme' + endSymbol;
|
---|
677 | var themeAttr = (options.hasTheme) ? 'md-theme="'+theme+'"': '';
|
---|
678 | return '<div class="md-dialog-container" tabindex="-1" ' + themeAttr + '>' + validatedTemplate(template) + '</div>';
|
---|
679 |
|
---|
680 | /**
|
---|
681 | * The specified template should contain a <md-dialog> wrapper element....
|
---|
682 | */
|
---|
683 | function validatedTemplate(template) {
|
---|
684 | if (options.autoWrap && !/<\/md-dialog>/g.test(template)) {
|
---|
685 | return '<md-dialog>' + (template || '') + '</md-dialog>';
|
---|
686 | } else {
|
---|
687 | return template || '';
|
---|
688 | }
|
---|
689 | }
|
---|
690 | }
|
---|
691 | };
|
---|
692 |
|
---|
693 | function beforeCompile(options) {
|
---|
694 | // Automatically apply the theme, if the user didn't specify a theme explicitly.
|
---|
695 | // Those option changes need to be done, before the compilation has started, because otherwise
|
---|
696 | // the option changes will be not available in the $mdCompilers locales.
|
---|
697 | options.defaultTheme = $mdTheming.defaultTheme();
|
---|
698 |
|
---|
699 | detectTheming(options);
|
---|
700 | }
|
---|
701 |
|
---|
702 | function beforeShow(scope, element, options, controller) {
|
---|
703 |
|
---|
704 | if (controller) {
|
---|
705 | var mdHtmlContent = controller.htmlContent || options.htmlContent || '';
|
---|
706 | var mdTextContent = controller.textContent || options.textContent || '';
|
---|
707 |
|
---|
708 | if (mdHtmlContent && !$injector.has('$sanitize')) {
|
---|
709 | throw Error('The ngSanitize module must be loaded in order to use htmlContent.');
|
---|
710 | }
|
---|
711 |
|
---|
712 | if (mdHtmlContent && mdTextContent) {
|
---|
713 | throw Error('md-dialog cannot have both `htmlContent` and `textContent`');
|
---|
714 | }
|
---|
715 |
|
---|
716 | // Only assign the content if nothing throws, otherwise it'll still be compiled.
|
---|
717 | controller.mdHtmlContent = mdHtmlContent;
|
---|
718 | controller.mdTextContent = mdTextContent;
|
---|
719 | }
|
---|
720 | }
|
---|
721 |
|
---|
722 | /** Show method for dialogs */
|
---|
723 | function onShow(scope, element, options) {
|
---|
724 | angular.element($document[0].body).addClass('md-dialog-is-showing');
|
---|
725 |
|
---|
726 | var dialogElement = element.find('md-dialog');
|
---|
727 |
|
---|
728 | // Once a dialog has `ng-cloak` applied on his template the dialog animation will not work
|
---|
729 | // properly. This is a very common problem, so we have to notify the developer about this.
|
---|
730 | if (dialogElement.hasClass('ng-cloak')) {
|
---|
731 | var message =
|
---|
732 | '$mdDialog: using `<md-dialog ng-cloak>` will affect the dialog opening animations.';
|
---|
733 | $log.warn(message, element[0]);
|
---|
734 | }
|
---|
735 |
|
---|
736 | captureParentAndFromToElements(options);
|
---|
737 | configureAria(dialogElement, options);
|
---|
738 | showBackdrop(scope, element, options);
|
---|
739 | activateListeners(element, options);
|
---|
740 |
|
---|
741 | return dialogPopIn(element, options)
|
---|
742 | .then(function() {
|
---|
743 | lockScreenReader(element, options);
|
---|
744 | focusOnOpen();
|
---|
745 | });
|
---|
746 |
|
---|
747 | /**
|
---|
748 | * For alerts, focus on content... otherwise focus on the close button (or equivalent)
|
---|
749 | */
|
---|
750 | function focusOnOpen() {
|
---|
751 | if (options.focusOnOpen) {
|
---|
752 | var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement;
|
---|
753 | target.focus();
|
---|
754 | }
|
---|
755 |
|
---|
756 | /**
|
---|
757 | * If no element with class dialog-close, try to find the last
|
---|
758 | * button child in md-dialog-actions and assume it is a close button.
|
---|
759 | *
|
---|
760 | * If we find no actions at all, log a warning to the console.
|
---|
761 | */
|
---|
762 | function findCloseButton() {
|
---|
763 | return element[0].querySelector('.dialog-close, md-dialog-actions button:last-child');
|
---|
764 | }
|
---|
765 | }
|
---|
766 | }
|
---|
767 |
|
---|
768 | /**
|
---|
769 | * Remove function for all dialogs
|
---|
770 | */
|
---|
771 | function onRemove(scope, element, options) {
|
---|
772 | options.deactivateListeners();
|
---|
773 | options.unlockScreenReader();
|
---|
774 | options.hideBackdrop(options.$destroy);
|
---|
775 |
|
---|
776 | // Remove the focus traps that we added earlier for keeping focus within the dialog.
|
---|
777 | if (removeFocusTrap) {
|
---|
778 | removeFocusTrap();
|
---|
779 | removeFocusTrap = null;
|
---|
780 | }
|
---|
781 |
|
---|
782 | // For navigation $destroy events, do a quick, non-animated removal,
|
---|
783 | // but for normal closes (from clicks, etc) animate the removal
|
---|
784 | return options.$destroy ? detachAndClean() : animateRemoval().then(detachAndClean);
|
---|
785 |
|
---|
786 | /**
|
---|
787 | * For normal closes, animate the removal.
|
---|
788 | * For forced closes (like $destroy events), skip the animations
|
---|
789 | */
|
---|
790 | function animateRemoval() {
|
---|
791 | return dialogPopOut(element, options);
|
---|
792 | }
|
---|
793 |
|
---|
794 | /**
|
---|
795 | * Detach the element
|
---|
796 | */
|
---|
797 | function detachAndClean() {
|
---|
798 | angular.element($document[0].body).removeClass('md-dialog-is-showing');
|
---|
799 |
|
---|
800 | // Reverse the container stretch if using a content element.
|
---|
801 | if (options.contentElement) {
|
---|
802 | options.reverseContainerStretch();
|
---|
803 | }
|
---|
804 |
|
---|
805 | // Exposed cleanup function from the $mdCompiler.
|
---|
806 | options.cleanupElement();
|
---|
807 |
|
---|
808 | // Restores the focus to the origin element if the last interaction upon opening was a keyboard.
|
---|
809 | if (!options.$destroy && options.originInteraction === 'keyboard') {
|
---|
810 | options.origin.focus();
|
---|
811 | }
|
---|
812 | }
|
---|
813 | }
|
---|
814 |
|
---|
815 | function detectTheming(options) {
|
---|
816 | // Once the user specifies a targetEvent, we will automatically try to find the correct
|
---|
817 | // nested theme.
|
---|
818 | var targetEl;
|
---|
819 | if (options.targetEvent && options.targetEvent.target) {
|
---|
820 | targetEl = angular.element(options.targetEvent.target);
|
---|
821 | }
|
---|
822 |
|
---|
823 | var themeCtrl = targetEl && targetEl.controller('mdTheme');
|
---|
824 |
|
---|
825 | options.hasTheme = (!!themeCtrl);
|
---|
826 |
|
---|
827 | if (!options.hasTheme) {
|
---|
828 | return;
|
---|
829 | }
|
---|
830 |
|
---|
831 | options.themeWatch = themeCtrl.$shouldWatch;
|
---|
832 |
|
---|
833 | var theme = options.theme || themeCtrl.$mdTheme;
|
---|
834 |
|
---|
835 | if (theme) {
|
---|
836 | options.scope.theme = theme;
|
---|
837 | }
|
---|
838 |
|
---|
839 | var unwatch = themeCtrl.registerChanges(function (newTheme) {
|
---|
840 | options.scope.theme = newTheme;
|
---|
841 |
|
---|
842 | if (!options.themeWatch) {
|
---|
843 | unwatch();
|
---|
844 | }
|
---|
845 | });
|
---|
846 | }
|
---|
847 |
|
---|
848 | /**
|
---|
849 | * Capture originator/trigger/from/to element information (if available)
|
---|
850 | * and the parent container for the dialog; defaults to the $rootElement
|
---|
851 | * unless overridden in the options.parent
|
---|
852 | */
|
---|
853 | function captureParentAndFromToElements(options) {
|
---|
854 | options.origin = angular.extend({
|
---|
855 | element: null,
|
---|
856 | bounds: null,
|
---|
857 | focus: angular.noop
|
---|
858 | }, options.origin || {});
|
---|
859 |
|
---|
860 | options.parent = getDomElement(options.parent, $rootElement);
|
---|
861 | options.closeTo = getBoundingClientRect(getDomElement(options.closeTo));
|
---|
862 | options.openFrom = getBoundingClientRect(getDomElement(options.openFrom));
|
---|
863 |
|
---|
864 | if (options.targetEvent) {
|
---|
865 | options.origin = getBoundingClientRect(options.targetEvent.target, options.origin);
|
---|
866 | options.originInteraction = $mdInteraction.getLastInteractionType();
|
---|
867 | }
|
---|
868 |
|
---|
869 |
|
---|
870 | /**
|
---|
871 | * Identify the bounding RECT for the target element
|
---|
872 | *
|
---|
873 | */
|
---|
874 | function getBoundingClientRect (element, orig) {
|
---|
875 | var source = angular.element((element || {}));
|
---|
876 | if (source && source.length) {
|
---|
877 | // Compute and save the target element's bounding rect, so that if the
|
---|
878 | // element is hidden when the dialog closes, we can shrink the dialog
|
---|
879 | // back to the same position it expanded from.
|
---|
880 | //
|
---|
881 | // Checking if the source is a rect object or a DOM element
|
---|
882 | var bounds = {top:0,left:0,height:0,width:0};
|
---|
883 | var hasFn = angular.isFunction(source[0].getBoundingClientRect);
|
---|
884 |
|
---|
885 | return angular.extend(orig || {}, {
|
---|
886 | element : hasFn ? source : undefined,
|
---|
887 | bounds : hasFn ? source[0].getBoundingClientRect() : angular.extend({}, bounds, source[0]),
|
---|
888 | focus : angular.bind(source, source.focus),
|
---|
889 | });
|
---|
890 | }
|
---|
891 | }
|
---|
892 |
|
---|
893 | /**
|
---|
894 | * If the specifier is a simple string selector, then query for
|
---|
895 | * the DOM element.
|
---|
896 | */
|
---|
897 | function getDomElement(element, defaultElement) {
|
---|
898 | if (angular.isString(element)) {
|
---|
899 | element = $document[0].querySelector(element);
|
---|
900 | }
|
---|
901 |
|
---|
902 | // If we have a reference to a raw dom element, always wrap it in jqLite
|
---|
903 | return angular.element(element || defaultElement);
|
---|
904 | }
|
---|
905 |
|
---|
906 | }
|
---|
907 |
|
---|
908 | /**
|
---|
909 | * Listen for escape keys and outside clicks to auto close
|
---|
910 | */
|
---|
911 | function activateListeners(element, options) {
|
---|
912 | var window = angular.element($window);
|
---|
913 | var onWindowResize = $mdUtil.debounce(function() {
|
---|
914 | stretchDialogContainerToViewport(element, options);
|
---|
915 | }, 60);
|
---|
916 |
|
---|
917 | var removeListeners = [];
|
---|
918 | var smartClose = function() {
|
---|
919 | // Only 'confirm' dialogs have a cancel button... escape/clickOutside will
|
---|
920 | // cancel or fallback to hide.
|
---|
921 | var closeFn = (options.$type === 'alert') ? $mdDialog.hide : $mdDialog.cancel;
|
---|
922 | $mdUtil.nextTick(closeFn, true);
|
---|
923 | };
|
---|
924 |
|
---|
925 | if (options.escapeToClose) {
|
---|
926 | var parentTarget = options.parent;
|
---|
927 | var keyHandlerFn = function(ev) {
|
---|
928 | if (ev.keyCode === $mdConstant.KEY_CODE.ESCAPE) {
|
---|
929 | ev.stopImmediatePropagation();
|
---|
930 | ev.preventDefault();
|
---|
931 |
|
---|
932 | smartClose();
|
---|
933 | }
|
---|
934 | };
|
---|
935 |
|
---|
936 | // Add keydown listeners
|
---|
937 | element.on('keydown', keyHandlerFn);
|
---|
938 | parentTarget.on('keydown', keyHandlerFn);
|
---|
939 |
|
---|
940 | // Queue remove listeners function
|
---|
941 | removeListeners.push(function() {
|
---|
942 | element.off('keydown', keyHandlerFn);
|
---|
943 | parentTarget.off('keydown', keyHandlerFn);
|
---|
944 | });
|
---|
945 | }
|
---|
946 |
|
---|
947 | // Register listener to update dialog on window resize
|
---|
948 | window.on('resize', onWindowResize);
|
---|
949 |
|
---|
950 | removeListeners.push(function() {
|
---|
951 | window.off('resize', onWindowResize);
|
---|
952 | });
|
---|
953 |
|
---|
954 | if (options.clickOutsideToClose) {
|
---|
955 | var target = element;
|
---|
956 | var sourceElem;
|
---|
957 |
|
---|
958 | // Keep track of the element on which the mouse originally went down
|
---|
959 | // so that we can only close the backdrop when the 'click' started on it.
|
---|
960 | // A simple 'click' handler does not work,
|
---|
961 | // it sets the target object as the element the mouse went down on.
|
---|
962 | var mousedownHandler = function(ev) {
|
---|
963 | sourceElem = ev.target;
|
---|
964 | };
|
---|
965 |
|
---|
966 | // We check if our original element and the target is the backdrop
|
---|
967 | // because if the original was the backdrop and the target was inside the dialog
|
---|
968 | // we don't want to dialog to close.
|
---|
969 | var mouseupHandler = function(ev) {
|
---|
970 | if (sourceElem === target[0] && ev.target === target[0]) {
|
---|
971 | ev.stopPropagation();
|
---|
972 | ev.preventDefault();
|
---|
973 |
|
---|
974 | smartClose();
|
---|
975 | }
|
---|
976 | };
|
---|
977 |
|
---|
978 | // Add listeners
|
---|
979 | target.on('mousedown', mousedownHandler);
|
---|
980 | target.on('mouseup', mouseupHandler);
|
---|
981 |
|
---|
982 | // Queue remove listeners function
|
---|
983 | removeListeners.push(function() {
|
---|
984 | target.off('mousedown', mousedownHandler);
|
---|
985 | target.off('mouseup', mouseupHandler);
|
---|
986 | });
|
---|
987 | }
|
---|
988 |
|
---|
989 | // Attach specific `remove` listener handler
|
---|
990 | options.deactivateListeners = function() {
|
---|
991 | removeListeners.forEach(function(removeFn) {
|
---|
992 | removeFn();
|
---|
993 | });
|
---|
994 | options.deactivateListeners = null;
|
---|
995 | };
|
---|
996 | }
|
---|
997 |
|
---|
998 | /**
|
---|
999 | * Show modal backdrop element...
|
---|
1000 | */
|
---|
1001 | function showBackdrop(scope, element, options) {
|
---|
1002 |
|
---|
1003 | if (options.disableParentScroll) {
|
---|
1004 | // !! DO this before creating the backdrop; since disableScrollAround()
|
---|
1005 | // configures the scroll offset; which is used by mdBackDrop postLink()
|
---|
1006 | options.restoreScroll = $mdUtil.disableScrollAround(element, options.parent);
|
---|
1007 | }
|
---|
1008 |
|
---|
1009 | if (options.hasBackdrop) {
|
---|
1010 | options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque");
|
---|
1011 | $animate.enter(options.backdrop, options.parent);
|
---|
1012 | }
|
---|
1013 |
|
---|
1014 | /**
|
---|
1015 | * Hide modal backdrop element...
|
---|
1016 | */
|
---|
1017 | options.hideBackdrop = function hideBackdrop($destroy) {
|
---|
1018 | if (options.backdrop) {
|
---|
1019 | if ($destroy) {
|
---|
1020 | options.backdrop.remove();
|
---|
1021 | } else {
|
---|
1022 | $animate.leave(options.backdrop);
|
---|
1023 | }
|
---|
1024 | }
|
---|
1025 |
|
---|
1026 | if (options.disableParentScroll) {
|
---|
1027 | options.restoreScroll && options.restoreScroll();
|
---|
1028 | delete options.restoreScroll;
|
---|
1029 | }
|
---|
1030 |
|
---|
1031 | options.hideBackdrop = null;
|
---|
1032 | };
|
---|
1033 | }
|
---|
1034 |
|
---|
1035 | /**
|
---|
1036 | * Inject ARIA-specific attributes appropriate for Dialogs
|
---|
1037 | */
|
---|
1038 | function configureAria(element, options) {
|
---|
1039 |
|
---|
1040 | var role = (options.$type === 'alert') ? 'alertdialog' : 'dialog';
|
---|
1041 | var dialogContent = element.find('md-dialog-content');
|
---|
1042 | var existingDialogId = element.attr('id');
|
---|
1043 | var dialogContentId = 'dialogContent_' + (existingDialogId || $mdUtil.nextUid());
|
---|
1044 |
|
---|
1045 | element.attr({
|
---|
1046 | 'role': role,
|
---|
1047 | 'tabIndex': '-1'
|
---|
1048 | });
|
---|
1049 |
|
---|
1050 | if (dialogContent.length === 0) {
|
---|
1051 | dialogContent = element;
|
---|
1052 | // If the dialog element already had an ID, don't clobber it.
|
---|
1053 | if (existingDialogId) {
|
---|
1054 | dialogContentId = existingDialogId;
|
---|
1055 | }
|
---|
1056 | }
|
---|
1057 |
|
---|
1058 | dialogContent.attr('id', dialogContentId);
|
---|
1059 | element.attr('aria-describedby', dialogContentId);
|
---|
1060 |
|
---|
1061 | if (options.ariaLabel) {
|
---|
1062 | $mdAria.expect(element, 'aria-label', options.ariaLabel);
|
---|
1063 | }
|
---|
1064 | else {
|
---|
1065 | $mdAria.expectAsync(element, 'aria-label', function() {
|
---|
1066 | // If dialog title is specified, set aria-label with it
|
---|
1067 | // See https://github.com/angular/material/issues/10582
|
---|
1068 | if (options.title) {
|
---|
1069 | return options.title;
|
---|
1070 | } else {
|
---|
1071 | var words = dialogContent.text().split(/\s+/);
|
---|
1072 | if (words.length > 3) words = words.slice(0, 3).concat('...');
|
---|
1073 | return words.join(' ');
|
---|
1074 | }
|
---|
1075 | });
|
---|
1076 | }
|
---|
1077 |
|
---|
1078 | // Set up elements before and after the dialog content to capture focus and
|
---|
1079 | // redirect back into the dialog.
|
---|
1080 | topFocusTrap = document.createElement('div');
|
---|
1081 | topFocusTrap.classList.add('md-dialog-focus-trap');
|
---|
1082 | topFocusTrap.tabIndex = 0;
|
---|
1083 |
|
---|
1084 | bottomFocusTrap = topFocusTrap.cloneNode(false);
|
---|
1085 |
|
---|
1086 | /**
|
---|
1087 | * When focus is about to move out of the end of the dialog, we intercept it and redirect it
|
---|
1088 | * back to the md-dialog element.
|
---|
1089 | * When focus is about to move out of the start of the dialog, we intercept it and redirect it
|
---|
1090 | * back to the last focusable element in the md-dialog.
|
---|
1091 | * @param {FocusEvent} event
|
---|
1092 | */
|
---|
1093 | var focusHandler = function(event) {
|
---|
1094 | if (event.target && event.target.nextSibling &&
|
---|
1095 | event.target.nextSibling.nodeName === 'MD-DIALOG') {
|
---|
1096 | var lastFocusableElement = $mdUtil.getLastTabbableElement(element[0]);
|
---|
1097 | if (angular.isElement(lastFocusableElement)) {
|
---|
1098 | lastFocusableElement.focus();
|
---|
1099 | }
|
---|
1100 | } else {
|
---|
1101 | element.focus();
|
---|
1102 | }
|
---|
1103 | };
|
---|
1104 |
|
---|
1105 | topFocusTrap.addEventListener('focus', focusHandler);
|
---|
1106 | bottomFocusTrap.addEventListener('focus', focusHandler);
|
---|
1107 |
|
---|
1108 | removeFocusTrap = function () {
|
---|
1109 | topFocusTrap.removeEventListener('focus', focusHandler);
|
---|
1110 | bottomFocusTrap.removeEventListener('focus', focusHandler);
|
---|
1111 |
|
---|
1112 | if (topFocusTrap && topFocusTrap.parentNode) {
|
---|
1113 | topFocusTrap.parentNode.removeChild(topFocusTrap);
|
---|
1114 | }
|
---|
1115 |
|
---|
1116 | if (bottomFocusTrap && bottomFocusTrap.parentNode) {
|
---|
1117 | bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
|
---|
1118 | }
|
---|
1119 | };
|
---|
1120 |
|
---|
1121 | // The top focus trap inserted immediately before the md-dialog element (as a sibling).
|
---|
1122 | // The bottom focus trap is inserted immediately after the md-dialog element (as a sibling).
|
---|
1123 | element[0].parentNode.insertBefore(topFocusTrap, element[0]);
|
---|
1124 | element.after(bottomFocusTrap);
|
---|
1125 | }
|
---|
1126 |
|
---|
1127 | /**
|
---|
1128 | * Prevents screen reader interaction behind modal window on swipe interfaces.
|
---|
1129 | */
|
---|
1130 | function lockScreenReader(element, options) {
|
---|
1131 | var isHidden = true;
|
---|
1132 |
|
---|
1133 | // get raw DOM node
|
---|
1134 | walkDOM(element[0]);
|
---|
1135 |
|
---|
1136 | options.unlockScreenReader = function () {
|
---|
1137 | isHidden = false;
|
---|
1138 | walkDOM(element[0]);
|
---|
1139 |
|
---|
1140 | options.unlockScreenReader = null;
|
---|
1141 | };
|
---|
1142 |
|
---|
1143 | /**
|
---|
1144 | * Get all of an element's parent elements up the DOM tree.
|
---|
1145 | * @param {Node & ParentNode} element the element to start from
|
---|
1146 | * @return {Element[]} The parent elements
|
---|
1147 | */
|
---|
1148 | function getParents(element) {
|
---|
1149 | var parents = [];
|
---|
1150 | while (element.parentNode) {
|
---|
1151 | if (element === document.body) {
|
---|
1152 | return parents;
|
---|
1153 | }
|
---|
1154 | var children = element.parentNode.children;
|
---|
1155 | for (var i = 0; i < children.length; i++) {
|
---|
1156 | // skip over child if it is an ascendant of the dialog
|
---|
1157 | // a script or style tag, or a live region.
|
---|
1158 | if (element !== children[i] &&
|
---|
1159 | !isNodeOneOf(children[i], ['SCRIPT', 'STYLE']) &&
|
---|
1160 | !children[i].hasAttribute('aria-live')) {
|
---|
1161 | parents.push(children[i]);
|
---|
1162 | }
|
---|
1163 | }
|
---|
1164 | element = element.parentNode;
|
---|
1165 | }
|
---|
1166 | return parents;
|
---|
1167 | }
|
---|
1168 |
|
---|
1169 | /**
|
---|
1170 | * Walk DOM to apply or remove aria-hidden on sibling nodes and parent sibling nodes.
|
---|
1171 | * @param {Element} element the element to start from when walking up the DOM
|
---|
1172 | * @returns {void}
|
---|
1173 | */
|
---|
1174 | function walkDOM(element) {
|
---|
1175 | var elements = getParents(element);
|
---|
1176 | for (var i = 0; i < elements.length; i++) {
|
---|
1177 | elements[i].setAttribute('aria-hidden', isHidden);
|
---|
1178 | }
|
---|
1179 | }
|
---|
1180 | }
|
---|
1181 |
|
---|
1182 | /**
|
---|
1183 | * Ensure the dialog container fill-stretches to the viewport.
|
---|
1184 | * @param {JQLite} container dialog container
|
---|
1185 | * @param {Object} options
|
---|
1186 | * @returns {function(): void} function that reverts the modified styles
|
---|
1187 | */
|
---|
1188 | function stretchDialogContainerToViewport(container, options) {
|
---|
1189 | var isFixed = $window.getComputedStyle($document[0].body).position === 'fixed';
|
---|
1190 | var backdrop = options.backdrop ? $window.getComputedStyle(options.backdrop[0]) : null;
|
---|
1191 | var height = backdrop ?
|
---|
1192 | Math.min($document[0].body.clientHeight, Math.ceil(Math.abs(parseInt(backdrop.height, 10))))
|
---|
1193 | : 0;
|
---|
1194 |
|
---|
1195 | var previousStyles = {
|
---|
1196 | top: container.css('top'),
|
---|
1197 | height: container.css('height')
|
---|
1198 | };
|
---|
1199 |
|
---|
1200 | // If the body is fixed, determine the distance to the viewport in relative from the parent.
|
---|
1201 | var parentTop = Math.abs(options.parent[0].getBoundingClientRect().top);
|
---|
1202 |
|
---|
1203 | container.css({
|
---|
1204 | top: (isFixed ? parentTop : 0) + 'px',
|
---|
1205 | height: height ? height + 'px' : '100%'
|
---|
1206 | });
|
---|
1207 |
|
---|
1208 | return function() {
|
---|
1209 | // Reverts the modified styles back to the previous values.
|
---|
1210 | // This is needed for contentElements, which should have the same styles after close
|
---|
1211 | // as before.
|
---|
1212 | container.css(previousStyles);
|
---|
1213 | };
|
---|
1214 | }
|
---|
1215 |
|
---|
1216 | /**
|
---|
1217 | * Dialog open and pop-in animation.
|
---|
1218 | * @param {JQLite} container dialog container
|
---|
1219 | * @param {Object} options
|
---|
1220 | * @returns {*}
|
---|
1221 | */
|
---|
1222 | function dialogPopIn(container, options) {
|
---|
1223 | // Add the `md-dialog-container` to the DOM
|
---|
1224 | options.parent.append(container);
|
---|
1225 | options.reverseContainerStretch = stretchDialogContainerToViewport(container, options);
|
---|
1226 |
|
---|
1227 | var dialogEl = container.find('md-dialog');
|
---|
1228 | var animator = $mdUtil.dom.animator;
|
---|
1229 | var buildTranslateToOrigin = animator.calculateZoomToOrigin;
|
---|
1230 | var translateOptions = {transitionInClass: 'md-transition-in', transitionOutClass: 'md-transition-out'};
|
---|
1231 | var from = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.openFrom || options.origin));
|
---|
1232 | var to = animator.toTransformCss(""); // defaults to center display (or parent or $rootElement)
|
---|
1233 |
|
---|
1234 | dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen);
|
---|
1235 |
|
---|
1236 | return animator
|
---|
1237 | .translate3d(dialogEl, from, to, translateOptions)
|
---|
1238 | .then(function(animateReversal) {
|
---|
1239 |
|
---|
1240 | // Build a reversal translate function synced to this translation...
|
---|
1241 | options.reverseAnimate = function() {
|
---|
1242 | delete options.reverseAnimate;
|
---|
1243 |
|
---|
1244 | if (options.closeTo) {
|
---|
1245 | // Using the opposite classes to create a close animation to the closeTo element
|
---|
1246 | translateOptions = {transitionInClass: 'md-transition-out', transitionOutClass: 'md-transition-in'};
|
---|
1247 | from = to;
|
---|
1248 | to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo));
|
---|
1249 |
|
---|
1250 | return animator
|
---|
1251 | .translate3d(dialogEl, from, to,translateOptions);
|
---|
1252 | }
|
---|
1253 |
|
---|
1254 | return animateReversal(
|
---|
1255 | to = animator.toTransformCss(
|
---|
1256 | // in case the origin element has moved or is hidden,
|
---|
1257 | // let's recalculate the translateCSS
|
---|
1258 | buildTranslateToOrigin(dialogEl, options.origin)
|
---|
1259 | )
|
---|
1260 | );
|
---|
1261 | };
|
---|
1262 |
|
---|
1263 | // Function to revert the generated animation styles on the dialog element.
|
---|
1264 | // Useful when using a contentElement instead of a template.
|
---|
1265 | options.clearAnimate = function() {
|
---|
1266 | delete options.clearAnimate;
|
---|
1267 |
|
---|
1268 | // Remove the transition classes, added from $animateCSS, since those can't be removed
|
---|
1269 | // by reversely running the animator.
|
---|
1270 | dialogEl.removeClass([
|
---|
1271 | translateOptions.transitionOutClass,
|
---|
1272 | translateOptions.transitionInClass
|
---|
1273 | ].join(' '));
|
---|
1274 |
|
---|
1275 | // Run the animation reversely to remove the previous added animation styles.
|
---|
1276 | return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {});
|
---|
1277 | };
|
---|
1278 |
|
---|
1279 | return true;
|
---|
1280 | });
|
---|
1281 | }
|
---|
1282 |
|
---|
1283 | /**
|
---|
1284 | * Dialog close and pop-out animation.
|
---|
1285 | * @param {JQLite} container dialog container
|
---|
1286 | * @param {Object} options
|
---|
1287 | * @returns {*}
|
---|
1288 | */
|
---|
1289 | function dialogPopOut(container, options) {
|
---|
1290 | return options.reverseAnimate().then(function() {
|
---|
1291 | if (options.contentElement) {
|
---|
1292 | // When we use a contentElement, we want the element to be the same as before.
|
---|
1293 | // That means, that we have to clear all the animation properties, like transform.
|
---|
1294 | options.clearAnimate();
|
---|
1295 | }
|
---|
1296 | });
|
---|
1297 | }
|
---|
1298 |
|
---|
1299 | /**
|
---|
1300 | * Utility function to filter out raw DOM nodes.
|
---|
1301 | * @param {Node} elem
|
---|
1302 | * @param {string[]} nodeTypeArray
|
---|
1303 | * @returns {boolean}
|
---|
1304 | */
|
---|
1305 | function isNodeOneOf(elem, nodeTypeArray) {
|
---|
1306 | return nodeTypeArray.indexOf(elem.nodeName) !== -1;
|
---|
1307 | }
|
---|
1308 | }
|
---|
1309 | }
|
---|
1310 |
|
---|
1311 | ngmaterial.components.dialog = angular.module("material.components.dialog"); |
---|