Skip to content

Instantly share code, notes, and snippets.

@JamesHarker
Created March 17, 2020 06:44
Show Gist options
  • Save JamesHarker/16e709e88c63538db6ba7cd370597833 to your computer and use it in GitHub Desktop.
Save JamesHarker/16e709e88c63538db6ba7cd370597833 to your computer and use it in GitHub Desktop.
Javascript context menu
/*
* Context.js
* Copyright Jacob Kelley
* MIT License
*
*/
contextMenu = (function () {
var options = {
fadeSpeed: 100,
filter: function ($obj) {
// Modify $obj, Do not return
},
above: 'auto',
left: 'auto',
preventDoubleContext: true,
compress: false
};
var _menus = {};
function initialize(opts) {
options = $.extend({}, options, opts);
$(document).on('click', function () {
$('.dropdown-context').fadeOut(options.fadeSpeed, function(){
$('.dropdown-context').css({display:''}).find('.drop-left').removeClass('drop-left');
});
});
if(options.preventDoubleContext){
$(document).on('contextmenu', '.dropdown-context', function (e) {
e.preventDefault();
});
}
$(document).on('mouseenter', '.dropdown-submenu', function(){
var $sub = $(this).find('.dropdown-context-sub:first'),
subWidth = $sub.width(),
subLeft = $sub.offset().left,
collision = (subWidth+subLeft) > window.innerWidth;
if(collision){
$sub.addClass('drop-left');
}
});
}
function updateOptions(opts){
options = $.extend({}, options, opts);
}
function buildMenu(data, id, subMenu) {
var subClass = (subMenu) ? ' dropdown-context-sub' : '',
compressed = options.compress ? ' compressed-context' : '',
$menu = $('<ul class="dropdown-menu dropdown-context' + subClass + compressed +'" id="dropdown-' + id + '"></ul>');
return buildMenuItems($menu, data, id, subMenu);
}
function buildMenuItems($menu, data, id, subMenu, addDynamicTag) {
var linkTarget = '';
for(var i = 0; i<data.length; i++) {
if (typeof data[i].divider !== 'undefined') {
var divider = '<li class="divider';
divider += (addDynamicTag) ? ' dynamic-menu-item' : '';
divider += '"></li>';
$menu.append(divider);
} else if (typeof data[i].header !== 'undefined') {
var header = '<li title-"Close Menu" class="dropdown-title';
header += (addDynamicTag) ? ' dynamic-menu-item' : '';
header += '">' + data[i].header + '</li>';
if (typeof data[i].action !== 'undefined') {
$action = data[i].action;
header = $(header);
header.on('click', createCallback($action));
}
$menu.append(header);
} else if (typeof data[i].menu_item_src !== 'undefined') {
var funcName;
if (typeof data[i].menu_item_src === 'function') {
if (data[i].menu_item_src.name === "") { // The function is declared like "foo = function() {}"
for (var globalVar in window) {
if (data[i].menu_item_src == window[globalVar]) {
funcName = globalVar;
break;
}
}
} else {
funcName = data[i].menu_item_src.name;
}
} else {
funcName = data[i].menu_item_src;
}
$menu.append('<li class="dynamic-menu-src" data-src="' + funcName + '"></li>');
} else {
if (typeof data[i].href == 'undefined') {
data[i].href = '#';
}
if (typeof data[i].target !== 'undefined') {
linkTarget = ' target="'+data[i].target+'"';
}
if (typeof data[i].subMenu !== 'undefined') {
var sub_menu = '<li class="dropdown-submenu';
sub_menu += (addDynamicTag) ? ' dynamic-menu-item' : '';
sub_menu += '"><a tabindex="-1" href="' + data[i].href + '">' + data[i].text + '</a></li>'
$sub = $(sub_menu);
} else {
var element = '<li';
element += (addDynamicTag) ? ' class="dynamic-menu-item"' : '';
element += '><a tabindex="-1" href="' + data[i].href + '"'+linkTarget+'>';
if (typeof data[i].icon !== 'undefined')
element += '<span class="glyphicon ' + data[i].icon + '"></span> ';
element += data[i].text + '</a></li>';
$sub = $(element);
}
if (typeof data[i].action !== 'undefined') {
$action = data[i].action;
$sub
.find('a')
.addClass('context-event')
.on('click', createCallback($action));
}
$menu.append($sub);
if (typeof data[i].subMenu != 'undefined') {
var subMenuData = buildMenu(data[i].subMenu, id, true);
$menu.find('li:last').append(subMenuData);
}
}
if (typeof options.filter == 'function') {
options.filter($menu.find('li:last'));
}
}
return $menu;
}
function addContext(selector, data) {
if (typeof data.id !== 'undefined' && typeof data.data !== 'undefined') {
var id = data.id;
$menu = $('body').find('#dropdown-' + id)[0];
if (typeof $menu === 'undefined') {
$menu = buildMenu(data.data, id);
$('body').append($menu);
}
} else {
var d = new Date(),
id = d.getTime(),
$menu = buildMenu(data, id);
$('body').append($menu);
}
this._menus[selector.attr("id")] = $menu;
$(selector).on('contextmenu', function (e) {
e.preventDefault();
e.stopPropagation();
currentContextSelector = $(this);
$('.dropdown-context:not(.dropdown-context-sub)').hide();
$dd = $('#dropdown-' + id);
$dd.find('.dynamic-menu-item').remove(); // Destroy any old dynamic menu items
$dd.find('.dynamic-menu-src').each(function(idx, element) {
var menuItems = window[$(element).data('src')]($(selector));
$parentMenu = $(element).closest('.dropdown-menu.dropdown-context');
$parentMenu = buildMenuItems($parentMenu, menuItems, id, undefined, true);
});
if (typeof options.above == 'boolean' && options.above) {
$dd.addClass('dropdown-context-up').css({
top: e.pageY - 20 - $('#dropdown-' + id).height(),
left: e.pageX - 13
}).fadeIn(options.fadeSpeed);
} else if (typeof options.above == 'string' && options.above == 'auto') {
$dd.removeClass('dropdown-context-up');
var autoH = $dd.height() + 12;
if ((e.pageY + autoH) > $('html').height()) {
$dd.addClass('dropdown-context-up').css({
top: e.pageY - 20 - autoH,
left: e.pageX - 13
}).fadeIn(options.fadeSpeed);
} else {
$dd.css({
top: e.pageY + 10,
left: e.pageX - 13
}).fadeIn(options.fadeSpeed);
}
}
if (typeof options.left == 'boolean' && options.left) {
$dd.addClass('dropdown-context-left').css({
left: e.pageX - $dd.width()
}).fadeIn(options.fadeSpeed);
} else if (typeof options.left == 'string' && options.left == 'auto') {
$dd.removeClass('dropdown-context-left');
var autoL = $dd.width() - 12;
if ((e.pageX + autoL) > $('html').width()) {
$dd.addClass('dropdown-context-left').css({
left: e.pageX - $dd.width() + 13
});
}
}
$dd.on('mouseleave', function() {
startMenuCloseCountdown($dd)
})
startMenuCloseCountdown($dd)
});
}
function startMenuCloseCountdown(menuSelector) {
setTimeout(function() {
beginMenuClose(menuSelector)
}, 2000);
}
function beginMenuClose(menuSelector) {
var checkClose = function() {
var mouseEl = $(this);
$("*").off("mouseenter", checkClose);
closeMenu(mouseEl, menuSelector)
}
$("*").on("mouseenter", checkClose);
}
function closeMenu(mouseEl, menuSelector) {
if (mouseEl.parents("#" + menuSelector[0].id).length === 0) {
if (!menuSelector) { return }
$(menuSelector).fadeOut(options.fadeSpeed, function(){
$(menuSelector).css({display:''}).find('.drop-left').removeClass('drop-left');
})
}
}
function destroyContext(selector) {
var element;
$(selector).off('contextmenu');
element = this._menus[selector.attr("id")];
if (element) {
element.remove();
delete this._menus[selector.attr("id")];
}
}
return {
init: initialize,
settings: updateOptions,
attach: addContext,
destroy: destroyContext,
_menus: _menus
};
})();
var createCallback = function(func) {
return function(event) { func(event, currentContextSelector) };
}
currentContextSelector = undefined;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment