modal.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934
  1. angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
  2. /**
  3. * A helper, internal data structure that stores all references attached to key
  4. */
  5. .factory('$$multiMap', function() {
  6. return {
  7. createNew: function() {
  8. var map = {};
  9. return {
  10. entries: function() {
  11. return Object.keys(map).map(function(key) {
  12. return {
  13. key: key,
  14. value: map[key]
  15. };
  16. });
  17. },
  18. get: function(key) {
  19. return map[key];
  20. },
  21. hasKey: function(key) {
  22. return !!map[key];
  23. },
  24. keys: function() {
  25. return Object.keys(map);
  26. },
  27. put: function(key, value) {
  28. if (!map[key]) {
  29. map[key] = [];
  30. }
  31. map[key].push(value);
  32. },
  33. remove: function(key, value) {
  34. var values = map[key];
  35. if (!values) {
  36. return;
  37. }
  38. var idx = values.indexOf(value);
  39. if (idx !== -1) {
  40. values.splice(idx, 1);
  41. }
  42. if (!values.length) {
  43. delete map[key];
  44. }
  45. }
  46. };
  47. }
  48. };
  49. })
  50. /**
  51. * A helper directive for the $modal service. It creates a backdrop element.
  52. */
  53. .directive('uibModalBackdrop', [
  54. '$animate', '$injector', '$uibModalStack',
  55. function($animate , $injector, $modalStack) {
  56. var $animateCss = null;
  57. if ($injector.has('$animateCss')) {
  58. $animateCss = $injector.get('$animateCss');
  59. }
  60. return {
  61. replace: true,
  62. templateUrl: 'template/modal/backdrop.html',
  63. compile: function(tElement, tAttrs) {
  64. tElement.addClass(tAttrs.backdropClass);
  65. return linkFn;
  66. }
  67. };
  68. function linkFn(scope, element, attrs) {
  69. // Temporary fix for prefixing
  70. element.addClass('modal-backdrop');
  71. if (attrs.modalInClass) {
  72. if ($animateCss) {
  73. $animateCss(element, {
  74. addClass: attrs.modalInClass
  75. }).start();
  76. } else {
  77. $animate.addClass(element, attrs.modalInClass);
  78. }
  79. scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
  80. var done = setIsAsync();
  81. if ($animateCss) {
  82. $animateCss(element, {
  83. removeClass: attrs.modalInClass
  84. }).start().then(done);
  85. } else {
  86. $animate.removeClass(element, attrs.modalInClass).then(done);
  87. }
  88. });
  89. }
  90. }
  91. }])
  92. .directive('uibModalWindow', [
  93. '$uibModalStack', '$q', '$animate', '$injector',
  94. function($modalStack , $q , $animate, $injector) {
  95. var $animateCss = null;
  96. if ($injector.has('$animateCss')) {
  97. $animateCss = $injector.get('$animateCss');
  98. }
  99. return {
  100. scope: {
  101. index: '@'
  102. },
  103. replace: true,
  104. transclude: true,
  105. templateUrl: function(tElement, tAttrs) {
  106. return tAttrs.templateUrl || 'template/modal/window.html';
  107. },
  108. link: function(scope, element, attrs) {
  109. element.addClass(attrs.windowClass || '');
  110. element.addClass(attrs.windowTopClass || '');
  111. scope.size = attrs.size;
  112. scope.close = function(evt) {
  113. var modal = $modalStack.getTop();
  114. if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
  115. evt.preventDefault();
  116. evt.stopPropagation();
  117. $modalStack.dismiss(modal.key, 'backdrop click');
  118. }
  119. };
  120. // moved from template to fix issue #2280
  121. element.on('click', scope.close);
  122. // This property is only added to the scope for the purpose of detecting when this directive is rendered.
  123. // We can detect that by using this property in the template associated with this directive and then use
  124. // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
  125. scope.$isRendered = true;
  126. // Deferred object that will be resolved when this modal is render.
  127. var modalRenderDeferObj = $q.defer();
  128. // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
  129. // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
  130. attrs.$observe('modalRender', function(value) {
  131. if (value == 'true') {
  132. modalRenderDeferObj.resolve();
  133. }
  134. });
  135. modalRenderDeferObj.promise.then(function() {
  136. var animationPromise = null;
  137. if (attrs.modalInClass) {
  138. if ($animateCss) {
  139. animationPromise = $animateCss(element, {
  140. addClass: attrs.modalInClass
  141. }).start();
  142. } else {
  143. animationPromise = $animate.addClass(element, attrs.modalInClass);
  144. }
  145. scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
  146. var done = setIsAsync();
  147. if ($animateCss) {
  148. $animateCss(element, {
  149. removeClass: attrs.modalInClass
  150. }).start().then(done);
  151. } else {
  152. $animate.removeClass(element, attrs.modalInClass).then(done);
  153. }
  154. });
  155. }
  156. $q.when(animationPromise).then(function() {
  157. var inputWithAutofocus = element[0].querySelector('[autofocus]');
  158. /**
  159. * Auto-focusing of a freshly-opened modal element causes any child elements
  160. * with the autofocus attribute to lose focus. This is an issue on touch
  161. * based devices which will show and then hide the onscreen keyboard.
  162. * Attempts to refocus the autofocus element via JavaScript will not reopen
  163. * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
  164. * the modal element if the modal does not contain an autofocus element.
  165. */
  166. if (inputWithAutofocus) {
  167. inputWithAutofocus.focus();
  168. } else {
  169. element[0].focus();
  170. }
  171. });
  172. // Notify {@link $modalStack} that modal is rendered.
  173. var modal = $modalStack.getTop();
  174. if (modal) {
  175. $modalStack.modalRendered(modal.key);
  176. }
  177. });
  178. }
  179. };
  180. }])
  181. .directive('uibModalAnimationClass', function() {
  182. return {
  183. compile: function(tElement, tAttrs) {
  184. if (tAttrs.modalAnimation) {
  185. tElement.addClass(tAttrs.uibModalAnimationClass);
  186. }
  187. }
  188. };
  189. })
  190. .directive('uibModalTransclude', function() {
  191. return {
  192. link: function($scope, $element, $attrs, controller, $transclude) {
  193. $transclude($scope.$parent, function(clone) {
  194. $element.empty();
  195. $element.append(clone);
  196. });
  197. }
  198. };
  199. })
  200. .factory('$uibModalStack', [
  201. '$animate', '$timeout', '$document', '$compile', '$rootScope',
  202. '$q',
  203. '$injector',
  204. '$$multiMap',
  205. '$$stackedMap',
  206. function($animate , $timeout , $document , $compile , $rootScope ,
  207. $q,
  208. $injector,
  209. $$multiMap,
  210. $$stackedMap) {
  211. var $animateCss = null;
  212. if ($injector.has('$animateCss')) {
  213. $animateCss = $injector.get('$animateCss');
  214. }
  215. var OPENED_MODAL_CLASS = 'modal-open';
  216. var backdropDomEl, backdropScope;
  217. var openedWindows = $$stackedMap.createNew();
  218. var openedClasses = $$multiMap.createNew();
  219. var $modalStack = {
  220. NOW_CLOSING_EVENT: 'modal.stack.now-closing'
  221. };
  222. //Modal focus behavior
  223. var focusableElementList;
  224. var focusIndex = 0;
  225. var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
  226. 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
  227. 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
  228. function backdropIndex() {
  229. var topBackdropIndex = -1;
  230. var opened = openedWindows.keys();
  231. for (var i = 0; i < opened.length; i++) {
  232. if (openedWindows.get(opened[i]).value.backdrop) {
  233. topBackdropIndex = i;
  234. }
  235. }
  236. return topBackdropIndex;
  237. }
  238. $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
  239. if (backdropScope) {
  240. backdropScope.index = newBackdropIndex;
  241. }
  242. });
  243. function removeModalWindow(modalInstance, elementToReceiveFocus) {
  244. var body = $document.find('body').eq(0);
  245. var modalWindow = openedWindows.get(modalInstance).value;
  246. //clean up the stack
  247. openedWindows.remove(modalInstance);
  248. removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
  249. var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
  250. openedClasses.remove(modalBodyClass, modalInstance);
  251. body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
  252. toggleTopWindowClass(true);
  253. });
  254. checkRemoveBackdrop();
  255. //move focus to specified element if available, or else to body
  256. if (elementToReceiveFocus && elementToReceiveFocus.focus) {
  257. elementToReceiveFocus.focus();
  258. } else {
  259. body.focus();
  260. }
  261. }
  262. // Add or remove "windowTopClass" from the top window in the stack
  263. function toggleTopWindowClass(toggleSwitch) {
  264. var modalWindow;
  265. if (openedWindows.length() > 0) {
  266. modalWindow = openedWindows.top().value;
  267. modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
  268. }
  269. }
  270. function checkRemoveBackdrop() {
  271. //remove backdrop if no longer needed
  272. if (backdropDomEl && backdropIndex() == -1) {
  273. var backdropScopeRef = backdropScope;
  274. removeAfterAnimate(backdropDomEl, backdropScope, function() {
  275. backdropScopeRef = null;
  276. });
  277. backdropDomEl = undefined;
  278. backdropScope = undefined;
  279. }
  280. }
  281. function removeAfterAnimate(domEl, scope, done) {
  282. var asyncDeferred;
  283. var asyncPromise = null;
  284. var setIsAsync = function() {
  285. if (!asyncDeferred) {
  286. asyncDeferred = $q.defer();
  287. asyncPromise = asyncDeferred.promise;
  288. }
  289. return function asyncDone() {
  290. asyncDeferred.resolve();
  291. };
  292. };
  293. scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
  294. // Note that it's intentional that asyncPromise might be null.
  295. // That's when setIsAsync has not been called during the
  296. // NOW_CLOSING_EVENT broadcast.
  297. return $q.when(asyncPromise).then(afterAnimating);
  298. function afterAnimating() {
  299. if (afterAnimating.done) {
  300. return;
  301. }
  302. afterAnimating.done = true;
  303. if ($animateCss) {
  304. $animateCss(domEl, {
  305. event: 'leave'
  306. }).start().then(function() {
  307. domEl.remove();
  308. });
  309. } else {
  310. $animate.leave(domEl);
  311. }
  312. scope.$destroy();
  313. if (done) {
  314. done();
  315. }
  316. }
  317. }
  318. $document.bind('keydown', function(evt) {
  319. if (evt.isDefaultPrevented()) {
  320. return evt;
  321. }
  322. var modal = openedWindows.top();
  323. if (modal && modal.value.keyboard) {
  324. switch (evt.which) {
  325. case 27: {
  326. evt.preventDefault();
  327. $rootScope.$apply(function() {
  328. $modalStack.dismiss(modal.key, 'escape key press');
  329. });
  330. break;
  331. }
  332. case 9: {
  333. $modalStack.loadFocusElementList(modal);
  334. var focusChanged = false;
  335. if (evt.shiftKey) {
  336. if ($modalStack.isFocusInFirstItem(evt)) {
  337. focusChanged = $modalStack.focusLastFocusableElement();
  338. }
  339. } else {
  340. if ($modalStack.isFocusInLastItem(evt)) {
  341. focusChanged = $modalStack.focusFirstFocusableElement();
  342. }
  343. }
  344. if (focusChanged) {
  345. evt.preventDefault();
  346. evt.stopPropagation();
  347. }
  348. break;
  349. }
  350. }
  351. }
  352. });
  353. $modalStack.open = function(modalInstance, modal) {
  354. var modalOpener = $document[0].activeElement,
  355. modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
  356. toggleTopWindowClass(false);
  357. openedWindows.add(modalInstance, {
  358. deferred: modal.deferred,
  359. renderDeferred: modal.renderDeferred,
  360. modalScope: modal.scope,
  361. backdrop: modal.backdrop,
  362. keyboard: modal.keyboard,
  363. openedClass: modal.openedClass,
  364. windowTopClass: modal.windowTopClass
  365. });
  366. openedClasses.put(modalBodyClass, modalInstance);
  367. var body = $document.find('body').eq(0),
  368. currBackdropIndex = backdropIndex();
  369. if (currBackdropIndex >= 0 && !backdropDomEl) {
  370. backdropScope = $rootScope.$new(true);
  371. backdropScope.index = currBackdropIndex;
  372. var angularBackgroundDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
  373. angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
  374. if (modal.animation) {
  375. angularBackgroundDomEl.attr('modal-animation', 'true');
  376. }
  377. backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
  378. body.append(backdropDomEl);
  379. }
  380. var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
  381. angularDomEl.attr({
  382. 'template-url': modal.windowTemplateUrl,
  383. 'window-class': modal.windowClass,
  384. 'window-top-class': modal.windowTopClass,
  385. 'size': modal.size,
  386. 'index': openedWindows.length() - 1,
  387. 'animate': 'animate'
  388. }).html(modal.content);
  389. if (modal.animation) {
  390. angularDomEl.attr('modal-animation', 'true');
  391. }
  392. var modalDomEl = $compile(angularDomEl)(modal.scope);
  393. openedWindows.top().value.modalDomEl = modalDomEl;
  394. openedWindows.top().value.modalOpener = modalOpener;
  395. body.append(modalDomEl);
  396. body.addClass(modalBodyClass);
  397. $modalStack.clearFocusListCache();
  398. };
  399. function broadcastClosing(modalWindow, resultOrReason, closing) {
  400. return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
  401. }
  402. $modalStack.close = function(modalInstance, result) {
  403. var modalWindow = openedWindows.get(modalInstance);
  404. if (modalWindow && broadcastClosing(modalWindow, result, true)) {
  405. modalWindow.value.modalScope.$$uibDestructionScheduled = true;
  406. modalWindow.value.deferred.resolve(result);
  407. removeModalWindow(modalInstance, modalWindow.value.modalOpener);
  408. return true;
  409. }
  410. return !modalWindow;
  411. };
  412. $modalStack.dismiss = function(modalInstance, reason) {
  413. var modalWindow = openedWindows.get(modalInstance);
  414. if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
  415. modalWindow.value.modalScope.$$uibDestructionScheduled = true;
  416. modalWindow.value.deferred.reject(reason);
  417. removeModalWindow(modalInstance, modalWindow.value.modalOpener);
  418. return true;
  419. }
  420. return !modalWindow;
  421. };
  422. $modalStack.dismissAll = function(reason) {
  423. var topModal = this.getTop();
  424. while (topModal && this.dismiss(topModal.key, reason)) {
  425. topModal = this.getTop();
  426. }
  427. };
  428. $modalStack.getTop = function() {
  429. return openedWindows.top();
  430. };
  431. $modalStack.modalRendered = function(modalInstance) {
  432. var modalWindow = openedWindows.get(modalInstance);
  433. if (modalWindow) {
  434. modalWindow.value.renderDeferred.resolve();
  435. }
  436. };
  437. $modalStack.focusFirstFocusableElement = function() {
  438. if (focusableElementList.length > 0) {
  439. focusableElementList[0].focus();
  440. return true;
  441. }
  442. return false;
  443. };
  444. $modalStack.focusLastFocusableElement = function() {
  445. if (focusableElementList.length > 0) {
  446. focusableElementList[focusableElementList.length - 1].focus();
  447. return true;
  448. }
  449. return false;
  450. };
  451. $modalStack.isFocusInFirstItem = function(evt) {
  452. if (focusableElementList.length > 0) {
  453. return (evt.target || evt.srcElement) == focusableElementList[0];
  454. }
  455. return false;
  456. };
  457. $modalStack.isFocusInLastItem = function(evt) {
  458. if (focusableElementList.length > 0) {
  459. return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1];
  460. }
  461. return false;
  462. };
  463. $modalStack.clearFocusListCache = function() {
  464. focusableElementList = [];
  465. focusIndex = 0;
  466. };
  467. $modalStack.loadFocusElementList = function(modalWindow) {
  468. if (focusableElementList === undefined || !focusableElementList.length) {
  469. if (modalWindow) {
  470. var modalDomE1 = modalWindow.value.modalDomEl;
  471. if (modalDomE1 && modalDomE1.length) {
  472. focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
  473. }
  474. }
  475. }
  476. };
  477. return $modalStack;
  478. }])
  479. .provider('$uibModal', function() {
  480. var $modalProvider = {
  481. options: {
  482. animation: true,
  483. backdrop: true, //can also be false or 'static'
  484. keyboard: true
  485. },
  486. $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', '$modalSuppressWarning', '$log',
  487. function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $modalSuppressWarning, $log) {
  488. var $modal = {};
  489. function getTemplatePromise(options) {
  490. return options.template ? $q.when(options.template) :
  491. $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl);
  492. }
  493. function getResolvePromises(resolves) {
  494. var promisesArr = [];
  495. angular.forEach(resolves, function(value) {
  496. if (angular.isFunction(value) || angular.isArray(value)) {
  497. promisesArr.push($q.when($injector.invoke(value)));
  498. } else if (angular.isString(value)) {
  499. promisesArr.push($q.when($injector.get(value)));
  500. } else {
  501. promisesArr.push($q.when(value));
  502. }
  503. });
  504. return promisesArr;
  505. }
  506. var promiseChain = null;
  507. $modal.getPromiseChain = function() {
  508. return promiseChain;
  509. };
  510. $modal.open = function(modalOptions) {
  511. var modalResultDeferred = $q.defer();
  512. var modalOpenedDeferred = $q.defer();
  513. var modalRenderDeferred = $q.defer();
  514. //prepare an instance of a modal to be injected into controllers and returned to a caller
  515. var modalInstance = {
  516. result: modalResultDeferred.promise,
  517. opened: modalOpenedDeferred.promise,
  518. rendered: modalRenderDeferred.promise,
  519. close: function (result) {
  520. return $modalStack.close(modalInstance, result);
  521. },
  522. dismiss: function (reason) {
  523. return $modalStack.dismiss(modalInstance, reason);
  524. }
  525. };
  526. //merge and clean up options
  527. modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
  528. modalOptions.resolve = modalOptions.resolve || {};
  529. //verify options
  530. if (!modalOptions.template && !modalOptions.templateUrl) {
  531. throw new Error('One of template or templateUrl options is required.');
  532. }
  533. var templateAndResolvePromise =
  534. $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
  535. function resolveWithTemplate() {
  536. return templateAndResolvePromise;
  537. }
  538. // Wait for the resolution of the existing promise chain.
  539. // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
  540. // Then add to $modalStack and resolve opened.
  541. // Finally clean up the chain variable if no subsequent modal has overwritten it.
  542. var samePromise;
  543. samePromise = promiseChain = $q.all([promiseChain])
  544. .then(resolveWithTemplate, resolveWithTemplate)
  545. .then(function resolveSuccess(tplAndVars) {
  546. var modalScope = (modalOptions.scope || $rootScope).$new();
  547. modalScope.$close = modalInstance.close;
  548. modalScope.$dismiss = modalInstance.dismiss;
  549. modalScope.$on('$destroy', function() {
  550. if (!modalScope.$$uibDestructionScheduled) {
  551. modalScope.$dismiss('$uibUnscheduledDestruction');
  552. }
  553. });
  554. var ctrlInstance, ctrlLocals = {};
  555. var resolveIter = 1;
  556. //controllers
  557. if (modalOptions.controller) {
  558. ctrlLocals.$scope = modalScope;
  559. ctrlLocals.$uibModalInstance = modalInstance;
  560. Object.defineProperty(ctrlLocals, '$modalInstance', {
  561. get: function() {
  562. if (!$modalSuppressWarning) {
  563. $log.warn('$modalInstance is now deprecated. Use $uibModalInstance instead.');
  564. }
  565. return modalInstance;
  566. }
  567. });
  568. angular.forEach(modalOptions.resolve, function(value, key) {
  569. ctrlLocals[key] = tplAndVars[resolveIter++];
  570. });
  571. ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
  572. if (modalOptions.controllerAs) {
  573. if (modalOptions.bindToController) {
  574. angular.extend(ctrlInstance, modalScope);
  575. }
  576. modalScope[modalOptions.controllerAs] = ctrlInstance;
  577. }
  578. }
  579. $modalStack.open(modalInstance, {
  580. scope: modalScope,
  581. deferred: modalResultDeferred,
  582. renderDeferred: modalRenderDeferred,
  583. content: tplAndVars[0],
  584. animation: modalOptions.animation,
  585. backdrop: modalOptions.backdrop,
  586. keyboard: modalOptions.keyboard,
  587. backdropClass: modalOptions.backdropClass,
  588. windowTopClass: modalOptions.windowTopClass,
  589. windowClass: modalOptions.windowClass,
  590. windowTemplateUrl: modalOptions.windowTemplateUrl,
  591. size: modalOptions.size,
  592. openedClass: modalOptions.openedClass
  593. });
  594. modalOpenedDeferred.resolve(true);
  595. }, function resolveError(reason) {
  596. modalOpenedDeferred.reject(reason);
  597. modalResultDeferred.reject(reason);
  598. })
  599. .finally(function() {
  600. if (promiseChain === samePromise) {
  601. promiseChain = null;
  602. }
  603. });
  604. return modalInstance;
  605. };
  606. return $modal;
  607. }
  608. ]
  609. };
  610. return $modalProvider;
  611. });
  612. /* deprecated modal below */
  613. angular.module('ui.bootstrap.modal')
  614. .value('$modalSuppressWarning', false)
  615. /**
  616. * A helper directive for the $modal service. It creates a backdrop element.
  617. */
  618. .directive('modalBackdrop', [
  619. '$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning',
  620. function($animate , $injector, $modalStack, $log, $modalSuppressWarning) {
  621. var $animateCss = null;
  622. if ($injector.has('$animateCss')) {
  623. $animateCss = $injector.get('$animateCss');
  624. }
  625. return {
  626. replace: true,
  627. templateUrl: 'template/modal/backdrop.html',
  628. compile: function(tElement, tAttrs) {
  629. tElement.addClass(tAttrs.backdropClass);
  630. return linkFn;
  631. }
  632. };
  633. function linkFn(scope, element, attrs) {
  634. if (!$modalSuppressWarning) {
  635. $log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.');
  636. }
  637. element.addClass('modal-backdrop');
  638. if (attrs.modalInClass) {
  639. if ($animateCss) {
  640. $animateCss(element, {
  641. addClass: attrs.modalInClass
  642. }).start();
  643. } else {
  644. $animate.addClass(element, attrs.modalInClass);
  645. }
  646. scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
  647. var done = setIsAsync();
  648. if ($animateCss) {
  649. $animateCss(element, {
  650. removeClass: attrs.modalInClass
  651. }).start().then(done);
  652. } else {
  653. $animate.removeClass(element, attrs.modalInClass).then(done);
  654. }
  655. });
  656. }
  657. }
  658. }])
  659. .directive('modalWindow', [
  660. '$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning',
  661. function($modalStack , $q , $animate, $injector, $log, $modalSuppressWarning) {
  662. var $animateCss = null;
  663. if ($injector.has('$animateCss')) {
  664. $animateCss = $injector.get('$animateCss');
  665. }
  666. return {
  667. scope: {
  668. index: '@'
  669. },
  670. replace: true,
  671. transclude: true,
  672. templateUrl: function(tElement, tAttrs) {
  673. return tAttrs.templateUrl || 'template/modal/window.html';
  674. },
  675. link: function(scope, element, attrs) {
  676. if (!$modalSuppressWarning) {
  677. $log.warn('modal-window is now deprecated. Use uib-modal-window instead.');
  678. }
  679. element.addClass(attrs.windowClass || '');
  680. element.addClass(attrs.windowTopClass || '');
  681. scope.size = attrs.size;
  682. scope.close = function(evt) {
  683. var modal = $modalStack.getTop();
  684. if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
  685. evt.preventDefault();
  686. evt.stopPropagation();
  687. $modalStack.dismiss(modal.key, 'backdrop click');
  688. }
  689. };
  690. // moved from template to fix issue #2280
  691. element.on('click', scope.close);
  692. // This property is only added to the scope for the purpose of detecting when this directive is rendered.
  693. // We can detect that by using this property in the template associated with this directive and then use
  694. // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
  695. scope.$isRendered = true;
  696. // Deferred object that will be resolved when this modal is render.
  697. var modalRenderDeferObj = $q.defer();
  698. // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
  699. // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
  700. attrs.$observe('modalRender', function(value) {
  701. if (value == 'true') {
  702. modalRenderDeferObj.resolve();
  703. }
  704. });
  705. modalRenderDeferObj.promise.then(function() {
  706. var animationPromise = null;
  707. if (attrs.modalInClass) {
  708. if ($animateCss) {
  709. animationPromise = $animateCss(element, {
  710. addClass: attrs.modalInClass
  711. }).start();
  712. } else {
  713. animationPromise = $animate.addClass(element, attrs.modalInClass);
  714. }
  715. scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
  716. var done = setIsAsync();
  717. if ($animateCss) {
  718. $animateCss(element, {
  719. removeClass: attrs.modalInClass
  720. }).start().then(done);
  721. } else {
  722. $animate.removeClass(element, attrs.modalInClass).then(done);
  723. }
  724. });
  725. }
  726. $q.when(animationPromise).then(function() {
  727. var inputWithAutofocus = element[0].querySelector('[autofocus]');
  728. /**
  729. * Auto-focusing of a freshly-opened modal element causes any child elements
  730. * with the autofocus attribute to lose focus. This is an issue on touch
  731. * based devices which will show and then hide the onscreen keyboard.
  732. * Attempts to refocus the autofocus element via JavaScript will not reopen
  733. * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
  734. * the modal element if the modal does not contain an autofocus element.
  735. */
  736. if (inputWithAutofocus) {
  737. inputWithAutofocus.focus();
  738. } else {
  739. element[0].focus();
  740. }
  741. });
  742. // Notify {@link $modalStack} that modal is rendered.
  743. var modal = $modalStack.getTop();
  744. if (modal) {
  745. $modalStack.modalRendered(modal.key);
  746. }
  747. });
  748. }
  749. };
  750. }])
  751. .directive('modalAnimationClass', [
  752. '$log', '$modalSuppressWarning',
  753. function ($log, $modalSuppressWarning) {
  754. return {
  755. compile: function(tElement, tAttrs) {
  756. if (!$modalSuppressWarning) {
  757. $log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.');
  758. }
  759. if (tAttrs.modalAnimation) {
  760. tElement.addClass(tAttrs.modalAnimationClass);
  761. }
  762. }
  763. };
  764. }])
  765. .directive('modalTransclude', [
  766. '$log', '$modalSuppressWarning',
  767. function ($log, $modalSuppressWarning) {
  768. return {
  769. link: function($scope, $element, $attrs, controller, $transclude) {
  770. if (!$modalSuppressWarning) {
  771. $log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.');
  772. }
  773. $transclude($scope.$parent, function(clone) {
  774. $element.empty();
  775. $element.append(clone);
  776. });
  777. }
  778. };
  779. }])
  780. .service('$modalStack', [
  781. '$animate', '$timeout', '$document', '$compile', '$rootScope',
  782. '$q',
  783. '$injector',
  784. '$$multiMap',
  785. '$$stackedMap',
  786. '$uibModalStack',
  787. '$log',
  788. '$modalSuppressWarning',
  789. function($animate , $timeout , $document , $compile , $rootScope ,
  790. $q,
  791. $injector,
  792. $$multiMap,
  793. $$stackedMap,
  794. $uibModalStack,
  795. $log,
  796. $modalSuppressWarning) {
  797. if (!$modalSuppressWarning) {
  798. $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.');
  799. }
  800. angular.extend(this, $uibModalStack);
  801. }])
  802. .provider('$modal', ['$uibModalProvider', function($uibModalProvider) {
  803. angular.extend(this, $uibModalProvider);
  804. this.$get = ['$injector', '$log', '$modalSuppressWarning',
  805. function ($injector, $log, $modalSuppressWarning) {
  806. if (!$modalSuppressWarning) {
  807. $log.warn('$modal is now deprecated. Use $uibModal instead.');
  808. }
  809. return $injector.invoke($uibModalProvider.$get);
  810. }];
  811. }]);