/** * Accessible Tabs - jQuery plugin for accessible, unobtrusive tabs * Build to seemlessly work with the CCS-Framework YAML (yaml.de) not depending on YAML though * @requires jQuery - tested with 1.9.1, 1.7 and 1.4.2 but might as well work with older versions * * english article: http://blog.ginader.de/archives/2009/02/07/jQuery-Accessible-Tabs-How-to-make-tabs-REALLY-accessible.php * german article: http://blog.ginader.de/archives/2009/02/07/jQuery-Accessible-Tabs-Wie-man-Tabs-WIRKLICH-zugaenglich-macht.php * * code: http://github.com/ginader/Accessible-Tabs * please report issues at: http://github.com/ginader/Accessible-Tabs/issues * * Copyright (c) 2007 Dirk Ginader (ginader.de) * Dual licensed under the MIT and GPL licenses: * http://www.opensource.org/licenses/mit-license.php * http://www.gnu.org/licenses/gpl.html * */ (function($) { var debugMode = true; $.fn.extend({ // We assume there could be multiple sets of tabs on a page, so, // the unique id for each invididual tab's heading is identified with params q and r (e.g., id="accessibletabscontent0-2") getUniqueId: function(p, q, r){ if (r===undefined) {r='';} else {r='-'+r;} return p + q + r; }, accessibleTabs: function(config) { var defaults = { wrapperClass: 'content', // Classname to apply to the div that is wrapped around the original Markup currentClass: 'current', // Classname to apply to the LI of the selected Tab tabhead: 'h4', // Tag or valid Query Selector of the Elements to Transform the Tabs-Navigation from (originals are removed) tabheadClass: 'tabhead', // Classname to apply to the target heading element for each tab div tabbody: '.tabbody', // Tag or valid Query Selector of the Elements to be treated as the Tab Body fx:'show', // can be "fadeIn", "slideDown", "show" fxspeed: 'normal', // speed (String|Number): "slow", "normal", or "fast") or the number of milliseconds to run the animation currentInfoText: 'current tab: ', // text to indicate for screenreaders which tab is the current one currentInfoPosition: 'prepend', // Definition where to insert the Info Text. Can be either "prepend" or "append" currentInfoClass: 'current-info', // Class to apply to the span wrapping the CurrentInfoText tabsListClass:'tabs-list', // Class to apply to the generated list of tabs above the content syncheights:false, // syncs the heights of the tab contents when the SyncHeight plugin is available http://blog.ginader.de/dev/jquery/syncheight/index.php 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 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 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) autoAnchor:false, // will move over any existing id of a headline in tabs markup so it can be linked to it pagination:false, // adds buttons to each tab to switch to the next/previous tab position:'top', // can be 'top' or 'bottom'. Defines where the tabs list is inserted. wrapInnerNavLinks: '', // inner wrap for a-tags in tab navigation. See http://api.jquery.com/wrapInner/ for further informations firstNavItemClass: 'first', // Classname of the first list item in the tab navigation lastNavItemClass: 'last', // Classname of the last list item in the tab navigation clearfixClass: 'clearfix' // Name of the Class that is used to clear/contain floats }; var keyCodes = { 37 : -1, //LEFT 38 : -1, //UP 39 : +1, //RIGHT 40 : +1 //DOWN }; var positions = { top : 'prepend', bottom : 'append' }; this.options = $.extend(defaults, config); var tabsCount = 0; if($("body").data('accessibleTabsCount') !== undefined){ tabsCount = $("body").data('accessibleTabsCount'); } $("body").data('accessibleTabsCount',this.size()+tabsCount); var o = this; return this.each(function(t) { var el = $(this); var list = ''; var tabCount = 0; var ids = []; $(el).wrapInner('
'); $(el).find(o.options.tabhead).each(function(i){ var id = ''; var elId = $(this).attr('id'); if(elId){ // Skip this item if it already exists. if(elId.indexOf('accessibletabscontent') === 0) { return; } id =' id="'+elId+'"'; } var tabId = o.getUniqueId('accessibletabscontent', tabsCount+t, i);//get a unique id to assign to this tab's heading var navItemId = o.getUniqueId('accessibletabsnavigation', tabsCount+t, i);//get a unique id for this navigation item ids.push(tabId); if(o.options.cssClassAvailable === true) { var cssClass = ''; if($(this).attr('class')) { cssClass = $(this).attr('class'); cssClass = ' class="'+cssClass+'"'; } list += '
  • '+$(this).html()+'
  • '; } else { list += '
  • '+$(this).html()+'
  • '; } $(this).attr({"id": tabId, "class": o.options.tabheadClass, "tabindex": "-1"});//assign the unique id and the tabheadClass class name to this tab's heading tabCount++; }); if (o.options.syncheights && $.fn[o.options.syncHeightMethodName]) { $(el).find(o.options.tabbody)[o.options.syncHeightMethodName](); $(window).resize(function(){ $(el).find(o.options.tabbody)[o.options.syncHeightMethodName](); }); } // Ensure that the call to setup tabs is re-runnable var tabs_selector = '.' + o.options.tabsListClass; if(!$(el).find(tabs_selector).length) { $(el)[positions[o.options.position]](''); } $(el).find(tabs_selector).append(list); // initial show first content block and hide the others var content = $(el).find(o.options.tabbody); if (content.length > 0) { $(content).hide(); $(content[0]).show(); } $(el).find("ul."+o.options.tabsListClass+">li:first").addClass(o.options.currentClass).addClass(o.options.firstNavItemClass) .find('a')[o.options.currentInfoPosition](''+o.options.currentInfoText+'') .parents("ul."+o.options.tabsListClass).children('li:last').addClass(o.options.lastNavItemClass); if (o.options.wrapInnerNavLinks) { $(el).find('ul.'+o.options.tabsListClass+'>li>a').wrapInner(o.options.wrapInnerNavLinks); } $(el).find('ul.'+o.options.tabsListClass+'>li>a').each(function(i){ $(this).click(function(event){ event.preventDefault(); el.trigger("showTab.accessibleTabs", [$(event.target)]); if(o.options.saveState && $.cookie){ $.cookie('accessibletab_'+el.attr('id')+'_active',i); } $(el).find('ul.'+o.options.tabsListClass+'>li.'+o.options.currentClass).removeClass(o.options.currentClass) .find("span."+o.options.currentInfoClass).remove(); $(this).blur(); $(el).find(o.options.tabbody+':visible').hide(); $(el).find(o.options.tabbody).eq(i)[o.options.fx](o.options.fxspeed); $(this)[o.options.currentInfoPosition](''+o.options.currentInfoText+'') .parent().addClass(o.options.currentClass); //now, only after writing the currentInfoText span to the tab list link, set focus to the tab's heading $($(this).attr("href")).focus().keyup(function(event){ if(keyCodes[event.keyCode]){ o.showAccessibleTab(i+keyCodes[event.keyCode]); $(this).unbind( "keyup" ); } }); // $(el).find('.accessibletabsanchor').keyup(function(event){ // if(keyCodes[event.keyCode]){ // o.showAccessibleTab(i+keyCodes[event.keyCode]); // } // }); }); $(this).focus(function(){ $(document).keyup(function(event){ if(keyCodes[event.keyCode]){ o.showAccessibleTab(i+keyCodes[event.keyCode]); } }); }); $(this).blur(function(){ $(document).unbind( "keyup" ); }); }); if(o.options.saveState && $.cookie){ var savedState = $.cookie('accessibletab_'+el.attr('id')+'_active'); debug($.cookie('accessibletab_'+el.attr('id')+'_active')); if(savedState !== null){ o.showAccessibleTab(savedState,el.attr('id')); } } if(o.options.autoAnchor && window.location.hash){ var anchorTab = $('.'+o.options.tabsListClass).find(window.location.hash); if(anchorTab.size()){ anchorTab.click(); } } if(o.options.pagination){ var m = ''; var tabs = $(el).find('.tabbody'); var tabcount = tabs.size(); tabs.each(function(idx){ $(this).append(m); var next = idx+1; if(next>=tabcount){next = 0;} var previous = idx-1; if(previous<0){previous = tabcount-1;} var p = $(this).find('.pagination'); var previousEl = p.find('.previous'); previousEl.find('span').text($('#'+ids[previous]).text()); previousEl.find('a').attr('href','#'+ids[previous]) .click(function(event){ event.preventDefault(); $(el).find('.tabs-list a').eq(previous).click(); }); var nextEl = p.find('.next'); nextEl.find('span').text($('#'+ids[next]).text()); nextEl.find('a').attr('href','#'+ids[next]) .click(function(event){ event.preventDefault(); $(el).find('.tabs-list a').eq(next).click(); }); }); } }); }, showAccessibleTab: function(index,id){ debug('showAccessibleTab'); var o = this; if(id) { var el = $('#'+id); var links = el.find('ul.'+o.options.tabsListClass+'>li>a'); el.trigger("showTab.accessibleTabs", [links.eq(index)]); links.eq(index).click(); } else { return this.each(function() { var el = $(this); el.trigger("showTab.accessibleTabs"); var links = el.find('ul.'+o.options.tabsListClass+'>li>a'); el.trigger("showTab.accessibleTabs", [links.eq(index)]); links.eq(index).click(); }); } }, showAccessibleTabSelector: function(selector){ debug('showAccessibleTabSelector'); var el = $(selector); if(el){ if(el.get(0).nodeName.toLowerCase() === 'a'){ el.click(); }else{ debug('the selector of a showAccessibleTabSelector() call needs to point to a tabs headline!'); } } } }); // private Methods function debug(msg,info){ if(debugMode && window.console && window.console.log){ if(info){ window.console.log(info+': ',msg); }else{ window.console.log(msg); } } } })(jQuery);