index.js

/**
 *  HTZ COLLAPSIBLES
 *
 * Responsive-enabled, accessible collapsible content
 *
 * @module htz-collapsibles
 * @license MIT
 */
import debounce from 'lodash-es/debounce';
import evaluate from 'htz-collapsibles/evaluate';
import { collapse, expand, toggle } from 'htz-collapsibles/changeState';

const allInstances = [];

/**
 * Initialize collapsible elements
 *
 * @param {String} element - The element to initialize.
 * @param {String} labelExpand - The label used to describe the toggle button
 *    when the element is collapsed and the toggle button is used for expanding.
 * @param {String} labelCollapse - The label used to describe the toggle button
 *    when the element is expanded and the toggle button is used for collapsing.
 * @param {String} [collapsedClass='o-collapsible-is-collapsed']
 *    The class to attach to the wrapper when it is collapsed.
 * @param {String} [expandedClass='o-collapsible-is-expanded']
 *    The class to attach to the wrapper when it is expanded.
 * @param {String} [bpsSelector='body']
 *    A selector for the element to which the active breakpoint data is attached.
 *    See [htz-parse-bps-state](https://github.com/haaretz/htz-parse-bps-state)
 *
 * @return {HTMLElement[]} Returns an array of collapsible elements.
 */
export default function collapsibles(
  element,
  labelExpand,
  labelCollapse,
  collapsedClass = 'o-collapsible-is-collapsed',
  expandedClass = 'o-collapsible-is-expanded',
  bpsSelector = 'body'
) {
  const toggleElem = element.getElementsByClassName('js-collapsible__toggle')[0];
  const contentElem = element.getElementsByClassName('js-collapsible__content')[0];
  const contentId = contentElem.id ||
    `collapsible__content${(Math.floor(Math.random() * 10000000000) + 1)}`;

  let isExpanded;

  toggleElem.addEventListener('click', toggleCb, false);
  toggleElem.setAttribute('aria-controls', contentId);


  window.addEventListener(
    'resize',
    debounce(
      () => {
        isExpanded = evaluate(
          element,
          contentElem,
          toggleElem,
          labelExpand,
          labelCollapse,
          collapsedClass,
          expandedClass,
          bpsSelector,
          isExpanded
        );
      },
      250
    )
  );

  isExpanded = evaluate(
    element,
    contentElem,
    toggleElem,
    labelExpand,
    labelCollapse,
    collapsedClass,
    expandedClass,
    bpsSelector,
    isExpanded
  );


  function toggleCb(evt) {
    // Only with left click, or when toggled programattically
    if (
      !evt || !evt.button || !evt.keyCode ||
      evt.button === 0 || evt.keyCode === 13 || evt.keyCode === 32
    ) {
      isExpanded = toggle(
        element,
        contentElem,
        toggleElem,
        labelExpand,
        labelCollapse,
        collapsedClass,
        expandedClass,
        bpsSelector,
        isExpanded
      );

      return isExpanded;
    }

    return isExpanded;
  }

  /**
   * Instance methods and properties
   *
   */
  const instance = {
    // Elements
    wrapper: element,
    toggleElem,
    contentElem,

    // State
    get isExpanded() { return isExpanded; },

    // Methods
    collapse() {
      isExpanded = collapse(
        element,
        contentElem,
        toggleElem,
        labelExpand,
        labelCollapse,
        collapsedClass,
        expandedClass,
        bpsSelector,
        isExpanded
      );

      return isExpanded;
    },
    expand() {
      isExpanded = expand(
        element,
        contentElem,
        toggleElem,
        labelExpand,
        labelCollapse,
        collapsedClass,
        expandedClass,
        bpsSelector,
        isExpanded
      );

      return isExpanded;
    },
    toggle: toggleCb,
  };

  if (allInstances.indexOf(instance) < 0) allInstances.push(instance);

  return instance;
}

/**
 * Get the instance API of a statful button.
 *
 * @memberof module:htz-collapsibles
 * @static
 *
 * @param {String|HTMLElement} collapsible - The `HTMLElement` or the `id` of a collapsible
 *   element's wrapper.
 *
 * @return {module:htz-collapsibles#API} - The API to control the instance.
 */
function getInstance(collapsible) {
  const instanceType = (
    Object
    .prototype
    .toString
    .call(collapsible)
    .match(/^\[object\s+(.*?)]$/)[1]
    || ''
  ).toLowerCase();

  const elem = instanceType === 'string' ? document.getElementById(collapsible) : collapsible;
  const instance = allInstances.filter(item => item.wrapper === elem)[0];

  return instance;
}

collapsibles.getInstance = getInstance;