superfish.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. /*
  2. * jQuery Superfish Menu Plugin
  3. * Copyright (c) 2013 Joel Birch
  4. *
  5. * Dual licensed under the MIT and GPL licenses:
  6. * http://www.opensource.org/licenses/mit-license.php
  7. * http://www.gnu.org/licenses/gpl.html
  8. */
  9. (function ($) {
  10. "use strict";
  11. var methods = (function () {
  12. // private properties and methods go here
  13. var c = {
  14. bcClass: 'sf-breadcrumb',
  15. menuClass: 'sf-js-enabled',
  16. anchorClass: 'sf-with-ul',
  17. menuArrowClass: 'sf-arrows'
  18. },
  19. ios = (function () {
  20. var ios = /iPhone|iPad|iPod/i.test(navigator.userAgent);
  21. if (ios) {
  22. // iOS clicks only bubble as far as body children
  23. $(window).load(function () {
  24. $('body').children().on('click', $.noop);
  25. });
  26. }
  27. return ios;
  28. })(),
  29. wp7 = (function () {
  30. var style = document.documentElement.style;
  31. return ('behavior' in style && 'fill' in style && /iemobile/i.test(navigator.userAgent));
  32. })(),
  33. toggleMenuClasses = function ($menu, o) {
  34. var classes = c.menuClass;
  35. if (o.cssArrows) {
  36. classes += ' ' + c.menuArrowClass;
  37. }
  38. $menu.toggleClass(classes);
  39. },
  40. setPathToCurrent = function ($menu, o) {
  41. return $menu.find('li.' + o.pathClass).slice(0, o.pathLevels)
  42. .addClass(o.hoverClass + ' ' + c.bcClass)
  43. .filter(function () {
  44. return ($(this).children(o.popUpSelector).hide().show().length);
  45. }).removeClass(o.pathClass);
  46. },
  47. toggleAnchorClass = function ($li) {
  48. $li.children('a').toggleClass(c.anchorClass);
  49. },
  50. toggleTouchAction = function ($menu) {
  51. var touchAction = $menu.css('ms-touch-action');
  52. touchAction = (touchAction === 'pan-y') ? 'auto' : 'pan-y';
  53. $menu.css('ms-touch-action', touchAction);
  54. },
  55. applyHandlers = function ($menu, o) {
  56. var targets = 'li:has(' + o.popUpSelector + ')';
  57. if ($.fn.hoverIntent && !o.disableHI) {
  58. $menu.hoverIntent(over, out, targets);
  59. }
  60. else {
  61. $menu
  62. .on('mouseenter.superfish', targets, over)
  63. .on('mouseleave.superfish', targets, out);
  64. }
  65. var touchevent = 'MSPointerDown.superfish';
  66. if (!ios) {
  67. touchevent += ' touchend.superfish';
  68. }
  69. if (wp7) {
  70. touchevent += ' mousedown.superfish';
  71. }
  72. $menu
  73. .on('focusin.superfish', 'li', over)
  74. .on('focusout.superfish', 'li', out)
  75. .on(touchevent, 'a', o, touchHandler);
  76. },
  77. touchHandler = function (e) {
  78. var $this = $(this),
  79. $ul = $this.siblings(e.data.popUpSelector);
  80. if ($ul.length > 0 && $ul.is(':hidden')) {
  81. $this.one('click.superfish', false);
  82. if (e.type === 'MSPointerDown') {
  83. $this.trigger('focus');
  84. } else {
  85. $.proxy(over, $this.parent('li'))();
  86. }
  87. }
  88. },
  89. over = function () {
  90. var $this = $(this),
  91. o = getOptions($this);
  92. clearTimeout(o.sfTimer);
  93. $this.siblings().superfish('hide').end().superfish('show');
  94. },
  95. out = function () {
  96. var $this = $(this),
  97. o = getOptions($this);
  98. if (ios) {
  99. $.proxy(close, $this, o)();
  100. }
  101. else {
  102. clearTimeout(o.sfTimer);
  103. o.sfTimer = setTimeout($.proxy(close, $this, o), o.delay);
  104. }
  105. },
  106. close = function (o) {
  107. o.retainPath = ($.inArray(this[0], o.$path) > -1);
  108. this.superfish('hide');
  109. if (!this.parents('.' + o.hoverClass).length) {
  110. o.onIdle.call(getMenu(this));
  111. if (o.$path.length) {
  112. $.proxy(over, o.$path)();
  113. }
  114. }
  115. },
  116. getMenu = function ($el) {
  117. return $el.closest('.' + c.menuClass);
  118. },
  119. getOptions = function ($el) {
  120. return getMenu($el).data('sf-options');
  121. };
  122. return {
  123. // public methods
  124. hide: function (instant) {
  125. if (this.length) {
  126. var $this = this,
  127. o = getOptions($this);
  128. if (!o) {
  129. return this;
  130. }
  131. var not = (o.retainPath === true) ? o.$path : '',
  132. $ul = $this.find('li.' + o.hoverClass).add(this).not(not).removeClass(o.hoverClass).children(o.popUpSelector),
  133. speed = o.speedOut;
  134. if (instant) {
  135. $ul.show();
  136. speed = 0;
  137. }
  138. o.retainPath = false;
  139. o.onBeforeHide.call($ul);
  140. $ul.stop(true, true).animate(o.animationOut, speed, function () {
  141. var $this = $(this);
  142. o.onHide.call($this);
  143. });
  144. }
  145. return this;
  146. },
  147. show: function () {
  148. var o = getOptions(this);
  149. if (!o) {
  150. return this;
  151. }
  152. var $this = this.addClass(o.hoverClass),
  153. $ul = $this.children(o.popUpSelector);
  154. o.onBeforeShow.call($ul);
  155. $ul.stop(true, true).animate(o.animation, o.speed, function () {
  156. o.onShow.call($ul);
  157. });
  158. return this;
  159. },
  160. destroy: function () {
  161. return this.each(function () {
  162. var $this = $(this),
  163. o = $this.data('sf-options'),
  164. $hasPopUp;
  165. if (!o) {
  166. return false;
  167. }
  168. $hasPopUp = $this.find(o.popUpSelector).parent('li');
  169. clearTimeout(o.sfTimer);
  170. toggleMenuClasses($this, o);
  171. toggleAnchorClass($hasPopUp);
  172. toggleTouchAction($this);
  173. // remove event handlers
  174. $this.off('.superfish').off('.hoverIntent');
  175. // clear animation's inline display style
  176. $hasPopUp.children(o.popUpSelector).attr('style', function (i, style) {
  177. return style.replace(/display[^;]+;?/g, '');
  178. });
  179. // reset 'current' path classes
  180. o.$path.removeClass(o.hoverClass + ' ' + c.bcClass).addClass(o.pathClass);
  181. $this.find('.' + o.hoverClass).removeClass(o.hoverClass);
  182. o.onDestroy.call($this);
  183. $this.removeData('sf-options');
  184. });
  185. },
  186. init: function (op) {
  187. return this.each(function () {
  188. var $this = $(this);
  189. if ($this.data('sf-options')) {
  190. return false;
  191. }
  192. var o = $.extend({}, $.fn.superfish.defaults, op),
  193. $hasPopUp = $this.find(o.popUpSelector).parent('li');
  194. o.$path = setPathToCurrent($this, o);
  195. $this.data('sf-options', o);
  196. toggleMenuClasses($this, o);
  197. toggleAnchorClass($hasPopUp);
  198. toggleTouchAction($this);
  199. applyHandlers($this, o);
  200. $hasPopUp.not('.' + c.bcClass).superfish('hide', true);
  201. o.onInit.call(this);
  202. });
  203. }
  204. };
  205. })();
  206. $.fn.superfish = function (method, args) {
  207. if (methods[method]) {
  208. return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  209. }
  210. else if (typeof method === 'object' || ! method) {
  211. return methods.init.apply(this, arguments);
  212. }
  213. else {
  214. return $.error('Method ' + method + ' does not exist on jQuery.fn.superfish');
  215. }
  216. };
  217. $.fn.superfish.defaults = {
  218. popUpSelector: 'ul,.sf-mega', // within menu context
  219. hoverClass: 'sfHover',
  220. pathClass: 'overrideThisToUse',
  221. pathLevels: 1,
  222. delay: 800,
  223. animation: false,
  224. animationOut: false,
  225. speed: 'normal',
  226. speedOut: 'fast',
  227. cssArrows: true,
  228. disableHI: false,
  229. onInit: $.noop,
  230. onBeforeShow: $.noop,
  231. onShow: $.noop,
  232. onBeforeHide: $.noop,
  233. onHide: $.noop,
  234. onIdle: $.noop,
  235. onDestroy: $.noop
  236. };
  237. // soon to be deprecated
  238. $.fn.extend({
  239. hideSuperfishUl: methods.hide,
  240. showSuperfishUl: methods.show
  241. });
  242. })(jQuery);