foundation.tab.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. ;(function ($, window, document, undefined) {
  2. 'use strict';
  3. Foundation.libs.tab = {
  4. name : 'tab',
  5. version : '5.5.3',
  6. settings : {
  7. active_class : 'active',
  8. callback : function () {},
  9. deep_linking : false,
  10. scroll_to_content : true,
  11. is_hover : false
  12. },
  13. default_tab_hashes : [],
  14. init : function (scope, method, options) {
  15. var self = this,
  16. S = this.S;
  17. // Store the default active tabs which will be referenced when the
  18. // location hash is absent, as in the case of navigating the tabs and
  19. // returning to the first viewing via the browser Back button.
  20. S('[' + this.attr_name() + '] > .active > a', this.scope).each(function () {
  21. self.default_tab_hashes.push(this.hash);
  22. });
  23. this.bindings(method, options);
  24. this.handle_location_hash_change();
  25. },
  26. events : function () {
  27. var self = this,
  28. S = this.S;
  29. var usual_tab_behavior = function (e, target) {
  30. var settings = S(target).closest('[' + self.attr_name() + ']').data(self.attr_name(true) + '-init');
  31. if (!settings.is_hover || Modernizr.touch) {
  32. // if user did not pressed tab key, prevent default action
  33. var keyCode = e.keyCode || e.which;
  34. if (keyCode !== 9) {
  35. e.preventDefault();
  36. e.stopPropagation();
  37. }
  38. self.toggle_active_tab(S(target).parent());
  39. }
  40. };
  41. S(this.scope)
  42. .off('.tab')
  43. // Key event: focus/tab key
  44. .on('keydown.fndtn.tab', '[' + this.attr_name() + '] > * > a', function(e) {
  45. var keyCode = e.keyCode || e.which;
  46. // if user pressed tab key
  47. if (keyCode === 13 || keyCode === 32) { // enter or space
  48. var el = this;
  49. usual_tab_behavior(e, el);
  50. }
  51. })
  52. // Click event: tab title
  53. .on('click.fndtn.tab', '[' + this.attr_name() + '] > * > a', function(e) {
  54. var el = this;
  55. usual_tab_behavior(e, el);
  56. })
  57. // Hover event: tab title
  58. .on('mouseenter.fndtn.tab', '[' + this.attr_name() + '] > * > a', function (e) {
  59. var settings = S(this).closest('[' + self.attr_name() + ']').data(self.attr_name(true) + '-init');
  60. if (settings.is_hover) {
  61. self.toggle_active_tab(S(this).parent());
  62. }
  63. });
  64. // Location hash change event
  65. S(window).on('hashchange.fndtn.tab', function (e) {
  66. e.preventDefault();
  67. self.handle_location_hash_change();
  68. });
  69. },
  70. handle_location_hash_change : function () {
  71. var self = this,
  72. S = this.S;
  73. S('[' + this.attr_name() + ']', this.scope).each(function () {
  74. var settings = S(this).data(self.attr_name(true) + '-init');
  75. if (settings.deep_linking) {
  76. // Match the location hash to a label
  77. var hash;
  78. if (settings.scroll_to_content) {
  79. hash = self.scope.location.hash;
  80. } else {
  81. // prefix the hash to prevent anchor scrolling
  82. hash = self.scope.location.hash.replace('fndtn-', '');
  83. }
  84. if (hash != '') {
  85. // Check whether the location hash references a tab content div or
  86. // another element on the page (inside or outside the tab content div)
  87. var hash_element = S(hash);
  88. if (hash_element.hasClass('content') && hash_element.parent().hasClass('tabs-content')) {
  89. // Tab content div
  90. self.toggle_active_tab($('[' + self.attr_name() + '] > * > a[href=' + hash + ']').parent());
  91. } else {
  92. // Not the tab content div. If inside the tab content, find the
  93. // containing tab and toggle it as active.
  94. var hash_tab_container_id = hash_element.closest('.content').attr('id');
  95. if (hash_tab_container_id != undefined) {
  96. self.toggle_active_tab($('[' + self.attr_name() + '] > * > a[href=#' + hash_tab_container_id + ']').parent(), hash);
  97. }
  98. }
  99. } else {
  100. // Reference the default tab hashes which were initialized in the init function
  101. for (var ind = 0; ind < self.default_tab_hashes.length; ind++) {
  102. self.toggle_active_tab($('[' + self.attr_name() + '] > * > a[href=' + self.default_tab_hashes[ind] + ']').parent());
  103. }
  104. }
  105. }
  106. });
  107. },
  108. toggle_active_tab : function (tab, location_hash) {
  109. var self = this,
  110. S = self.S,
  111. tabs = tab.closest('[' + this.attr_name() + ']'),
  112. tab_link = tab.find('a'),
  113. anchor = tab.children('a').first(),
  114. target_hash = '#' + anchor.attr('href').split('#')[1],
  115. target = S(target_hash),
  116. siblings = tab.siblings(),
  117. settings = tabs.data(this.attr_name(true) + '-init'),
  118. interpret_keyup_action = function (e) {
  119. // Light modification of Heydon Pickering's Practical ARIA Examples: http://heydonworks.com/practical_aria_examples/js/a11y.js
  120. // define current, previous and next (possible) tabs
  121. var $original = $(this);
  122. var $prev = $(this).parents('li').prev().children('[role="tab"]');
  123. var $next = $(this).parents('li').next().children('[role="tab"]');
  124. var $target;
  125. // find the direction (prev or next)
  126. switch (e.keyCode) {
  127. case 37:
  128. $target = $prev;
  129. break;
  130. case 39:
  131. $target = $next;
  132. break;
  133. default:
  134. $target = false
  135. break;
  136. }
  137. if ($target.length) {
  138. $original.attr({
  139. 'tabindex' : '-1',
  140. 'aria-selected' : null
  141. });
  142. $target.attr({
  143. 'tabindex' : '0',
  144. 'aria-selected' : true
  145. }).focus();
  146. }
  147. // Hide panels
  148. $('[role="tabpanel"]')
  149. .attr('aria-hidden', 'true');
  150. // Show panel which corresponds to target
  151. $('#' + $(document.activeElement).attr('href').substring(1))
  152. .attr('aria-hidden', null);
  153. },
  154. go_to_hash = function(hash) {
  155. // This function allows correct behaviour of the browser's back button when deep linking is enabled. Without it
  156. // the user would get continually redirected to the default hash.
  157. var default_hash = settings.scroll_to_content ? self.default_tab_hashes[0] : 'fndtn-' + self.default_tab_hashes[0].replace('#', '');
  158. if (hash !== default_hash || window.location.hash) {
  159. window.location.hash = hash;
  160. }
  161. };
  162. // allow usage of data-tab-content attribute instead of href
  163. if (anchor.data('tab-content')) {
  164. target_hash = '#' + anchor.data('tab-content').split('#')[1];
  165. target = S(target_hash);
  166. }
  167. if (settings.deep_linking) {
  168. if (settings.scroll_to_content) {
  169. // retain current hash to scroll to content
  170. go_to_hash(location_hash || target_hash);
  171. if (location_hash == undefined || location_hash == target_hash) {
  172. tab.parent()[0].scrollIntoView();
  173. } else {
  174. S(target_hash)[0].scrollIntoView();
  175. }
  176. } else {
  177. // prefix the hashes so that the browser doesn't scroll down
  178. if (location_hash != undefined) {
  179. go_to_hash('fndtn-' + location_hash.replace('#', ''));
  180. } else {
  181. go_to_hash('fndtn-' + target_hash.replace('#', ''));
  182. }
  183. }
  184. }
  185. // WARNING: The activation and deactivation of the tab content must
  186. // occur after the deep linking in order to properly refresh the browser
  187. // window (notably in Chrome).
  188. // Clean up multiple attr instances to done once
  189. tab.addClass(settings.active_class).triggerHandler('opened');
  190. tab_link.attr({'aria-selected' : 'true', tabindex : 0});
  191. siblings.removeClass(settings.active_class)
  192. siblings.find('a').attr({'aria-selected' : 'false'/*, tabindex : -1*/});
  193. target.siblings().removeClass(settings.active_class).attr({'aria-hidden' : 'true'/*, tabindex : -1*/});
  194. target.addClass(settings.active_class).attr('aria-hidden', 'false').removeAttr('tabindex');
  195. settings.callback(tab);
  196. target.triggerHandler('toggled', [target]);
  197. tabs.triggerHandler('toggled', [tab]);
  198. tab_link.off('keydown').on('keydown', interpret_keyup_action );
  199. },
  200. data_attr : function (str) {
  201. if (this.namespace.length > 0) {
  202. return this.namespace + '-' + str;
  203. }
  204. return str;
  205. },
  206. off : function () {},
  207. reflow : function () {}
  208. };
  209. }(jQuery, window, window.document));