modal.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. /**
  2. * angular-strap
  3. * @version v2.3.9 - 2016-06-10
  4. * @link http://mgcrea.github.io/angular-strap
  5. * @author Olivier Louvignes <olivier@mg-crea.com> (https://github.com/mgcrea)
  6. * @license MIT License, http://www.opensource.org/licenses/MIT
  7. */
  8. 'use strict';
  9. angular.module('mgcrea.ngStrap.modal', [ 'mgcrea.ngStrap.core', 'mgcrea.ngStrap.helpers.dimensions' ]).provider('$modal', function() {
  10. var defaults = this.defaults = {
  11. animation: 'am-fade',
  12. backdropAnimation: 'am-fade',
  13. customClass: '',
  14. prefixClass: 'modal',
  15. prefixEvent: 'modal',
  16. placement: 'top',
  17. templateUrl: 'modal/modal.tpl.html',
  18. template: '',
  19. contentTemplate: false,
  20. container: false,
  21. element: null,
  22. backdrop: true,
  23. keyboard: true,
  24. html: false,
  25. show: true,
  26. size: null
  27. };
  28. this.$get = [ '$window', '$rootScope', '$bsCompiler', '$animate', '$timeout', '$sce', 'dimensions', function($window, $rootScope, $bsCompiler, $animate, $timeout, $sce, dimensions) {
  29. var forEach = angular.forEach;
  30. var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
  31. var bodyElement = angular.element($window.document.body);
  32. var backdropCount = 0;
  33. var dialogBaseZindex = 1050;
  34. var backdropBaseZindex = 1040;
  35. var validSizes = {
  36. lg: 'modal-lg',
  37. sm: 'modal-sm'
  38. };
  39. function ModalFactory(config) {
  40. var $modal = {};
  41. var options = $modal.$options = angular.extend({}, defaults, config);
  42. var promise = $modal.$promise = $bsCompiler.compile(options);
  43. var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
  44. if (!options.element && !options.container) {
  45. options.container = 'body';
  46. }
  47. $modal.$id = options.id || options.element && options.element.attr('id') || '';
  48. forEach([ 'title', 'content' ], function(key) {
  49. if (options[key]) scope[key] = $sce.trustAsHtml(options[key]);
  50. });
  51. scope.$hide = function() {
  52. scope.$$postDigest(function() {
  53. $modal.hide();
  54. });
  55. };
  56. scope.$show = function() {
  57. scope.$$postDigest(function() {
  58. $modal.show();
  59. });
  60. };
  61. scope.$toggle = function() {
  62. scope.$$postDigest(function() {
  63. $modal.toggle();
  64. });
  65. };
  66. $modal.$isShown = scope.$isShown = false;
  67. var compileData;
  68. var modalElement;
  69. var modalScope;
  70. var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>');
  71. backdropElement.css({
  72. position: 'fixed',
  73. top: '0px',
  74. left: '0px',
  75. bottom: '0px',
  76. right: '0px'
  77. });
  78. promise.then(function(data) {
  79. compileData = data;
  80. $modal.init();
  81. });
  82. $modal.init = function() {
  83. if (options.show) {
  84. scope.$$postDigest(function() {
  85. $modal.show();
  86. });
  87. }
  88. };
  89. $modal.destroy = function() {
  90. destroyModalElement();
  91. if (backdropElement) {
  92. backdropElement.remove();
  93. backdropElement = null;
  94. }
  95. scope.$destroy();
  96. };
  97. $modal.show = function() {
  98. if ($modal.$isShown) return;
  99. var parent;
  100. var after;
  101. if (angular.isElement(options.container)) {
  102. parent = options.container;
  103. after = options.container[0].lastChild ? angular.element(options.container[0].lastChild) : null;
  104. } else {
  105. if (options.container) {
  106. parent = findElement(options.container);
  107. after = parent[0] && parent[0].lastChild ? angular.element(parent[0].lastChild) : null;
  108. } else {
  109. parent = null;
  110. after = options.element;
  111. }
  112. }
  113. if (modalElement) destroyModalElement();
  114. modalScope = $modal.$scope.$new();
  115. modalElement = $modal.$element = compileData.link(modalScope, function(clonedElement, scope) {});
  116. if (options.backdrop) {
  117. modalElement.css({
  118. 'z-index': dialogBaseZindex + backdropCount * 20
  119. });
  120. backdropElement.css({
  121. 'z-index': backdropBaseZindex + backdropCount * 20
  122. });
  123. backdropCount++;
  124. }
  125. if (scope.$emit(options.prefixEvent + '.show.before', $modal).defaultPrevented) {
  126. return;
  127. }
  128. if (angular.isDefined(options.onBeforeShow) && angular.isFunction(options.onBeforeShow)) {
  129. options.onBeforeShow($modal);
  130. }
  131. modalElement.css({
  132. display: 'block'
  133. }).addClass(options.placement);
  134. if (options.customClass) {
  135. modalElement.addClass(options.customClass);
  136. }
  137. if (options.size && validSizes[options.size]) {
  138. angular.element(findElement('.modal-dialog', modalElement[0])).addClass(validSizes[options.size]);
  139. }
  140. if (options.animation) {
  141. if (options.backdrop) {
  142. backdropElement.addClass(options.backdropAnimation);
  143. }
  144. modalElement.addClass(options.animation);
  145. }
  146. if (options.backdrop) {
  147. $animate.enter(backdropElement, bodyElement, null);
  148. }
  149. if (angular.version.minor <= 2) {
  150. $animate.enter(modalElement, parent, after, enterAnimateCallback);
  151. } else {
  152. $animate.enter(modalElement, parent, after).then(enterAnimateCallback);
  153. }
  154. $modal.$isShown = scope.$isShown = true;
  155. safeDigest(scope);
  156. var el = modalElement[0];
  157. requestAnimationFrame(function() {
  158. el.focus();
  159. });
  160. bodyElement.addClass(options.prefixClass + '-open');
  161. if (options.animation) {
  162. bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
  163. }
  164. bindBackdropEvents();
  165. bindKeyboardEvents();
  166. };
  167. function enterAnimateCallback() {
  168. scope.$emit(options.prefixEvent + '.show', $modal);
  169. if (angular.isDefined(options.onShow) && angular.isFunction(options.onShow)) {
  170. options.onShow($modal);
  171. }
  172. }
  173. $modal.hide = function() {
  174. if (!$modal.$isShown) return;
  175. if (scope.$emit(options.prefixEvent + '.hide.before', $modal).defaultPrevented) {
  176. return;
  177. }
  178. if (angular.isDefined(options.onBeforeHide) && angular.isFunction(options.onBeforeHide)) {
  179. options.onBeforeHide($modal);
  180. }
  181. if (angular.version.minor <= 2) {
  182. $animate.leave(modalElement, leaveAnimateCallback);
  183. } else {
  184. $animate.leave(modalElement).then(leaveAnimateCallback);
  185. }
  186. if (options.backdrop) {
  187. backdropCount--;
  188. $animate.leave(backdropElement);
  189. }
  190. $modal.$isShown = scope.$isShown = false;
  191. safeDigest(scope);
  192. unbindBackdropEvents();
  193. unbindKeyboardEvents();
  194. };
  195. function leaveAnimateCallback() {
  196. scope.$emit(options.prefixEvent + '.hide', $modal);
  197. if (angular.isDefined(options.onHide) && angular.isFunction(options.onHide)) {
  198. options.onHide($modal);
  199. }
  200. bodyElement.removeClass(options.prefixClass + '-open');
  201. if (options.animation) {
  202. bodyElement.removeClass(options.prefixClass + '-with-' + options.animation);
  203. }
  204. }
  205. $modal.toggle = function() {
  206. if ($modal.$isShown) {
  207. $modal.hide();
  208. } else {
  209. $modal.show();
  210. }
  211. };
  212. $modal.focus = function() {
  213. modalElement[0].focus();
  214. };
  215. $modal.$onKeyUp = function(evt) {
  216. if (evt.which === 27 && $modal.$isShown) {
  217. $modal.hide();
  218. evt.stopPropagation();
  219. }
  220. };
  221. function bindBackdropEvents() {
  222. if (options.backdrop) {
  223. modalElement.on('click', hideOnBackdropClick);
  224. backdropElement.on('click', hideOnBackdropClick);
  225. backdropElement.on('wheel', preventEventDefault);
  226. }
  227. }
  228. function unbindBackdropEvents() {
  229. if (options.backdrop) {
  230. modalElement.off('click', hideOnBackdropClick);
  231. backdropElement.off('click', hideOnBackdropClick);
  232. backdropElement.off('wheel', preventEventDefault);
  233. }
  234. }
  235. function bindKeyboardEvents() {
  236. if (options.keyboard) {
  237. modalElement.on('keyup', $modal.$onKeyUp);
  238. }
  239. }
  240. function unbindKeyboardEvents() {
  241. if (options.keyboard) {
  242. modalElement.off('keyup', $modal.$onKeyUp);
  243. }
  244. }
  245. function hideOnBackdropClick(evt) {
  246. if (evt.target !== evt.currentTarget) return;
  247. if (options.backdrop === 'static') {
  248. $modal.focus();
  249. } else {
  250. $modal.hide();
  251. }
  252. }
  253. function preventEventDefault(evt) {
  254. evt.preventDefault();
  255. }
  256. function destroyModalElement() {
  257. if ($modal.$isShown && modalElement !== null) {
  258. unbindBackdropEvents();
  259. unbindKeyboardEvents();
  260. }
  261. if (modalScope) {
  262. modalScope.$destroy();
  263. modalScope = null;
  264. }
  265. if (modalElement) {
  266. modalElement.remove();
  267. modalElement = $modal.$element = null;
  268. }
  269. }
  270. return $modal;
  271. }
  272. function safeDigest(scope) {
  273. scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
  274. }
  275. function findElement(query, element) {
  276. return angular.element((element || document).querySelectorAll(query));
  277. }
  278. return ModalFactory;
  279. } ];
  280. }).directive('bsModal', [ '$window', '$sce', '$parse', '$modal', function($window, $sce, $parse, $modal) {
  281. return {
  282. restrict: 'EAC',
  283. scope: true,
  284. link: function postLink(scope, element, attr, transclusion) {
  285. var options = {
  286. scope: scope,
  287. element: element,
  288. show: false
  289. };
  290. angular.forEach([ 'template', 'templateUrl', 'controller', 'controllerAs', 'contentTemplate', 'placement', 'backdrop', 'keyboard', 'html', 'container', 'animation', 'backdropAnimation', 'id', 'prefixEvent', 'prefixClass', 'customClass', 'modalClass', 'size' ], function(key) {
  291. if (angular.isDefined(attr[key])) options[key] = attr[key];
  292. });
  293. if (options.modalClass) {
  294. options.customClass = options.modalClass;
  295. }
  296. var falseValueRegExp = /^(false|0|)$/i;
  297. angular.forEach([ 'backdrop', 'keyboard', 'html', 'container' ], function(key) {
  298. if (angular.isDefined(attr[key]) && falseValueRegExp.test(attr[key])) options[key] = false;
  299. });
  300. angular.forEach([ 'onBeforeShow', 'onShow', 'onBeforeHide', 'onHide' ], function(key) {
  301. var bsKey = 'bs' + key.charAt(0).toUpperCase() + key.slice(1);
  302. if (angular.isDefined(attr[bsKey])) {
  303. options[key] = scope.$eval(attr[bsKey]);
  304. }
  305. });
  306. angular.forEach([ 'title', 'content' ], function(key) {
  307. if (attr[key]) {
  308. attr.$observe(key, function(newValue, oldValue) {
  309. scope[key] = $sce.trustAsHtml(newValue);
  310. });
  311. }
  312. });
  313. if (attr.bsModal) {
  314. scope.$watch(attr.bsModal, function(newValue, oldValue) {
  315. if (angular.isObject(newValue)) {
  316. angular.extend(scope, newValue);
  317. } else {
  318. scope.content = newValue;
  319. }
  320. }, true);
  321. }
  322. var modal = $modal(options);
  323. element.on(attr.trigger || 'click', modal.toggle);
  324. scope.$on('$destroy', function() {
  325. if (modal) modal.destroy();
  326. options = null;
  327. modal = null;
  328. });
  329. }
  330. };
  331. } ]);