description-list.js

import { queryAll } from '@ecl/dom-utils';

/**
 * @param {HTMLElement} element DOM element for component instantiation and scope
 * @param {Object} options
 * @param {String} options.moreItemLabelSelector Selector for more button label attribute
 * @param {String} options.listsSelector Selector for list element
 * @param {String} options.visibleItemsSelector Selector to retrieve the number of visible items
 * @param {Boolean} options.attachClickListener Whether or not to bind click events
 */
export class DescriptionList {
  /**
   * @static
   * Shorthand for instance creation and initialisation.
   *
   * @param {HTMLElement} root DOM element for component instantiation and scope
   *
   * @return {DescriptionList} An instance of DescriptionList.
   */
  static autoInit(root, { DESCRIPTION_LIST: defaultOptions = {} } = {}) {
    const descriptionList = new DescriptionList(root, defaultOptions);
    descriptionList.init();
    root.ECLDescriptionList = descriptionList;
    return descriptionList;
  }

  constructor(
    element,
    {
      listsSelector = '[data-ecl-description-list-collapsible]',
      visibleItemsSelector = 'data-ecl-description-list-visible-items',
      moreItemLabelSelector = 'data-ecl-description-list-more-label',
      attachClickListener = true,
    } = {},
  ) {
    // Check element
    if (!element || element.nodeType !== Node.ELEMENT_NODE) {
      throw new TypeError(
        'DOM element should be given to initialize this widget.',
      );
    }

    this.element = element;

    // Options
    this.moreItemLabelSelector = moreItemLabelSelector;
    this.listsSelector = listsSelector;
    this.attachClickListener = attachClickListener;
    this.visibleItemsSelector = visibleItemsSelector;

    // Private variables
    this.moreItemLabel = null;
    this.lists = null;
    this.handleClickOnMore = this.handleClickOnMore.bind(this);
  }

  /**
   * Initialise component.
   */
  init() {
    if (!ECL) {
      throw new TypeError('Called init but ECL is not present');
    }
    ECL.components = ECL.components || new Map();

    this.moreItemLabel = this.element.getAttribute(this.moreItemLabelSelector);
    this.visibleItems = this.element.getAttribute(this.visibleItemsSelector);
    this.lists = queryAll(this.listsSelector, this.element);

    // Add see more button in each list and bind click event on it
    if (this.lists[0] && this.visibleItems > 0 && this.moreItemLabel) {
      this.lists.forEach((list) => {
        if (list.children && list.children.length > this.visibleItems) {
          const listItem = document.createElement('li');
          listItem.classList.add('ecl-description-list__see_more');

          const button = document.createElement('a');
          button.classList.add('ecl-link', 'ecl-link--standalone');
          button.href = '#';
          button.innerHTML = this.moreItemLabel;
          listItem.appendChild(button);
          list.appendChild(listItem);

          this.showHide(
            queryAll('.ecl-description-list__definition-item', list),
          );
          if (this.attachClickListener) {
            button.addEventListener('click', this.handleClickOnMore);
          }
        }
      });

      // Set ecl initialized attribute
      this.element.setAttribute('data-ecl-auto-initialized', 'true');
      ECL.components.set(this.element, this);
    }
  }

  /**
   * showHide elements basing on user preference.
   */
  showHide(elements) {
    if (elements) {
      const items = Array.from(elements);
      const baseClass = 'ecl-description-list__definition-item';
      const hiddenClass = `${baseClass}--hidden`;
      const lastVisibleClass = `${baseClass}--last-visible`;

      for (let i = 0; i < items.length; i += 1) {
        const el = items[i];

        if (i < this.visibleItems) {
          el.classList.remove(hiddenClass);
          el.classList.remove(lastVisibleClass);
        } else if (i >= this.visibleItems) {
          el.classList.add(hiddenClass);
        }

        if (i === this.visibleItems - 1) {
          el.classList.add(lastVisibleClass);
        } else {
          el.classList.remove(lastVisibleClass);
        }
      }
    }
  }

  /**
   * Destroy component.
   */
  destroy() {
    if (this.attachClickListener && this.visibleItems > 0) {
      const moreItems = queryAll('.ecl-description-list__see_more');
      if (moreItems[0]) {
        moreItems.forEach((item) => {
          item.removeEventListener('click', this.handleClickOnMore);
        });
      }
    }
    if (this.element) {
      this.element.removeAttribute('data-ecl-auto-initialized');
      ECL.components.delete(this.element);
    }
  }

  /**
   * Expands the list of items.
   * @param {Event} e
   */
  handleClickOnMore(e) {
    e.preventDefault();

    const listItem = e.target.parentNode;
    const list = listItem.parentNode;
    if (this.element.contains(list)) {
      const parentChildren = Array.from(list.children);
      parentChildren.forEach((item) => {
        item.classList.remove('ecl-description-list__definition-item--hidden');
        item.classList.remove(
          'ecl-description-list__definition-item--last-visible',
        );
      });
      // Remove the button
      listItem.remove();
    }
  }
}

export default DescriptionList;