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.

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