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