389 lines
13 KiB
JavaScript
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);
|