[32796] | 1 | !function(factory) {
|
---|
| 2 |
|
---|
| 3 | if(typeof define === 'function' && define.amd) define(['index', 'jquery', 'jquery.ba-resize'], factory);
|
---|
| 4 | else TabsAccordion = factory(Index, jQuery);
|
---|
| 5 | }
|
---|
| 6 | (function(Index, $) {
|
---|
| 7 |
|
---|
| 8 | var count = 0,
|
---|
| 9 | namespace = 'tabsaccordion',
|
---|
| 10 | $window = $(window),
|
---|
| 11 | $html = $(document.documentElement).addClass('js'),
|
---|
| 12 | $body = $(document.body);
|
---|
| 13 |
|
---|
| 14 | $.resize.throttleWindow = false;
|
---|
| 15 | $.fn.TabsAccordion = function(options) {
|
---|
| 16 |
|
---|
| 17 | function T(element, options) {
|
---|
| 18 |
|
---|
| 19 | var idNamespace = namespace + '-' + count++,
|
---|
| 20 | $element = $(element),
|
---|
| 21 | $panels,
|
---|
| 22 | $tabs,
|
---|
| 23 | $tablist,
|
---|
| 24 | $content,
|
---|
| 25 | self = {
|
---|
| 26 | version: '1.2.0',
|
---|
| 27 | type: ($element.hasClass('accordion') && 'accordion') || ($element.hasClass('tabs') && 'tabs'),
|
---|
| 28 |
|
---|
| 29 | create: function() {
|
---|
| 30 |
|
---|
| 31 | $panels = $element.children();
|
---|
| 32 | $tabs = $panels.children(':first-child');
|
---|
| 33 |
|
---|
| 34 | if(self.index) var prev = self.index.curr;
|
---|
| 35 | (self.index = Index($panels.length - 1)).loop = true; // <- index looping for keyboard accessibility
|
---|
| 36 | if(prev) self.index.set(prev);
|
---|
| 37 |
|
---|
| 38 | if(self.type === 'tabs') $element.prepend(($tabs = self.tabsCreateTablist($tabs).children()).end());
|
---|
| 39 |
|
---|
| 40 | $tablist = (self.type === 'tabs' ? $tabs.parent() : $element).attr('role', 'tablist');
|
---|
| 41 |
|
---|
| 42 | $tabs.attr({
|
---|
| 43 | 'id': function(index) {
|
---|
| 44 |
|
---|
| 45 | return this.id || idNamespace + '-tab-' + index;
|
---|
| 46 | },
|
---|
| 47 | 'role': 'tab'
|
---|
| 48 | });
|
---|
| 49 |
|
---|
| 50 | ($content = $panels.map(function(index) {
|
---|
| 51 |
|
---|
| 52 | return $(this)
|
---|
| 53 | .attr({
|
---|
| 54 | 'aria-labelledby': $tabs[index].id,
|
---|
| 55 | 'id': this.id || idNamespace + '-panel-' + index,
|
---|
| 56 | 'role': 'tabpanel'
|
---|
| 57 | })
|
---|
| 58 | .children()
|
---|
| 59 | .slice(1)
|
---|
| 60 | .wrapAll('<div><div></div></div>')
|
---|
| 61 | .parent()
|
---|
| 62 | .parent()
|
---|
| 63 | .get();
|
---|
| 64 | }))
|
---|
| 65 | .each(self.collapse);
|
---|
| 66 |
|
---|
| 67 | $element
|
---|
| 68 | .attr({
|
---|
| 69 | 'id': element.id || idNamespace,
|
---|
| 70 | 'tabindex': 0
|
---|
| 71 | })
|
---|
| 72 | .on('click.' + idNamespace, self.type === 'accordion' && '> * > :first-child' || '> :first-child > *', function(event) {
|
---|
| 73 |
|
---|
| 74 | self.goTo($tabs.index($(event.target).closest($tabs)));
|
---|
| 75 | })
|
---|
| 76 | .on('keydown.' + idNamespace, function(event) {
|
---|
| 77 |
|
---|
| 78 | // event.target should be the element and not a descendant
|
---|
| 79 | if(event.target !== element) return;
|
---|
| 80 |
|
---|
| 81 | var match = {
|
---|
| 82 | 37: 'prev',
|
---|
| 83 | 38: 'prev',
|
---|
| 84 | 39: 'next',
|
---|
| 85 | 40: 'next'
|
---|
| 86 | }[event.keyCode];
|
---|
| 87 |
|
---|
| 88 | if(match) {
|
---|
| 89 |
|
---|
| 90 | event.preventDefault();
|
---|
| 91 |
|
---|
| 92 | self.goTo(self.index[match]);
|
---|
| 93 | }
|
---|
| 94 | })
|
---|
| 95 | .on('resize.' + idNamespace, self.resize)
|
---|
| 96 | .trigger('create');
|
---|
| 97 |
|
---|
| 98 |
|
---|
| 99 | if(options.saveState) self.extensions.saveState(options.saveState);
|
---|
| 100 | if(options.responsiveSwitch) self.extensions.responsiveSwitch(options.responsiveSwitch);
|
---|
| 101 | if(options.hashWatch) self.extensions.hashWatch();
|
---|
| 102 | if(options.pauseMedia) self.extensions.pauseMedia();
|
---|
| 103 |
|
---|
| 104 | if(typeof self.index.curr !== 'number') self.index.set(0);
|
---|
| 105 |
|
---|
| 106 | setTimeout(function() {
|
---|
| 107 |
|
---|
| 108 | $element.addClass('transition');
|
---|
| 109 | })
|
---|
| 110 |
|
---|
| 111 | return self.expand(self.index.curr);
|
---|
| 112 | },
|
---|
| 113 |
|
---|
| 114 | destroy: function(keepData) {
|
---|
| 115 |
|
---|
| 116 | if(self.type === 'tabs') {
|
---|
| 117 |
|
---|
| 118 | $element.height('auto');
|
---|
| 119 |
|
---|
| 120 | $tablist.remove();
|
---|
| 121 | }
|
---|
| 122 | else {
|
---|
| 123 |
|
---|
| 124 | $tabs
|
---|
| 125 | .removeAttr('role')
|
---|
| 126 | .filter('[id^="' + idNamespace + '"]').removeAttr('id');
|
---|
| 127 |
|
---|
| 128 | $tablist.removeAttr('role');
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | $panels
|
---|
| 132 | .removeAttr('aria-expanded aria-labelledby role')
|
---|
| 133 | .filter('[id^="' + idNamespace + '"]').removeAttr('id');
|
---|
| 134 |
|
---|
| 135 | $content
|
---|
| 136 | .children()
|
---|
| 137 | .children()
|
---|
| 138 | .unwrap()
|
---|
| 139 | .unwrap();
|
---|
| 140 |
|
---|
| 141 | if(!keepData)
|
---|
| 142 | $element
|
---|
| 143 | .removeData(namespace)
|
---|
| 144 | .removeData('responsiveBreakpoint.' + idNamespace);
|
---|
| 145 |
|
---|
| 146 | $element
|
---|
| 147 | .add([window, document.body])
|
---|
| 148 | .off('.' + idNamespace)
|
---|
| 149 | .end()
|
---|
| 150 | .removeAttr('aria-activedescendant tabindex')
|
---|
| 151 | .removeClass(self.type)
|
---|
| 152 | .filter('[id^="' + idNamespace + '"]').removeAttr('id')
|
---|
| 153 | .end()
|
---|
| 154 | .trigger('destroy');
|
---|
| 155 |
|
---|
| 156 | return self;
|
---|
| 157 | },
|
---|
| 158 |
|
---|
| 159 | resize: function() {
|
---|
| 160 |
|
---|
| 161 | if(self.type === 'tabs') $element.height($tablist.outerHeight() + $panels.eq(self.index.curr).outerHeight());
|
---|
| 162 | else if(self.type === 'accordion' && $panels[self.index.curr].ariaExpanded)
|
---|
| 163 | $content
|
---|
| 164 | .eq(self.index.curr)
|
---|
| 165 | .height($content.eq(self.index.curr).children().outerHeight());
|
---|
| 166 |
|
---|
| 167 | return self;
|
---|
| 168 | },
|
---|
| 169 |
|
---|
| 170 | expand: function(index) {
|
---|
| 171 |
|
---|
| 172 | var $panel = $panels.eq(index).attr('aria-expanded', $panels[index].ariaExpanded = true);
|
---|
| 173 |
|
---|
| 174 | if(self.resize().type === 'tabs') $tabs.eq(index).addClass('current');
|
---|
| 175 |
|
---|
| 176 | $element
|
---|
| 177 | .attr('aria-activedescendant', $panels[self.index.curr].id)
|
---|
| 178 | .trigger('expand', [index, $panel]);
|
---|
| 179 |
|
---|
| 180 | return self;
|
---|
| 181 | },
|
---|
| 182 |
|
---|
| 183 | collapse: function(index) {
|
---|
| 184 |
|
---|
| 185 | var $panel = $panels.eq(index).attr('aria-expanded', $panels[index].ariaExpanded = false);
|
---|
| 186 |
|
---|
| 187 | if(self.type === 'tabs') $tabs.eq(index).removeClass('current');
|
---|
| 188 | else $content.eq(index).height(0);
|
---|
| 189 |
|
---|
| 190 | $element.trigger('collapse', [index, $panel]);
|
---|
| 191 |
|
---|
| 192 | return self;
|
---|
| 193 | },
|
---|
| 194 |
|
---|
| 195 | goTo: function(index) {
|
---|
| 196 |
|
---|
| 197 | if(self.index.curr !== index && typeof self.index.curr === 'number') self.collapse(self.index.curr);
|
---|
| 198 | self.index.set(index);
|
---|
| 199 |
|
---|
| 200 | return self[self.type === 'accordion' && $panels.eq(index).prop('ariaExpanded') ? 'collapse' : 'expand'](self.index.curr);
|
---|
| 201 | },
|
---|
| 202 |
|
---|
| 203 | tabsCreateTablist: options.tabsCreateTablist || function(titles) {
|
---|
| 204 |
|
---|
| 205 | for(var i = 0, li = ''; i < titles.length; i++) li += '<li>' + titles[i].innerHTML + '</li>';
|
---|
| 206 |
|
---|
| 207 | return $('<ul>' + li + '</ul>');
|
---|
| 208 | },
|
---|
| 209 |
|
---|
| 210 | extensions: {
|
---|
| 211 | hashWatch: function() {
|
---|
| 212 |
|
---|
| 213 | var that = {
|
---|
| 214 | changeHash: function(hash, $target) {
|
---|
| 215 |
|
---|
| 216 | var id = $target[0].id;
|
---|
| 217 |
|
---|
| 218 | $target[0].id = '';
|
---|
| 219 | location.hash = hash;
|
---|
| 220 | $target[0].id = id;
|
---|
| 221 |
|
---|
| 222 | return that;
|
---|
| 223 | },
|
---|
| 224 |
|
---|
| 225 | expand: function(hash, event) {
|
---|
| 226 |
|
---|
| 227 | var $target = $element.find(hash);
|
---|
| 228 |
|
---|
| 229 | if($target.length) {
|
---|
| 230 |
|
---|
| 231 | var $panel = $target.closest($panels);
|
---|
| 232 |
|
---|
| 233 | if($panel.length) {
|
---|
| 234 |
|
---|
| 235 | if(event) event.preventDefault();
|
---|
| 236 |
|
---|
| 237 | self.goTo($panels.index($panel));
|
---|
| 238 | that.changeHash(hash, $target);
|
---|
| 239 |
|
---|
| 240 | if(event) {
|
---|
| 241 | setTimeout(function() {
|
---|
| 242 |
|
---|
| 243 | $html
|
---|
| 244 | .add($body)
|
---|
| 245 | .animate({
|
---|
| 246 | scrollTop: $target.offset().top
|
---|
| 247 | });
|
---|
| 248 | },250);
|
---|
| 249 | }
|
---|
| 250 | }
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | return that;
|
---|
| 254 | }
|
---|
| 255 | };
|
---|
| 256 |
|
---|
| 257 | $body
|
---|
| 258 | // hash anchor activation
|
---|
| 259 | .on('click.' + idNamespace, 'a[href^="#"]:not([href="#"])', function(event) {
|
---|
| 260 |
|
---|
| 261 | that.expand($(event.target).attr('href'), event);
|
---|
| 262 | })
|
---|
| 263 | // navigation e.g. back button
|
---|
| 264 | .on('hashchange.' + idNamespace, function() {
|
---|
| 265 |
|
---|
| 266 | that.expand(location.hash);
|
---|
| 267 | });
|
---|
| 268 |
|
---|
| 269 | return that.expand(location.hash);
|
---|
| 270 | },
|
---|
| 271 |
|
---|
| 272 | saveStateLoaded: false,
|
---|
| 273 | saveState: function(storage) {
|
---|
| 274 |
|
---|
| 275 | if(typeof storage !== 'object') return;
|
---|
| 276 |
|
---|
| 277 | var state = {
|
---|
| 278 | remove: function() {
|
---|
| 279 |
|
---|
| 280 | storage.removeItem(idNamespace);
|
---|
| 281 | },
|
---|
| 282 |
|
---|
| 283 | load: function() {
|
---|
| 284 |
|
---|
| 285 | var item = storage.getItem(idNamespace),
|
---|
| 286 | data = JSON.parse(item);
|
---|
| 287 |
|
---|
| 288 | if(data && data.current) self.index.set(data.current);
|
---|
| 289 |
|
---|
| 290 | self.extensions.saveStateLoaded = true;
|
---|
| 291 | },
|
---|
| 292 |
|
---|
| 293 | save: function() {
|
---|
| 294 |
|
---|
| 295 | storage.setItem(idNamespace, JSON.stringify({current: self.index.curr, expanded: $panels[self.index.curr].ariaExpanded}));
|
---|
| 296 | }
|
---|
| 297 | };
|
---|
| 298 |
|
---|
| 299 | // load only once per instance per page load
|
---|
| 300 | if(!self.extensions.saveStateLoaded) state.load();
|
---|
| 301 |
|
---|
| 302 | $window.on('unload.' + idNamespace, state.save);
|
---|
| 303 |
|
---|
| 304 | return state;
|
---|
| 305 | },
|
---|
| 306 |
|
---|
| 307 | responsiveSwitch: function(breakpoint) {
|
---|
| 308 |
|
---|
| 309 | if(breakpoint === 'tablist') {
|
---|
| 310 |
|
---|
| 311 | if(self.type === 'tabs') $element.data('responsiveBreakpoint.' + idNamespace, breakpoint = getTablistWidth());
|
---|
| 312 | else breakpoint = $element.data('responsiveBreakpoint.' + idNamespace);
|
---|
| 313 | }
|
---|
| 314 |
|
---|
| 315 | function getTablistWidth() {
|
---|
| 316 |
|
---|
| 317 | // measure combined width of all tabs instead of single width of tablist, because tabs are floated and can jump to the next line
|
---|
| 318 | for(var i = 0, width = 0; i < $tabs.length; i++) width += $tabs.eq(i).outerWidth(true);
|
---|
| 319 |
|
---|
| 320 | return width;
|
---|
| 321 | }
|
---|
| 322 |
|
---|
| 323 | function switchTo(type) {
|
---|
| 324 |
|
---|
| 325 | var current = self.index.curr,
|
---|
| 326 | expanded = $panels[current].ariaExpanded;
|
---|
| 327 |
|
---|
| 328 | self.destroy(true);
|
---|
| 329 |
|
---|
| 330 | $element.addClass(self.type = type);
|
---|
| 331 |
|
---|
| 332 | self.index.set(current);
|
---|
| 333 | self.create();
|
---|
| 334 |
|
---|
| 335 | $element.trigger('typechange', type);
|
---|
| 336 | }
|
---|
| 337 |
|
---|
| 338 | function checkBreakpoint() {
|
---|
| 339 |
|
---|
| 340 | var type = $element.outerWidth() <= breakpoint ? 'accordion' : 'tabs';
|
---|
| 341 |
|
---|
| 342 | if(self.type !== type) switchTo(type);
|
---|
| 343 | }
|
---|
| 344 |
|
---|
| 345 | $element.on('resize.' + idNamespace, checkBreakpoint);
|
---|
| 346 | },
|
---|
| 347 |
|
---|
| 348 | pauseMedia: function() {
|
---|
| 349 |
|
---|
| 350 | if(typeof Modernizr === 'undefined' || !Modernizr.audio || !Modernizr.video || !$element.find('audio, video').length) return;
|
---|
| 351 |
|
---|
| 352 | $element.on('collapse.' + idNamespace, function(event, index, $panel) {
|
---|
| 353 |
|
---|
| 354 | $panel.find('audio, video').each(function() {
|
---|
| 355 |
|
---|
| 356 | this.pause();
|
---|
| 357 | });
|
---|
| 358 | });
|
---|
| 359 | }
|
---|
| 360 | }
|
---|
| 361 | };
|
---|
| 362 |
|
---|
| 363 | return self.create();
|
---|
| 364 | }
|
---|
| 365 |
|
---|
| 366 | var options = options || {},
|
---|
| 367 | args = Array.prototype.slice.call(arguments, 1);
|
---|
| 368 |
|
---|
| 369 | return this.each(function(index) {
|
---|
| 370 |
|
---|
| 371 | var $this = $(this);
|
---|
| 372 |
|
---|
| 373 | // method call : instantiation
|
---|
| 374 | return $this.data(namespace) ? $this.data(namespace)[options].apply(this, args) : $this.data(namespace, T(this, options));
|
---|
| 375 | });
|
---|
| 376 | }
|
---|
| 377 | }); |
---|