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