dialog.js 48 KB

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