389 lines
13 KiB
JavaScript

'use strict';
// https://www.jqueryscript.net/demo/Multi-level-Side-Menu-Plugin-jQuery/#
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
// TODO: make this library agnostic
// TODO: document the events
(function ($) {
var PLUGIN_NAME = 'slideMenu';
var DEFAULT_OPTIONS = {
position: 'right',
showBackLink: true,
keycodeOpen: null,
keycodeClose: 27, //esc
submenuLinkBefore: '',
submenuLinkAfter: '',
backLinkBefore: '',
backLinkAfter: ''
};
var SlideMenu = function () {
function SlideMenu(options) {
_classCallCheck(this, SlideMenu);
this.options = options;
this._menu = options.elem;
// Add wrapper
this._menu.find('ul:first').wrap('<div class="slider">');
this._anchors = this._menu.find('a');
this._slider = this._menu.find('.slider:first');
this._level = 0;
this._isOpen = false;
this._isAnimating = false;
this._hasMenu = this._anchors.length > 0;
this._lastAction = null;
this._setupEventHandlers();
this._setupMenu();
if (this._hasMenu) this._setupSubmenus();
}
/**
* Toggle the menu
* @param {boolean|null} open
* @param {boolean} animate
*/
_createClass(SlideMenu, [{
key: 'toggle',
value: function toggle() {
var open = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
var animate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
var offset = void 0;
if (open === null) {
if (this._isOpen) {
this.close();
} else {
this.open();
}
return;
} else if (open) {
offset = 0;
this._isOpen = true;
} else {
offset = this.options.position === 'left' ? '-100%' : '100%';
this._isOpen = false;
}
this._triggerEvent();
if (animate) this._triggerAnimation(this._menu, offset);else {
this._pauseAnimations(this._triggerAnimation.bind(this, this._menu, offset));
this._isAnimating = false;
}
}
/**
* Open the menu
* @param {boolean} animate Use CSS transitions
*/
}, {
key: 'open',
value: function open() {
var animate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
$('.slide-darker').show();
$('body').css('overflow', 'hidden');
this._lastAction = 'open';
this.toggle(true, animate);
}
/**
* Close the menu
* @param {boolean} animate Use CSS transitions
*/
}, {
key: 'close',
value: function close() {
var animate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
$('.slide-darker').hide();
$('body').css('overflow', 'auto');
this._lastAction = 'close';
this.toggle(false, animate);
}
/**
* Navigate one menu hierarchy back if possible
*/
}, {
key: 'back',
value: function back() {
this._lastAction = 'back';
this._navigate(null, -1);
}
/**
* Navigate to a specific link on any level (useful to open the correct hierarchy directly)
* @param {string|object} target A string selector a plain DOM object or a jQuery instance
*/
}, {
key: 'navigateTo',
value: function navigateTo(target) {
var _this = this;
target = this._menu.find($(target)).first();
if (!target.length) return false;
var parents = target.parents('ul');
var level = parents.length - 1;
if (level === 0) return false;
this._pauseAnimations(function () {
_this._level = level;
parents.show().first().addClass('active');
_this._triggerAnimation(_this._slider, -_this._level * 100);
});
}
/**
* Set up all event handlers
* @private
*/
}, {
key: '_setupEventHandlers',
value: function _setupEventHandlers() {
var _this2 = this;
if (this._hasMenu) {
this._anchors.click(function (event) {
var anchor = $(event.target).is('a') ? $(event.target) : $(event.target).parents('a:first');
_this2._navigate(anchor);
});
}
$(this._menu.add(this._slider)).on('transitionend msTransitionEnd', function () {
_this2._isAnimating = false;
_this2._triggerEvent(true);
});
$(document).keydown(function (e) {
switch (e.which) {
case _this2.options.keycodeClose:
_this2.close();
break;
case _this2.options.keycodeOpen:
_this2.open();
break;
default:
return;
}
e.preventDefault();
});
this._menu.on('sm.back-after', function () {
var lastActiveUl = 'ul ' + '.active '.repeat(_this2._level + 1);
_this2._menu.find(lastActiveUl).removeClass('active').hide();
});
}
/**
* Trigger a custom event to support callbacks
* @param {boolean} afterAnimation Mark this event as `before` or `after` callback
* @private
*/
}, {
key: '_triggerEvent',
value: function _triggerEvent() {
var afterAnimation = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
var eventName = 'sm.' + this._lastAction;
if (afterAnimation) eventName += '-after';
this._menu.trigger(eventName);
}
/**
* Navigate the _menu - that is slide it one step left or right
* @param {jQuery} anchor The clicked anchor or button element
* @param {int} dir Navigation direction: 1 = forward, 0 = backwards
* @private
*/
}, {
key: '_navigate',
value: function _navigate(anchor) {
var dir = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
// Abort if an animation is still running
if (this._isAnimating) {
return;
}
var offset = (this._level + dir) * -100;
if (dir > 0) {
if (!anchor.next('ul').length) return;
anchor.next('ul').addClass('active').show();
} else if (this._level === 0) {
return;
}
this._lastAction = dir > 0 ? 'forward' : 'back';
this._level = this._level + dir;
this._triggerAnimation(this._slider, offset);
}
/**
* Start the animation (the CSS transition)
* @param elem
* @param offset
* @private
*/
}, {
key: '_triggerAnimation',
value: function _triggerAnimation(elem, offset) {
this._triggerEvent();
if (!(String(offset).indexOf('%') !== -1)) offset += '%';
elem.css('transform', 'translateX(' + offset + ')');
this._isAnimating = true;
}
/**
* Initialize the menu
* @private
*/
}, {
key: '_setupMenu',
value: function _setupMenu() {
var _this3 = this;
this._pauseAnimations(function () {
switch (_this3.options.position) {
case 'left':
_this3._menu.css({
left: 0,
right: 'auto',
transform: 'translateX(-100%)'
});
break;
default:
_this3._menu.css({
left: 'auto',
right: 0
});
break;
}
_this3._menu.show();
});
}
/**
* Pause the CSS transitions, to apply CSS changes directly without an animation
* @param work
* @private
*/
}, {
key: '_pauseAnimations',
value: function _pauseAnimations(work) {
this._menu.addClass('no-transition');
work();
this._menu[0].offsetHeight; // trigger a reflow, flushing the CSS changes
this._menu.removeClass('no-transition');
}
/**
* Enhance the markup of menu items which contain a submenu
* @private
*/
}, {
key: '_setupSubmenus',
value: function _setupSubmenus() {
var _this4 = this;
this._anchors.each(function (i, anchor) {
anchor = $(anchor);
if (anchor.next('ul').length) {
// prevent default behaviour (use link just to navigate)
anchor.click(function (ev) {
ev.preventDefault();
});
// add `before` and `after` text
var anchorTitle = anchor.text();
anchor.html(_this4.options.submenuLinkBefore + anchorTitle + _this4.options.submenuLinkAfter);
// add a back button
if (_this4.options.showBackLink) {
var backLink = $('<a href class="slide-menu-control" data-action="back">' + anchorTitle + '</a>');
backLink.html(_this4.options.backLinkBefore + backLink.text() + _this4.options.backLinkAfter);
anchor.next('ul').prepend($('<li>').append(backLink));
}
}
});
}
}]);
return SlideMenu;
}();
// Link control buttons with the API
$('body').on('click', '.slide-menu-control', function (e) {
var menu = void 0;
var target = $(this).data('target');
$(this).toggleClass('collapsed');
if (!target || target === 'this') {
menu = $(this).parents('.slide-menu:first');
} else {
menu = $('#' + target);
}
if (!menu.length) return;
var instance = menu.data(PLUGIN_NAME);
var action = $(this).data('action');
if (instance && typeof instance[action] === 'function') {
instance[action]();
}
return false;
});
// Register the jQuery plugin
$.fn[PLUGIN_NAME] = function (options) {
if (!$(this).length) {
console.warn('Slide Menu: Unable to find menu DOM element. Maybe a typo?');
return;
}
options = $.extend({}, DEFAULT_OPTIONS, options);
options.elem = $(this);
var instance = new SlideMenu(options);
$(this).data(PLUGIN_NAME, instance);
return instance;
};
})(jQuery);