説明なし

angular-toastr.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. (function() {
  2. 'use strict';
  3. angular.module('toastr', [])
  4. .factory('toastr', toastr);
  5. toastr.$inject = ['$animate', '$injector', '$document', '$rootScope', '$sce', 'toastrConfig', '$q'];
  6. function toastr($animate, $injector, $document, $rootScope, $sce, toastrConfig, $q) {
  7. var container;
  8. var index = 0;
  9. var toasts = [];
  10. var previousToastMessage = '';
  11. var openToasts = {};
  12. var containerDefer = $q.defer();
  13. var toast = {
  14. clear: clear,
  15. error: error,
  16. info: info,
  17. remove: remove,
  18. success: success,
  19. warning: warning
  20. };
  21. return toast;
  22. /* Public API */
  23. function clear(toast) {
  24. if (toast) {
  25. remove(toast.toastId);
  26. } else {
  27. for (var i = 0; i < toasts.length; i++) {
  28. remove(toasts[i].toastId);
  29. }
  30. }
  31. }
  32. function error(message, title, optionsOverride) {
  33. var type = _getOptions().iconClasses.error;
  34. return _buildNotification(type, message, title, optionsOverride);
  35. }
  36. function info(message, title, optionsOverride) {
  37. var type = _getOptions().iconClasses.info;
  38. return _buildNotification(type, message, title, optionsOverride);
  39. }
  40. function success(message, title, optionsOverride) {
  41. var type = _getOptions().iconClasses.success;
  42. return _buildNotification(type, message, title, optionsOverride);
  43. }
  44. function warning(message, title, optionsOverride) {
  45. var type = _getOptions().iconClasses.warning;
  46. return _buildNotification(type, message, title, optionsOverride);
  47. }
  48. function remove(toastId, wasClicked) {
  49. var toast = findToast(toastId);
  50. if (toast && ! toast.deleting) { // Avoid clicking when fading out
  51. toast.deleting = true;
  52. toast.isOpened = false;
  53. $animate.leave(toast.el).then(function() {
  54. if (toast.scope.options.onHidden) {
  55. toast.scope.options.onHidden(wasClicked);
  56. }
  57. toast.scope.$destroy();
  58. var index = toasts.indexOf(toast);
  59. delete openToasts[toast.scope.message];
  60. toasts.splice(index, 1);
  61. var maxOpened = toastrConfig.maxOpened;
  62. if (maxOpened && toasts.length >= maxOpened) {
  63. toasts[maxOpened - 1].open.resolve();
  64. }
  65. if (lastToast()) {
  66. container.remove();
  67. container = null;
  68. containerDefer = $q.defer();
  69. }
  70. });
  71. }
  72. function findToast(toastId) {
  73. for (var i = 0; i < toasts.length; i++) {
  74. if (toasts[i].toastId === toastId) {
  75. return toasts[i];
  76. }
  77. }
  78. }
  79. function lastToast() {
  80. return !toasts.length;
  81. }
  82. }
  83. /* Internal functions */
  84. function _buildNotification(type, message, title, optionsOverride)
  85. {
  86. if (angular.isObject(title)) {
  87. optionsOverride = title;
  88. title = null;
  89. }
  90. return _notify({
  91. iconClass: type,
  92. message: message,
  93. optionsOverride: optionsOverride,
  94. title: title
  95. });
  96. }
  97. function _getOptions() {
  98. return angular.extend({}, toastrConfig);
  99. }
  100. function _createOrGetContainer(options) {
  101. if(container) { return containerDefer.promise; }
  102. container = angular.element('<div></div>');
  103. container.attr('id', options.containerId);
  104. container.addClass(options.positionClass);
  105. container.css({'pointer-events': 'auto'});
  106. var target = angular.element(document.querySelector(options.target));
  107. if ( ! target || ! target.length) {
  108. throw 'Target for toasts doesn\'t exist';
  109. }
  110. $animate.enter(container, target).then(function() {
  111. containerDefer.resolve();
  112. });
  113. return containerDefer.promise;
  114. }
  115. function _notify(map) {
  116. var options = _getOptions();
  117. if (shouldExit()) { return; }
  118. var newToast = createToast();
  119. toasts.push(newToast);
  120. if (options.autoDismiss && options.maxOpened > 0) {
  121. var oldToasts = toasts.slice(0, (toasts.length - options.maxOpened));
  122. for (var i = 0, len = oldToasts.length; i < len; i++) {
  123. remove(oldToasts[i].toastId);
  124. }
  125. }
  126. if (maxOpenedNotReached()) {
  127. newToast.open.resolve();
  128. }
  129. newToast.open.promise.then(function() {
  130. _createOrGetContainer(options).then(function() {
  131. newToast.isOpened = true;
  132. if (options.newestOnTop) {
  133. $animate.enter(newToast.el, container).then(function() {
  134. newToast.scope.init();
  135. });
  136. } else {
  137. var sibling = container[0].lastChild ? angular.element(container[0].lastChild) : null;
  138. $animate.enter(newToast.el, container, sibling).then(function() {
  139. newToast.scope.init();
  140. });
  141. }
  142. });
  143. });
  144. return newToast;
  145. function createScope(toast, map, options) {
  146. if (options.allowHtml) {
  147. toast.scope.allowHtml = true;
  148. toast.scope.title = $sce.trustAsHtml(map.title);
  149. toast.scope.message = $sce.trustAsHtml(map.message);
  150. } else {
  151. toast.scope.title = map.title;
  152. toast.scope.message = map.message;
  153. }
  154. toast.scope.toastType = toast.iconClass;
  155. toast.scope.toastId = toast.toastId;
  156. toast.scope.options = {
  157. extendedTimeOut: options.extendedTimeOut,
  158. messageClass: options.messageClass,
  159. onHidden: options.onHidden,
  160. onShown: options.onShown,
  161. progressBar: options.progressBar,
  162. tapToDismiss: options.tapToDismiss,
  163. timeOut: options.timeOut,
  164. titleClass: options.titleClass,
  165. toastClass: options.toastClass
  166. };
  167. if (options.closeButton) {
  168. toast.scope.options.closeHtml = options.closeHtml;
  169. }
  170. }
  171. function createToast() {
  172. var newToast = {
  173. toastId: index++,
  174. isOpened: false,
  175. scope: $rootScope.$new(),
  176. open: $q.defer()
  177. };
  178. newToast.iconClass = map.iconClass;
  179. if (map.optionsOverride) {
  180. options = angular.extend(options, cleanOptionsOverride(map.optionsOverride));
  181. newToast.iconClass = map.optionsOverride.iconClass || newToast.iconClass;
  182. }
  183. createScope(newToast, map, options);
  184. newToast.el = createToastEl(newToast.scope);
  185. return newToast;
  186. function cleanOptionsOverride(options) {
  187. var badOptions = ['containerId', 'iconClasses', 'maxOpened', 'newestOnTop',
  188. 'positionClass', 'preventDuplicates', 'preventOpenDuplicates', 'templates'];
  189. for (var i = 0, l = badOptions.length; i < l; i++) {
  190. delete options[badOptions[i]];
  191. }
  192. return options;
  193. }
  194. }
  195. function createToastEl(scope) {
  196. var angularDomEl = angular.element('<div toast></div>'),
  197. $compile = $injector.get('$compile');
  198. return $compile(angularDomEl)(scope);
  199. }
  200. function maxOpenedNotReached() {
  201. return options.maxOpened && toasts.length <= options.maxOpened || !options.maxOpened;
  202. }
  203. function shouldExit() {
  204. var isDuplicateOfLast = options.preventDuplicates && map.message === previousToastMessage;
  205. var isDuplicateOpen = options.preventOpenDuplicates && openToasts[map.message];
  206. if (isDuplicateOfLast || isDuplicateOpen) {
  207. return true;
  208. }
  209. previousToastMessage = map.message;
  210. openToasts[map.message] = true;
  211. return false;
  212. }
  213. }
  214. }
  215. }());
  216. (function() {
  217. 'use strict';
  218. angular.module('toastr')
  219. .constant('toastrConfig', {
  220. allowHtml: false,
  221. autoDismiss: false,
  222. closeButton: false,
  223. closeHtml: '<button>&times;</button>',
  224. containerId: 'toast-container',
  225. extendedTimeOut: 1000,
  226. iconClasses: {
  227. error: 'toast-error',
  228. info: 'toast-info',
  229. success: 'toast-success',
  230. warning: 'toast-warning'
  231. },
  232. maxOpened: 0,
  233. messageClass: 'toast-message',
  234. newestOnTop: true,
  235. onHidden: null,
  236. onShown: null,
  237. positionClass: 'toast-top-right',
  238. preventDuplicates: false,
  239. preventOpenDuplicates: false,
  240. progressBar: false,
  241. tapToDismiss: true,
  242. target: 'body',
  243. templates: {
  244. toast: 'directives/toast/toast.html',
  245. progressbar: 'directives/progressbar/progressbar.html'
  246. },
  247. timeOut: 5000,
  248. titleClass: 'toast-title',
  249. toastClass: 'toast'
  250. });
  251. }());
  252. (function() {
  253. 'use strict';
  254. angular.module('toastr')
  255. .directive('progressBar', progressBar);
  256. progressBar.$inject = ['toastrConfig'];
  257. function progressBar(toastrConfig) {
  258. return {
  259. replace: true,
  260. require: '^toast',
  261. templateUrl: function() {
  262. return toastrConfig.templates.progressbar;
  263. },
  264. link: linkFunction
  265. };
  266. function linkFunction(scope, element, attrs, toastCtrl) {
  267. var intervalId, currentTimeOut, hideTime;
  268. toastCtrl.progressBar = scope;
  269. scope.start = function(duration) {
  270. if (intervalId) {
  271. clearInterval(intervalId);
  272. }
  273. currentTimeOut = parseFloat(duration);
  274. hideTime = new Date().getTime() + currentTimeOut;
  275. intervalId = setInterval(updateProgress, 10);
  276. };
  277. scope.stop = function() {
  278. if (intervalId) {
  279. clearInterval(intervalId);
  280. }
  281. };
  282. function updateProgress() {
  283. var percentage = ((hideTime - (new Date().getTime())) / currentTimeOut) * 100;
  284. element.css('width', percentage + '%');
  285. }
  286. scope.$on('$destroy', function() {
  287. // Failsafe stop
  288. clearInterval(intervalId);
  289. });
  290. }
  291. }
  292. }());
  293. (function() {
  294. 'use strict';
  295. angular.module('toastr')
  296. .controller('ToastController', ToastController);
  297. function ToastController() {
  298. this.progressBar = null;
  299. this.startProgressBar = function(duration) {
  300. if (this.progressBar) {
  301. this.progressBar.start(duration);
  302. }
  303. };
  304. this.stopProgressBar = function() {
  305. if (this.progressBar) {
  306. this.progressBar.stop();
  307. }
  308. };
  309. }
  310. }());
  311. (function() {
  312. 'use strict';
  313. angular.module('toastr')
  314. .directive('toast', toast);
  315. toast.$inject = ['$injector', '$interval', 'toastrConfig', 'toastr'];
  316. function toast($injector, $interval, toastrConfig, toastr) {
  317. return {
  318. replace: true,
  319. templateUrl: function() {
  320. return toastrConfig.templates.toast;
  321. },
  322. controller: 'ToastController',
  323. link: toastLinkFunction
  324. };
  325. function toastLinkFunction(scope, element, attrs, toastCtrl) {
  326. var timeout;
  327. scope.toastClass = scope.options.toastClass;
  328. scope.titleClass = scope.options.titleClass;
  329. scope.messageClass = scope.options.messageClass;
  330. scope.progressBar = scope.options.progressBar;
  331. if (wantsCloseButton()) {
  332. var button = angular.element(scope.options.closeHtml),
  333. $compile = $injector.get('$compile');
  334. button.addClass('toast-close-button');
  335. button.attr('ng-click', 'close()');
  336. $compile(button)(scope);
  337. element.prepend(button);
  338. }
  339. scope.init = function() {
  340. if (scope.options.timeOut) {
  341. timeout = createTimeout(scope.options.timeOut);
  342. }
  343. if (scope.options.onShown) {
  344. scope.options.onShown();
  345. }
  346. };
  347. element.on('mouseenter', function() {
  348. hideAndStopProgressBar();
  349. if (timeout) {
  350. $interval.cancel(timeout);
  351. }
  352. });
  353. scope.tapToast = function () {
  354. if (scope.options.tapToDismiss) {
  355. scope.close(true);
  356. }
  357. };
  358. scope.close = function (wasClicked) {
  359. toastr.remove(scope.toastId, wasClicked);
  360. };
  361. element.on('mouseleave', function() {
  362. if (scope.options.timeOut === 0 && scope.options.extendedTimeOut === 0) { return; }
  363. scope.$apply(function() {
  364. scope.progressBar = scope.options.progressBar;
  365. });
  366. timeout = createTimeout(scope.options.extendedTimeOut);
  367. });
  368. function createTimeout(time) {
  369. toastCtrl.startProgressBar(time);
  370. return $interval(function() {
  371. toastCtrl.stopProgressBar();
  372. toastr.remove(scope.toastId);
  373. }, time, 1);
  374. }
  375. function hideAndStopProgressBar() {
  376. scope.progressBar = false;
  377. toastCtrl.stopProgressBar();
  378. }
  379. function wantsCloseButton() {
  380. return scope.options.closeHtml;
  381. }
  382. }
  383. }
  384. }());