foundation.dropdown.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. ;(function ($, window, document, undefined) {
  2. 'use strict';
  3. Foundation.libs.dropdown = {
  4. name : 'dropdown',
  5. version : '5.5.3',
  6. settings : {
  7. active_class : 'open',
  8. disabled_class : 'disabled',
  9. mega_class : 'mega',
  10. align : 'bottom',
  11. is_hover : false,
  12. hover_timeout : 150,
  13. opened : function () {},
  14. closed : function () {}
  15. },
  16. init : function (scope, method, options) {
  17. Foundation.inherit(this, 'throttle');
  18. $.extend(true, this.settings, method, options);
  19. this.bindings(method, options);
  20. },
  21. events : function (scope) {
  22. var self = this,
  23. S = self.S;
  24. S(this.scope)
  25. .off('.dropdown')
  26. .on('click.fndtn.dropdown', '[' + this.attr_name() + ']', function (e) {
  27. var settings = S(this).data(self.attr_name(true) + '-init') || self.settings;
  28. if (!settings.is_hover || Modernizr.touch) {
  29. e.preventDefault();
  30. if (S(this).parent('[data-reveal-id]').length) {
  31. e.stopPropagation();
  32. }
  33. self.toggle($(this));
  34. }
  35. })
  36. .on('mouseenter.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
  37. var $this = S(this),
  38. dropdown,
  39. target;
  40. clearTimeout(self.timeout);
  41. if ($this.data(self.data_attr())) {
  42. dropdown = S('#' + $this.data(self.data_attr()));
  43. target = $this;
  44. } else {
  45. dropdown = $this;
  46. target = S('[' + self.attr_name() + '="' + dropdown.attr('id') + '"]');
  47. }
  48. var settings = target.data(self.attr_name(true) + '-init') || self.settings;
  49. if (S(e.currentTarget).data(self.data_attr()) && settings.is_hover) {
  50. self.closeall.call(self);
  51. }
  52. if (settings.is_hover) {
  53. self.open.apply(self, [dropdown, target]);
  54. }
  55. })
  56. .on('mouseleave.fndtn.dropdown', '[' + this.attr_name() + '], [' + this.attr_name() + '-content]', function (e) {
  57. var $this = S(this);
  58. var settings;
  59. if ($this.data(self.data_attr())) {
  60. settings = $this.data(self.data_attr(true) + '-init') || self.settings;
  61. } else {
  62. var target = S('[' + self.attr_name() + '="' + S(this).attr('id') + '"]'),
  63. settings = target.data(self.attr_name(true) + '-init') || self.settings;
  64. }
  65. self.timeout = setTimeout(function () {
  66. if ($this.data(self.data_attr())) {
  67. if (settings.is_hover) {
  68. self.close.call(self, S('#' + $this.data(self.data_attr())));
  69. }
  70. } else {
  71. if (settings.is_hover) {
  72. self.close.call(self, $this);
  73. }
  74. }
  75. }.bind(this), settings.hover_timeout);
  76. })
  77. .on('click.fndtn.dropdown', function (e) {
  78. var parent = S(e.target).closest('[' + self.attr_name() + '-content]');
  79. var links = parent.find('a');
  80. if (links.length > 0 && parent.attr('aria-autoclose') !== 'false') {
  81. self.close.call(self, S('[' + self.attr_name() + '-content]'));
  82. }
  83. if (e.target !== document && !$.contains(document.documentElement, e.target)) {
  84. return;
  85. }
  86. if (S(e.target).closest('[' + self.attr_name() + ']').length > 0) {
  87. return;
  88. }
  89. if (!(S(e.target).data('revealId')) &&
  90. (parent.length > 0 && (S(e.target).is('[' + self.attr_name() + '-content]') ||
  91. $.contains(parent.first()[0], e.target)))) {
  92. e.stopPropagation();
  93. return;
  94. }
  95. self.close.call(self, S('[' + self.attr_name() + '-content]'));
  96. })
  97. .on('opened.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
  98. self.settings.opened.call(this);
  99. })
  100. .on('closed.fndtn.dropdown', '[' + self.attr_name() + '-content]', function () {
  101. self.settings.closed.call(this);
  102. });
  103. S(window)
  104. .off('.dropdown')
  105. .on('resize.fndtn.dropdown', self.throttle(function () {
  106. self.resize.call(self);
  107. }, 50));
  108. this.resize();
  109. },
  110. close : function (dropdown) {
  111. var self = this;
  112. dropdown.each(function (idx) {
  113. var original_target = $('[' + self.attr_name() + '=' + dropdown[idx].id + ']') || $('aria-controls=' + dropdown[idx].id + ']');
  114. original_target.attr('aria-expanded', 'false');
  115. if (self.S(this).hasClass(self.settings.active_class)) {
  116. self.S(this)
  117. .css(Foundation.rtl ? 'right' : 'left', '-99999px')
  118. .attr('aria-hidden', 'true')
  119. .removeClass(self.settings.active_class)
  120. .prev('[' + self.attr_name() + ']')
  121. .removeClass(self.settings.active_class)
  122. .removeData('target');
  123. self.S(this).trigger('closed.fndtn.dropdown', [dropdown]);
  124. }
  125. });
  126. dropdown.removeClass('f-open-' + this.attr_name(true));
  127. },
  128. closeall : function () {
  129. var self = this;
  130. $.each(self.S('.f-open-' + this.attr_name(true)), function () {
  131. self.close.call(self, self.S(this));
  132. });
  133. },
  134. open : function (dropdown, target) {
  135. this
  136. .css(dropdown
  137. .addClass(this.settings.active_class), target);
  138. dropdown.prev('[' + this.attr_name() + ']').addClass(this.settings.active_class);
  139. dropdown.data('target', target.get(0)).trigger('opened.fndtn.dropdown', [dropdown, target]);
  140. dropdown.attr('aria-hidden', 'false');
  141. target.attr('aria-expanded', 'true');
  142. dropdown.focus();
  143. dropdown.addClass('f-open-' + this.attr_name(true));
  144. },
  145. data_attr : function () {
  146. if (this.namespace.length > 0) {
  147. return this.namespace + '-' + this.name;
  148. }
  149. return this.name;
  150. },
  151. toggle : function (target) {
  152. if (target.hasClass(this.settings.disabled_class)) {
  153. return;
  154. }
  155. var dropdown = this.S('#' + target.data(this.data_attr()));
  156. if (dropdown.length === 0) {
  157. // No dropdown found, not continuing
  158. return;
  159. }
  160. this.close.call(this, this.S('[' + this.attr_name() + '-content]').not(dropdown));
  161. if (dropdown.hasClass(this.settings.active_class)) {
  162. this.close.call(this, dropdown);
  163. if (dropdown.data('target') !== target.get(0)) {
  164. this.open.call(this, dropdown, target);
  165. }
  166. } else {
  167. this.open.call(this, dropdown, target);
  168. }
  169. },
  170. resize : function () {
  171. var dropdown = this.S('[' + this.attr_name() + '-content].open');
  172. var target = $(dropdown.data("target"));
  173. if (dropdown.length && target.length) {
  174. this.css(dropdown, target);
  175. }
  176. },
  177. css : function (dropdown, target) {
  178. var left_offset = Math.max((target.width() - dropdown.width()) / 2, 8),
  179. settings = target.data(this.attr_name(true) + '-init') || this.settings,
  180. parentOverflow = dropdown.parent().css('overflow-y') || dropdown.parent().css('overflow');
  181. this.clear_idx();
  182. if (this.small()) {
  183. var p = this.dirs.bottom.call(dropdown, target, settings);
  184. dropdown.attr('style', '').removeClass('drop-left drop-right drop-top').css({
  185. position : 'absolute',
  186. width : '95%',
  187. 'max-width' : 'none',
  188. top : p.top
  189. });
  190. dropdown.css(Foundation.rtl ? 'right' : 'left', left_offset);
  191. }
  192. // detect if dropdown is in an overflow container
  193. else if (parentOverflow !== 'visible') {
  194. var offset = target[0].offsetTop + target[0].offsetHeight;
  195. dropdown.attr('style', '').css({
  196. position : 'absolute',
  197. top : offset
  198. });
  199. dropdown.css(Foundation.rtl ? 'right' : 'left', left_offset);
  200. }
  201. else {
  202. this.style(dropdown, target, settings);
  203. }
  204. return dropdown;
  205. },
  206. style : function (dropdown, target, settings) {
  207. var css = $.extend({position : 'absolute'},
  208. this.dirs[settings.align].call(dropdown, target, settings));
  209. dropdown.attr('style', '').css(css);
  210. },
  211. // return CSS property object
  212. // `this` is the dropdown
  213. dirs : {
  214. // Calculate target offset
  215. _base : function (t, s) {
  216. var o_p = this.offsetParent(),
  217. o = o_p.offset(),
  218. p = t.offset();
  219. p.top -= o.top;
  220. p.left -= o.left;
  221. //set some flags on the p object to pass along
  222. p.missRight = false;
  223. p.missTop = false;
  224. p.missLeft = false;
  225. p.leftRightFlag = false;
  226. //lets see if the panel will be off the screen
  227. //get the actual width of the page and store it
  228. var actualBodyWidth;
  229. var windowWidth = window.innerWidth;
  230. if (document.getElementsByClassName('row')[0]) {
  231. actualBodyWidth = document.getElementsByClassName('row')[0].clientWidth;
  232. } else {
  233. actualBodyWidth = windowWidth;
  234. }
  235. var actualMarginWidth = (windowWidth - actualBodyWidth) / 2;
  236. var actualBoundary = actualBodyWidth;
  237. if (!this.hasClass('mega') && !s.ignore_repositioning) {
  238. var outerWidth = this.outerWidth();
  239. var o_left = t.offset().left;
  240. //miss top
  241. if (t.offset().top <= this.outerHeight()) {
  242. p.missTop = true;
  243. actualBoundary = windowWidth - actualMarginWidth;
  244. p.leftRightFlag = true;
  245. }
  246. //miss right
  247. if (o_left + outerWidth > o_left + actualMarginWidth && o_left - actualMarginWidth > outerWidth) {
  248. p.missRight = true;
  249. p.missLeft = false;
  250. }
  251. //miss left
  252. if (o_left - outerWidth <= 0) {
  253. p.missLeft = true;
  254. p.missRight = false;
  255. }
  256. }
  257. return p;
  258. },
  259. top : function (t, s) {
  260. var self = Foundation.libs.dropdown,
  261. p = self.dirs._base.call(this, t, s);
  262. this.addClass('drop-top');
  263. if (p.missTop == true) {
  264. p.top = p.top + t.outerHeight() + this.outerHeight();
  265. this.removeClass('drop-top');
  266. }
  267. if (p.missRight == true) {
  268. p.left = p.left - this.outerWidth() + t.outerWidth();
  269. }
  270. if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
  271. self.adjust_pip(this, t, s, p);
  272. }
  273. if (Foundation.rtl) {
  274. return {left : p.left - this.outerWidth() + t.outerWidth(),
  275. top : p.top - this.outerHeight()};
  276. }
  277. return {left : p.left, top : p.top - this.outerHeight()};
  278. },
  279. bottom : function (t, s) {
  280. var self = Foundation.libs.dropdown,
  281. p = self.dirs._base.call(this, t, s);
  282. if (p.missRight == true) {
  283. p.left = p.left - this.outerWidth() + t.outerWidth();
  284. }
  285. if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
  286. self.adjust_pip(this, t, s, p);
  287. }
  288. if (self.rtl) {
  289. return {left : p.left - this.outerWidth() + t.outerWidth(), top : p.top + t.outerHeight()};
  290. }
  291. return {left : p.left, top : p.top + t.outerHeight()};
  292. },
  293. left : function (t, s) {
  294. var p = Foundation.libs.dropdown.dirs._base.call(this, t, s);
  295. this.addClass('drop-left');
  296. if (p.missLeft == true) {
  297. p.left = p.left + this.outerWidth();
  298. p.top = p.top + t.outerHeight();
  299. this.removeClass('drop-left');
  300. }
  301. return {left : p.left - this.outerWidth(), top : p.top};
  302. },
  303. right : function (t, s) {
  304. var p = Foundation.libs.dropdown.dirs._base.call(this, t, s);
  305. this.addClass('drop-right');
  306. if (p.missRight == true) {
  307. p.left = p.left - this.outerWidth();
  308. p.top = p.top + t.outerHeight();
  309. this.removeClass('drop-right');
  310. } else {
  311. p.triggeredRight = true;
  312. }
  313. var self = Foundation.libs.dropdown;
  314. if (t.outerWidth() < this.outerWidth() || self.small() || this.hasClass(s.mega_menu)) {
  315. self.adjust_pip(this, t, s, p);
  316. }
  317. return {left : p.left + t.outerWidth(), top : p.top};
  318. }
  319. },
  320. // Insert rule to style psuedo elements
  321. adjust_pip : function (dropdown, target, settings, position) {
  322. var sheet = Foundation.stylesheet,
  323. pip_offset_base = 8;
  324. if (dropdown.hasClass(settings.mega_class)) {
  325. pip_offset_base = position.left + (target.outerWidth() / 2) - 8;
  326. } else if (this.small()) {
  327. pip_offset_base += position.left - 8;
  328. }
  329. this.rule_idx = sheet.cssRules.length;
  330. //default
  331. var sel_before = '.f-dropdown.open:before',
  332. sel_after = '.f-dropdown.open:after',
  333. css_before = 'left: ' + pip_offset_base + 'px;',
  334. css_after = 'left: ' + (pip_offset_base - 1) + 'px;';
  335. if (position.missRight == true) {
  336. pip_offset_base = dropdown.outerWidth() - 23;
  337. sel_before = '.f-dropdown.open:before',
  338. sel_after = '.f-dropdown.open:after',
  339. css_before = 'left: ' + pip_offset_base + 'px;',
  340. css_after = 'left: ' + (pip_offset_base - 1) + 'px;';
  341. }
  342. //just a case where right is fired, but its not missing right
  343. if (position.triggeredRight == true) {
  344. sel_before = '.f-dropdown.open:before',
  345. sel_after = '.f-dropdown.open:after',
  346. css_before = 'left:-12px;',
  347. css_after = 'left:-14px;';
  348. }
  349. if (sheet.insertRule) {
  350. sheet.insertRule([sel_before, '{', css_before, '}'].join(' '), this.rule_idx);
  351. sheet.insertRule([sel_after, '{', css_after, '}'].join(' '), this.rule_idx + 1);
  352. } else {
  353. sheet.addRule(sel_before, css_before, this.rule_idx);
  354. sheet.addRule(sel_after, css_after, this.rule_idx + 1);
  355. }
  356. },
  357. // Remove old dropdown rule index
  358. clear_idx : function () {
  359. var sheet = Foundation.stylesheet;
  360. if (typeof this.rule_idx !== 'undefined') {
  361. sheet.deleteRule(this.rule_idx);
  362. sheet.deleteRule(this.rule_idx);
  363. delete this.rule_idx;
  364. }
  365. },
  366. small : function () {
  367. return matchMedia(Foundation.media_queries.small).matches &&
  368. !matchMedia(Foundation.media_queries.medium).matches;
  369. },
  370. off : function () {
  371. this.S(this.scope).off('.fndtn.dropdown');
  372. this.S('html, body').off('.fndtn.dropdown');
  373. this.S(window).off('.fndtn.dropdown');
  374. this.S('[data-dropdown-content]').off('.fndtn.dropdown');
  375. },
  376. reflow : function () {}
  377. };
  378. }(jQuery, window, window.document));