jquery.tabs.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /**
  2. * Accessible Tabs - jQuery plugin for accessible, unobtrusive tabs
  3. * Build to seemlessly work with the CCS-Framework YAML (yaml.de) not depending on YAML though
  4. * @requires jQuery - tested with 1.7 and 1.4.2 but might as well work with older versions
  5. *
  6. * english article: http://blog.ginader.de/archives/2009/02/07/jQuery-Accessible-Tabs-How-to-make-tabs-REALLY-accessible.php
  7. * german article: http://blog.ginader.de/archives/2009/02/07/jQuery-Accessible-Tabs-Wie-man-Tabs-WIRKLICH-zugaenglich-macht.php
  8. *
  9. * code: http://github.com/ginader/Accessible-Tabs
  10. * please report issues at: http://github.com/ginader/Accessible-Tabs/issues
  11. *
  12. * Copyright (c) 2007 Dirk Ginader (ginader.de)
  13. * Dual licensed under the MIT and GPL licenses:
  14. * http://www.opensource.org/licenses/mit-license.php
  15. * http://www.gnu.org/licenses/gpl.html
  16. *
  17. * Version: 1.9.4
  18. *
  19. * History:
  20. * * 1.0 initial release
  21. * * 1.1 added a lot of Accessibility enhancements
  22. * * * rewrite to use "fn.extend" structure
  23. * * * added check for existing ids on the content containers to use to proper anchors in the tabs
  24. * * 1.1.1 changed the headline markup. thanks to Mike Davies for the hint.
  25. * * 1.5 thanks to Dirk Jesse, Ansgar Hein, David Maciejewski and Mike West for commiting patches to this release
  26. * * * new option syncheights that syncs the heights of the tab contents when the SyncHeight plugin
  27. * * is available http://blog.ginader.de/dev/jquery/syncheight/index.php
  28. * * * fixed the hardcoded current class
  29. * * * new option tabsListClass to be applied to the generated list of tabs above the content so lists
  30. * * inside the tabscontent can be styled differently
  31. * * * added clearfix and tabcounter that adds a class in the schema "tabamount{number amount of tabs}"
  32. * * to the ul containg the tabs so one can style the tabs to fit 100% into the width
  33. * * * new option "syncHeightMethodName" fixed issue: http://github.com/ginader/Accessible-Tabs/issues/2/find
  34. * * * new Method showAccessibleTab({index number of the tab to show starting with 0}) fixed issue: http://github.com/ginader/Accessible-Tabs/issues/3/find
  35. * * * added support for the Cursor Keys to come closer to the WAI ARIA Tab Panel Best Practices http://github.com/ginader/Accessible-Tabs/issues/1/find
  36. * * 1.6
  37. * * * new option "saveState" to allow tabs remember their selected state using cookies requires the cookie plugin: http://plugins.jquery.com/project/Cookie
  38. * * * changed supported jquery version to 1.4.2 to make sure it's future compatible
  39. * * * new option "autoAnchor" which allows to add ID's to headlines in the tabs markup that allow direct linking into a tab i.e.: file.html#headlineID
  40. * * 1.7
  41. * * * new option "pagination" that adds links to show the next/previous tab. This adds the following markup to each tab for you to style:
  42. <ul class="pagination">
  43. <li class="previous"><a href="#{the-id-of-the-previous-tab}"><span>{the headline of the previous tab}</span></a></li>
  44. <li class="next"><a href="#{the-id-of-the-next-tab}"><span>{the headline of the previous tab}</span></a></li>
  45. </ul>
  46. * * 1.8
  47. * * * new option "position" can be 'top' or 'bottom'. Defines where the tabs list is inserted.
  48. * * 1.8.1
  49. * * * Bugfix for broken pagination in ie6 and 7: Selector and object access modified by Daniel Köntös (www.MilkmanMedia.de). Thanks to Carolin Moll for the report.
  50. * * 1.8.2
  51. * * * Bugfix for issue described by Sunshine here: http://blog.ginader.de/archives/2009/02/07/jQuery-Accessible-Tabs-How-to-make-tabs-REALLY-accessible.php#c916
  52. * * 1.8.3
  53. * * * Bugfix by Michael Schulze: Only change current class in tab navigation and not in all unordered lists inside the tabs.
  54. * * 1.9
  55. * * * new method showAccessibleTabSelector({valid jQuery selector of the tab to show}) that allows the opening of tabs \
  56. * * * by jQuery Selector instead of the index in showAccessibleTab() fixing issue https://github.com/ginader/Accessible-Tabs/issues/15
  57. * * 1.9.1 by Michael Schulze:
  58. * * * firstNavItemClass and lastNavItemClass to define a custom classname on the first and last tab
  59. * * * wrapInnerNavLinks: inner wrap for a-tags in tab navigation.
  60. * * 1.9.2
  61. * * * Bugfix by Dirk Jesse: fixing an issue that happened when passing multiple selectors to the init call instead of one
  62. * * * Bugfix that fixes a reset of the tabs counter when accessibleTabs() was called more than once on a page
  63. * * 1.9.3
  64. * * * Bugfix by Norm: before, when cssClassAvailable was true, all of the tabhead elements had to have classes or they wouldn't get pulled out into tabs.
  65. * * * This commit fixes this assumption, as I only want classes on some elements https://github.com/ginader/Accessible-Tabs/pull/25
  66. * * 1.9.4 Bugfix by Patrick Bruckner to fix issue with Internet Explorer using jQuery 1.7 https://github.com/ginader/Accessible-Tabs/issues/26
  67. */
  68. (function($) {
  69. var debugMode = true;
  70. $.fn.extend({
  71. // We assume there could be multiple sets of tabs on a page, so,
  72. // the unique id for each invididual tab's heading is identified with params q and r (e.g., id="accessibletabscontent0-2")
  73. getUniqueId: function(p, q, r){
  74. if (r===undefined) {r='';} else {r='-'+r;}
  75. return p + q + r;
  76. },
  77. accessibleTabs: function(config) {
  78. var defaults = {
  79. wrapperClass: 'content', // Classname to apply to the div that is wrapped around the original Markup
  80. currentClass: 'current', // Classname to apply to the LI of the selected Tab
  81. tabhead: 'h4', // Tag or valid Query Selector of the Elements to Transform the Tabs-Navigation from (originals are removed)
  82. tabheadClass: 'tabhead', // Classname to apply to the target heading element for each tab div
  83. tabbody: '.tabbody', // Tag or valid Query Selector of the Elements to be treated as the Tab Body
  84. fx:'show', // can be "fadeIn", "slideDown", "show"
  85. fxspeed: 'normal', // speed (String|Number): "slow", "normal", or "fast") or the number of milliseconds to run the animation
  86. currentInfoText: 'current tab: ', // text to indicate for screenreaders which tab is the current one
  87. currentInfoPosition: 'prepend', // Definition where to insert the Info Text. Can be either "prepend" or "append"
  88. currentInfoClass: 'current-info', // Class to apply to the span wrapping the CurrentInfoText
  89. tabsListClass:'tabs-list', // Class to apply to the generated list of tabs above the content
  90. syncheights:false, // syncs the heights of the tab contents when the SyncHeight plugin is available http://blog.ginader.de/dev/jquery/syncheight/index.php
  91. syncHeightMethodName:'syncHeight', // set the Method name of the plugin you want to use to sync the tab contents. Defaults to the SyncHeight plugin: http://github.com/ginader/syncHeight
  92. cssClassAvailable:false, // Enable individual css classes for tabs. Gets the appropriate class name of a tabhead element and apply it to the tab list element. Boolean value
  93. saveState:false, // save the selected tab into a cookie so it stays selected after a reload. This requires that the wrapping div needs to have an ID (so we know which tab we're saving)
  94. autoAnchor:false, // will move over any existing id of a headline in tabs markup so it can be linked to it
  95. pagination:false, // adds buttons to each tab to switch to the next/previous tab
  96. position:'top', // can be 'top' or 'bottom'. Defines where the tabs list is inserted.
  97. wrapInnerNavLinks: '', // inner wrap for a-tags in tab navigation. See http://api.jquery.com/wrapInner/ for further informations
  98. firstNavItemClass: 'first', // Classname of the first list item in the tab navigation
  99. lastNavItemClass: 'last' // Classname of the last list item in the tab navigation
  100. };
  101. var keyCodes = {
  102. 37 : -1, //LEFT
  103. 38 : -1, //UP
  104. 39 : +1, //RIGHT
  105. 40 : +1 //DOWN
  106. };
  107. var positions = {
  108. top : 'prepend',
  109. bottom : 'append'
  110. };
  111. this.options = $.extend(defaults, config);
  112. var tabsCount = 0;
  113. if($("body").data('accessibleTabsCount') !== undefined){
  114. tabsCount = $("body").data('accessibleTabsCount');
  115. }
  116. $("body").data('accessibleTabsCount',this.size()+tabsCount);
  117. var o = this;
  118. return this.each(function(t) {
  119. var el = $(this);
  120. var list = '';
  121. var tabCount = 0;
  122. var ids = [];
  123. $(el).wrapInner('<div class="'+o.options.wrapperClass+'"></div>');
  124. $(el).find(o.options.tabhead).each(function(i){
  125. var id = '';
  126. elId = $(this).attr('id');
  127. if(elId){
  128. // Skip this item if it already exists.
  129. if(elId.indexOf('accessibletabscontent') === 0) {
  130. return;
  131. }
  132. id =' id="'+elId+'"';
  133. }
  134. var tabId = o.getUniqueId('accessibletabscontent', tabsCount+t, i);//get a unique id to assign to this tab's heading
  135. var navItemId = o.getUniqueId('accessibletabsnavigation', tabsCount+t, i);//get a unique id for this navigation item
  136. ids.push(tabId);
  137. if(o.options.cssClassAvailable === true) {
  138. var cssClass = '';
  139. if($(this).attr('class')) {
  140. cssClass = $(this).attr('class');
  141. cssClass = ' class="'+cssClass+'"';
  142. }
  143. list += '<li id="'+navItemId+'"><a'+id+''+cssClass+' href="#'+tabId+'">'+$(this).html()+'</a></li>';
  144. } else {
  145. list += '<li id="'+navItemId+'"><a'+id+' href="#'+tabId+'">'+$(this).html()+'</a></li>';
  146. }
  147. $(this).attr({"id": tabId, "class": o.options.tabheadClass, "tabindex": "-1"});//assign the unique id and the tabheadClass class name to this tab's heading
  148. tabCount++;
  149. });
  150. if (o.options.syncheights && $.fn[o.options.syncHeightMethodName]) {
  151. $(el).find(o.options.tabbody)[o.options.syncHeightMethodName]();
  152. $(window).resize(function(){
  153. $(el).find(o.options.tabbody)[o.options.syncHeightMethodName]();
  154. });
  155. }
  156. // Ensure that the call to setup tabs is re-runnable
  157. var tabs_selector = '.' + o.options.tabsListClass;
  158. if(!$(el).find(tabs_selector).length) {
  159. $(el)[positions[o.options.position]]('<ul class="clearfix '+o.options.tabsListClass+' tabamount'+tabCount+'"></ul>');
  160. }
  161. $(el).find(tabs_selector).append(list);
  162. // initial show first content block and hide the others
  163. var content = $(el).find(o.options.tabbody);
  164. if (content.length > 0) {
  165. $(content).hide();
  166. $(content[0]).show();
  167. }
  168. $(el).find("ul."+o.options.tabsListClass+">li:first").addClass(o.options.currentClass).addClass(o.options.firstNavItemClass)
  169. .find('a')[o.options.currentInfoPosition]('<span class="'+o.options.currentInfoClass+'">'+o.options.currentInfoText+'</span>')
  170. .parents("ul."+o.options.tabsListClass).children('li:last').addClass(o.options.lastNavItemClass);
  171. if (o.options.wrapInnerNavLinks) {
  172. $(el).find('ul.'+o.options.tabsListClass+'>li>a').wrapInner(o.options.wrapInnerNavLinks);
  173. }
  174. $(el).find('ul.'+o.options.tabsListClass+'>li>a').each(function(i){
  175. $(this).click(function(event){
  176. event.preventDefault();
  177. el.trigger("showTab.accessibleTabs", [$(event.target)]);
  178. if(o.options.saveState && $.cookie){
  179. $.cookie('accessibletab_'+el.attr('id')+'_active',i);
  180. }
  181. $(el).find('ul.'+o.options.tabsListClass+'>li.'+o.options.currentClass).removeClass(o.options.currentClass)
  182. .find("span."+o.options.currentInfoClass).remove();
  183. $(this).blur();
  184. $(el).find(o.options.tabbody+':visible').hide();
  185. $(el).find(o.options.tabbody).eq(i)[o.options.fx](o.options.fxspeed);
  186. $(this)[o.options.currentInfoPosition]('<span class="'+o.options.currentInfoClass+'">'+o.options.currentInfoText+'</span>')
  187. .parent().addClass(o.options.currentClass);
  188. //now, only after writing the currentInfoText span to the tab list link, set focus to the tab's heading
  189. $($(this).attr("href"),true).focus().keyup(function(event){
  190. if(keyCodes[event.keyCode]){
  191. o.showAccessibleTab(i+keyCodes[event.keyCode]);
  192. $(this).unbind( "keyup" );
  193. }
  194. });
  195. // $(el).find('.accessibletabsanchor').keyup(function(event){
  196. // if(keyCodes[event.keyCode]){
  197. // o.showAccessibleTab(i+keyCodes[event.keyCode]);
  198. // }
  199. // });
  200. });
  201. $(this).focus(function(event){
  202. $(document).keyup(function(event){
  203. if(keyCodes[event.keyCode]){
  204. o.showAccessibleTab(i+keyCodes[event.keyCode]);
  205. }
  206. });
  207. });
  208. $(this).blur(function(event){
  209. $(document).unbind( "keyup" );
  210. });
  211. });
  212. if(o.options.saveState && $.cookie){
  213. var savedState = $.cookie('accessibletab_'+el.attr('id')+'_active');
  214. debug($.cookie('accessibletab_'+el.attr('id')+'_active'));
  215. if(savedState !== null){
  216. o.showAccessibleTab(savedState,el.attr('id'));
  217. }
  218. }
  219. if(o.options.autoAnchor && window.location.hash){
  220. var anchorTab = $('.'+o.options.tabsListClass).find(window.location.hash);
  221. if(anchorTab.size()){
  222. anchorTab.click();
  223. }
  224. }
  225. if(o.options.pagination){
  226. var m = '<ul class="pagination">';
  227. m +=' <li class="previous"><a href="#{previousAnchor}"><span>{previousHeadline}</span></a></li>';
  228. m +=' <li class="next"><a href="#{nextAnchor}"><span>{nextHeadline}</span></a></li>';
  229. m +='</ul>';
  230. var tabs = $(el).find('.tabbody');
  231. var tabcount = tabs.size();
  232. tabs.each(function(idx){
  233. $(this).append(m);
  234. var next = idx+1;
  235. if(next>=tabcount){next = 0;}
  236. var previous = idx-1;
  237. if(previous<0){previous = tabcount-1;}
  238. var p = $(this).find('.pagination');
  239. var previousEl = p.find('.previous');
  240. previousEl.find('span').text($('#'+ids[previous]).text());
  241. previousEl.find('a').attr('href','#'+ids[previous])
  242. .click(function(event){
  243. event.preventDefault();
  244. $(el).find('.tabs-list a').eq(previous).click();
  245. });
  246. var nextEl = p.find('.next');
  247. nextEl.find('span').text($('#'+ids[next]).text());
  248. nextEl.find('a').attr('href','#'+ids[next])
  249. .click(function(event){
  250. event.preventDefault();
  251. $(el).find('.tabs-list a').eq(next).click();
  252. });
  253. });
  254. }
  255. });
  256. },
  257. showAccessibleTab: function(index,id){
  258. debug('showAccessibleTab');
  259. var o = this;
  260. if(id) {
  261. var el = $('#'+id);
  262. var links = el.find('ul.'+o.options.tabsListClass+'>li>a');
  263. el.trigger("showTab.accessibleTabs", [links.eq(index)]);
  264. links.eq(index).click();
  265. } else {
  266. return this.each(function() {
  267. var el = $(this);
  268. el.trigger("showTab.accessibleTabs");
  269. var links = el.find('ul.'+o.options.tabsListClass+'>li>a');
  270. el.trigger("showTab.accessibleTabs", [links.eq(index)]);
  271. links.eq(index).click();
  272. });
  273. }
  274. },
  275. showAccessibleTabSelector: function(selector){
  276. debug('showAccessibleTabSelector');
  277. var o = this;
  278. var el = $(selector);
  279. if(el){
  280. if(el.get(0).nodeName.toLowerCase() == 'a'){
  281. el.click();
  282. }else{
  283. debug('the selector of a showAccessibleTabSelector() call needs to point to a tabs headline!');
  284. }
  285. }
  286. }
  287. });
  288. // private Methods
  289. function debug(msg,info){
  290. if(debugMode && window.console && window.console.log){
  291. if(info){
  292. window.console.log(info+': ',msg);
  293. }else{
  294. window.console.log(msg);
  295. }
  296. }
  297. }
  298. })(jQuery);