2  * Angular Material Design
 
   3  * https://github.com/angular/material
 
   7 goog.provide('ngmaterial.components.dialog');
 
   8 goog.require('ngmaterial.components.backdrop');
 
   9 goog.require('ngmaterial.core');
 
  12  * @name material.components.dialog
 
  14 MdDialogDirective['$inject'] = ["$$rAF", "$mdTheming", "$mdDialog"];
 
  15 MdDialogProvider['$inject'] = ["$$interimElementProvider"];
 
  17   .module('material.components.dialog', [
 
  19     'material.components.backdrop'
 
  21   .directive('mdDialog', MdDialogDirective)
 
  22   .provider('$mdDialog', MdDialogProvider);
 
  27  * @module material.components.dialog
 
  32  * `<md-dialog>` - The dialog's template must be inside this element.
 
  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.
 
  38  * - `.md-dialog-content` - class that sets the padding on the content as the spec file
 
  41  * - If you specify an `id` for the `<md-dialog>`, the `<md-dialog-content>` will have the same `id`
 
  42  * prefixed with `dialogContent_`.
 
  47  * <md-dialog aria-label="List dialog">
 
  50  *       <md-list-item ng-repeat="item in items">
 
  51  *         <p>Number {{item}}</p>
 
  54  *   </md-dialog-content>
 
  56  *     <md-button ng-click="closeDialog()" class="md-primary">Close Dialog</md-button>
 
  57  *   </md-dialog-actions>
 
  61 function MdDialogDirective($$rAF, $mdTheming, $mdDialog) {
 
  64     link: function(scope, element) {
 
  65       element.addClass('_md');     // private md component indicator for styling
 
  70         var content = element[0].querySelector('md-dialog-content');
 
  73           images = content.getElementsByTagName('img');
 
  75           //-- delayed image loading may impact scroll height, check after images are loaded
 
  76           angular.element(images).on('load', addOverflowClass);
 
  79         scope.$on('$destroy', function() {
 
  80           $mdDialog.destroy(element);
 
  86         function addOverflowClass() {
 
  87           element.toggleClass('md-content-overflow', content.scrollHeight > content.clientHeight);
 
  99  * @module material.components.dialog
 
 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.
 
 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.
 
 116  * - Complex dialogs can be sized with `flex="percentage"`, i.e. `flex="66"`.
 
 117  * - Default max-width is 80% of the `rootElement` or `parent`.
 
 120  * - `.md-dialog-content` - class that sets the padding on the content as the spec file
 
 124  * <div  ng-app="demoApp" ng-controller="EmployeeController">
 
 126  *     <md-button ng-click="showAlert()" class="md-raised md-warn">
 
 131  *     <md-button ng-click="showDialog($event)" class="md-raised">
 
 136  *     <md-button ng-click="closeAlert()" ng-disabled="!hasAlert()" class="md-raised">
 
 141  *     <md-button ng-click="showGreeting($event)" class="md-raised md-primary" >
 
 148  * ### JavaScript: object syntax
 
 150  * (function(angular, undefined){
 
 154  *    .module('demoApp', ['ngMaterial'])
 
 155  *    .controller('AppCtrl', AppController);
 
 157  *   function AppController($scope, $mdDialog) {
 
 159  *     $scope.showAlert = showAlert;
 
 160  *     $scope.showDialog = showDialog;
 
 161  *     $scope.items = [1, 2, 3];
 
 164  *     function showAlert() {
 
 165  *       alert = $mdDialog.alert({
 
 166  *         title: 'Attention',
 
 167  *         textContent: 'This is an example of how easy dialogs can be!',
 
 173  *         .finally(function() {
 
 178  *     function showDialog($event) {
 
 179  *        var parentEl = angular.element(document.body);
 
 182  *          targetEvent: $event,
 
 184  *            '<md-dialog aria-label="List dialog">' +
 
 185  *            '  <md-dialog-content>'+
 
 187  *            '      <md-list-item ng-repeat="item in items">'+
 
 188  *            '       <p>Number {{item}}</p>' +
 
 191  *            '  </md-dialog-content>' +
 
 192  *            '  <md-dialog-actions>' +
 
 193  *            '    <md-button ng-click="closeDialog()" class="md-primary">' +
 
 196  *            '  </md-dialog-actions>' +
 
 199  *            items: $scope.items
 
 201  *          controller: DialogController
 
 203  *       function DialogController($scope, $mdDialog, items) {
 
 204  *         $scope.items = items;
 
 205  *         $scope.closeDialog = function() {
 
 214  * ### Multiple Dialogs
 
 215  * Using the `multiple` option for the `$mdDialog` service allows developers to show multiple dialogs
 
 219  *   // From plain options
 
 224  *   // From a dialog preset
 
 233  * ### Pre-Rendered Dialogs
 
 234  * By using the `contentElement` option, it is possible to use an already existing element in the DOM.
 
 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`)
 
 240  *   $scope.showPrerenderedDialog = function() {
 
 242  *       contentElement: '#myStaticDialog',
 
 243  *       parent: angular.element(document.body)
 
 248  * When using a string as value, `$mdDialog` will automatically query the DOM for the specified CSS selector.
 
 251  *   <div style="visibility: hidden">
 
 252  *     <div class="md-dialog-container" id="myStaticDialog">
 
 254  *         This is a pre-rendered dialog.
 
 260  * **Notice**: It is important, to use the `.md-dialog-container` as the content element, otherwise the dialog
 
 263  * It also possible to use a DOM element for the `contentElement` option.
 
 264  * - `contentElement: document.querySelector('#myStaticDialog')`
 
 265  * - `contentElement: angular.element(TEMPLATE)`
 
 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.
 
 271  * Developers are also able to create their own preset, which can be easily used without repeating
 
 272  * their options each time.
 
 275  *   $mdDialogProvider.addPreset('testPreset', {
 
 276  *     options: function() {
 
 280  *             'This is a custom preset' +
 
 282  *         controllerAs: 'dialog',
 
 283  *         bindToController: true,
 
 284  *         clickOutsideToClose: true,
 
 285  *         escapeToClose: true
 
 291  * After you created your preset at config phase, you can easily access it.
 
 295  *     $mdDialog.testPreset()
 
 299  * ### JavaScript: promise API syntax, custom dialog template
 
 301  * (function(angular, undefined){
 
 305  *     .module('demoApp', ['ngMaterial'])
 
 306  *     .controller('EmployeeController', EmployeeEditor)
 
 307  *     .controller('GreetingController', GreetingController);
 
 309  *   // Fictitious Employee Editor to show how to use simple and complex dialogs.
 
 311  *   function EmployeeEditor($scope, $mdDialog) {
 
 314  *     $scope.showAlert = showAlert;
 
 315  *     $scope.closeAlert = closeAlert;
 
 316  *     $scope.showGreeting = showCustomGreeting;
 
 318  *     $scope.hasAlert = function() { return !!alert };
 
 319  *     $scope.userName = $scope.userName || 'Bobby';
 
 321  *     // Dialog #1 - Show simple alert dialog and cache
 
 322  *     // reference to dialog instance
 
 324  *     function showAlert() {
 
 325  *       alert = $mdDialog.alert()
 
 326  *         .title('Attention, ' + $scope.userName)
 
 327  *         .textContent('This is an example of how easy dialogs can be!')
 
 332  *           .finally(function() {
 
 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.
 
 341  *     function closeAlert() {
 
 342  *       $mdDialog.hide( alert, "finished" );
 
 346  *     // Dialog #2 - Demonstrate more complex dialogs construction and popup.
 
 348  *     function showCustomGreeting($event) {
 
 350  *           targetEvent: $event,
 
 354  *             '  <md-dialog-content>Hello {{ employee }}!</md-dialog-content>' +
 
 356  *             '  <md-dialog-actions>' +
 
 357  *             '    <md-button ng-click="closeDialog()" class="md-primary">' +
 
 358  *             '      Close Greeting' +
 
 360  *             '  </md-dialog-actions>' +
 
 362  *           controller: 'GreetingController',
 
 363  *           onComplete: afterShowAnimation,
 
 364  *           locals: { employee: $scope.userName }
 
 367  *         // When the 'enter' animation finishes...
 
 369  *         function afterShowAnimation(scope, element, options) {
 
 370  *            // post-show code here: DOM element focus, etc.
 
 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>
 
 378  *     function showCustomGreeting() {
 
 381  *           clickOutsideToClose: true,
 
 383  *           scope: $scope,        // use parent scope in template
 
 384  *           preserveScope: true,  // do not forget this if use parent scope
 
 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
 
 390  *           template: '<md-dialog>' +
 
 391  *                     '  <md-dialog-content>' +
 
 392  *                     '     Hi There {{vm.employee}}' +
 
 393  *                     '  </md-dialog-content>' +
 
 396  *           controller: function DialogController($scope, $mdDialog) {
 
 397  *             $scope.closeDialog = function() {
 
 406  *   // Greeting controller used with the more complex 'showCustomGreeting()' custom dialog
 
 408  *   function GreetingController($scope, $mdDialog, employee) {
 
 409  *     // Assigned from construction <code>locals</code> options...
 
 410  *     $scope.employee = employee;
 
 412  *     $scope.closeDialog = function() {
 
 413  *       // Easily hides most recent dialog shown...
 
 414  *       // no specific instance reference is needed.
 
 425  * @name $mdDialog#alert
 
 428  * Builds a preconfigured dialog with the specified message.
 
 430  * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 
 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
 
 446  * @name $mdDialog#confirm
 
 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.
 
 452  * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 
 454  * Additionally, it supports the following methods:
 
 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
 
 471  * @name $mdDialog#prompt
 
 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.
 
 477  * @returns {obj} an `$mdDialogPreset` with the chainable configuration methods:
 
 479  * Additionally, it supports the following methods:
 
 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
 
 498  * @name $mdDialog#show
 
 501  * Show a dialog with the specified options.
 
 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
 
 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
 
 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
 
 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
 
 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.
 
 533  *   - `hasBackdrop` - `{boolean=}`: Whether there should be an opaque backdrop behind the dialog.
 
 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.
 
 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.
 
 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
 
 556  *   - `onComplete` - `function(scope, element)`: Callback function used to announce when the show() action is
 
 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()`.
 
 569  * @name $mdDialog#hide
 
 572  * Hide an existing dialog and resolve the promise returned from `$mdDialog.show()`.
 
 574  * @param {*=} response An argument for the resolved promise.
 
 576  * @returns {promise} A promise that is resolved when the dialog has been closed.
 
 581  * @name $mdDialog#cancel
 
 584  * Hide an existing dialog and reject the promise returned from `$mdDialog.show()`.
 
 586  * @param {*=} response An argument for the rejected promise.
 
 588  * @returns {promise} A promise that is resolved when the dialog has been closed.
 
 591 function 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;
 
 597   return $$interimElementProvider('$mdDialog')
 
 599       methods: ['disableParentScroll', 'hasBackdrop', 'clickOutsideToClose', 'escapeToClose',
 
 600           'targetEvent', 'closeTo', 'openFrom', 'parent', 'fullscreen', 'multiple'],
 
 601       options: dialogDefaultOptions
 
 603     .addPreset('alert', {
 
 604       methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'theme',
 
 606       options: advancedDialogOptions
 
 608     .addPreset('confirm', {
 
 609       methods: ['title', 'htmlContent', 'textContent', 'content', 'ariaLabel', 'ok', 'cancel',
 
 611       options: advancedDialogOptions
 
 613     .addPreset('prompt', {
 
 614       methods: ['title', 'htmlContent', 'textContent', 'initialValue', 'content', 'placeholder', 'ariaLabel',
 
 615           'ok', 'cancel', 'theme', 'css'],
 
 616       options: advancedDialogOptions
 
 620   function advancedDialogOptions($mdDialog, $mdConstant) {
 
 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>',
 
 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 }}',
 
 641         '    <md-button ng-click="dialog.hide()" class="md-primary md-confirm-button" md-autofocus="dialog.$type===\'alert\'">',
 
 644         '  </md-dialog-actions>',
 
 646       ].join('').replace(/\s\s+/g, ''),
 
 647       controller: function mdDialogCtrl() {
 
 648         var isPrompt = this.$type == 'prompt';
 
 650         if (isPrompt && this.initialValue) {
 
 651           this.result = this.initialValue;
 
 654         this.hide = function() {
 
 655           $mdDialog.hide(isPrompt ? this.result : true);
 
 657         this.abort = function() {
 
 660         this.keypress = function($event) {
 
 661           if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) {
 
 662             $mdDialog.hide(this.result);
 
 666       controllerAs: 'dialog',
 
 667       bindToController: true,
 
 672   function dialogDefaultOptions($mdDialog, $mdAria, $mdUtil, $mdConstant, $animate, $document, $window, $rootElement,
 
 673                                 $log, $injector, $mdTheming, $interpolate, $mdInteraction) {
 
 678       onCompiling: beforeCompile,
 
 680       onShowing: beforeShow,
 
 682       clickOutsideToClose: false,
 
 688       disableParentScroll: true,
 
 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>';
 
 702          * The specified template should contain a <md-dialog> wrapper element....
 
 704         function validatedTemplate(template) {
 
 705           if (options.autoWrap && !/<\/md-dialog>/g.test(template)) {
 
 706             return '<md-dialog>' + (template || '') + '</md-dialog>';
 
 708             return template || '';
 
 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();
 
 720       detectTheming(options);
 
 723     function beforeShow(scope, element, options, controller) {
 
 726         var mdHtmlContent = controller.htmlContent || options.htmlContent || '';
 
 727         var mdTextContent = controller.textContent || options.textContent ||
 
 728             controller.content || options.content || '';
 
 730         if (mdHtmlContent && !$injector.has('$sanitize')) {
 
 731           throw Error('The ngSanitize module must be loaded in order to use htmlContent.');
 
 734         if (mdHtmlContent && mdTextContent) {
 
 735           throw Error('md-dialog cannot have both `htmlContent` and `textContent`');
 
 738         // Only assign the content if nothing throws, otherwise it'll still be compiled.
 
 739         controller.mdHtmlContent = mdHtmlContent;
 
 740         controller.mdTextContent = mdTextContent;
 
 744     /** Show method for dialogs */
 
 745     function onShow(scope, element, options, controller) {
 
 746       angular.element($document[0].body).addClass('md-dialog-is-showing');
 
 748       var dialogElement = element.find('md-dialog');
 
 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] );
 
 757       captureParentAndFromToElements(options);
 
 758       configureAria(dialogElement, options);
 
 759       showBackdrop(scope, element, options);
 
 760       activateListeners(element, options);
 
 762       return dialogPopIn(element, options)
 
 764           lockScreenReader(element, options);
 
 765           warnDeprecatedActions();
 
 770        * Check to see if they used the deprecated .md-actions class and log a warning
 
 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>.');
 
 779        * For alerts, focus on content... otherwise focus on
 
 780        * the close button (or equivalent)
 
 782       function focusOnOpen() {
 
 783         if (options.focusOnOpen) {
 
 784           var target = $mdUtil.findFocusTarget(element) || findCloseButton() || dialogElement;
 
 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.
 
 792          * If we find no actions at all, log a warning to the console.
 
 794         function findCloseButton() {
 
 795           return element[0].querySelector('.dialog-close, md-dialog-actions button:last-child');
 
 801      * Remove function for all dialogs
 
 803     function onRemove(scope, element, options) {
 
 804       options.deactivateListeners();
 
 805       options.unlockScreenReader();
 
 806       options.hideBackdrop(options.$destroy);
 
 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);
 
 813       if (bottomFocusTrap && bottomFocusTrap.parentNode) {
 
 814         bottomFocusTrap.parentNode.removeChild(bottomFocusTrap);
 
 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 );
 
 822        * For normal closes, animate the removal.
 
 823        * For forced closes (like $destroy events), skip the animations
 
 825       function animateRemoval() {
 
 826         return dialogPopOut(element, options);
 
 832       function detachAndClean() {
 
 833         angular.element($document[0].body).removeClass('md-dialog-is-showing');
 
 835         // Reverse the container stretch if using a content element.
 
 836         if (options.contentElement) {
 
 837           options.reverseContainerStretch();
 
 840         // Exposed cleanup function from the $mdCompiler.
 
 841         options.cleanupElement();
 
 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();
 
 850     function detectTheming(options) {
 
 851       // Once the user specifies a targetEvent, we will automatically try to find the correct
 
 854       if (options.targetEvent && options.targetEvent.target) {
 
 855         targetEl = angular.element(options.targetEvent.target);
 
 858       var themeCtrl = targetEl && targetEl.controller('mdTheme');
 
 864       options.themeWatch = themeCtrl.$shouldWatch;
 
 866       var theme = options.theme || themeCtrl.$mdTheme;
 
 869         options.scope.theme = theme;
 
 872       var unwatch = themeCtrl.registerChanges(function (newTheme) {
 
 873         options.scope.theme = newTheme;
 
 875         if (!options.themeWatch) {
 
 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
 
 886     function captureParentAndFromToElements(options) {
 
 887           options.origin = angular.extend({
 
 891           }, options.origin || {});
 
 893           options.parent   = getDomElement(options.parent, $rootElement);
 
 894           options.closeTo  = getBoundingClientRect(getDomElement(options.closeTo));
 
 895           options.openFrom = getBoundingClientRect(getDomElement(options.openFrom));
 
 897           if ( options.targetEvent ) {
 
 898             options.origin = getBoundingClientRect(options.targetEvent.target, options.origin);
 
 899             options.originInteraction = $mdInteraction.getLastInteractionType();
 
 904            * Identify the bounding RECT for the target element
 
 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.
 
 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);
 
 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),
 
 927            * If the specifier is a simple string selector, then query for
 
 930           function getDomElement(element, defaultElement) {
 
 931             if (angular.isString(element)) {
 
 932               element = $document[0].querySelector(element);
 
 935             // If we have a reference to a raw dom element, always wrap it in jqLite
 
 936             return angular.element(element || defaultElement);
 
 942      * Listen for escape keys and outside clicks to auto close
 
 944     function activateListeners(element, options) {
 
 945       var window = angular.element($window);
 
 946       var onWindowResize = $mdUtil.debounce(function() {
 
 947         stretchDialogContainerToViewport(element, options);
 
 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);
 
 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();
 
 969         // Add keydown listeners
 
 970         element.on('keydown', keyHandlerFn);
 
 971         parentTarget.on('keydown', keyHandlerFn);
 
 973         // Queue remove listeners function
 
 974         removeListeners.push(function() {
 
 976           element.off('keydown', keyHandlerFn);
 
 977           parentTarget.off('keydown', keyHandlerFn);
 
 982       // Register listener to update dialog on window resize
 
 983       window.on('resize', onWindowResize);
 
 985       removeListeners.push(function() {
 
 986         window.off('resize', onWindowResize);
 
 989       if (options.clickOutsideToClose) {
 
 990         var target = element;
 
 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;
 
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();
 
1014         target.on('mousedown', mousedownHandler);
 
1015         target.on('mouseup', mouseupHandler);
 
1017         // Queue remove listeners function
 
1018         removeListeners.push(function() {
 
1019           target.off('mousedown', mousedownHandler);
 
1020           target.off('mouseup', mouseupHandler);
 
1024       // Attach specific `remove` listener handler
 
1025       options.deactivateListeners = function() {
 
1026         removeListeners.forEach(function(removeFn) {
 
1029         options.deactivateListeners = null;
 
1034      * Show modal backdrop element...
 
1036     function showBackdrop(scope, element, options) {
 
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);
 
1044       if (options.hasBackdrop) {
 
1045         options.backdrop = $mdUtil.createBackdrop(scope, "md-dialog-backdrop md-opaque");
 
1046         $animate.enter(options.backdrop, options.parent);
 
1050        * Hide modal backdrop element...
 
1052       options.hideBackdrop = function hideBackdrop($destroy) {
 
1053         if (options.backdrop) {
 
1054           if ( !!$destroy ) options.backdrop.remove();
 
1055           else              $animate.leave(options.backdrop);
 
1059         if (options.disableParentScroll) {
 
1060           options.restoreScroll && options.restoreScroll();
 
1061           delete options.restoreScroll;
 
1064         options.hideBackdrop = null;
 
1069      * Inject ARIA-specific attributes appropriate for Dialogs
 
1071     function configureAria(element, options) {
 
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());
 
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;
 
1091       dialogContent.attr('id', dialogContentId);
 
1092       element.attr('aria-describedby', dialogContentId);
 
1094       if (options.ariaLabel) {
 
1095         $mdAria.expect(element, 'aria-label', options.ariaLabel);
 
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(' ');
 
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;
 
1111       bottomFocusTrap = topFocusTrap.cloneNode(false);
 
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() {
 
1118       topFocusTrap.addEventListener('focus', focusHandler);
 
1119       bottomFocusTrap.addEventListener('focus', focusHandler);
 
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);
 
1128      * Prevents screen reader interaction behind modal window
 
1129      * on swipe interfaces
 
1131     function lockScreenReader(element, options) {
 
1132       var isHidden = true;
 
1135       walkDOM(element[0]);
 
1137       options.unlockScreenReader = function() {
 
1139         walkDOM(element[0]);
 
1141         options.unlockScreenReader = null;
 
1145        * Walk DOM to apply or remove aria-hidden on sibling nodes
 
1146        * and parent sibling nodes
 
1149       function walkDOM(element) {
 
1150         while (element.parentNode) {
 
1151           if (element === document.body) {
 
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);
 
1163           walkDOM(element = element.parentNode);
 
1169      * Ensure the dialog container fill-stretches to the viewport
 
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;
 
1176       var previousStyles = {
 
1177         top: container.css('top'),
 
1178         height: container.css('height')
 
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);
 
1185         top: (isFixed ? parentTop : 0) + 'px',
 
1186         height: height ? height + 'px' : '100%'
 
1190         // Reverts the modified styles back to the previous values.
 
1191         // This is needed for contentElements, which should have the same styles after close
 
1193         container.css(previousStyles);
 
1198      *  Dialog open and pop-in animation
 
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);
 
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)
 
1212       dialogEl.toggleClass('md-dialog-fullscreen', !!options.fullscreen);
 
1215         .translate3d(dialogEl, from, to, translateOptions)
 
1216         .then(function(animateReversal) {
 
1218           // Build a reversal translate function synced to this translation...
 
1219           options.reverseAnimate = function() {
 
1220             delete options.reverseAnimate;
 
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'};
 
1226               to = animator.toTransformCss(buildTranslateToOrigin(dialogEl, options.closeTo));
 
1229                 .translate3d(dialogEl, from, to,translateOptions);
 
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)
 
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;
 
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
 
1254             // Run the animation reversely to remove the previous added animation styles.
 
1255             return animator.translate3d(dialogEl, to, animator.toTransformCss(''), {});
 
1263      * Dialog close and pop-out animation
 
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();
 
1276      * Utility function to filter out raw DOM nodes
 
1278     function isNodeOneOf(elem, nodeTypeArray) {
 
1279       if (nodeTypeArray.indexOf(elem.nodeName) !== -1) {
 
1287 ngmaterial.components.dialog = angular.module("material.components.dialog");