API Docs for:
Show:

File: development/modules/accordion/js/accordion.js

import $ from 'jquery';
import DOMModule from '../../__base/dommodule';

/**
 * @module Modules
 */

const AccordionStates = [
    {name: 'init', events: {render: 'rendered'}},
    {name: 'rendered', events: {start: 'closed'}},
    {name: 'closed', events: {open: 'opened'}},
    {name: 'opened', events: {close: 'closed'}}
];

// PRIVATE METHODS
const handleClick = Symbol('_handleClick');
const handleResize = Symbol('_handleResize');
const handleTransition = Symbol('_handleTransition');



/**
 * Accordion module
 * @class Modules.Accordion
 * @constructor
 * @extends Modules._DOMModule
 */
class Accordion extends DOMModule {
    constructor(options) {
        super(options);

        this.addStates(AccordionStates);

        this.iconClassesOpen = this.$data('accordion-icon-open');
        this.iconClassesClose = this.$data('accordion-icon-close');
        this.initialState = this.$data('accordion-initialstate');
        this.prefix = this.$data('accordion-prefix') || 'accordion'

        this.$headNode = this.$findChilds('[data-accordion-action]');
        this.$bodyNode = this.$findChilds('[data-accordion-effect]');
        this.$maskNode = $('<div></div>').addClass('accordion_mask');
        this.$iconNode = $('<div></div>').
            addClass(this.prefix + '_head_icon').
            addClass(this.iconClassesOpen);
    }
    /**
     * Creates a new Accordion instance without the "new" keyword but does not
     *     initialize it
     * @method of
     * @static
     * @for Modules.Accordion
     * @param {node|string|configs} node Node, CSS selector or configuration object
     * @return {Accordion} A new instance
     * 
     * @example
     *     import Accordion from './modules/accordion/js/accordion';
     *
     *     const myAccordion = Accordion.of('[data-widget=Accordion]');
     *     myAccordion.render().startUp();
     */
    static of (node) {
        return new Accordion(node);
    }
    /**
     * Finds and initializes all Accordions on a page at once
     * @method findAll
     * @static
     * @for Modules.Accordion
     * @param {string} selector CSS selector to match nodes against
     * @return {array} A list of instances
     * 
     * @example
     *     import Accordion from './modules/accordion/js/accordion';
     *
     *     Accordion.findAll('[data-widget=Accordion]');
     */
    static findAll(selector) {
        return $(selector).toArray().filter((node) => {
                return !$(node).data('defer');
            }).map((node) => {
                const accordion = Accordion.of(node);
                accordion.render().startUp();
                return accordion;
            });
    }
    /**
     * Renders missing nodes into the instance and returns it
     * @method render
     * @for Modules.Accordion
     * @return {this} The instance
     * 
     * @example
     *     import Accordion from './modules/accordion/js/accordion';
     *
     *     const myAccordion = Accordion.of('[data-widget=Accordion]');
     *     myAccordion.render();
     */
    render() {
        if (this.getState() === 'init') {
            this.consume('render');

            const body = this.$bodyNode.detach();
            this.$maskNode.
                append(body).
                css('height', '0').
                addClass('accordion_mask--hidden');

            this.$rootNode.append(this.$maskNode);
            this.$headNode.append(this.$iconNode);
        }
        return this;
    }
    /**
     * Attaches all eventlisteners to the accordion instance and opens it if
     *     the initial state is defined as visible
     * @method startUp
     * @for Modules.Accordion
     * @return {undefined} Nothing
     * 
     * @example
     *     import Accordion from './modules/accordion/js/accordion';
     *
     *     const myAccordion = Accordion.of('[data-widget=Accordion]');
     *     myAccordion.render().startUp();
     */
    startUp() {
        if (this.getState() === 'rendered') {
            this.consume('start');

            this.$rootNode.removeClass('is-open');

            this.onClick('[data-accordion-action]', handleClick);
            this.onTransitionEnd('.accordion_mask', handleTransition);
            this.onResize(handleResize);

            if (this.initialState === 'visible') {
                this.show();
            }
        }
    }
    /**
     * Opens the accordion if it is closed
     * @method show
     * @for Modules.Accordion
     * @return {this} The instance
     * 
     * @example
     *     import Accordion from './modules/accordion/js/accordion';
     *
     *     const myAccordion = Accordion.of('[data-widget=Accordion]');
     *     myAccordion.render().startUp();
     *
     *     myAccordion.show();
     */
    show() {
        if (this.getState() === 'closed') {
            this.consume('open');

            this.$rootNode.addClass('is-open');

            this.$maskNode.
                css({ height: this.$bodyNode.innerHeight() + 1 }).
                removeClass('accordion_mask--hidden');

            this.$iconNode.
                removeClass(this.iconClassesOpen).
                addClass(this.iconClassesClose);
        }
        return this;
    }
    /**
     * Closes the accordion if it is opened
     * @method hide
     * @for Modules.Accordion
     * @return {this} The instance
     * 
     * @example
     *     import Accordion from './modules/accordion/js/accordion';
     *
     *     const myAccordion = Accordion.of('[data-widget=Accordion]');
     *     myAccordion.render().startUp();
     *
     *     myAccordion.hide();
     */
    hide() {
        if (this.getState() === 'opened') {
            this.consume('close');
            
            this.$rootNode.removeClass('is-open');

            this.$maskNode.
                css({ height: '0px' }).
                addClass('accordion_mask--hidden').
                removeClass('accordion_mask--overflow');

            this.$iconNode.
                removeClass(this.iconClassesClose).
                addClass(this.iconClassesOpen);
        }
        return this;
    }
    // === PRIVATE STUFF ===
    // === EVENT HANDLERS ===
    [handleClick](event) {
        event.preventDefault();
        if (this.getState() === 'opened') {
            this.hide();
            return null;
        }
        this.show();
    }
    [handleTransition]() {
        if (this.getState() === 'opened'){
            this.$maskNode.addClass('accordion_mask--overflow');
        }
        this.publish('w-widget-action-viewchanged', {
            $root: this.$rootNode,
            module: this
        });
    }
    [handleResize]() {
        if (this.getState() === 'opened') {
            this.$maskNode.css({height: this.$bodyNode.innerHeight() + 1});
        }
        return this;
    }
}



export default Accordion;