1
0

toast.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. /*!
  2. * # Fomantic-UI - Toast
  3. * http://github.com/fomantic/Fomantic-UI/
  4. *
  5. *
  6. * Released under the MIT license
  7. * http://opensource.org/licenses/MIT
  8. *
  9. */
  10. ;(function ($, window, document, undefined) {
  11. 'use strict';
  12. $.isFunction = $.isFunction || function(obj) {
  13. return typeof obj === "function" && typeof obj.nodeType !== "number";
  14. };
  15. window = (typeof window != 'undefined' && window.Math == Math)
  16. ? window
  17. : (typeof self != 'undefined' && self.Math == Math)
  18. ? self
  19. : Function('return this')()
  20. ;
  21. $.fn.toast = function(parameters) {
  22. var
  23. $allModules = $(this),
  24. moduleSelector = $allModules.selector || '',
  25. time = new Date().getTime(),
  26. performance = [],
  27. query = arguments[0],
  28. methodInvoked = (typeof query == 'string'),
  29. queryArguments = [].slice.call(arguments, 1),
  30. returnedValue
  31. ;
  32. $allModules
  33. .each(function() {
  34. var
  35. settings = ( $.isPlainObject(parameters) )
  36. ? $.extend(true, {}, $.fn.toast.settings, parameters)
  37. : $.extend({}, $.fn.toast.settings),
  38. className = settings.className,
  39. selector = settings.selector,
  40. error = settings.error,
  41. namespace = settings.namespace,
  42. eventNamespace = '.' + namespace,
  43. moduleNamespace = namespace + '-module',
  44. $module = $(this),
  45. $toastBox = $('<div/>',{'class':settings.className.box}),
  46. $toast = $('<div/>'),
  47. $progress = $('<div/>',{'class':settings.className.progress+' '+settings.class}),
  48. $progressBar = $('<div/>',{'class':'bar'}),
  49. $close = $('<i/>',{'class':'close icon'}),
  50. $context = (settings.context)
  51. ? $(settings.context)
  52. : $('body'),
  53. element = this,
  54. instance = $module.data(moduleNamespace),
  55. module
  56. ;
  57. module = {
  58. initialize: function() {
  59. module.verbose('Initializing element');
  60. if(typeof settings.showProgress !== 'string' || ['top','bottom'].indexOf(settings.showProgress) === -1 ) {
  61. settings.showProgress = false;
  62. }
  63. if (!module.has.container()) {
  64. module.create.container();
  65. }
  66. module.create.toast();
  67. module.bind.events();
  68. if(settings.displayTime > 0) {
  69. module.closeTimer = setTimeout(module.close, settings.displayTime+(!!settings.showProgress ? 300 : 0));
  70. }
  71. module.show();
  72. },
  73. destroy: function() {
  74. module.debug('Removing toast', $toast);
  75. $toast.remove();
  76. $toast = undefined;
  77. settings.onRemove.call($toast, element);
  78. },
  79. show: function(callback) {
  80. callback = callback || function(){};
  81. module.debug('Showing toast');
  82. if(settings.onShow.call($toast, element) === false) {
  83. module.debug('onShow callback returned false, cancelling toast animation');
  84. return;
  85. }
  86. module.animate.show(callback);
  87. },
  88. close: function(callback) {
  89. if(module.closeTimer) {
  90. clearTimeout(module.closeTimer);
  91. }
  92. callback = callback || function(){};
  93. module.remove.visible();
  94. module.unbind.events();
  95. module.animate.close(callback);
  96. },
  97. create: {
  98. container: function() {
  99. module.verbose('Creating container');
  100. $context.append('<div class="ui ' + settings.position + ' ' + className.container + '"></div>');
  101. },
  102. toast: function() {
  103. var $content = $('<div/>').addClass(className.content);
  104. module.verbose('Creating toast');
  105. if(settings.closeIcon) {
  106. $toast.append($close);
  107. $toast.css('cursor','default');
  108. }
  109. var iconClass = typeof settings.showIcon === 'string' ? settings.showIcon : settings.showIcon && settings.icons[settings.class] ? settings.icons[settings.class] : '';
  110. if (iconClass != '') {
  111. var $icon = $('<i/>').addClass(iconClass + ' ' + className.icon);
  112. $toast
  113. .addClass(className.icon)
  114. .append($icon)
  115. ;
  116. }
  117. if (settings.title !== '') {
  118. var
  119. $title = $('<div/>')
  120. .addClass(className.title)
  121. .text(settings.title)
  122. ;
  123. $content.append($title);
  124. }
  125. $content.append($('<div/>').html(settings.message));
  126. $toast
  127. .addClass(settings.class + ' ' + className.toast)
  128. .append($content)
  129. ;
  130. $toast.css('opacity', settings.opacity);
  131. if(settings.compact || $toast.hasClass('compact')) {
  132. $toastBox.addClass('compact');
  133. }
  134. if($toast.hasClass('toast') && !$toast.hasClass('inverted')){
  135. $progress.addClass('inverted');
  136. } else {
  137. $progress.removeClass('inverted');
  138. }
  139. $toast = $toastBox.append($toast);
  140. if(!!settings.showProgress && settings.displayTime > 0){
  141. $progress
  142. .addClass(settings.showProgress)
  143. .append($progressBar);
  144. if ($progress.hasClass('top')) {
  145. $toast.prepend($progress);
  146. } else {
  147. $toast.append($progress);
  148. }
  149. $progressBar.css('transition','width '+(settings.displayTime/1000)+'s linear');
  150. $progressBar.width(settings.progressUp?'0%':'100%');
  151. setTimeout(function() {
  152. if(typeof $progress !== 'undefined'){
  153. $progressBar.width(settings.progressUp?'100%':'0%');
  154. }
  155. },300);
  156. }
  157. if (settings.newestOnTop) {
  158. $toast.prependTo(module.get.container());
  159. }
  160. else {
  161. $toast.appendTo(module.get.container());
  162. }
  163. }
  164. },
  165. bind: {
  166. events: function() {
  167. module.debug('Binding events to toast');
  168. (settings.closeIcon ? $close : $toast)
  169. .on('click' + eventNamespace, module.event.click)
  170. ;
  171. }
  172. },
  173. unbind: {
  174. events: function() {
  175. module.debug('Unbinding events to toast');
  176. (settings.closeIcon ? $close : $toast)
  177. .off('click' + eventNamespace)
  178. ;
  179. }
  180. },
  181. animate: {
  182. show: function(callback) {
  183. callback = $.isFunction(callback) ? callback : function(){};
  184. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  185. module.set.visible();
  186. $toast
  187. .transition({
  188. animation : settings.transition.showMethod + ' in',
  189. queue : false,
  190. debug : settings.debug,
  191. verbose : settings.verbose,
  192. duration : settings.transition.showDuration,
  193. onComplete : function() {
  194. callback.call($toast, element);
  195. settings.onVisible.call($toast, element);
  196. }
  197. })
  198. ;
  199. }
  200. else {
  201. module.error(error.noTransition);
  202. }
  203. },
  204. close: function(callback) {
  205. callback = $.isFunction(callback) ? callback : function(){};
  206. module.debug('Closing toast');
  207. if(settings.onHide.call($toast, element) === false) {
  208. module.debug('onHide callback returned false, cancelling toast animation');
  209. return;
  210. }
  211. if(settings.transition && $.fn.transition !== undefined && $module.transition('is supported')) {
  212. $toast
  213. .transition({
  214. animation : settings.transition.hideMethod + ' out',
  215. queue : false,
  216. duration : settings.transition.hideDuration,
  217. debug : settings.debug,
  218. verbose : settings.verbose,
  219. onBeforeHide: function(callback){
  220. callback = $.isFunction(callback)?callback : function(){};
  221. if(settings.transition.closeEasing !== ''){
  222. $toast.css('opacity',0);
  223. $toast.wrap('<div/>').parent().slideUp(500,settings.transition.closeEasing,function(){
  224. $toast.parent().remove();
  225. callback.call($toast);
  226. });
  227. } else {
  228. callback.call($toast);
  229. }
  230. },
  231. onComplete : function() {
  232. module.destroy();
  233. callback.call($toast, element);
  234. settings.onHidden.call($toast, element);
  235. }
  236. })
  237. ;
  238. }
  239. else {
  240. module.error(error.noTransition);
  241. }
  242. }
  243. },
  244. has: {
  245. container: function() {
  246. module.verbose('Determining if there is already a container');
  247. return ($context.find(module.helpers.toClass(settings.position) + selector.container).length > 0);
  248. }
  249. },
  250. get: {
  251. container: function() {
  252. return ($context.find(module.helpers.toClass(settings.position) + selector.container)[0]);
  253. }
  254. },
  255. set: {
  256. visible: function() {
  257. $toast.addClass(className.visible);
  258. }
  259. },
  260. remove: {
  261. visible: function() {
  262. $toast.removeClass(className.visible);
  263. }
  264. },
  265. event: {
  266. click: function() {
  267. settings.onClick.call($toast, element);
  268. module.close();
  269. }
  270. },
  271. helpers: {
  272. toClass: function(selector) {
  273. var
  274. classes = selector.split(' '),
  275. result = ''
  276. ;
  277. classes.forEach(function (element) {
  278. result += '.' + element;
  279. });
  280. return result;
  281. }
  282. },
  283. setting: function(name, value) {
  284. module.debug('Changing setting', name, value);
  285. if( $.isPlainObject(name) ) {
  286. $.extend(true, settings, name);
  287. }
  288. else if(value !== undefined) {
  289. if($.isPlainObject(settings[name])) {
  290. $.extend(true, settings[name], value);
  291. }
  292. else {
  293. settings[name] = value;
  294. }
  295. }
  296. else {
  297. return settings[name];
  298. }
  299. },
  300. internal: function(name, value) {
  301. if( $.isPlainObject(name) ) {
  302. $.extend(true, module, name);
  303. }
  304. else if(value !== undefined) {
  305. module[name] = value;
  306. }
  307. else {
  308. return module[name];
  309. }
  310. },
  311. debug: function() {
  312. if(!settings.silent && settings.debug) {
  313. if(settings.performance) {
  314. module.performance.log(arguments);
  315. }
  316. else {
  317. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  318. module.debug.apply(console, arguments);
  319. }
  320. }
  321. },
  322. verbose: function() {
  323. if(!settings.silent && settings.verbose && settings.debug) {
  324. if(settings.performance) {
  325. module.performance.log(arguments);
  326. }
  327. else {
  328. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  329. module.verbose.apply(console, arguments);
  330. }
  331. }
  332. },
  333. error: function() {
  334. if(!settings.silent) {
  335. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  336. module.error.apply(console, arguments);
  337. }
  338. },
  339. performance: {
  340. log: function(message) {
  341. var
  342. currentTime,
  343. executionTime,
  344. previousTime
  345. ;
  346. if(settings.performance) {
  347. currentTime = new Date().getTime();
  348. previousTime = time || currentTime;
  349. executionTime = currentTime - previousTime;
  350. time = currentTime;
  351. performance.push({
  352. 'Name' : message[0],
  353. 'Arguments' : [].slice.call(message, 1) || '',
  354. 'Element' : element,
  355. 'Execution Time' : executionTime
  356. });
  357. }
  358. clearTimeout(module.performance.timer);
  359. module.performance.timer = setTimeout(module.performance.display, 500);
  360. },
  361. display: function() {
  362. var
  363. title = settings.name + ':',
  364. totalTime = 0
  365. ;
  366. time = false;
  367. clearTimeout(module.performance.timer);
  368. $.each(performance, function(index, data) {
  369. totalTime += data['Execution Time'];
  370. });
  371. title += ' ' + totalTime + 'ms';
  372. if(moduleSelector) {
  373. title += ' \'' + moduleSelector + '\'';
  374. }
  375. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  376. console.groupCollapsed(title);
  377. if(console.table) {
  378. console.table(performance);
  379. }
  380. else {
  381. $.each(performance, function(index, data) {
  382. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  383. });
  384. }
  385. console.groupEnd();
  386. }
  387. performance = [];
  388. }
  389. },
  390. invoke: function(query, passedArguments, context) {
  391. var
  392. object = instance,
  393. maxDepth,
  394. found,
  395. response
  396. ;
  397. passedArguments = passedArguments || queryArguments;
  398. context = element || context;
  399. if(typeof query == 'string' && object !== undefined) {
  400. query = query.split(/[\. ]/);
  401. maxDepth = query.length - 1;
  402. $.each(query, function(depth, value) {
  403. var camelCaseValue = (depth != maxDepth)
  404. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  405. : query
  406. ;
  407. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  408. object = object[camelCaseValue];
  409. }
  410. else if( object[camelCaseValue] !== undefined ) {
  411. found = object[camelCaseValue];
  412. return false;
  413. }
  414. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  415. object = object[value];
  416. }
  417. else if( object[value] !== undefined ) {
  418. found = object[value];
  419. return false;
  420. }
  421. else {
  422. module.error(error.method, query);
  423. return false;
  424. }
  425. });
  426. }
  427. if ( $.isFunction( found ) ) {
  428. response = found.apply(context, passedArguments);
  429. }
  430. else if(found !== undefined) {
  431. response = found;
  432. }
  433. if(Array.isArray(returnedValue)) {
  434. returnedValue.push(response);
  435. }
  436. else if(returnedValue !== undefined) {
  437. returnedValue = [returnedValue, response];
  438. }
  439. else if(response !== undefined) {
  440. returnedValue = response;
  441. }
  442. return found;
  443. }
  444. };
  445. if(methodInvoked) {
  446. if(instance === undefined) {
  447. module.initialize();
  448. }
  449. module.invoke(query);
  450. }
  451. else {
  452. if(instance !== undefined) {
  453. instance.invoke('destroy');
  454. }
  455. module.initialize();
  456. }
  457. })
  458. ;
  459. return (returnedValue !== undefined)
  460. ? returnedValue
  461. : this
  462. ;
  463. };
  464. $.fn.toast.settings = {
  465. name : 'Toast',
  466. namespace : 'toast',
  467. silent : false,
  468. debug : false,
  469. verbose : false,
  470. performance : true,
  471. context : 'body',
  472. position : 'top right',
  473. class : 'info',
  474. title : '',
  475. message : '',
  476. displayTime : 3000, // set to zero to require manually dismissal, otherwise hides on its own
  477. showIcon : true,
  478. newestOnTop : false,
  479. showProgress : false,
  480. progressUp : true, //if false, the bar will start at 100% and decrease to 0%
  481. opacity : 1,
  482. compact : true,
  483. closeIcon : false,
  484. // transition settings
  485. transition : {
  486. showMethod : 'scale',
  487. showDuration : 500,
  488. hideMethod : 'scale',
  489. hideDuration : 500,
  490. closeEasing : 'easeOutBounce' //Set to empty string to stack the closed toast area immediately (old behaviour)
  491. },
  492. error: {
  493. method : 'The method you called is not defined.',
  494. noTransition : 'This module requires ui transitions <https://github.com/Semantic-Org/UI-Transition>'
  495. },
  496. className : {
  497. container : 'toast-container',
  498. box : 'toast-box',
  499. progress : 'ui attached active progress',
  500. toast : 'ui toast',
  501. icon : 'icon',
  502. visible : 'visible',
  503. content : 'content',
  504. title : 'header'
  505. },
  506. icons : {
  507. info : 'info',
  508. success : 'checkmark',
  509. warning : 'warning',
  510. error : 'times'
  511. },
  512. selector : {
  513. container : '.toast-container',
  514. box : '.toast-box',
  515. toast : '.ui.toast'
  516. },
  517. // callbacks
  518. onShow : function(){},
  519. onVisible : function(){},
  520. onClick : function(){},
  521. onHide : function(){},
  522. onHidden : function(){},
  523. onRemove : function(){},
  524. };
  525. $.extend( $.easing, {
  526. easeOutBounce: function (x, t, b, c, d) {
  527. if ((t/=d) < (1/2.75)) {
  528. return c*(7.5625*t*t) + b;
  529. } else if (t < (2/2.75)) {
  530. return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
  531. } else if (t < (2.5/2.75)) {
  532. return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
  533. } else {
  534. return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
  535. }
  536. }
  537. });
  538. })( jQuery, window, document );