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.

340 lines
9.6 KiB

  1. /* ******************************
  2. Nag
  3. Author: Jack Lukic
  4. Notes: First Commit July 19, 2012
  5. Simple fixed position nag
  6. ****************************** */
  7. ;(function ($, window, document, undefined) {
  8. $.fn.nag = function(parameters) {
  9. var
  10. settings = $.extend(true, {}, $.fn.nag.settings, parameters),
  11. // hoist arguments
  12. moduleArguments = arguments || false
  13. ;
  14. $(this)
  15. .each(function() {
  16. var
  17. $module = $(this),
  18. $close = $module.find(settings.selector.close),
  19. $context = $(settings.context),
  20. instance = $module.data('module'),
  21. className = settings.className,
  22. moduleOffset,
  23. moduleHeight,
  24. contextWidth,
  25. contextHeight,
  26. contextOffset,
  27. yOffset,
  28. yPosition,
  29. timer,
  30. module,
  31. requestAnimationFrame = window.requestAnimationFrame
  32. || window.mozRequestAnimationFrame
  33. || window.webkitRequestAnimationFrame
  34. || window.msRequestAnimationFrame
  35. || function(callback) { setTimeout(callback, 0); }
  36. ;
  37. module = {
  38. initialize: function() {
  39. // calculate module offset once
  40. moduleOffset = $module.offset();
  41. moduleHeight = $module.outerHeight();
  42. contextWidth = $context.outerWidth();
  43. contextHeight = $context.outerHeight();
  44. contextOffset = $context.offset();
  45. $module
  46. .data('module', module)
  47. ;
  48. $close
  49. .on('click', module.dismiss)
  50. ;
  51. // lets avoid javascript if we dont need to reposition
  52. if(settings.context == window && settings.position == 'fixed') {
  53. $module
  54. .addClass(className.fixed)
  55. ;
  56. }
  57. if(settings.sticky) {
  58. // retrigger on scroll for absolute
  59. if(settings.position == 'absolute') {
  60. $context
  61. .on('scroll resize', module.event.scroll)
  62. ;
  63. }
  64. // fixed is always relative to window
  65. else {
  66. $(window)
  67. .on('scroll resize', module.event.scroll)
  68. ;
  69. }
  70. // fire once to position on init
  71. $.proxy(module.event.scroll, this)();
  72. }
  73. if(settings.displayTime > 0) {
  74. setTimeout(module.hide, settings.displayTime);
  75. }
  76. if(module.should.show()) {
  77. if( !$module.is(':visible') ) {
  78. module.show();
  79. }
  80. }
  81. else {
  82. module.hide();
  83. }
  84. },
  85. refresh: function() {
  86. moduleOffset = $module.offset();
  87. moduleHeight = $module.outerHeight();
  88. contextWidth = $context.outerWidth();
  89. contextHeight = $context.outerHeight();
  90. contextOffset = $context.offset();
  91. },
  92. show: function() {
  93. if($.fn.popIn !== undefined) {
  94. $module
  95. .popIn(settings.duration)
  96. ;
  97. }
  98. else {
  99. $module
  100. .fadeIn(settings.duration, settings.easing)
  101. ;
  102. }
  103. },
  104. hide: function() {
  105. $module
  106. .fadeOut(settings.duration, settings.easing, this.onHide)
  107. ;
  108. },
  109. onHide: function() {
  110. $module.remove();
  111. if (settings.onHide) {
  112. settings.onHide();
  113. };
  114. },
  115. stick: function() {
  116. module.refresh();
  117. if(settings.position == 'fixed') {
  118. var
  119. windowScroll = $(window).prop('pageYOffset') || $(window).scrollTop(),
  120. fixedOffset = ( $module.hasClass(className.bottom) )
  121. ? contextOffset.top + (contextHeight - moduleHeight) - windowScroll
  122. : contextOffset.top - windowScroll
  123. ;
  124. $module
  125. .css({
  126. position : 'fixed',
  127. top : fixedOffset,
  128. left : contextOffset.left,
  129. width : contextWidth - settings.scrollBarWidth
  130. })
  131. ;
  132. }
  133. else {
  134. $module
  135. .css({
  136. top : yPosition
  137. })
  138. ;
  139. }
  140. },
  141. unStick: function() {
  142. $module
  143. .css({
  144. top : ''
  145. })
  146. ;
  147. },
  148. dismiss: function(event) {
  149. if(settings.storageMethod) {
  150. module.storage.set(settings.storedKey, settings.storedValue);
  151. }
  152. module.hide();
  153. event.stopImmediatePropagation();
  154. event.preventDefault();
  155. },
  156. should: {
  157. show: function() {
  158. if( !settings.persist && module.storage.get(settings.storedKey) == settings.storedValue) {
  159. return false;
  160. }
  161. return true;
  162. },
  163. stick: function() {
  164. yOffset = $context.prop('pageYOffset') || $context.scrollTop();
  165. yPosition = ( $module.hasClass(className.bottom) )
  166. ? (contextHeight - $module.outerHeight() ) + yOffset
  167. : yOffset
  168. ;
  169. // absolute position calculated when y offset met
  170. if(yPosition > moduleOffset.top) {
  171. return true;
  172. }
  173. else if(settings.position == 'fixed') {
  174. return true;
  175. }
  176. return false;
  177. }
  178. },
  179. storage: {
  180. set: function(key, value) {
  181. if(settings.storageMethod == 'local' && window.store !== undefined) {
  182. window.store.set(key, value);
  183. }
  184. // store by cookie
  185. else if($.cookie !== undefined) {
  186. $.cookie(key, value);
  187. }
  188. else {
  189. module.error(settings.errors.noStorage);
  190. }
  191. },
  192. get: function(key) {
  193. if(settings.storageMethod == 'local' && window.store !== undefined) {
  194. return window.store.get(key);
  195. }
  196. // get by cookie
  197. else if($.cookie !== undefined) {
  198. return $.cookie(key);
  199. }
  200. else {
  201. module.error(settings.errors.noStorage);
  202. }
  203. }
  204. },
  205. event: {
  206. scroll: function() {
  207. if(timer !== undefined) {
  208. clearTimeout(timer);
  209. }
  210. timer = setTimeout(function() {
  211. if(module.should.stick() ) {
  212. requestAnimationFrame(module.stick);
  213. }
  214. else {
  215. module.unStick();
  216. }
  217. }, settings.lag);
  218. }
  219. },
  220. error: function(error) {
  221. console.log('Nag Module:' + error);
  222. },
  223. // allows for dot notation method calls
  224. invoke: function(methodName, context, methodArguments) {
  225. var
  226. method
  227. ;
  228. methodArguments = methodArguments || Array.prototype.slice.call( arguments, 2 );
  229. if(typeof methodName == 'string' && instance !== undefined) {
  230. methodName = methodName.split('.');
  231. $.each(methodName, function(index, name) {
  232. if( $.isPlainObject( instance[name] ) ) {
  233. instance = instance[name];
  234. return true;
  235. }
  236. else if( $.isFunction( instance[name] ) ) {
  237. method = instance[name];
  238. return true;
  239. }
  240. module.error(settings.errors.method);
  241. return false;
  242. });
  243. }
  244. if ( $.isFunction( method ) ) {
  245. return method.apply(context, methodArguments);
  246. }
  247. // return retrieved variable or chain
  248. return method;
  249. }
  250. };
  251. if(instance !== undefined && moduleArguments) {
  252. if(moduleArguments[0] == 'invoke') {
  253. moduleArguments = Array.prototype.slice.call( moduleArguments, 1 );
  254. }
  255. return module.invoke(moduleArguments[0], this, Array.prototype.slice.call( moduleArguments, 1 ) );
  256. }
  257. module.initialize();
  258. })
  259. ;
  260. return this;
  261. };
  262. $.fn.nag.settings = {
  263. // allows cookie to be overriden
  264. persist : false,
  265. // set to zero to manually dismiss, otherwise hides on its own
  266. displayTime : 0,
  267. // method of stickyness
  268. position : 'fixed',
  269. scrollBarWidth : 18,
  270. // type of storage to use
  271. storageMethod : 'cookie',
  272. // value to store in dismissed localstorage/cookie
  273. storedKey : 'nag',
  274. storedValue : 'dismiss',
  275. // need to calculate stickyness on scroll
  276. sticky : false,
  277. // how often to check scroll event
  278. lag : 0,
  279. // context for scroll event
  280. context : window,
  281. errors: {
  282. noStorage : 'Neither $.cookie or store is defined. A storage solution is required for storing state'
  283. },
  284. className : {
  285. bottom : 'bottom',
  286. fixed : 'fixed'
  287. },
  288. selector : {
  289. close: '.icon.close'
  290. },
  291. speed : 500,
  292. easing : 'easeOutQuad',
  293. onHide: function() {}
  294. };
  295. })( jQuery, window , document );