source: trip-planner-front/node_modules/angular-material/modules/closure/dialog/dialog.js@ 6a3a178

Last change on this file since 6a3a178 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 47.0 KB
Line 
1/*!
2 * AngularJS Material Design
3 * https://github.com/angular/material
4 * @license MIT
5 * v1.2.3
6 */
7goog.provide('ngmaterial.components.dialog');
8goog.require('ngmaterial.components.backdrop');
9goog.require('ngmaterial.core');
10/**
11 * @ngdoc module
12 * @name material.components.dialog
13 */
14MdDialogDirective['$inject'] = ["$$rAF", "$mdTheming", "$mdDialog"];
15MdDialogProvider['$inject'] = ["$$interimElementProvider"];
16angular
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 */
61function 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
555function 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
1311ngmaterial.components.dialog = angular.module("material.components.dialog");
Note: See TracBrowser for help on using the repository browser.