API Docs for:
Show:

File: development/navigations/megadropdown/js/megadropdownlist.js

import $ from 'jquery';
import {eventType} from '../../../js/utils/events';
import DOMModule from '../../../modules/__base/dommodule';

import config from './config';
import g from './globals';
import { NAVMODE_CONF } from './megadropdownlist-mixins';

/**
 * @module Navigations
 */

/**
 * MegadropdownList module
 * @class Navigations.MegadropdownList
 * @constructor
 * @extends Modules._DOMModule
 */
class MegadropdownList extends DOMModule {
    // Properties: 

    // $rootNode: null,            // the list node (.megadropdown_section)
    // $triggerNode: null,         // trigger that opens the list (data-megadropdown-cmd="Level::jump-in")

    // $clipNode: null,            // node in which the navigationlists are being moved left/right
    // $overlayNode: null,         // the overlay the list is part of
    // $overlayInnerNode: null,    // the inner of the overlay, whose height is being manipulated
    // $teaserLeftNode: null,
    // $teaserRightNode: null,
    // $backButtonNode: null,

    // $$parentList: null,         // list that is directly superior to the navigation list
    // $$subLists: null,           // lists that are directly subordinate to the navigation list 
    // $$openSublist: null,

    // _level: 0,                  // navigation level of the list
    // _height: 0,                 // height of the list
    // _posLeftOpen: 0,            // left position of the list when list is being activated
    // _posLeftClosed: 0,          // left position of the list when list is being closed

    // _isInPath: false,           // whether or not the list is part of the current path
    // _isOpen: false,
    // _mode: null,                // which mode the navigation is in (NAVMODE_CONF);
    
    constructor(options) {
        super(options);

        this._mode = options.mode;

        this.$clipNode = this.$rootNode.closest('.megadropdown_clip');
        this.$overlayNode = this.$rootNode.closest('.megadropdown_overlay');
        this.$overlayInnerNode = this.$overlayNode.find('.megadropdown_overlay_inner');

        this.$$parentList = options.parentList || null;
        this._level = options.level || 1;

        this.$triggerNode = options.trigger ? $(options.trigger) : this._findTrigger();

        this.$teaserLeftNode = this.$overlayNode.find('.megadropdown_teaser--left');
        this.$teaserRightNode = this.$overlayNode.find('.megadropdown_teaser--right');
        this.$backButtonNode = this.$overlayNode.find('.megadropdown_back-button');

        this._isInPath = this.$triggerNode.hasClass('is-current');
        this._name = this.$triggerNode.find('span').first().html() || 'Home';
    }
    /**
     * Creates a new MegadropdownList instance without the "new" keyword but does not
     *     initialize it
     * @method of
     * @static
     * @for Navigations.MegadropdownList
     * @param {node|string|configs} node Node, CSS selector or configuration object
     * @return {MegadropdownList} A new instance
     * 
     * @example
     *     import MegadropdownList from './navigations/megadropdown/js/megadropdownlist';
     *
     *     const myMegadropdownList = MegadropdownList.of('[data-widget=MegadropdownList]');
     *     myMegadropdownList.render().startUp();
     */
    static of (options) {
        return new MegadropdownList(options);
    }

    /**
     * Initializes it's sublists recursively
     * @method render
     * @for Navigations.MegadropdownList
     * @return {this} The instance
     * 
     * @example
     *     import MegadropdownList from './navigations/megadropdown/js/megadropdownlist';
     *
     *     const myMegadropdownList = MegadropdownList.of('[data-widget=MegadropdownList]');
     *     myMegadropdownList.render();
     */
    render() {
        this._initSubLists();
        return this;
    }

    /**
     * Attaches all eventlisteners to the instance and subscribes to necessary
     *      mediator channels navigation for 
     * @method startUp
     * @for Navigations.MegadropdownList
     * @return {undefined} Nothing
     * 
     * @example
     *     import MegadropdownList from './navigations/megadropdown/js/megadropdownlist';
     *
     *     const myMegadropdownList = MegadropdownList.of('[data-widget=MegadropdownList]');
     *     myMegadropdownList.render().startUp();
     */
    startUp() {
        this.$triggerNode.on(eventType.CLICK, this._handleTriggerClick.bind(this));
        this.subscribe('Navigation::open', this._handleOtherTriggerClick.bind(this));
    }

    getParent(howFar) {
        if (!howFar) { 
            return this.$$parentList;
        }
        return this.$$parentList.getParent(howFar - 1);
    }

    _initSubLists() {
        var _selector;
        _selector = '> .mainnavi_list > li > .megadropdown_section'; // desktop list selector
        _selector += ', > .mainnavi_list > li > .megadropdown_overlay > .megadropdown_overlay_inner > .megadropdown_clip > .megadropdown_section'; // mobile/tablet list selector

        this.$$subLists = this.$findChilds(_selector).toArray().map((rawNode) => {
            let _instance;

            _instance = new MegadropdownList({
                container: $(rawNode),
                parentList: this,
                level: this._level + 1,
                mode: this._mode
            });
            _instance.render().startUp();
            _instance.addObserver(this);
            return _instance;
        });
    }

    resetToInitialState () {
        this.$$subLists.forEach(($$list) => {
            $$list.resetToInitialState();
        });

        if (this._isInPath) {
            if (this._level > config.ENTRYLEVELS[this._mode]) {
                this.open(true); 
            } else {
                this.close();
            }
        } else if (this._isOpen) {
            this.close();
        }
    }

    recalcDimensions () {
        this._calcPos();
        // this._calcHeight(); // called on open now

        this.$$subLists.forEach(($$list) => {
            $$list.recalcDimensions();
        });
    }

    _handleTriggerClick (event) {
        event.preventDefault();

        if (this._isOpen) {
            this.close();
            return;
        }

        // Close every navigation list with same level:
        // Publish the implicit click on a navigationlist-trigger
        // over a mediator channel, so that every other navigation list is 
        // being notified.
        // Why?
        // Because if there's an open MegadropdownList in the same level
        // it has to be closed.
        this.publish('Navigation::open', {
            triggerParent: this.$triggerNode.parent()[0],
            mode: this._mode
        });

        if (this._level === config.ENTRYLEVELS[this._mode]) {
            this.$clipNode.addClass('no-animations');
        }

        this.open();

        setTimeout(() => {
            this.$clipNode.removeClass('no-animations');
        }, 100);
    }

    _handleOtherTriggerClick(data) {
        if (this._mode === data.mode && this._isOpen) {
            if (this._hasDirectSibling(data.triggerParent)) {
                this.close();
            }
        }
    }

    _hasDirectSibling(potentialSibling) {
        return this.$triggerNode.
            parent().
            siblings().
            toArray().
            some(function (el) {
                return el === potentialSibling;
            });
    }

    _calcPos() {
        if (this._level > config.COLS[g.getViewport()]) {
            this._posLeftOpen = - ((this._level - config.COLS[g.getViewport()]) * 100 / config.COLS[g.getViewport()]) + "%";
            this._posLeftClosed = - ((this._level - config.COLS[g.getViewport()] - 1) * 100 / config.COLS[g.getViewport()]) + "%";
        } else {
            this._posLeftOpen = 0;
            this._posLeftClosed = 0;
        }
    }

    open(resetCall) {
        // calc height on open. 
        // this function was initially called in "recalcDimensions", which caused problems
        this._calcHeight();

        this.notifyObservers({
            action: 'MegadropdownList::open',
            instance: this
        });

        this.$triggerNode.addClass('is-active');
        this.$rootNode.addClass('is-active');

        if (this._level === config.ENTRYLEVELS[this._mode]) {
            this.$overlayNode.addClass('is-active');
        }

        NAVMODE_CONF[this._mode].open.call(this);

        if (!resetCall) {
            this.$overlayInnerNode.height(this._height);
        }

        this._isOpen = true;
        g.setLevel(this._level);

        if (this.$$openSublist) {
            this.$$openSublist.open(resetCall);
        }
    }

    close(levelToClose) {
        if (!levelToClose) {
            this.notifyObservers({
                action: 'MegadropdownList::close',
                instance: this
            });

            if (this._level > config.ENTRYLEVELS[this._mode]) {
                if (this.$$openSublist) {
                    this.$$openSublist.close();
                }
            }

            NAVMODE_CONF[this._mode].close.call(this);

            if (this._level === config.ENTRYLEVELS[this._mode]) {
                this.$overlayNode.removeClass('is-active');
            }

            this.$triggerNode.removeClass('is-active');
            this.$rootNode.removeClass('is-active');
            this.$overlayInnerNode.height(this.$$parentList ? this.$$parentList._height : 0);
            this._isOpen = false;
            g.setLevel(this._level - 1);
        } else if (this._level === levelToClose) {
            this.close();
        } else if (this.$$openSublist) {
            this.$$openSublist.close(levelToClose);
        }
    }

    _calcHeight() {
        NAVMODE_CONF[this._mode]._calcHeight.call(this);
    }

    _findTrigger() {
        return NAVMODE_CONF[this._mode]._findTrigger.call(this);
    }

    getListHeight() {
        var listHeight = this.$rootNode.find('> .mainnavi_list').outerHeight() || 0,
            metaHeight = this.$rootNode.find('.metanavi').outerHeight(true) || 0;

        return metaHeight + listHeight;
    }

    update(data) {
        if (data.action === 'MegadropdownList::open') { // sublist opened
            this.$$openSublist = data.instance;
            if (this._level < config.ENTRYLEVELS[this._mode]) {
                this.notifyObservers(data);
            }
        } else { // sublist closed
            this.$$openSublist = null;
        }
    }
}





export default MegadropdownList;