1/** 2 * @file 3 * Tabs. 4 * 5 * Documentation, see https://kennis.limoengroen.nl/js-plugins/v1/ 6 */ 7 8(function ($) { 9 10 // Populate global object container "lt" with plugin specific object. 11 lt.data.Tabs = {}; 12 lt.data.Tabs.usedID = {}; 13 14 $.fn.ltTabs = function(options) { 15 16 // Default settings. 17 var settings = $.extend({ 18 toggleMenuItems: false, 19 toggleMenuTitle: false, 20 toggleMenuLocation: false, 21 tabActive: 0, 22 tabAddition: false, 23 tablistAriaLabel: 'tabs', 24 removeHeading: false, 25 animationSpeed: 0, 26 responsive: false, 27 breakpointMin: '0', 28 breakpointMax: '999999' 29 }, options); 30 31 return $(once('tabs', this)).each(function() { 32 new lt.classes.Tabs($(this), settings); 33 }); 34 }; 35 36 lt.classes.Tabs = function(element, settings) { 37 this.status = false; 38 this.element = element; 39 this.settings = settings; 40 this.resizeTimer = null; 41 42 if (this.element.length && this.element.find(this.settings.toggleMenuItems).length > 1) { 43 this.toggle(); 44 45 if (this.settings.responsive) { 46 var that = this; 47 var width = $(window).width(); 48 var height = $(window).height(); 49 $(window).resize(function (e) { 50 if ($(window).width() != width || $(window).height() != height) { 51 that.resizeTimer = setTimeout(function() { 52 that.toggle() 53 }, 250); 54 } 55 }); 56 window.addEventListener("orientationchange", function (e) { 57 that.toggle(); 58 }); 59 } 60 } 61 }; 62 63 lt.classes.Tabs.prototype.toggle = function() { 64 var minRes = this.settings.breakpointMin; 65 var maxRes = this.settings.breakpointMax; 66 67 var browserWidth = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 68 if (browserWidth > minRes && browserWidth < maxRes) { 69 if (!this.status) { 70 this.apply(); 71 } 72 } 73 else { 74 if (this.status) { 75 this.destroy(); 76 } 77 } 78 }; 79 80 // Open from hash. 81 lt.classes.Tabs.prototype.openHash = function() { 82 var newHash = ('tab-' + location.hash.substr(1)); 83 var tabLinks = $('[aria-controls="' + newHash + '"]').closest('.tabs-toggler').find('.tab-nav-link'); 84 tabLinks.removeClass('active').attr({'aria-selected' : 'false', 'tabindex': -1 }); 85 $('[aria-controls="' + newHash + '"]').addClass('active').attr({'aria-selected' : 'true', 'tabindex': 0 }); 86 $('#' + newHash).show(this.settings.animationSpeed).siblings('.tab-content').hide(this.settings.animationSpeed); 87 location.hash = newHash.substr(4); 88 }; 89 90 lt.classes.Tabs.prototype.apply = function() { 91 // Wrap elements and add id. 92 var that = this; 93 94 var currentHash = ''; 95 if (location.hash.length > 0) { 96 currentHash = 'tab-' + location.hash.substr(1); 97 } 98 99 var tabHeading = this.element.find(this.settings.toggleMenuItems).each(function() { 100 // Add a sibling if desired. Only the first one will be added. 101 if (that.settings.tabAddition) { 102 $(this).siblings(that.settings.tabAddition + ':first').andSelf().wrapAll('<div></div>'); 103 } 104 else { 105 $(this).wrapAll('<div></div>'); 106 } 107 // Create title and prevent double id's. 108 var title = $(this).parent().find(that.settings.toggleMenuTitle).text(); 109 $(this).parent().attr('id', 'tab-' + that.uniqueHtmlId(title)); 110 }); 111 // Define tab-content. 112 var tabContent = $(tabHeading).parent(); 113 114 // Create the tabs-toggler. 115 var tabsToggler = $('<div class="tabs-toggler"><ul class="content-list" role="tablist" aria-label="' + this.settings.tablistAriaLabel + '"></ul></div>'); 116 $(once('tabs', tabContent)).each(function(i) { 117 $(this).attr({ 118 'class' : 'tab-content', 119 'role': 'tabpanel', 120 'aria-labelledby': 'tab' + (i + 1) 121 }); 122 var tabName = $(this).attr('id'); 123 if (tabName == currentHash) { 124 that.settings.tabActive = i; 125 } 126 var tabTitle = $(this).find(that.settings.toggleMenuTitle).text(); 127 128 var tabLi = $('<li>').attr({ 129 'class': 'tab', 130 'role': 'presentation' 131 }).css('cursor', 'pointer'); 132 var tabLink = $('<a>').attr({ 133 'href': '#' + tabName, 134 'id': 'tab' + (i + 1), 135 'class': 'tab-nav-link', 136 'role': 'tab', 137 'tabindex' : -1, 138 'aria-controls': tabName 139 }).text(tabTitle); 140 tabLink.appendTo(tabLi); 141 tabLi.appendTo(tabsToggler.children('ul')); 142 }).not(':eq(' + this.settings.tabActive + ')').hide(); 143 144 tabsToggler.find('ul li:eq(' + this.settings.tabActive + ') a').attr('tabindex', 0); 145 146 // Remove headings if wanted. 147 if (this.settings.removeHeading) { 148 this.element.find(this.settings.toggleMenuTitle).remove(); 149 } 150 151 // Place the tabs-toggler after the heading. 152 if (this.settings.toggleMenuLocation) { 153 this.element.children(this.settings.toggleMenuLocation).after(tabsToggler); 154 } 155 else {
156 this.element.prepend(tabsToggler); 157 } 158 159 var tabLinks = $(tabsToggler).find('.tab-nav-link'); 160 161 // Make given item active. 162 $(tabsToggler).find('li:eq(' + this.settings.tabActive + ') .tab-nav-link').addClass('active').attr('aria-selected', 'true'); 163 $(tabsToggler).find('li .tab-nav-link').not('[aria-selected="true"]').attr('aria-selected', 'false'); 164 165 // Tabs-toggler behaviour. 166 tabLinks.click(function(e) { 167 // We only need to update the hash, the rest is done by 168 // lt.classes.Tabs.prototype.openHash via eventListener hashchange. 169 var target = $(this).attr('aria-controls'); 170 location.hash = target.substr(4); 171 e.preventDefault(); 172 }); 173 174 // Respond to the Enter key on the tabs. 175 $(".tab-nav-link").keyup(function(event){ 176 if (event.keyCode == 13) { 177 $(this).click(); 178 } 179 }); 180 181 // When tablinks have focus, make other items accessible via left and right key. 182 var tabLinksNumberOfItems = tabLinks.length - 1; 183 var tabLinksActiveItem = that.settings.tabActive; 184 tabLinks.each(function(i) { 185 this.addEventListener('keydown', function(e) { 186 // Left. 187 if (event.keyCode == 37) { 188 if (tabLinksActiveItem < 1) { 189 $(tabLinks[tabLinksNumberOfItems]).click().focus(); 190 tabLinksActiveItem = tabLinksNumberOfItems; 191 } else { 192 $(tabLinks[tabLinksActiveItem - 1]).click().focus(); 193 tabLinksActiveItem--; 194 } 195 } 196 // Right. 197 if (event.keyCode == 39) { 198 if (tabLinksActiveItem < tabLinksNumberOfItems) { 199 $(tabLinks[tabLinksActiveItem + 1]).click().focus(); 200 tabLinksActiveItem++; 201 } else { 202 $(tabLinks[0]).click().focus(); 203 tabLinksActiveItem = 0; 204 } 205 } 206 }); 207 }); 208 209 // On pageload openHash. 210 that.openHash(); 211 212 // Act on hashchange. 213 window.addEventListener('hashchange', function() { 214 that.openHash(); 215 }); 216 217 this.status = true; 218 }; 219 220 lt.classes.Tabs.prototype.destroy = function() { 221 this.element.find('.tabs-toggler').remove(); 222 this.element.find('.tab-content').find('> *').unwrap(); 223 this.status = false; 224 lt.data.Tabs.usedID = {}; 225 }; 226 227 lt.classes.Tabs.prototype.uniqueHtmlId = function(text) { 228 var id = lt.helpers.cleanAttrName(text); 229 if (lt.data.Tabs.usedID[id]) { 230 lt.data.Tabs.usedID[id].count++; 231 return id + '--tabs-' + lt.data.Tabs.usedID[id].count; 232 } 233 else { 234 lt.data.Tabs.usedID[id] = {}; 235 lt.data.Tabs.usedID[id].count = 1; 236 return id + '--tabs'; 237 } 238 }; 239 240}(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.