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.

328 lines
9.3 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)
  107. ;
  108. },
  109. stick: function() {
  110. module.refresh();
  111. if(settings.position == 'fixed') {
  112. var
  113. windowScroll = $(window).prop('pageYOffset') || $(window).scrollTop(),
  114. fixedOffset = ( $module.hasClass(className.bottom) )
  115. ? contextOffset.top + (contextHeight - moduleHeight) - windowScroll
  116. : contextOffset.top - windowScroll
  117. ;
  118. $module
  119. .css({
  120. position : 'fixed',
  121. top : fixedOffset,
  122. left : contextOffset.left,
  123. width : contextWidth - settings.scrollBarWidth
  124. })
  125. ;
  126. }
  127. else {
  128. $module
  129. .css({
  130. top : yPosition
  131. })
  132. ;
  133. }
  134. },
  135. unStick: function() {
  136. $module
  137. .css({
  138. top : ''
  139. })
  140. ;
  141. },
  142. dismiss: function() {
  143. if(settings.storageMethod) {
  144. module.storage.set(settings.storedKey, settings.storedValue);
  145. }
  146. module.hide();
  147. },
  148. should: {
  149. show: function() {
  150. if( !settings.persist && module.storage.get(settings.storedKey) == settings.storedValue) {
  151. return false;
  152. }
  153. return true;
  154. },
  155. stick: function() {
  156. yOffset = $context.prop('pageYOffset') || $context.scrollTop();
  157. yPosition = ( $module.hasClass(className.bottom) )
  158. ? (contextHeight - $module.outerHeight() ) + yOffset
  159. : yOffset
  160. ;
  161. // absolute position calculated when y offset met
  162. if(yPosition > moduleOffset.top) {
  163. return true;
  164. }
  165. else if(settings.position == 'fixed') {
  166. return true;
  167. }
  168. return false;
  169. }
  170. },
  171. storage: {
  172. set: function(key, value) {
  173. if(settings.storageMethod == 'local' && window.store !== undefined) {
  174. window.store.set(key, value);
  175. }
  176. // store by cookie
  177. else if($.cookie !== undefined) {
  178. $.cookie(key, value);
  179. }
  180. else {
  181. module.error(settings.errors.noStorage);
  182. }
  183. },
  184. get: function(key) {
  185. if(settings.storageMethod == 'local' && window.store !== undefined) {
  186. return window.store.get(key);
  187. }
  188. // get by cookie
  189. else if($.cookie !== undefined) {
  190. return $.cookie(key);
  191. }
  192. else {
  193. module.error(settings.errors.noStorage);
  194. }
  195. }
  196. },
  197. event: {
  198. scroll: function() {
  199. if(timer !== undefined) {
  200. clearTimeout(timer);
  201. }
  202. timer = setTimeout(function() {
  203. if(module.should.stick() ) {
  204. requestAnimationFrame(module.stick);
  205. }
  206. else {
  207. module.unStick();
  208. }
  209. }, settings.lag);
  210. }
  211. },
  212. error: function(error) {
  213. console.log('Nag Module:' + error);
  214. },
  215. // allows for dot notation method calls
  216. invoke: function(methodName, context, methodArguments) {
  217. var
  218. method
  219. ;
  220. methodArguments = methodArguments || Array.prototype.slice.call( arguments, 2 );
  221. if(typeof methodName == 'string' && instance !== undefined) {
  222. methodName = methodName.split('.');
  223. $.each(methodName, function(index, name) {
  224. if( $.isPlainObject( instance[name] ) ) {
  225. instance = instance[name];
  226. return true;
  227. }
  228. else if( $.isFunction( instance[name] ) ) {
  229. method = instance[name];
  230. return true;
  231. }
  232. module.error(settings.errors.method);
  233. return false;
  234. });
  235. }
  236. if ( $.isFunction( method ) ) {
  237. return method.apply(context, methodArguments);
  238. }
  239. // return retrieved variable or chain
  240. return method;
  241. }
  242. };
  243. if(instance !== undefined && moduleArguments) {
  244. if(moduleArguments[0] == 'invoke') {
  245. moduleArguments = Array.prototype.slice.call( moduleArguments, 1 );
  246. }
  247. return module.invoke(moduleArguments[0], this, Array.prototype.slice.call( moduleArguments, 1 ) );
  248. }
  249. module.initialize();
  250. })
  251. ;
  252. return this;
  253. };
  254. $.fn.nag.settings = {
  255. // allows cookie to be overriden
  256. persist : false,
  257. // set to zero to manually dismiss, otherwise hides on its own
  258. displayTime : 0,
  259. // method of stickyness
  260. position : 'fixed',
  261. scrollBarWidth : 18,
  262. // type of storage to use
  263. storageMethod : 'cookie',
  264. // value to store in dismissed localstorage/cookie
  265. storedKey : 'nag',
  266. storedValue : 'dismiss',
  267. // need to calculate stickyness on scroll
  268. sticky : false,
  269. // how often to check scroll event
  270. lag : 0,
  271. // context for scroll event
  272. context : window,
  273. errors: {
  274. noStorage : 'Neither $.cookie or store is defined. A storage solution is required for storing state'
  275. },
  276. className : {
  277. bottom : 'bottom',
  278. fixed : 'fixed'
  279. },
  280. selector : {
  281. close: '.icon.close'
  282. },
  283. speed : 500,
  284. easing : 'easeOutQuad'
  285. };
  286. })( jQuery, window , document );