UNPKG

11.1 kBJavaScriptView Raw
1/*!
2 * BeefUp 1.4.9 - A jQuery Accordion Plugin
3 * Copyright Sascha Künstler https://www.schaschaweb.de/
4 */
5
6(function($) {
7 'use strict';
8
9 // BeefUp object
10 var beefup = {};
11
12 // Defaults
13 beefup.defaults = {
14 // Boolean: Enable accessibility features like tab control
15 accessibility: true,
16
17 // String: Selector of the trigger element
18 trigger: '.beefup__head',
19
20 // String: Selector of the collapsible content
21 content: '.beefup__body',
22
23 // String: Name of the class which shows if a accordion is triggered or not
24 openClass: 'is-open',
25
26 // String: Set animation type to "slide", "fade" or leave empty "", default is "slide"
27 animation: 'slide',
28
29 // Integer: Set the speed of the open animation
30 openSpeed: 200,
31
32 // Integer: Set the speed of the close animation
33 closeSpeed: 200,
34
35 // Boolean: Scroll to accordion on open
36 scroll: false,
37
38 // Integer: Set the speed of the scroll animation
39 scrollSpeed: 400,
40
41 // Integer: Additional offset to accordion position
42 scrollOffset: 0,
43
44 // Boolean: Open just one accordion at once
45 openSingle: false,
46
47 // Mixed: Leave items open, accepts null, integer (index) or string (selector, 'first' or 'last')
48 stayOpen: null,
49
50 // Boolean: Block close event on click
51 selfBlock: false,
52
53 // Boolean: Close accordion on click outside
54 selfClose: false,
55
56 // Boolean: Open accordion with id on hash change
57 hash: true,
58
59 // Array: Array of objects, default is null
60 breakpoints: null,
61
62 // Callback: Fires after the accordions initially setup
63 onInit: null,
64
65 // Callback: Fires after accordion opens content
66 onOpen: null,
67
68 // Callback: Fires after accordion close content
69 onClose: null,
70
71 // Callback: Fires after scroll animation
72 onScroll: null
73 };
74
75 // Counter
76 beefup.id = 0;
77
78 // Private methods
79 beefup.methods = {
80
81 /**
82 * Extend options
83 *
84 * @param {jQuery} $el
85 */
86 _getVars: function($el) {
87 var vars = $.extend(true, {}, $el.data('beefup'), $el.data('beefup-options'));
88
89 return (vars.breakpoints) ? beefup.methods._getResponsiveVars(vars) : vars;
90 },
91
92 /**
93 * Overwrite options depending on breakpoints
94 *
95 * @param {object} vars
96 * @returns {*}
97 */
98 _getResponsiveVars: function(vars) {
99 var windowWidth = window.innerWidth || $(window).width();
100
101 // Sort
102 vars.breakpoints.sort(function(a, b) {
103 return ((a.breakpoint < b.breakpoint) ? -1 : ((a.breakpoint > b.breakpoint) ? 1 : 0));
104 });
105
106 $.each(vars.breakpoints, function(index, value) {
107 if (windowWidth >= value.breakpoint) {
108 vars = $.extend({}, vars, value.settings);
109 }
110 });
111
112 return vars;
113 },
114
115 /**
116 * Animation types
117 *
118 * @param {string} type
119 * @param {jQuery} $el
120 * @param {number} speed
121 * @param {function} callback
122 */
123 _animation: function(type, $el, speed, callback) {
124 switch (type) {
125 case 'slideDown':
126 $el.slideDown(speed, callback);
127 break;
128 case 'slideUp':
129 $el.slideUp(speed, callback);
130 break;
131 case 'fadeIn':
132 $el.fadeIn(speed, callback);
133 break;
134 case 'fadeOut':
135 $el.fadeOut(speed, callback);
136 break;
137 case 'show':
138 $el.show(speed, callback);
139 break;
140 case 'hide':
141 $el.hide(speed, callback);
142 break;
143 }
144 },
145
146 /**
147 * Close helper
148 *
149 * @param {jQuery} $el
150 * @param {object} vars
151 * @return {undefined}
152 */
153 _close: function($el, vars) {
154 var
155 $content = $el.find(vars.content + ':first'),
156 $trigger = $el.find(vars.trigger + ':first')
157 ;
158
159 $el.removeClass(vars.openClass);
160 $content.hide().css('overflow', '');
161
162 if (vars.accessibility) {
163 $content.attr('hidden', true);
164 $trigger.attr('aria-expanded', false).removeAttr('aria-disabled');
165 }
166 },
167
168 /**
169 * open helper
170 *
171 * @param {jQuery} $el
172 * @param {object} vars
173 * @return {undefined}
174 */
175 _open: function($el, vars) {
176 var
177 $content = $el.find(vars.content + ':first'),
178 $trigger = $el.find(vars.trigger + ':first')
179 ;
180
181 $el.addClass(vars.openClass);
182 $content.show().css('overflow', 'hidden');
183
184 if (vars.accessibility) {
185 $content.attr('hidden', false);
186 $trigger.attr('aria-expanded', true);
187
188 if (vars.selfBlock) {
189 $trigger.attr('aria-disabled', true);
190 }
191 }
192 },
193
194 /**
195 * Get stayOpen element
196 *
197 * @param {jQuery} $obj
198 * @param {number|string} value
199 * @returns {*}
200 */
201 _getStayOpen: function($obj, value) {
202 var $el;
203
204 if (typeof value === 'number') {
205 $el = $obj.eq(value);
206 } else if (typeof value === 'string') {
207 switch (value) {
208 case 'first':
209 $el = $obj.first();
210 break;
211 case 'last':
212 $el = $obj.last();
213 break;
214 default:
215 $el = $obj.filter(value);
216 }
217 }
218
219 return $el;
220 },
221
222 /**
223 * Open just one accordion at once
224 *
225 * @param {jQuery} $obj
226 * @param {jQuery} $el
227 * @param {object} vars
228 * @return {undefined}
229 */
230 _openSingle: function($obj, $el, vars) {
231 if (!vars.openSingle) {
232 return;
233 }
234
235 var $close = $obj.not($el);
236
237 if (vars.stayOpen !== null) {
238 $close = $close.not(beefup.methods._getStayOpen($obj, vars.stayOpen));
239 }
240
241 if (!$close.length) {
242 return;
243 }
244
245 $obj.close($close.filter(function() {
246 return !$(this).find($el).length;
247 }));
248 },
249
250 /**
251 * Add self close event
252 *
253 * @param {jQuery} $obj
254 * @param {object} vars
255 */
256 _addSelfCloseEvent: function($obj, vars) {
257 if (!vars.selfClose) {
258 return;
259 }
260
261 $(document).on('click', function(e) {
262 if ($(e.target).closest($obj).length) {
263 return;
264 }
265
266 // Find open items
267 var $el = $obj.filter('.' + vars.openClass);
268
269 // Exclude stayOpen item
270 if (vars.stayOpen !== null) {
271 $el = $el.not(beefup.methods._getStayOpen($obj, vars.stayOpen));
272 }
273
274 // Close remaining items
275 if ($el.length) {
276 $obj.close($el);
277 }
278 });
279 },
280
281 /**
282 * Add hash change event
283 *
284 * @param {jQuery} $obj
285 * @param {object} vars
286 */
287 _addHashchangeEvent: function($obj, vars) {
288 if (!vars.hash) {
289 return;
290 }
291
292 var hashChange = function() {
293 var $el = $obj.filter(window.location.hash);
294
295 if ($el.length && !$el.hasClass(vars.openClass)) {
296 $obj.click($el);
297 }
298 };
299
300 hashChange();
301 $(window).on('hashchange', hashChange);
302 },
303
304 /**
305 * Initialize accessibility features
306 *
307 * @param {jQuery} $el
308 * @param {object} vars
309 * @return {object}
310 */
311 _initAccessibility: function($el, vars) {
312 var
313 $trigger = $el.find(vars.trigger + ':first'),
314 id = 'acc' + beefup.id++,
315 label = id + 'id',
316 $button = $trigger.children('button')
317 ;
318
319 if (vars.accessibility) {
320 if ($trigger.is('button') || $trigger.is('a')) {
321 $button = $trigger;
322 } else {
323 if (!$trigger.children('button').length) {
324 $trigger.wrapInner('<button type="button" aria-controls="' + id + '" aria-expanded="false" id="' + label + '"></button>');
325 $button = $trigger.children('button');
326 }
327
328 vars.trigger += ' > button';
329 }
330
331 $button
332 .attr('aria-controls', id)
333 .attr('aria-expanded', false)
334 .attr('id', label);
335
336 $el.find(vars.content + ':first')
337 .attr('aria-labelledby', label)
338 .attr('id', id)
339 .attr('role', 'region');
340 }
341
342 return vars;
343 }
344 };
345
346 $.fn.beefup = function(options) {
347 var $obj = this;
348
349 /**
350 * Open
351 *
352 * @param {jQuery} [$el]
353 * @param {function} [callback]
354 * @returns {jQuery}
355 */
356 this.open = function($el, callback) {
357 if (!$el || !$el.length) {
358 $el = $obj;
359 }
360
361 $el.each(function() {
362 var
363 $this = $(this),
364 vars = beefup.methods._getVars($this),
365 $content = $this.find(vars.content + ':first'),
366 animation = (vars.animation === 'slide') ? 'slideDown' :
367 (vars.animation === 'fade') ? 'fadeIn' : 'show'
368 ;
369
370 // Open single
371 beefup.methods._openSingle($obj, $el, vars);
372
373 // Fix #11: Remove hidden attribute before animation
374 if (vars.accessibility) {
375 $content.attr('hidden', false);
376 }
377
378 // Animation
379 beefup.methods._animation(animation, $content, vars.openSpeed, function() {
380 beefup.methods._open($this, vars);
381
382 // Scroll
383 if (vars.scroll) {
384 $obj.scroll($el);
385 }
386
387 // Callbacks
388 if (callback && typeof callback === 'function') {
389 callback($this);
390 }
391
392 if (vars.onOpen && typeof vars.onOpen === 'function') {
393 vars.onOpen($this);
394 }
395 });
396 });
397
398 return $obj;
399 };
400
401 /**
402 * Close
403 *
404 * @param {jQuery} [$el]
405 * @param {function} [callback]
406 * @returns {jQuery}
407 */
408 this.close = function($el, callback) {
409 if (!$el || !$el.length) {
410 $el = $obj;
411 }
412
413 $el.each(function() {
414 var
415 $this = $(this),
416 vars = beefup.methods._getVars($this),
417 $content = $this.find(vars.content + ':first'),
418 animation = (vars.animation === 'slide') ? 'slideUp' :
419 (vars.animation === 'fade') ? 'fadeOut' : 'hide'
420 ;
421
422 // Animation
423 beefup.methods._animation(animation, $content, vars.closeSpeed, function() {
424 beefup.methods._close($this, vars);
425
426 // Callbacks
427 if (callback && typeof callback === 'function') {
428 callback($this);
429 }
430
431 if (vars.onClose && typeof vars.onClose === 'function') {
432 vars.onClose($this);
433 }
434 });
435 });
436
437 return $obj;
438 };
439
440 /**
441 * Scroll
442 *
443 * @param {jQuery} $el
444 * @returns {jQuery}
445 */
446 this.scroll = function($el) {
447 var vars = beefup.methods._getVars($el);
448
449 $('html, body').animate(
450 {scrollTop: $el.offset().top + vars.scrollOffset},
451 vars.scrollSpeed
452 ).promise().done(function() {
453 if (vars.onScroll && typeof vars.onScroll === 'function') {
454 vars.onScroll($el);
455 }
456 });
457
458 return $obj;
459 };
460
461 /**
462 * Click
463 *
464 * @param {jQuery} $el
465 * @returns {jQuery}
466 */
467 this.click = function($el) {
468 var vars = beefup.methods._getVars($el);
469
470 if (!$el.hasClass(vars.openClass)) {
471 $obj.open($el);
472 } else {
473 if (!vars.selfBlock) {
474 $obj.close($el);
475 }
476 }
477
478 return $obj;
479 };
480
481 return this.each(function(index, el) {
482 var
483 $el = $(el),
484 vars = beefup.methods._initAccessibility($el,
485 $.extend({}, beefup.defaults, options, $el.data('beefup-options')
486 ))
487 ;
488
489 if ($el.data('beefup')) {
490 return;
491 }
492
493 $el.data('beefup', vars);
494
495 // Breakpoints
496 if (vars.breakpoints) {
497 vars = beefup.methods._getResponsiveVars(vars);
498 }
499
500 // Open stayOpen item
501 if ($el.is('.' + vars.openClass) || vars.stayOpen !== null && $el.is(beefup.methods._getStayOpen($obj, vars.stayOpen))) {
502 beefup.methods._open($el, vars);
503 }
504
505 // Close inactive items
506 beefup.methods._close($el.not('.' + vars.openClass), vars);
507
508 // Callback
509 if (vars.onInit && typeof vars.onInit === 'function') {
510 vars.onInit($el);
511 }
512
513 // Click event
514 $el.on('click', vars.trigger + ':first', function(e) {
515 e.preventDefault();
516 $obj.click($el);
517 });
518
519 // Trigger only once
520 if (index === $obj.length - 1) {
521 beefup.methods._addHashchangeEvent($obj, vars);
522 beefup.methods._addSelfCloseEvent($obj, vars);
523 }
524 });
525 };
526
527})(jQuery);