bootstrap-notify.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. /*
  2. Creative Tim Modifications
  3. Lines: 236 was changed from top: 5px to top: 50% and we added margin-top: -9px. In this way the close button will be aligned vertically
  4. Line:219 - modified when the icon is set, we add the class "alert-with-icon", so there will be enough space for the icon.
  5. Lines: 179/222 - class() was changed to html() so we can add the Material Design Icons
  6. */
  7. /*
  8. * Project: Bootstrap Notify = v3.1.5
  9. * Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
  10. * Author: Mouse0270 aka Robert McIntosh
  11. * License: MIT License
  12. * Website: https://github.com/mouse0270/bootstrap-growl
  13. */
  14. /* global define:false, require: false, jQuery:false */
  15. (function(factory) {
  16. if (typeof define === 'function' && define.amd) {
  17. // AMD. Register as an anonymous module.
  18. define(['jquery'], factory);
  19. } else if (typeof exports === 'object') {
  20. // Node/CommonJS
  21. factory(require('jquery'));
  22. } else {
  23. // Browser globals
  24. factory(jQuery);
  25. }
  26. }(function($) {
  27. // Create the defaults once
  28. var defaults = {
  29. element: 'body',
  30. position: null,
  31. type: "info",
  32. allow_dismiss: true,
  33. allow_duplicates: true,
  34. newest_on_top: false,
  35. showProgressbar: false,
  36. placement: {
  37. from: "top",
  38. align: "right"
  39. },
  40. offset: 20,
  41. spacing: 10,
  42. z_index: 1031,
  43. delay: 5000,
  44. timer: 1000,
  45. url_target: '_blank',
  46. mouse_over: null,
  47. animate: {
  48. enter: 'animated fadeInDown',
  49. exit: 'animated fadeOutUp'
  50. },
  51. onShow: null,
  52. onShown: null,
  53. onClose: null,
  54. onClosed: null,
  55. icon_type: 'class',
  56. template: '<div data-notify="container" class="col-xs-11 col-sm-4 alert alert-{0}" role="alert"><button type="button" aria-hidden="true" class="close" data-notify="dismiss"><i class="material-icons">close</i></button><i data-notify="icon" class="material-icons"></i><span data-notify="title">{1}</span> <span data-notify="message">{2}</span><div class="progress" data-notify="progressbar"><div class="progress-bar progress-bar-{0}" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"></div></div><a href="{3}" target="{4}" data-notify="url"></a></div>'
  57. };
  58. String.format = function() {
  59. var str = arguments[0];
  60. for (var i = 1; i < arguments.length; i++) {
  61. str = str.replace(RegExp("\\{" + (i - 1) + "\\}", "gm"), arguments[i]);
  62. }
  63. return str;
  64. };
  65. function isDuplicateNotification(notification) {
  66. var isDupe = false;
  67. $('[data-notify="container"]').each(function(i, el) {
  68. var $el = $(el);
  69. var title = $el.find('[data-notify="title"]').text().trim();
  70. var message = $el.find('[data-notify="message"]').html().trim();
  71. // The input string might be different than the actual parsed HTML string!
  72. // (<br> vs <br /> for example)
  73. // So we have to force-parse this as HTML here!
  74. var isSameTitle = title === $("<div>" + notification.settings.content.title + "</div>").html().trim();
  75. var isSameMsg = message === $("<div>" + notification.settings.content.message + "</div>").html().trim();
  76. var isSameType = $el.hasClass('alert-' + notification.settings.type);
  77. if (isSameTitle && isSameMsg && isSameType) {
  78. //we found the dupe. Set the var and stop checking.
  79. isDupe = true;
  80. }
  81. return !isDupe;
  82. });
  83. return isDupe;
  84. }
  85. function Notify(element, content, options) {
  86. // Setup Content of Notify
  87. var contentObj = {
  88. content: {
  89. message: typeof content === 'object' ? content.message : content,
  90. title: content.title ? content.title : '',
  91. icon: content.icon ? content.icon : '',
  92. url: content.url ? content.url : '#',
  93. target: content.target ? content.target : '-'
  94. }
  95. };
  96. options = $.extend(true, {}, contentObj, options);
  97. this.settings = $.extend(true, {}, defaults, options);
  98. this._defaults = defaults;
  99. if (this.settings.content.target === "-") {
  100. this.settings.content.target = this.settings.url_target;
  101. }
  102. this.animations = {
  103. start: 'webkitAnimationStart oanimationstart MSAnimationStart animationstart',
  104. end: 'webkitAnimationEnd oanimationend MSAnimationEnd animationend'
  105. };
  106. if (typeof this.settings.offset === 'number') {
  107. this.settings.offset = {
  108. x: this.settings.offset,
  109. y: this.settings.offset
  110. };
  111. }
  112. //if duplicate messages are not allowed, then only continue if this new message is not a duplicate of one that it already showing
  113. if (this.settings.allow_duplicates || (!this.settings.allow_duplicates && !isDuplicateNotification(this))) {
  114. this.init();
  115. }
  116. }
  117. $.extend(Notify.prototype, {
  118. init: function() {
  119. var self = this;
  120. this.buildNotify();
  121. if (this.settings.content.icon) {
  122. this.setIcon();
  123. }
  124. if (this.settings.content.url != "#") {
  125. this.styleURL();
  126. }
  127. this.styleDismiss();
  128. this.placement();
  129. this.bind();
  130. this.notify = {
  131. $ele: this.$ele,
  132. update: function(command, update) {
  133. var commands = {};
  134. if (typeof command === "string") {
  135. commands[command] = update;
  136. } else {
  137. commands = command;
  138. }
  139. for (var cmd in commands) {
  140. switch (cmd) {
  141. case "type":
  142. this.$ele.removeClass('alert-' + self.settings.type);
  143. this.$ele.find('[data-notify="progressbar"] > .progress-bar').removeClass('progress-bar-' + self.settings.type);
  144. self.settings.type = commands[cmd];
  145. this.$ele.addClass('alert-' + commands[cmd]).find('[data-notify="progressbar"] > .progress-bar').addClass('progress-bar-' + commands[cmd]);
  146. break;
  147. case "icon":
  148. var $icon = this.$ele.find('[data-notify="icon"]');
  149. if (self.settings.icon_type.toLowerCase() === 'class') {
  150. $icon.html(commands[cmd]);
  151. } else {
  152. if (!$icon.is('img')) {
  153. $icon.find('img');
  154. }
  155. $icon.attr('src', commands[cmd]);
  156. }
  157. break;
  158. case "progress":
  159. var newDelay = self.settings.delay - (self.settings.delay * (commands[cmd] / 100));
  160. this.$ele.data('notify-delay', newDelay);
  161. this.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', commands[cmd]).css('width', commands[cmd] + '%');
  162. break;
  163. case "url":
  164. this.$ele.find('[data-notify="url"]').attr('href', commands[cmd]);
  165. break;
  166. case "target":
  167. this.$ele.find('[data-notify="url"]').attr('target', commands[cmd]);
  168. break;
  169. default:
  170. this.$ele.find('[data-notify="' + cmd + '"]').html(commands[cmd]);
  171. }
  172. }
  173. var posX = this.$ele.outerHeight() + parseInt(self.settings.spacing) + parseInt(self.settings.offset.y);
  174. self.reposition(posX);
  175. },
  176. close: function() {
  177. self.close();
  178. }
  179. };
  180. },
  181. buildNotify: function() {
  182. var content = this.settings.content;
  183. this.$ele = $(String.format(this.settings.template, this.settings.type, content.title, content.message, content.url, content.target));
  184. this.$ele.attr('data-notify-position', this.settings.placement.from + '-' + this.settings.placement.align);
  185. if (!this.settings.allow_dismiss) {
  186. this.$ele.find('[data-notify="dismiss"]').css('display', 'none');
  187. }
  188. if ((this.settings.delay <= 0 && !this.settings.showProgressbar) || !this.settings.showProgressbar) {
  189. this.$ele.find('[data-notify="progressbar"]').remove();
  190. }
  191. },
  192. setIcon: function() {
  193. this.$ele.addClass('alert-with-icon');
  194. if (this.settings.icon_type.toLowerCase() === 'class') {
  195. this.$ele.find('[data-notify="icon"]').html(this.settings.content.icon);
  196. } else {
  197. if (this.$ele.find('[data-notify="icon"]').is('img')) {
  198. this.$ele.find('[data-notify="icon"]').attr('src', this.settings.content.icon);
  199. } else {
  200. this.$ele.find('[data-notify="icon"]').append('<img src="' + this.settings.content.icon + '" alt="Notify Icon" />');
  201. }
  202. }
  203. },
  204. styleDismiss: function() {
  205. this.$ele.find('[data-notify="dismiss"]').css({
  206. position: 'absolute',
  207. right: '10px',
  208. top: '50%',
  209. marginTop: '-9px',
  210. zIndex: this.settings.z_index + 2
  211. });
  212. },
  213. styleURL: function() {
  214. this.$ele.find('[data-notify="url"]').css({
  215. backgroundImage: 'url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)',
  216. height: '100%',
  217. left: 0,
  218. position: 'absolute',
  219. top: 0,
  220. width: '100%',
  221. zIndex: this.settings.z_index + 1
  222. });
  223. },
  224. placement: function() {
  225. var self = this,
  226. offsetAmt = this.settings.offset.y,
  227. css = {
  228. display: 'inline-block',
  229. margin: '15px auto',
  230. position: this.settings.position ? this.settings.position : (this.settings.element === 'body' ? 'fixed' : 'absolute'),
  231. transition: 'all .5s ease-in-out',
  232. zIndex: this.settings.z_index
  233. },
  234. hasAnimation = false,
  235. settings = this.settings;
  236. $('[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])').each(function() {
  237. offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + parseInt($(this).outerHeight()) + parseInt(settings.spacing));
  238. });
  239. if (this.settings.newest_on_top === true) {
  240. offsetAmt = this.settings.offset.y;
  241. }
  242. css[this.settings.placement.from] = offsetAmt + 'px';
  243. switch (this.settings.placement.align) {
  244. case "left":
  245. case "right":
  246. css[this.settings.placement.align] = this.settings.offset.x + 'px';
  247. break;
  248. case "center":
  249. css.left = 0;
  250. css.right = 0;
  251. break;
  252. }
  253. this.$ele.css(css).addClass(this.settings.animate.enter);
  254. $.each(Array('webkit-', 'moz-', 'o-', 'ms-', ''), function(index, prefix) {
  255. self.$ele[0].style[prefix + 'AnimationIterationCount'] = 1;
  256. });
  257. $(this.settings.element).append(this.$ele);
  258. if (this.settings.newest_on_top === true) {
  259. offsetAmt = (parseInt(offsetAmt) + parseInt(this.settings.spacing)) + this.$ele.outerHeight();
  260. this.reposition(offsetAmt);
  261. }
  262. if ($.isFunction(self.settings.onShow)) {
  263. self.settings.onShow.call(this.$ele);
  264. }
  265. this.$ele.one(this.animations.start, function() {
  266. hasAnimation = true;
  267. }).one(this.animations.end, function() {
  268. if ($.isFunction(self.settings.onShown)) {
  269. self.settings.onShown.call(this);
  270. }
  271. });
  272. setTimeout(function() {
  273. if (!hasAnimation) {
  274. if ($.isFunction(self.settings.onShown)) {
  275. self.settings.onShown.call(this);
  276. }
  277. }
  278. }, 600);
  279. },
  280. bind: function() {
  281. var self = this;
  282. this.$ele.find('[data-notify="dismiss"]').on('click', function() {
  283. self.close();
  284. });
  285. this.$ele.mouseover(function() {
  286. $(this).data('data-hover', "true");
  287. }).mouseout(function() {
  288. $(this).data('data-hover', "false");
  289. });
  290. this.$ele.data('data-hover', "false");
  291. if (this.settings.delay > 0) {
  292. self.$ele.data('notify-delay', self.settings.delay);
  293. var timer = setInterval(function() {
  294. var delay = parseInt(self.$ele.data('notify-delay')) - self.settings.timer;
  295. if ((self.$ele.data('data-hover') === 'false' && self.settings.mouse_over === "pause") || self.settings.mouse_over != "pause") {
  296. var percent = ((self.settings.delay - delay) / self.settings.delay) * 100;
  297. self.$ele.data('notify-delay', delay);
  298. self.$ele.find('[data-notify="progressbar"] > div').attr('aria-valuenow', percent).css('width', percent + '%');
  299. }
  300. if (delay <= -(self.settings.timer)) {
  301. clearInterval(timer);
  302. self.close();
  303. }
  304. }, self.settings.timer);
  305. }
  306. },
  307. close: function() {
  308. var self = this,
  309. posX = parseInt(this.$ele.css(this.settings.placement.from)),
  310. hasAnimation = false;
  311. this.$ele.data('closing', 'true').addClass(this.settings.animate.exit);
  312. self.reposition(posX);
  313. if ($.isFunction(self.settings.onClose)) {
  314. self.settings.onClose.call(this.$ele);
  315. }
  316. this.$ele.one(this.animations.start, function() {
  317. hasAnimation = true;
  318. }).one(this.animations.end, function() {
  319. $(this).remove();
  320. if ($.isFunction(self.settings.onClosed)) {
  321. self.settings.onClosed.call(this);
  322. }
  323. });
  324. setTimeout(function() {
  325. if (!hasAnimation) {
  326. self.$ele.remove();
  327. if (self.settings.onClosed) {
  328. self.settings.onClosed(self.$ele);
  329. }
  330. }
  331. }, 600);
  332. },
  333. reposition: function(posX) {
  334. var self = this,
  335. notifies = '[data-notify-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]:not([data-closing="true"])',
  336. $elements = this.$ele.nextAll(notifies);
  337. if (this.settings.newest_on_top === true) {
  338. $elements = this.$ele.prevAll(notifies);
  339. }
  340. $elements.each(function() {
  341. $(this).css(self.settings.placement.from, posX);
  342. posX = (parseInt(posX) + parseInt(self.settings.spacing)) + $(this).outerHeight();
  343. });
  344. }
  345. });
  346. $.notify = function(content, options) {
  347. var plugin = new Notify(this, content, options);
  348. return plugin.notify;
  349. };
  350. $.notifyDefaults = function(options) {
  351. defaults = $.extend(true, {}, defaults, options);
  352. return defaults;
  353. };
  354. $.notifyClose = function(command) {
  355. if (typeof command === "undefined" || command === "all") {
  356. $('[data-notify]').find('[data-notify="dismiss"]').trigger('click');
  357. } else {
  358. $('[data-notify-position="' + command + '"]').find('[data-notify="dismiss"]').trigger('click');
  359. }
  360. };
  361. }));