File: development/navigations/offcanvas/js/offcanvas.js
import $ from 'jquery';
import DOMModule from '../../../modules/__base/dommodule';
import {eventType} from '../../../js/utils/events';
import mediaQuery from '../../../js/utils/mq';
import OffCanvasMenuLevel from './offcanvaslevel';
/**
* @module Navigations
*/
const BP_INITIALLY_VISIBLE = 'screen and (min-width: 1280px)';
const BP_FLUIDCONTENT = 'screen and (min-width: 1280px)';
const $body = $('body');
// PRIVATE METHODS
const handleResize = Symbol('_handleResize');
/**
* OffCanvasMenu module
* @class Navigations.OffCanvasMenu
* @constructor
* @extends Modules._DOMModule
*/
class OffCanvasMenu extends DOMModule {
constructor(options) {
super(options);
this.$triggerNode = $('[data-offcanvas="'+ this.$data('offcanvas-id')+'"]');
this._levelstack = [];
this._bp_visible = this.$data('offcanvas-bp-visible') || BP_INITIALLY_VISIBLE;
this._bp_fluid = this.$data('offcanvas-bp-fluid') || BP_FLUIDCONTENT;
}
/**
* Creates a new OffCanvasMenu instance without the "new" keyword but does not
* initialize it
* @method of
* @static
* @for Navigations.OffCanvasMenu
* @param {node|string|configs} node Node, CSS selector or configuration object
* @return {OffCanvasMenu} A new instance
*
* @example
* import OffCanvasMenu from './navigations/offcanvas/js/offcanvas';
*
* const myOffCanvas = OffCanvasMenu.of('[data-widget=OffCanvasMenu]');
* myOffCanvas.render().startUp();
*/
static of (node) {
return new OffCanvasMenu(node);
}
/**
* Finds and initializes all OffCanvasMenus on a page at once
* @method findAll
* @static
* @for Navigations.OffCanvasMenu
* @param {string} selector CSS selector to match nodes against
* @return {array} A list of instances
*
* @example
* import OffCanvasMenu from './navigations/offcanvas/js/offcanvas';
*
* OffCanvasMenu.findAll('[data-widget=OffCanvasMenu]');
*/
static findAll(selector) {
return $(selector).toArray().map((node) => {
const navigation = OffCanvasMenu.of(node);
navigation.render().startUp();
return navigation;
});
}
/**
* Does nothing
* @method render
* @for Navigations.OffCanvasMenu
* @return {this} The instance
*/
render() {
return this;
}
/**
* Attaches all eventlisteners to the instance and prepares navigation for
* the initial viewport
* @method startUp
* @for Navigations.OffCanvasMenu
* @return {undefined} Nothing
*
* @example
* import OffCanvasMenu from './navigations/offcanvas/js/offcanvas';
*
* const myOffCanvas = OffCanvasMenu.of('[data-widget=OffCanvasMenu]');
* myOffCanvas.render().startUp();
*/
startUp () {
var _instance,
_node;
this.$levels = this.$findChilds('.offcanvas_menu_level').map((idx, rawNode) => {
_node = $(rawNode);
_instance = OffCanvasMenuLevel.of(rawNode);
_instance.render().startUp();
_instance.addObserver(this);
if (!idx) { _node.addClass('offcanvas_menu_level--open'); }
if (_node.hasClass('offcanvas_menu_level--open')) {
this._levelstack.push(_instance);
}
return _instance;
});
this._manageOverflows();
this[handleResize]();
// bind functions
this._toggleOpen = this._toggleOpen.bind(this);
this._handleScrollerClick = this._handleScrollerClick.bind(this);
// attach listeners
this.onResize(handleResize);
this.$triggerNode.on(eventType.CLICK, this._toggleOpen);
$('.offcanvas_pusher').on(eventType.TRANSITION, () => {
this.publish('offcanvas-state--changed');
});
}
/**
* Opens the navigation layer
* @method open
* @for Navigations.OffCanvasMenu
* @return {undefined} Nothing
*
* @example
* import OffCanvasMenu from './navigations/offcanvas/js/offcanvas';
*
* const myOffCanvas = OffCanvasMenu.of('[data-widget=OffCanvasMenu]');
* myOffCanvas.render().startUp();
* myOffCanvas.open();
*/
open () {
$body.addClass('body--menu-open');
this.$rootNode.addClass('offcanvas_menu--open');
if (!mediaQuery(this._bp_fluid).matches) {
$('.offcanvas_scroller').on(eventType.CLICK, this._handleScrollerClick);
}
this.publish('offcanvas-init-statechange', { state: 'open' });
}
/**
* Closes the navigation layer
* @method open
* @for Navigations.OffCanvasMenu
* @return {undefined} Nothing
*
* @example
* import OffCanvasMenu from './navigations/offcanvas/js/offcanvas';
*
* const myOffCanvas = OffCanvasMenu.of('[data-widget=OffCanvasMenu]');
* myOffCanvas.render().startUp();
* myOffCanvas.open();
*/
close () {
$body.removeClass('body--menu-open');
this.$rootNode.removeClass('offcanvas_menu--open');
$('.offcanvas_scroller').off(eventType.CLICK, this._handleScrollerClick);
this.publish('offcanvas-init-statechange', { state: 'close' });
}
// === PRIVATE STUFF ===
_manageOverflows () {
this._levelstack.forEach((item, idx) => {
if (idx < this._levelstack.length - 1) {
item.setOverflow(false);
}
});
}
update (data) {
var curr;
if (data.action === 'open') {
curr = this._levelstack[this._levelstack.length - 1];
this._levelstack.push(data.level);
curr.setOverflow(false).
done(data.level.open.bind(data.level));
} else {
this._levelstack.pop();
curr = this._levelstack[this._levelstack.length - 1];
curr.setOverflow(true).
done(data.level.close.bind(data.level));
}
}
// === EVENT HANDLERS ===
_handleScrollerClick (evt) {
evt.preventDefault();
evt.stopPropagation();
this.close();
}
_toggleOpen (evt) {
evt.preventDefault();
evt.stopPropagation();
if ($body.hasClass('body--menu-open')) {
this.close();
} else {
this.open();
}
}
[handleResize]() {
if (mediaQuery(this._bp_visible).matches) {
this.open();
} else {
this.close();
}
return this;
}
}
export default OffCanvasMenu;