forked from stazna01/jQuery-at-Accordion-or-Tabs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jquery.atAccordionOrTabs.js
executable file
·258 lines (220 loc) · 12.7 KB
/
jquery.atAccordionOrTabs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
/* jQuery at Responsive Accordion or Tabs - v1.0.6 - 2020-01-13
* https://github.com/stazna01/jQuery-at-Accordion-or-Tabs
*
* This plugin is built heavily upon the work by Stuart Robson
* found at http://codepen.io/sturobson/pen/xgfeI
*
* Copyright (c) 2020 Nathan Stazewski; Licensed MIT */
(function ( $ ) {
$.fn.accordionortabs = function( options ) {
// This is the easiest way to have default options.
var settings = $.extend({
// These are the defaults.
defaultOpened: 0,
containerBreakPoint: 0, //allows a user to force the vertical mode at a certain pixel width of its container, in the case when tabs may technically fit but you'd prefer the vertical mode
tabsIfPossible: true,
hashbangPrefix: 'tabset_',
centerTabs: false,
minCenterTabs: 0
}, options );
startingOuterWidth = $(window).width(); //used later to detect orientation change across all mobile browsers (other methods don't always work on Android)
is_iOS = /(iPad|iPhone|iPod)/g.test( navigator.userAgent ); //needed due to the fact that iOS scrolling causes false resizes
function find_max_tab_width (accordion_or_tabs_object, tabs_when_possible_index, skip_fix_accordion_or_tabs_function) {
skip_fix_accordion_or_tabs_function = (typeof skip_fix_accordion_or_tabs_function === 'undefined') ? false : skip_fix_accordion_or_tabs_function;
// if first width check is when the screen is smaller and an accordion pane has wrapped to two lines, the max tab width will be incorrect, so anytime an accordion is switching back to tabs, this function is called again to make sure it really should be changing at that point
accordion_or_tabs_object.addClass('at-tabs');
tabs_width = 0;
$('> li > a', accordion_or_tabs_object ).each(function( index ) {
tabs_width = tabs_width + $(this).outerWidth(true) + 5;
if (index == 0 && settings.centerTabs === true) {
tabs_width = tabs_width - $(this).css('margin-left').replace(/[^-\d\.]/g, '');
}
if (index == $(this).closest('.at-accordion-or-tabs').find('> li > a').length-1) {
largest_tab_widths[tabs_when_possible_index] = tabs_width + 15;
if (skip_fix_accordion_or_tabs_function===false) {
fix_accordion_or_tabs();
}
}
})
}
function find_first_tab_width (accordion_or_tabs_object, tabs_when_possible_index) {
// need to check if tab sizes have changed, which would indicate a breakpoint has been hit that changed the size of the tabs (which means we'd need to recompute the max tab width). The easiest way is to keep track of the first tab for each tab set and then check on each resize if the first tabs have changed size.
skip_fix_accordion_or_tabs_function = (typeof skip_fix_accordion_or_tabs_function === 'undefined') ? false : skip_fix_accordion_or_tabs_function;
//accordion_or_tabs_object.addClass('at-tabs');
$first_tab_width = $('> li > a', accordion_or_tabs_object ).eq( 0 ).outerWidth(true);
if(typeof first_tab_widths[tabs_when_possible_index] == 'undefined') {
first_tab_widths[tabs_when_possible_index] = $first_tab_width;
} else if ($first_tab_width != first_tab_widths[tabs_when_possible_index]) {
first_tab_widths[tabs_when_possible_index] = $first_tab_width;
find_max_tab_width (accordion_or_tabs_object, tabs_when_possible_index,true);
}
}
window.fix_accordion_or_tabs = function() {
$(".bbq.at-accordion-or-tabs.at-tabs-when-possible").each(function( index ) {
$tabset = $(this); // "This" is the current tab set. Save us frequently rewrapping "this".
$tabs = $tabset.find('>li>a'); // Let’s get all the tabs, too
tabs_when_possible_index = index;
if ($tabset.attr("data-rtContainerBreakPoint")) {
rt_user_defined_container_breakpoint = $tabset.attr("data-rtContainerBreakPoint");
} else {
rt_user_defined_container_breakpoint = settings.containerBreakPoint;
}
find_first_tab_width($tabset,tabs_when_possible_index);
if (largest_tab_widths[index] > $tabset.width() || rt_user_defined_container_breakpoint >= $tabset.width()) { //the width the tabs needs is greater than the available width and the optional user defined container width
if (settings.centerTabs === true) {
$tabs.eq(0).css('margin-left','');
}
$tabset.removeClass('at-tabs');
var idx = $.bbq.getState( $tabset.attr('data-tabset-id'), true ) || 0;
if(idx == 0) { //the first tab is showing but only because a tab has to be open, when converting back to an accordion it gets shut because no hashbang exists to have it open
$tabset.addClass('at-accordion-closed')
$tabs.eq(0).removeClass('active').attr('aria-expanded','false').next('section').removeClass('is-open').hide();
}
} else { //there is enough room for the tabs to be shown
if (settings.centerTabs === true && $tabs.length >= settings.minCenterTabs) {
$tabs.eq(0).css('margin-left',($tabset.outerWidth(true) - largest_tab_widths[index])/2 + 10);
}
if($tabset.hasClass('at-accordion-closed')) { //at-accordion-closed is assigned when an accordion gets converted to tabs and a tab has to be open. this class lets it be known that it should be closed again if converted back to an accordion
$tabset.removeClass('at-accordion-closed');
$tabs.eq(0).addClass('active').attr('aria-expanded','true').next('section').addClass('is-open').show().focus();
find_max_tab_width ($tabset,tabs_when_possible_index); //it's possible the browser was started so small that the text in an accordion pane was taking up more than one line (so the max width is wrong), therefore when accordions switch to tabs we recheck the tab widths and update the array holding those widths
}
}
});
}
if (settings.tabsIfPossible == true) {
this.addClass('at-tabs-when-possible');
}
//this.find('>li>section').attr('aria-live','assertive');
if (settings.defaultOpened != 0) {
this.each(function( index ) {
if(settings.defaultOpened <= $(this).find('>li').length) {
$(this).attr('data-default-opened',settings.defaultOpened);
}
});
}
this.addClass('bbq clearfix at-accordion-or-tabs').find('>li>a').prepend('<span class="at-tab-one-pixel-fix-left"></span><span class="at-tab-one-pixel-fix-right"></span>');
this.each(function( index ) {
$(this).keydown(function(e){
// Listen for arrow keys
if ([37,38,39,40].indexOf(e.keyCode) == -1) {
return;
}
$temp_tab = $(this).find('>li>a:focus');
switch(e.keyCode) {
case 38: //up arrow
case 37: // left arrow
// Make sure to stop event bubbling
e.preventDefault();
e.stopPropagation();
// This is the first item in the accordion
//$temp_tab = $(this).find('>li').first().find('a');
if(($temp_tab.closest("ul").find("li").first()).is($temp_tab.parent())){
//When pressing left arrow on first item, go to last item
$temp_tab.parent().nextAll('li').last().find('>a').focus();
} else {
// Focus on the previous item in accordion from active item
$temp_tab.parent().prevAll().first().find('>a').focus();
}
break;
case 40: //down arrow
case 39: // right arrow
// Make sure to stop event bubbling
e.preventDefault();
e.stopPropagation();
if(($temp_tab.closest("ul").find("li").last()).is($temp_tab.parent())) {
// Focus on the last item in the top level
$temp_tab.parent().prevAll('li').last().find('a').first().focus();
} else {
$temp_tab.parent().nextAll('li').first().find('a').first().focus();
}
break;
}
}); //end keydown
});
this.each(function( index ) {
$(this).attr('data-tabset-id',settings.hashbangPrefix+index);
$(this).find('>li>a').each(function( index2 ) {
$(this).attr('href','!#'+settings.hashbangPrefix+index+'='+index2).attr('aria-controls','sect_'+settings.hashbangPrefix+index+'_'+index2).attr('id','accordion_'+settings.hashbangPrefix+index+'_'+index2).attr('aria-expanded','false'); //set all to aria-expanded false before going through and marking any true
$(this).next('section').attr('id','sect_'+settings.hashbangPrefix+index+'_'+index2).attr('aria-labelledby','accordion_'+settings.hashbangPrefix+index+'_'+index2);
});
});
$(document).ready(function () {
$.param.fragment.ajaxCrawlable( true ); // Enable "AJAX Crawlable" mode. (uses #! instead of just #)
largest_tab_widths = new Array();
first_tab_widths = new Array();
$(".bbq.at-accordion-or-tabs.at-tabs-when-possible").each(function( index ) {
find_first_tab_width ($(this), index);
find_max_tab_width ($(this), index);
});
$('.bbq.at-accordion-or-tabs').each(function( index ) {
var current_hash = $.bbq.getState( $(this).attr('data-tabset-id'), true ) || 0;
var default_state = $(this).attr('data-default-opened');
if (current_hash == 0 && default_state !== undefined && default_state != '') { //if there is no hash for this set but there is a default set, push that default
var state = {},
// Get the id of this tab widget.
id = $(this).attr( 'data-tabset-id' );
// Set the state!
state[ id ] = default_state;
$.bbq.pushState( state );
}
});
$('.at-accordion-or-tabs').on('click', '> li > a', function(e) {
if(!$(this).hasClass('active')) {
var state = {},
// Get the id of this tab widget.
id = $(this).closest( '.bbq' ).attr( 'data-tabset-id' ),
// Get the index of this tab.
idx = $(this).parent().prevAll().length + 1;
// Set the state!
state[ id ] = idx;
$.bbq.pushState( state );
} else if (!$(this).closest('.at-accordion-or-tabs').hasClass('at-tabs')) { // if it's an accordion (which can open and close)
var id = $(this).closest( '.bbq' ).attr( 'data-tabset-id' ); // Get the id of this tab widget.
$.bbq.removeState(id);
}
$(this).blur();
e.preventDefault();
});
$(window).trigger( "hashchange" ); //in case it's loaded immediately with a hashbang we need to trigger it on the first load
}); //end $(document).ready
$(window).resize(function() {
if(!is_iOS || (is_iOS && (startingOuterWidth !== $(window).width()))) {
startingOuterWidth = $(window).width(); //MUST update the starting width so future orientation changes will be noticed
fix_accordion_or_tabs();
}
});
$(window).on( 'hashchange', function(e) {
// Iterate over all tab widgets.
$('.bbq').each(function(){
// Get the index for this tab widget from the hash, based on the
// appropriate id property. In jQuery 1.4, you should use e.getState()
// instead of $.bbq.getState(). The second, 'true' argument coerces the
// string value to a number.
var idx = $.bbq.getState( $(this).attr('data-tabset-id'), true ) || 0;
if (idx > 0) { //if at least some sort of hash has been set for this .bbq item
if (!$(this).find('>li>a').eq(idx - 1).hasClass('active')) { //only run this section if a different tab/accordion pane has been selected than the active one (note the !...and also note that an accordion might have no active pain...such as an all closed accordion...which would skip this if statement)
if($(this).hasClass('at-tabs')) {
$(this).find('.is-open').removeClass('is-open').hide();
$(this).find('>li>section').eq( idx - 1 ).toggleClass('is-open').toggle();
} else {
$(this).find('.is-open').removeClass('is-open').slideToggle();
$(this).find('>li>section').eq( idx - 1 ).toggleClass('is-open').slideToggle();
}
$(this).find('.active').removeClass('active').attr('aria-expanded','false');
$(this).find('>li>a').eq( idx - 1 ).addClass('active').attr('aria-expanded','true').blur();
}
$(this).removeClass('at-accordion-closed'); //needs to be after the if statement so that a fully closed accordion that is opened will have this class removed
} else { // if no hash has been set for this .bbq item
if ($(this).hasClass("at-accordion-or-tabs") && (!$(this).hasClass("at-tabs"))) { //this is how accordion panes get closed
$(this).addClass('at-accordion-closed').find('>li>a.active').removeClass('active').attr('aria-expanded','false').next('section').removeClass('is-open').slideUp();
} else if ($(this).hasClass("at-accordion-or-tabs") && ($(this).hasClass("at-tabs") && !$(this).hasClass('at-accordion-closed'))) { //this is how accordion panes get closed
$(this).addClass('at-accordion-closed').find('>li>a.active').removeClass('active').attr('aria-expanded','false').next('section').removeClass('is-open').hide();
$(this).children('li').first().children('a').addClass('active').attr('aria-expanded','true').next('section').addClass('is-open').show();
}
}
});
});
return this;
};
}( jQuery ));