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.9.1, 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) 2016 Dirk Ginader (http://dir.kg) 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 */ 18 19(function($) { 20 var debugMode = false; 21 $.fn.extend({ 22 // We assume there could be multiple sets of tabs on a page, so, 23 // the unique id for each invididual tab's heading is identified with params q and r (e.g., id="accessibletabscontent0-2") 24 getUniqueId: function(p, q, r){ 25 if (r===undefined) {r='';} else {r='-'+r;} 26 return p + q + r; 27 }, 28 // in case a className i.e. currentInfoClass contains more than one className this converts 29 // 'current-info visuallyhidden' to '.current-info.visuallyhidden' to be used as jQuery selector 30 getClassSelectorsFromClassNames: function(className){ 31 if(className.indexOf(' ' > -1)){ 32 // the className has spaces so it probably contains more than one classNames 33 return '.'+className.split(' ').join('.'); 34 }else{ 35 return '.'+className; 36 } 37 }, 38 accessibleTabs: function(config) { 39 var defaults = { 40 wrapperClass: 'content', // Classname to apply to the div that is wrapped around the original Markup 41 currentClass: 'current', // Classname to apply to the LI of the selected Tab 42 tabhead: 'h4', // Tag or valid Query Selector of the Elements to Transform the Tabs-Navigation from (originals are removed) 43 tabheadClass: 'tabhead', // Classname to apply to the target heading element for each tab div 44 tabbody: '.tabbody', // Tag or valid Query Selector of the Elements to be treated as the Tab Body 45 fx:'show', // can be "fadeIn", "slideDown", "show" 46 fxspeed: 'normal', // speed (String|Number): "slow", "normal", or "fast") or the number of milliseconds to run the animation 47 currentInfoText: 'current tab: ', // text to indicate for screenreaders which tab is the current one 48 currentInfoPosition: 'prepend', // Definition where to insert the Info Text. Can be either "prepend" or "append" 49 currentInfoClass: 'current-info', // Class to apply to the span wrapping the CurrentInfoText 50 tabsListClass:'tabs-list', // Class to apply to the generated list of tabs above the content 51 syncheights:false, // syncs the heights of the tab contents when the SyncHeight plugin is available http://blog.ginader.de/dev/jquery/syncheight/index.php 52 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 53 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 54 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) 55 autoAnchor:false, // will move over any existing id of a headline in tabs markup so it can be linked to it 56 pagination:false, // adds buttons to each tab to switch to the next/previous tab 57 position:'top', // can be 'top' or 'bottom'. Defines where the tabs list is inserted. 58 wrapInnerNavLinks: '', // inner wrap for a-tags in tab navigation. See http://api.jquery.com/wrapInner/ for further informations 59 firstNavItemClass: 'first', // Classname of the first list item in the tab navigation 60 lastNavItemClass: 'last', // Classname of the last list item in the tab navigation 61 clearfixClass: 'clearfix' // Name of the Class that is used to clear/contain floats 62 }; 63 var keyCodes = { 64 37 : -1, //LEFT 65 38 : -1, //UP 66 39 : +1, //RIGHT 67 40 : +1 //DOWN 68 }; 69 var positions = {
70 top : 'prepend', 71 bottom : 'append' 72 }; 73 this.options = $.extend(defaults, config); 74 75 var tabsCount = 0; 76 if($("body").data('accessibleTabsCount') !== undefined){ 77 tabsCount = $("body").data('accessibleTabsCount'); 78 } 79 $("body").data('accessibleTabsCount',this.length+tabsCount); 80 81 var o = this; 82 return this.each(function(t) { 83 var el = $(this); 84 var list = ''; 85 var tabCount = 0; 86 var ids = []; 87 88 $(el).wrapInner('<div class="'+o.options.wrapperClass+'"></div>'); 89 90 $(el).find(o.options.tabhead).each(function(i){ 91 var id = ''; 92 var elId = $(this).attr('id'); 93 if(elId){ 94 // Skip this item if it already exists. 95 if(elId.indexOf('accessibletabscontent') === 0) { 96 return; 97 } 98 id =' id="'+elId+'"'; 99 } 100 var tabId = o.getUniqueId('accessibletabscontent', tabsCount+t, i);//get a unique id to assign to this tab's heading 101 var navItemId = o.getUniqueId('accessibletabsnavigation', tabsCount+t, i);//get a unique id for this navigation item 102 ids.push(tabId); 103 104 // SNK: support tab head with links 105 var navItemText = ($(this).children("a").length>0)? $(this).children("a").html() :$(this).html(); 106 var ariaLabel=""; 107 if($(this).attr("aria-label")) { 108 ariaLabel='aria-label="'+$(this).attr("aria-label")+'"'; 109 } 110 111 if(o.options.cssClassAvailable === true) { 112 var cssClass = ''; 113 if($(this).attr('class')) { 114 cssClass = $(this).attr('class'); 115 cssClass = ' class="'+cssClass+'"'; 116 } 117 list += '<li id="'+navItemId+'"><a'+id+''+cssClass+' href="#'+tabId+'" '+ariaLabel+'>'+navItemText+'</a></li>'; 118 } else { 119 list += '<li id="'+navItemId+'"><a'+id+' href="#'+tabId+'" '+ariaLabel+'>'+navItemText+'</a></li>'; 120 } 121 $(this).attr({"id": tabId, "class": o.options.tabheadClass, "tabindex": "-1"});//assign the unique id and the tabheadClass class name to this tab's heading 122 tabCount++; 123 }); 124 125 if (o.options.syncheights && $.fn[o.options.syncHeightMethodName]) { 126 $(el).find(o.options.tabbody)[o.options.syncHeightMethodName](); 127 $(window).resize(function(){ 128 $(el).find(o.options.tabbody)[o.options.syncHeightMethodName](); 129 }); 130 } 131 132 // Ensure that the call to setup tabs is re-runnable 133 var tabs_selector = o.getClassSelectorsFromClassNames(o.options.tabsListClass); 134 if(!$(el).find(tabs_selector).length) { 135 $(el)[positions[o.options.position]]('<ul class="'+o.options.clearfixClass+' '+o.options.tabsListClass+' tabamount'+tabCount+'"></ul>'); 136 } 137 138 $(el).find(tabs_selector).append(list); 139 140 // initial show first content block and hide the others 141 var content = $(el).find(o.options.tabbody); 142 if (content.length > 0) { 143 $(content).hide(); 144 $(content[0]).show(); 145 } 146 $(el).find("ul"+o.getClassSelectorsFromClassNames(o.options.tabsListClass)+">li:first").addClass(o.options.currentClass).addClass(o.options.firstNavItemClass) 147 .find('a')[o.options.currentInfoPosition]('<span class="'+o.options.currentInfoClass+'">'+o.options.currentInfoText+'</span>') 148 .parents("ul"+o.getClassSelectorsFromClassNames(o.options.tabsListClass)).children('li:last').addClass(o.options.lastNavItemClass); 149 150 // SNK: add first and last class to tabhead 151 152 var tabheads = $(el).find(o.options.tabhead); 153 if (tabheads.length > 0) { 154 $(tabheads[0]).addClass(o.options.currentClass).addClass(o.options.firstNavItemClass); 155 $(tabheads[tabheads.length-1]).addClass(o.options.lastNavItemClass); 156 } 157 158 if (o.options.wrapInnerNavLinks) { 159 $(el).find('ul'+o.getClassSelectorsFromClassNames(o.options.tabsListClass)+'>li>a').wrapInner(o.options.wrapInnerNavLinks); 160 } 161 162 $(el).find('ul'+o.getClassSelectorsFromClassNames(o.options.tabsListClass)+'>li>a').each(function(i){ 163 $(this).click(function(event){ 164 event.preventDefault(); 165 el.trigger("showTab.accessibleTabs", [$(event.target)]); 166 if(o.options.saveState && $.cookie){ 167 $.cookie('accessibletab_'+el.attr('id')+'_active',i); 168 } 169 $(el).find('ul'+o.getClassSelectorsFromClassNames(o.options.tabsListClass)+'>li.'+o.options.currentClass).removeClass(o.options.currentClass) 170 .find("span"+o.getClassSelectorsFromClassNames(o.options.currentInfoClass)).remove(); 171 172 // SNK: remove current class from tabhead 173 $(el).find(o.options.tabhead).removeClass(o.options.currentClass); 174 175 $(this).blur(); 176 $(el).find(o.options.tabbody+':visible').hide(); 177 $(el).find(o.options.tabbody).eq(i)[o.options.fx](o.options.fxspeed); 178 $(this)[o.options.currentInfoPosition]('<span class="'+o.options.currentInfoClass+'">'+o.options.currentInfoText+'</span>') 179 .parent().addClass(o.options.currentClass); 180 181 // SNK: add active class to current tabhead 182 $($(this).attr("href")).addClass(o.options.currentClass); 183 184 //now, only after writing the currentInfoText span to the tab list link, set focus to the tab's heading 185 186 $($(this).attr("href")).focus().keyup(function(event){ 187 if(keyCodes[event.keyCode]){ 188 o.showAccessibleTab(i+keyCodes[event.keyCode]); 189 $(this).unbind( "keyup" ); 190 } 191 }); 192 193 // $(el).find('.accessibletabsanchor').keyup(function(event){ 194 // if(keyCodes[event.keyCode]){ 195 // o.showAccessibleTab(i+keyCodes[event.keyCode]); 196 // } 197 // }); 198 }); 199 200 $(this).focus(function(){ 201 $(document).keyup(function(event){ 202 if(keyCodes[event.keyCode]){ 203 o.showAccessibleTab(i+keyCodes[event.keyCode]); 204 } 205 }); 206 }); 207 $(this).blur(function(){ 208 $(document).unbind( "keyup" ); 209 }); 210 211 }); 212 213 if(o.options.saveState && $.cookie){ 214 var savedState = $.cookie('accessibletab_'+el.attr('id')+'_active'); 215 debug($.cookie('accessibletab_'+el.attr('id')+'_active')); 216 if(savedState !== null){ 217 o.showAccessibleTab(savedState,el.attr('id')); 218 } 219 } 220 221 if(o.options.autoAnchor && window.location.hash){ 222 var anchorTab = $(o.getClassSelectorsFromClassNames(o.options.tabsListClass)).find(window.location.hash); 223 if(anchorTab.length){ 224 anchorTab.click(); 225 } 226 } 227 228 if(o.options.pagination){ 229 var m = '<ul class="pagination">'; 230 m +=' <li class="previous"><a href="#{previousAnchor}"><span>{previousHeadline}</span></a></li>'; 231 m +=' <li class="next"><a href="#{nextAnchor}"><span>{nextHeadline}</span></a></li>'; 232 m +='</ul>'; 233 var tabs = $(el).find('.tabbody'); 234 var tabcount = tabs.length; 235 tabs.each(function(idx){ 236 $(this).append(m); 237 var next = idx+1; 238 if(next>=tabcount){next = 0;} 239 var previous = idx-1; 240 if(previous<0){previous = tabcount-1;} 241 var p = $(this).find('.pagination'); 242 var previousEl = p.find('.previous'); 243 previousEl.find('span').text($('#'+ids[previous]).text()); 244 previousEl.find('a').attr('href','#'+ids[previous]) 245 .click(function(event){ 246 event.preventDefault(); 247 $(el).find('.tabs-list a').eq(previous).click(); 248 }); 249 var nextEl = p.find('.next'); 250 nextEl.find('span').text($('#'+ids[next]).text()); 251 nextEl.find('a').attr('href','#'+ids[next]) 252 .click(function(event){ 253 event.preventDefault(); 254 $(el).find('.tabs-list a').eq(next).click(); 255 }); 256 }); 257 } 258 }); 259 }, 260 showAccessibleTab: function(index,id){ 261 debug('showAccessibleTab'); 262 var o = this; 263 if(id) { 264 var el = $('#'+id); 265 var links = el.find('ul'+o.getClassSelectorsFromClassNames(o.options.tabsListClass)+'>li>a'); 266 el.trigger("showTab.accessibleTabs", [links.eq(index)]); 267 links.eq(index).click(); 268 } else { 269 return this.each(function() { 270 var el = $(this); 271 el.trigger("showTab.accessibleTabs"); 272 var links = el.find('ul'+o.getClassSelectorsFromClassNames(o.options.tabsListClass)+'>li>a'); 273 el.trigger("showTab.accessibleTabs", [links.eq(index)]); 274 links.eq(index).click(); 275 }); 276 } 277 }, 278 showAccessibleTabSelector: function(selector){ 279 debug('showAccessibleTabSelector'); 280 var el = $(selector); 281 if(el){ 282 if(el.get(0).nodeName.toLowerCase() === 'a'){ 283 el.click(); 284 }else{ 285 debug('the selector of a showAccessibleTabSelector() call needs to point to a tabs headline!'); 286 } 287 } 288 } 289 }); 290 // private Methods 291 function debug(msg,info){ 292 if(debugMode && window.console && window.console.log){ 293 if(info){ 294 window.console.log(info+': ',msg); 295 }else{ 296 window.console.log(msg); 297 } 298 } 299 } 300})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.