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;