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.

543 lines
16 KiB

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