;(function ($, window, document, undefined) { var pluginName = 'accordion', defaults = { transitionSpeed: 300, transitionEasing: 'ease', controlElement: '[data-control]', contentElement: '[data-content]', groupElement: '[data-accordion-group]', singleOpen: true, } function Accordion(element, options) { this.element = element this.options = $.extend({}, defaults, options) this._defaults = defaults this._name = pluginName this.init() } Accordion.prototype.init = function () { var self = this, opts = self.options var $accordion = $(self.element), $controls = $accordion.find('> ' + opts.controlElement), $content = $accordion.find('> ' + opts.contentElement) var accordionParentsQty = $accordion.parents('[data-accordion]').length, accordionHasParent = accordionParentsQty > 0 var closedCSS = { 'max-height': 0, overflow: 'hidden' } var CSStransitions = supportsTransitions() function debounce(func, threshold, execAsap) { var timeout return function debounced() { var obj = this, args = arguments function delayed() { if (!execAsap) func.apply(obj, args) timeout = null } if (timeout) clearTimeout(timeout) else if (execAsap) func.apply(obj, args) timeout = setTimeout(delayed, threshold || 100) } } function supportsTransitions() { var b = document.body || document.documentElement, s = b.style, p = 'transition' if (typeof s[p] == 'string') { return true } var v = ['Moz', 'webkit', 'Webkit', 'Khtml', 'O', 'ms'] p = 'Transition' for (var i = 0; i < v.length; i++) { if (typeof s[v[i] + p] == 'string') { return true } } return false } function requestAnimFrame(cb) { if (window.requestAnimationFrame) { requestAnimationFrame(cb) } else if (window.webkitRequestAnimationFrame) { webkitRequestAnimationFrame(cb) } else if (window.mozRequestAnimationFrame) { mozRequestAnimationFrame(cb) } else { setTimeout(cb, 1000 / 60) } } function toggleTransition($el, remove) { if (!remove) { $content.css({ '-webkit-transition': 'max-height ' + opts.transitionSpeed + 'ms ' + opts.transitionEasing, transition: 'max-height ' + opts.transitionSpeed + 'ms ' + opts.transitionEasing, }) } else { $content.css({ '-webkit-transition': '', transition: '', }) } } function calculateHeight($el) { var height = 0 $el.children().each(function () { height = height + $(this).outerHeight(true) }) $el.data('oHeight', height) } function updateParentHeight( $parentAccordion, $currentAccordion, qty, operation ) { var $content = $parentAccordion.filter('.open').find('> [data-content]'), $childs = $content.find('[data-accordion].open > [data-content]'), $matched if (!opts.singleOpen) { $childs = $childs.not( $currentAccordion .siblings('[data-accordion].open') .find('> [data-content]') ) } $matched = $content.add($childs) if ($parentAccordion.hasClass('open')) { $matched.each(function () { var currentHeight = $(this).data('oHeight') switch (operation) { case '+': $(this).data('oHeight', currentHeight + qty) break case '-': $(this).data('oHeight', currentHeight - qty) break default: throw 'updateParentHeight method needs an operation' } $(this).css('max-height', $(this).data('oHeight')) }) } } function refreshHeight($accordion) { if ($accordion.hasClass('open')) { var $content = $accordion.find('> [data-content]'), $childs = $content.find('[data-accordion].open > [data-content]'), $matched = $content.add($childs) calculateHeight($matched) $matched.css('max-height', $matched.data('oHeight')) } } function closeAccordion($accordion, $content) { $accordion.trigger('accordion.close') if (CSStransitions) { if (accordionHasParent) { var $parentAccordions = $accordion.parents('[data-accordion]') updateParentHeight( $parentAccordions, $accordion, $content.data('oHeight'), '-' ) } $content.css(closedCSS) $accordion.removeClass('open') } else { $content.css('max-height', $content.data('oHeight')) $content.animate(closedCSS, opts.transitionSpeed) $accordion.removeClass('open') } } function openAccordion($accordion, $content) { $accordion.trigger('accordion.open') if (CSStransitions) { toggleTransition($content) if (accordionHasParent) { var $parentAccordions = $accordion.parents('[data-accordion]') updateParentHeight( $parentAccordions, $accordion, $content.data('oHeight'), '+' ) } requestAnimFrame(function () { $content.css('max-height', $content.data('oHeight')) }) $accordion.addClass('open') } else { $content.animate( { 'max-height': $content.data('oHeight'), }, opts.transitionSpeed, function () { $content.css({ 'max-height': 'none' }) } ) $accordion.addClass('open') } } function closeSiblingAccordions($accordion) { var $accordionGroup = $accordion.closest(opts.groupElement) var $siblings = $accordion.siblings('[data-accordion]').filter('.open'), $siblingsChildren = $siblings.find('[data-accordion]').filter('.open') var $otherAccordions = $siblings.add($siblingsChildren) $otherAccordions.each(function () { var $accordion = $(this), $content = $accordion.find(opts.contentElement) closeAccordion($accordion, $content) }) $otherAccordions.removeClass('open') } function toggleAccordion() { var isAccordionGroup = opts.singleOpen ? $accordion.parents(opts.groupElement).length > 0 : false calculateHeight($content) if (isAccordionGroup) { closeSiblingAccordions($accordion) } if ($accordion.hasClass('open')) { closeAccordion($accordion, $content) } else { openAccordion($accordion, $content) } } function addEventListeners() { $controls.on('click', toggleAccordion) $controls.on('accordion.toggle', function () { if (opts.singleOpen && $controls.length > 1) { return false } toggleAccordion() }) $controls.on('accordion.refresh', function () { refreshHeight($accordion) }) $(window).on( 'resize', debounce(function () { refreshHeight($accordion) }) ) } function setup() { $content.each(function () { var $curr = $(this) if ($curr.css('max-height') != 0) { if (!$curr.closest('[data-accordion]').hasClass('open')) { $curr.css({ 'max-height': 0, overflow: 'hidden' }) } else { toggleTransition($curr) calculateHeight($curr) $curr.css('max-height', $curr.data('oHeight')) } } }) if (!$accordion.attr('data-accordion')) { $accordion.attr('data-accordion', '') $accordion.find(opts.controlElement).attr('data-control', '') $accordion.find(opts.contentElement).attr('data-content', '') } } setup() addEventListeners() } $.fn[pluginName] = function (options) { return this.each(function () { if (!$.data(this, 'plugin_' + pluginName)) { $.data(this, 'plugin_' + pluginName, new Accordion(this, options)) } }) } })(jQuery, window, document)