You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

546 lines
16 KiB

10 years ago
10 years ago
  1. /*
  2. * # Semantic - Nag
  3. * http://github.com/semantic-org/semantic-ui/
  4. *
  5. *
  6. * Copyright 2014 Contributor
  7. * Released under the MIT license
  8. * http://opensource.org/licenses/MIT
  9. *
  10. */
  11. ;(function ($, window, document, undefined) {
  12. "use strict";
  13. $.fn.nag = function(parameters) {
  14. var
  15. $allModules = $(this),
  16. moduleSelector = $allModules.selector || '',
  17. time = new Date().getTime(),
  18. performance = [],
  19. query = arguments[0],
  20. methodInvoked = (typeof query == 'string'),
  21. queryArguments = [].slice.call(arguments, 1),
  22. returnedValue
  23. ;
  24. $(this)
  25. .each(function() {
  26. var
  27. settings = $.extend(true, {}, $.fn.nag.settings, parameters),
  28. className = settings.className,
  29. selector = settings.selector,
  30. error = settings.error,
  31. namespace = settings.namespace,
  32. eventNamespace = '.' + namespace,
  33. moduleNamespace = namespace + '-module',
  34. $module = $(this),
  35. $close = $module.find(selector.close),
  36. $context = $(settings.context),
  37. element = this,
  38. instance = $module.data(moduleNamespace),
  39. moduleOffset,
  40. moduleHeight,
  41. contextWidth,
  42. contextHeight,
  43. contextOffset,
  44. yOffset,
  45. yPosition,
  46. timer,
  47. module,
  48. requestAnimationFrame = window.requestAnimationFrame
  49. || window.mozRequestAnimationFrame
  50. || window.webkitRequestAnimationFrame
  51. || window.msRequestAnimationFrame
  52. || function(callback) { setTimeout(callback, 0); }
  53. ;
  54. module = {
  55. initialize: function() {
  56. module.verbose('Initializing element');
  57. // calculate module offset once
  58. moduleOffset = $module.offset();
  59. moduleHeight = $module.outerHeight();
  60. contextWidth = $context.outerWidth();
  61. contextHeight = $context.outerHeight();
  62. contextOffset = $context.offset();
  63. $module
  64. .data(moduleNamespace, module)
  65. ;
  66. $close
  67. .on('click' + eventNamespace, module.dismiss)
  68. ;
  69. // lets avoid javascript if we dont need to reposition
  70. if(settings.context == window && settings.position == 'fixed') {
  71. $module
  72. .addClass(className.fixed)
  73. ;
  74. }
  75. if(settings.sticky) {
  76. module.verbose('Adding scroll events');
  77. // retrigger on scroll for absolute
  78. if(settings.position == 'absolute') {
  79. $context
  80. .on('scroll' + eventNamespace, module.event.scroll)
  81. .on('resize' + eventNamespace, module.event.scroll)
  82. ;
  83. }
  84. // fixed is always relative to window
  85. else {
  86. $(window)
  87. .on('scroll' + eventNamespace, module.event.scroll)
  88. .on('resize' + eventNamespace, module.event.scroll)
  89. ;
  90. }
  91. // fire once to position on init
  92. $.proxy(module.event.scroll, this)();
  93. }
  94. if(settings.displayTime > 0) {
  95. setTimeout(module.hide, settings.displayTime);
  96. }
  97. if(module.should.show()) {
  98. if( !$module.is(':visible') ) {
  99. module.show();
  100. }
  101. }
  102. else {
  103. module.hide();
  104. }
  105. },
  106. destroy: function() {
  107. module.verbose('Destroying instance');
  108. $module
  109. .removeData(moduleNamespace)
  110. .off(eventNamespace)
  111. ;
  112. if(settings.sticky) {
  113. $context
  114. .off(eventNamespace)
  115. ;
  116. }
  117. },
  118. refresh: function() {
  119. module.debug('Refreshing cached calculations');
  120. moduleOffset = $module.offset();
  121. moduleHeight = $module.outerHeight();
  122. contextWidth = $context.outerWidth();
  123. contextHeight = $context.outerHeight();
  124. contextOffset = $context.offset();
  125. },
  126. show: function() {
  127. module.debug('Showing nag', settings.animation.show);
  128. if(settings.animation.show == 'fade') {
  129. $module
  130. .fadeIn(settings.duration, settings.easing)
  131. ;
  132. }
  133. else {
  134. $module
  135. .slideDown(settings.duration, settings.easing)
  136. ;
  137. }
  138. },
  139. hide: function() {
  140. module.debug('Showing nag', settings.animation.hide);
  141. if(settings.animation.show == 'fade') {
  142. $module
  143. .fadeIn(settings.duration, settings.easing)
  144. ;
  145. }
  146. else {
  147. $module
  148. .slideUp(settings.duration, settings.easing)
  149. ;
  150. }
  151. },
  152. onHide: function() {
  153. module.debug('Removing nag', settings.animation.hide);
  154. $module.remove();
  155. if (settings.onHide) {
  156. settings.onHide();
  157. }
  158. },
  159. stick: function() {
  160. module.refresh();
  161. if(settings.position == 'fixed') {
  162. var
  163. windowScroll = $(window).prop('pageYOffset') || $(window).scrollTop(),
  164. fixedOffset = ( $module.hasClass(className.bottom) )
  165. ? contextOffset.top + (contextHeight - moduleHeight) - windowScroll
  166. : contextOffset.top - windowScroll
  167. ;
  168. $module
  169. .css({
  170. position : 'fixed',
  171. top : fixedOffset,
  172. left : contextOffset.left,
  173. width : contextWidth - settings.scrollBarWidth
  174. })
  175. ;
  176. }
  177. else {
  178. $module
  179. .css({
  180. top : yPosition
  181. })
  182. ;
  183. }
  184. },
  185. unStick: function() {
  186. $module
  187. .css({
  188. top : ''
  189. })
  190. ;
  191. },
  192. dismiss: function(event) {
  193. if(settings.storageMethod) {
  194. module.storage.set(settings.storedKey, settings.storedValue);
  195. }
  196. module.hide();
  197. event.stopImmediatePropagation();
  198. event.preventDefault();
  199. },
  200. should: {
  201. show: function() {
  202. if(settings.persist) {
  203. module.debug('Persistent nag is set, can show nag');
  204. return true;
  205. }
  206. if(module.storage.get(settings.storedKey) != settings.storedValue) {
  207. module.debug('Stored value is not set, can show nag', module.storage.get(settings.storedKey));
  208. return true;
  209. }
  210. module.debug('Stored value is set, cannot show nag', module.storage.get(settings.storedKey));
  211. return false;
  212. },
  213. stick: function() {
  214. yOffset = $context.prop('pageYOffset') || $context.scrollTop();
  215. yPosition = ( $module.hasClass(className.bottom) )
  216. ? (contextHeight - $module.outerHeight() ) + yOffset
  217. : yOffset
  218. ;
  219. // absolute position calculated when y offset met
  220. if(yPosition > moduleOffset.top) {
  221. return true;
  222. }
  223. else if(settings.position == 'fixed') {
  224. return true;
  225. }
  226. return false;
  227. }
  228. },
  229. storage: {
  230. set: function(key, value) {
  231. module.debug('Setting stored value', key, value, settings.storageMethod);
  232. if(settings.storageMethod == 'local' && window.store !== undefined) {
  233. window.store.set(key, value);
  234. }
  235. // store by cookie
  236. else if($.cookie !== undefined) {
  237. $.cookie(key, value);
  238. }
  239. else {
  240. module.error(error.noStorage);
  241. }
  242. },
  243. get: function(key) {
  244. module.debug('Getting stored value', key, settings.storageMethod);
  245. if(settings.storageMethod == 'local' && window.store !== undefined) {
  246. return window.store.get(key);
  247. }
  248. // get by cookie
  249. else if($.cookie !== undefined) {
  250. return $.cookie(key);
  251. }
  252. else {
  253. module.error(error.noStorage);
  254. }
  255. }
  256. },
  257. event: {
  258. scroll: function() {
  259. if(timer !== undefined) {
  260. clearTimeout(timer);
  261. }
  262. timer = setTimeout(function() {
  263. if(module.should.stick() ) {
  264. requestAnimationFrame(module.stick);
  265. }
  266. else {
  267. module.unStick();
  268. }
  269. }, settings.lag);
  270. }
  271. },
  272. setting: function(name, value) {
  273. module.debug('Changing setting', name, value);
  274. if( $.isPlainObject(name) ) {
  275. $.extend(true, settings, name);
  276. }
  277. else if(value !== undefined) {
  278. settings[name] = value;
  279. }
  280. else {
  281. return settings[name];
  282. }
  283. },
  284. internal: function(name, value) {
  285. module.debug('Changing internal', name, value);
  286. if(value !== undefined) {
  287. if( $.isPlainObject(name) ) {
  288. $.extend(true, module, name);
  289. }
  290. else {
  291. module[name] = value;
  292. }
  293. }
  294. else {
  295. return module[name];
  296. }
  297. },
  298. debug: function() {
  299. if(settings.debug) {
  300. if(settings.performance) {
  301. module.performance.log(arguments);
  302. }
  303. else {
  304. module.debug = Function.prototype.bind.call(console.info, console, settings.name + ':');
  305. module.debug.apply(console, arguments);
  306. }
  307. }
  308. },
  309. verbose: function() {
  310. if(settings.verbose && settings.debug) {
  311. if(settings.performance) {
  312. module.performance.log(arguments);
  313. }
  314. else {
  315. module.verbose = Function.prototype.bind.call(console.info, console, settings.name + ':');
  316. module.verbose.apply(console, arguments);
  317. }
  318. }
  319. },
  320. error: function() {
  321. module.error = Function.prototype.bind.call(console.error, console, settings.name + ':');
  322. module.error.apply(console, arguments);
  323. },
  324. performance: {
  325. log: function(message) {
  326. var
  327. currentTime,
  328. executionTime,
  329. previousTime
  330. ;
  331. if(settings.performance) {
  332. currentTime = new Date().getTime();
  333. previousTime = time || currentTime;
  334. executionTime = currentTime - previousTime;
  335. time = currentTime;
  336. performance.push({
  337. 'Element' : element,
  338. 'Name' : message[0],
  339. 'Arguments' : [].slice.call(message, 1) || '',
  340. 'Execution Time' : executionTime
  341. });
  342. }
  343. clearTimeout(module.performance.timer);
  344. module.performance.timer = setTimeout(module.performance.display, 100);
  345. },
  346. display: function() {
  347. var
  348. title = settings.name + ':',
  349. totalTime = 0
  350. ;
  351. time = false;
  352. clearTimeout(module.performance.timer);
  353. $.each(performance, function(index, data) {
  354. totalTime += data['Execution Time'];
  355. });
  356. title += ' ' + totalTime + 'ms';
  357. if(moduleSelector) {
  358. title += ' \'' + moduleSelector + '\'';
  359. }
  360. if($allModules.size() > 1) {
  361. title += ' ' + '(' + $allModules.size() + ')';
  362. }
  363. if( (console.group !== undefined || console.table !== undefined) && performance.length > 0) {
  364. console.groupCollapsed(title);
  365. if(console.table) {
  366. console.table(performance);
  367. }
  368. else {
  369. $.each(performance, function(index, data) {
  370. console.log(data['Name'] + ': ' + data['Execution Time']+'ms');
  371. });
  372. }
  373. console.groupEnd();
  374. }
  375. performance = [];
  376. }
  377. },
  378. invoke: function(query, passedArguments, context) {
  379. var
  380. object = instance,
  381. maxDepth,
  382. found,
  383. response
  384. ;
  385. passedArguments = passedArguments || queryArguments;
  386. context = element || context;
  387. if(typeof query == 'string' && object !== undefined) {
  388. query = query.split(/[\. ]/);
  389. maxDepth = query.length - 1;
  390. $.each(query, function(depth, value) {
  391. var camelCaseValue = (depth != maxDepth)
  392. ? value + query[depth + 1].charAt(0).toUpperCase() + query[depth + 1].slice(1)
  393. : query
  394. ;
  395. if( $.isPlainObject( object[camelCaseValue] ) && (depth != maxDepth) ) {
  396. object = object[camelCaseValue];
  397. }
  398. else if( object[camelCaseValue] !== undefined ) {
  399. found = object[camelCaseValue];
  400. return false;
  401. }
  402. else if( $.isPlainObject( object[value] ) && (depth != maxDepth) ) {
  403. object = object[value];
  404. }
  405. else if( object[value] !== undefined ) {
  406. found = object[value];
  407. return false;
  408. }
  409. else {
  410. return false;
  411. }
  412. });
  413. }
  414. if ( $.isFunction( found ) ) {
  415. response = found.apply(context, passedArguments);
  416. }
  417. else if(found !== undefined) {
  418. response = found;
  419. }
  420. if($.isArray(returnedValue)) {
  421. returnedValue.push(response);
  422. }
  423. else if(returnedValue !== undefined) {
  424. returnedValue = [returnedValue, response];
  425. }
  426. else if(response !== undefined) {
  427. returnedValue = response;
  428. }
  429. return found;
  430. }
  431. };
  432. if(methodInvoked) {
  433. if(instance === undefined) {
  434. module.initialize();
  435. }
  436. module.invoke(query);
  437. }
  438. else {
  439. if(instance !== undefined) {
  440. module.destroy();
  441. }
  442. module.initialize();
  443. }
  444. })
  445. ;
  446. return (returnedValue !== undefined)
  447. ? returnedValue
  448. : this
  449. ;
  450. };
  451. $.fn.nag.settings = {
  452. name : 'Nag',
  453. debug : false,
  454. verbose : true,
  455. performance : true,
  456. namespace : 'Nag',
  457. // allows cookie to be overriden
  458. persist : false,
  459. // set to zero to manually dismiss, otherwise hides on its own
  460. displayTime : 0,
  461. animation : {
  462. show: 'slide',
  463. hide: 'slide'
  464. },
  465. // method of stickyness
  466. position : 'fixed',
  467. scrollBarWidth : 18,
  468. // type of storage to use
  469. storageMethod : 'cookie',
  470. // value to store in dismissed localstorage/cookie
  471. storedKey : 'nag',
  472. storedValue : 'dismiss',
  473. // need to calculate stickyness on scroll
  474. sticky : false,
  475. // how often to check scroll event
  476. lag : 0,
  477. // context for scroll event
  478. context : window,
  479. error: {
  480. noStorage : 'Neither $.cookie or store is defined. A storage solution is required for storing state',
  481. method : 'The method you called is not defined.'
  482. },
  483. className : {
  484. bottom : 'bottom',
  485. fixed : 'fixed'
  486. },
  487. selector : {
  488. close: '.icon.close'
  489. },
  490. speed : 500,
  491. easing : 'easeOutQuad',
  492. onHide: function() {}
  493. };
  494. })( jQuery, window , document );