|                                                                                                                                                                                                                                                                                                                                        | 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 | ;(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)
 |