1/* 2 * jQuery plugin. 3 * Accessible tabs with WAI ARIA. 4 * In mobile tabs become accordion. 5 * */ 6 7$.fn.accessibleResponsiveTabs = function () { 8 9 $(this).each(function () { 10 var obj = $(this), 11 tabs = $('.standardTabs', obj), 12 tabContainer = $('.tabContainer', obj); 13 14 tabs.attr('role', 'tablist'); 15 16 // tabs init 17 function initTabs() { 18 $('li', tabs).each(function () { 19 var rel = $(this).attr('rel'); 20 $('a', $(this)).attr({ 21 'role': 'tab', 22 'aria-controls': rel, 23 'tabindex': -1, 24 'aria-selected': false, 25 'id': 'link' + rel 26 }); 27 }); 28 } 29 30 function activeTab(active) { 31 // Default tab active 32 var activeLink = $('li.active a', tabs), 33 activeTab = activeLink.parent(); 34 // current active tab 35 if (active) { 36 // if click on accordion mode 37 if (active.hasClass("accordionHeading")) { 38 relAccordionHeading = active.attr('rel'); 39 activeLink = $('li[rel^=' + relAccordionHeading + '] a', tabs); 40 activeTab = activeLink.parent(); 41 } else { 42 activeLink = active; 43 activeTab = active.parent(); 44 } 45 } 46 // reset tabs 47 initTabs(); 48 $('li', tabs).removeClass('active'); 49 // active tab 50 activeLink.attr({ 51 'tabindex': 0, 52 'aria-selected': true 53 }); 54 activeTab.addClass('active'); 55 } 56 57 initTabs(); 58 activeTab(); 59 60 // Accordion init 61 function initAccordions() { 62 $('.accordionHeading', tabContainer).each(function () { 63 var rel = $(this).attr('rel'); 64 $(this).attr({ 65 'role': 'tab', 66 'aria-controls': rel, 67 'tabindex': -1, 68 'aria-selected': false 69 }); 70 }); 71 } 72 73 function activeAccordion() { 74 var activeTabRel = $('li.active', tabs).attr('rel'), 75 activeAccordionHeading = $('.accordionHeading[rel^=' + activeTabRel + ']'); 76 77 // Reset all accordions 78 initAccordions(); 79 // Active current accordion heading 80 activeAccordionHeading.attr({ 81 'tabindex': 0, 82 'aria-selected': true 83 }); 84 } 85 86 initAccordions(); 87 activeAccordion(); 88 89 // Content init 90 function initContents() { 91 $('.tabContent', tabContainer).each(function () { 92 var labelledby = 'link' + $(this).attr('id'); 93 $(this).attr({ 94 'role': 'tabpanel', 95 'aria-labelledby': labelledby, 96 'aria-hidden': true 97 }); 98 }); 99 } 100 101 function activeContent() { 102 var activeTabRel = $('li.active', tabs).attr('rel'), 103 activeContent = $('#' + activeTabRel); 104 105 // Reset all contents 106 initContents(); 107 $(".tabContent", obj).removeClass('active'); 108 // Active current content 109 activeContent.addClass('active').attr({ 110 'aria-hidden': false 111 }); 112 } 113 114 initContents(); 115 activeContent(); 116 117 118 // If in tab mode 119 $('li > a', tabs).click(function (e) { 120 // active current tab 121 activeTab($(this)); 122 123 // active current content 124 activeContent(); 125 126 // active current accordion 127 activeAccordion(); 128 129 e.preventDefault(); 130 }); 131 132 // Keyboard if in tab mode 133 $('li > a', tabs).on('keydown', function (e) { 134 var activeTab = $('li.active', tabs), 135 firstTab = $('li:first-child', tabs), 136 lastTab = $('li:last-child', tabs); 137 138 // prev 139 if (e.keyCode == 37 || e.keyCode == 38) { 140 if (activeTab.is($('li:first-child', tabs))) { 141 lastTab.children('a').click().focus(); 142 } else { 143 activeTab.prev().children('a').click().focus(); 144 } 145 } 146 // next 147 else if (e.keyCode == 40 || e.keyCode == 39) { 148 if (activeTab.is($('li:last-child', tabs))) { 149 firstTab.children('a').click().focus(); 150 } else { 151 activeTab.next().children('a').click().focus(); 152 } 153 } 154 }); 155 156 // If in accordion mode 157 $('.accordionHeading', obj).click(function () { 158 // active current tab 159 activeTab($(this)); 160 161 // active current content 162 activeContent(); 163 164 // active current accordion 165 activeAccordion(); 166 }); 167 168 // Keyboard if in accordion mode 169 $('.accordionHeading', tabContainer).on('keydown', function (e) { 170 var activeAccordionHeading = $(".accordionHeading[aria-selected='true']", tabContainer), 171 firstAccordionHeading = $('.accordionHeading:first-of-type', tabContainer), 172 lastAccordionHeading = $('.accordionHeading:last-of-type', tabContainer); 173 174 // prev 175 if (e.keyCode == 37 || e.keyCode == 38) { 176 if (activeAccordionHeading.is($('.accordionHeading:first-of-type', tabContainer))) { 177 lastAccordionHeading.click().focus(); 178 } else { 179 activeAccordionHeading.prev().prev().click().focus(); 180 } 181 } 182 // next 183 if (e.keyCode == 40 || e.keyCode == 39) { 184 if (activeAccordionHeading.is($('.accordionHeading:last-of-type', tabContainer))) { 185 firstAccordionHeading.click().focus(); 186 } else { 187 activeAccordionHeading.next().next().click().focus(); 188 } 189 } 190 }); 191 }); 192};
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.