conventions

This commit is contained in:
Simon Vieille 2015-03-05 20:06:13 +01:00
parent 7287652be9
commit caa28b51bf

View file

@ -12,17 +12,17 @@
* *
*/ */
(function($, undefined){ (function($, undefined) {
// TODO: - // TODO: -
// ARIA stuff: menuitem, menuitemcheckbox und menuitemradio // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
// create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
// determine html5 compatibility // determine html5 compatibility
$.support.htmlMenuitem = ('HTMLMenuItemElement' in window); $.support.htmlMenuitem = ('HTMLMenuItemElement' in window);
$.support.htmlCommand = ('HTMLCommandElement' in window); $.support.htmlCommand = ('HTMLCommandElement' in window);
$.support.eventSelectstart = ("onselectstart" in document.documentElement); $.support.eventSelectstart = ("onselectstart" in document.documentElement);
/* // should the need arise, test for css user-select /* // should the need arise, test for css user-select
$.support.cssUserSelect = (function(){ $.support.cssUserSelect = (function(){
var t = false, var t = false,
e = document.createElement('div'); e = document.createElement('div');
@ -44,22 +44,23 @@ $.support.cssUserSelect = (function(){
})(); })();
*/ */
if (!$.ui || !$.ui.widget) { if (!$.ui || !$.ui.widget) {
// duck punch $.cleanData like jQueryUI does to get that remove event // duck punch $.cleanData like jQueryUI does to get that remove event
// https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js#L16-24 // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js#L16-24
var _cleanData = $.cleanData; var _cleanData = $.cleanData;
$.cleanData = function( elems ) { $.cleanData = function(elems) {
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { for (var i = 0, elem;
(elem = elems[i]) != null; i++) {
try { try {
$( elem ).triggerHandler( "remove" ); $(elem).triggerHandler("remove");
// http://bugs.jquery.com/ticket/8235 // http://bugs.jquery.com/ticket/8235
} catch( e ) {} } catch (e) {}
} }
_cleanData( elems ); _cleanData(elems);
}; };
} }
var // currently active contextMenu trigger var // currently active contextMenu trigger
$currentTrigger = null, $currentTrigger = null,
// is contextMenu initialized with at least one menu? // is contextMenu initialized with at least one menu?
initialized = false, initialized = false,
@ -122,7 +123,10 @@ var // currently active contextMenu trigger
offset = opt.$menu.position(); offset = opt.$menu.position();
} else { } else {
// x and y are given (by mouse event) // x and y are given (by mouse event)
offset = {top: y, left: x}; offset = {
top: y,
left: x
};
} }
// correct offset if viewport demands it // correct offset if viewport demands it
@ -193,7 +197,7 @@ var // currently active contextMenu trigger
while (true) { while (true) {
zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);
$tt = $tt.parent(); $tt = $tt.parent();
if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase()) > -1 ) { if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase()) > -1) {
break; break;
} }
} }
@ -203,7 +207,7 @@ var // currently active contextMenu trigger
// event handlers // event handlers
handle = { handle = {
// abort anything // abort anything
abortevent: function(e){ abortevent: function(e) {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
}, },
@ -266,7 +270,11 @@ var // currently active contextMenu trigger
click: function(e) { click: function(e) {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
$(this).trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); $(this).trigger($.Event("contextmenu", {
data: e.data,
pageX: e.pageX,
pageY: e.pageY
}));
}, },
// contextMenu right-click trigger // contextMenu right-click trigger
mousedown: function(e) { mousedown: function(e) {
@ -291,7 +299,11 @@ var // currently active contextMenu trigger
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
$currentTrigger = $this; $currentTrigger = $this;
$this.trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY })); $this.trigger($.Event("contextmenu", {
data: e.data,
pageX: e.pageX,
pageY: e.pageY
}));
} }
$this.removeData('contextMenuActive'); $this.removeData('contextMenuActive');
@ -320,8 +332,12 @@ var // currently active contextMenu trigger
hoveract.timer = null; hoveract.timer = null;
$document.off('mousemove.contextMenuShow'); $document.off('mousemove.contextMenuShow');
$currentTrigger = $this; $currentTrigger = $this;
$this.trigger($.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY })); $this.trigger($.Event("contextmenu", {
}, e.data.delay ); data: hoveract.data,
pageX: hoveract.pageX,
pageY: hoveract.pageY
}));
}, e.data.delay);
}, },
// contextMenu hover trigger // contextMenu hover trigger
mousemove: function(e) { mousemove: function(e) {
@ -338,7 +354,7 @@ var // currently active contextMenu trigger
try { try {
clearTimeout(hoveract.timer); clearTimeout(hoveract.timer);
} catch(e) {} } catch (e) {}
hoveract.timer = null; hoveract.timer = null;
}, },
@ -400,7 +416,10 @@ var // currently active contextMenu trigger
if (target && triggerAction) { if (target && triggerAction) {
root.$trigger.one('contextmenu:hidden', function() { root.$trigger.one('contextmenu:hidden', function() {
$(target).contextMenu({x: x, y: y}); $(target).contextMenu({
x: x,
y: y
});
}); });
} }
@ -530,10 +549,7 @@ var // currently active contextMenu trigger
var k = (String.fromCharCode(e.keyCode)).toUpperCase(); var k = (String.fromCharCode(e.keyCode)).toUpperCase();
if (opt.accesskeys[k]) { if (opt.accesskeys[k]) {
// according to the specs accesskeys must be invoked immediately // according to the specs accesskeys must be invoked immediately
opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup');
? 'contextmenu:focus'
: 'mouseup'
);
return; return;
} }
break; break;
@ -820,7 +836,7 @@ var // currently active contextMenu trigger
opt.$menu.find('ul').css('zIndex', css.zIndex + 1); opt.$menu.find('ul').css('zIndex', css.zIndex + 1);
// position and show context menu // position and show context menu
opt.$menu.css( css )[opt.animation.show](opt.animation.duration, function() { opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function() {
$trigger.trigger('contextmenu:visible'); $trigger.trigger('contextmenu:visible');
}); });
// make options available and set state // make options available and set state
@ -866,14 +882,14 @@ var // currently active contextMenu trigger
if (opt.$layer) { if (opt.$layer) {
// keep layer for a bit so the contextmenu event can be aborted properly by opera // keep layer for a bit so the contextmenu event can be aborted properly by opera
setTimeout((function($layer) { setTimeout((function($layer) {
return function(){ return function() {
$layer.remove(); $layer.remove();
}; };
})(opt.$layer), 10); })(opt.$layer), 10);
try { try {
delete opt.$layer; delete opt.$layer;
} catch(e) { } catch (e) {
opt.$layer = null; opt.$layer = null;
} }
} }
@ -887,7 +903,7 @@ var // currently active contextMenu trigger
//$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705
$(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');
// hide menu // hide menu
opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function (){ opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function() {
// tear down dynamically built menu after animation is completed. // tear down dynamically built menu after animation is completed.
if (opt.build) { if (opt.build) {
opt.$menu.remove(); opt.$menu.remove();
@ -924,7 +940,7 @@ var // currently active contextMenu trigger
'contextMenuRoot': root 'contextMenuRoot': root
}); });
$.each(['callbacks', 'commands', 'inputs'], function(i,k){ $.each(['callbacks', 'commands', 'inputs'], function(i, k) {
opt[k] = {}; opt[k] = {};
if (!root[k]) { if (!root[k]) {
root[k] = {}; root[k] = {};
@ -934,7 +950,7 @@ var // currently active contextMenu trigger
root.accesskeys || (root.accesskeys = {}); root.accesskeys || (root.accesskeys = {});
// create contextMenu items // create contextMenu items
$.each(opt.items, function(key, item){ $.each(opt.items, function(key, item) {
var $t = $('<li class="context-menu-item"></li>').addClass(item.className || ""), var $t = $('<li class="context-menu-item"></li>').addClass(item.className || ""),
$label = null, $label = null,
$input = null; $input = null;
@ -953,7 +969,7 @@ var // currently active contextMenu trigger
// NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that
if (item.accesskey) { if (item.accesskey) {
var aks = splitAccesskey(item.accesskey); var aks = splitAccesskey(item.accesskey);
for (var i=0, ak; ak = aks[i]; i++) { for (var i = 0, ak; ak = aks[i]; i++) {
if (!root.accesskeys[ak]) { if (!root.accesskeys[ak]) {
root.accesskeys[ak] = item; root.accesskeys[ak] = item;
item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>'); item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');
@ -968,7 +984,7 @@ var // currently active contextMenu trigger
// run custom type handler // run custom type handler
types[item.type].call($t, item, opt, root); types[item.type].call($t, item, opt, root);
// register commands // register commands
$.each([opt, root], function(i,k){ $.each([opt, root], function(i, k) {
k.commands[key] = item; k.commands[key] = item;
if ($.isFunction(item.callback)) { if ($.isFunction(item.callback)) {
k.callbacks[key] = item.callback; k.callbacks[key] = item.callback;
@ -983,7 +999,7 @@ var // currently active contextMenu trigger
$('<span></span>').html(item._name || item.name).appendTo($label); $('<span></span>').html(item._name || item.name).appendTo($label);
$t.addClass('context-menu-input'); $t.addClass('context-menu-input');
opt.hasTypes = true; opt.hasTypes = true;
$.each([opt, root], function(i,k){ $.each([opt, root], function(i, k) {
k.commands[key] = item; k.commands[key] = item;
k.inputs[key] = item; k.inputs[key] = item;
}); });
@ -1039,7 +1055,6 @@ var // currently active contextMenu trigger
break; break;
case 'sub': case 'sub':
// FIXME: shouldn't this .html() be a .text()?
$('<span></span>').html(item._name || item.name).appendTo($t); $('<span></span>').html(item._name || item.name).appendTo($t);
item.appendTo = item.$node; item.appendTo = item.$node;
op.create(item, root); op.create(item, root);
@ -1052,13 +1067,12 @@ var // currently active contextMenu trigger
break; break;
default: default:
$.each([opt, root], function(i,k){ $.each([opt, root], function(i, k) {
k.commands[key] = item; k.commands[key] = item;
if ($.isFunction(item.callback)) { if ($.isFunction(item.callback)) {
k.callbacks[key] = item.callback; k.callbacks[key] = item.callback;
} }
}); });
// FIXME: shouldn't this .html() be a .text()?
$('<span></span>').html(item._name || item.name || "").appendTo($t); $('<span></span>').html(item._name || item.name || "").appendTo($t);
break; break;
} }
@ -1107,7 +1121,10 @@ var // currently active contextMenu trigger
// kinda sucks hard... // kinda sucks hard...
// determine width of absolutely positioned element // determine width of absolutely positioned element
$menu.css({position: 'absolute', display: 'block'}); $menu.css({
position: 'absolute',
display: 'block'
});
// don't apply yet, because that would break nested elements' widths // don't apply yet, because that would break nested elements' widths
// add a pixel to circumvent word-break issue in IE9 - #80 // add a pixel to circumvent word-break issue in IE9 - #80
$menu.data('width', Math.ceil($menu.width()) + 1); $menu.data('width', Math.ceil($menu.width()) + 1);
@ -1141,7 +1158,7 @@ var // currently active contextMenu trigger
op.resize(opt.$menu); op.resize(opt.$menu);
} }
// re-check disabled for each item // re-check disabled for each item
opt.$menu.children().each(function(){ opt.$menu.children().each(function() {
var $item = $(this), var $item = $(this),
key = $item.data('contextMenuKey'), key = $item.data('contextMenuKey'),
item = opt.items[key], item = opt.items[key],
@ -1182,7 +1199,11 @@ var // currently active contextMenu trigger
// add transparent layer for click area // add transparent layer for click area
// filter and background for Internet Explorer, Issue #23 // filter and background for Internet Explorer, Issue #23
var $layer = opt.$layer = $('<div id="context-menu-layer" style="position:fixed; z-index:' + zIndex + '; top:0; left:0; opacity: 0; filter: alpha(opacity=0); background-color: #000;"></div>') var $layer = opt.$layer = $('<div id="context-menu-layer" style="position:fixed; z-index:' + zIndex + '; top:0; left:0; opacity: 0; filter: alpha(opacity=0); background-color: #000;"></div>')
.css({height: $win.height(), width: $win.width(), display: 'block'}) .css({
height: $win.height(),
width: $win.width(),
display: 'block'
})
.data('contextMenuRoot', opt) .data('contextMenuRoot', opt)
.insertBefore(this) .insertBefore(this)
.on('contextmenu', handle.abortevent) .on('contextmenu', handle.abortevent)
@ -1191,8 +1212,8 @@ var // currently active contextMenu trigger
// IE6 doesn't know position:fixed; // IE6 doesn't know position:fixed;
if (!$.support.fixedPosition) { if (!$.support.fixedPosition) {
$layer.css({ $layer.css({
'position' : 'absolute', 'position': 'absolute',
'height' : $(document).height() 'height': $(document).height()
}); });
} }
@ -1200,12 +1221,12 @@ var // currently active contextMenu trigger
} }
}; };
// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key
function splitAccesskey(val) { function splitAccesskey(val) {
var t = val.split(/\s+/), var t = val.split(/\s+/),
keys = []; keys = [];
for (var i=0, k; k = t[i]; i++) { for (var i = 0, k; k = t[i]; i++) {
k = k[0].toUpperCase(); // first character only k = k[0].toUpperCase(); // first character only
// theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it. // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.
// a map to look up already used access keys would be nice // a map to look up already used access keys would be nice
@ -1213,19 +1234,24 @@ function splitAccesskey(val) {
} }
return keys; return keys;
} }
// handle contextMenu triggers // handle contextMenu triggers
$.fn.contextMenu = function(operation) { $.fn.contextMenu = function(operation) {
if (operation === undefined) { if (operation === undefined) {
this.first().trigger('contextmenu'); this.first().trigger('contextmenu');
} else if (operation.x && operation.y) { } else if (operation.x && operation.y) {
this.first().trigger($.Event("contextmenu", {pageX: operation.x, pageY: operation.y})); this.first().trigger($.Event("contextmenu", {
pageX: operation.x,
pageY: operation.y
}));
} else if (operation === "hide") { } else if (operation === "hide") {
var $menu = this.data('contextMenu').$menu; var $menu = this.data('contextMenu').$menu;
$menu && $menu.trigger('contextmenu:hide'); $menu && $menu.trigger('contextmenu:hide');
} else if (operation === "destroy") { } else if (operation === "destroy") {
$.contextMenu("destroy", {context: this}); $.contextMenu("destroy", {
context: this
});
} else if ($.isPlainObject(operation)) { } else if ($.isPlainObject(operation)) {
operation.context = this; operation.context = this;
$.contextMenu("create", operation); $.contextMenu("create", operation);
@ -1236,17 +1262,19 @@ $.fn.contextMenu = function(operation) {
} }
return this; return this;
}; };
// manage contextMenu instances // manage contextMenu instances
$.contextMenu = function(operation, options) { $.contextMenu = function(operation, options) {
if (typeof operation != 'string') { if (typeof operation != 'string') {
options = operation; options = operation;
operation = 'create'; operation = 'create';
} }
if (typeof options == 'string') { if (typeof options == 'string') {
options = {selector: options}; options = {
selector: options
};
} else if (options === undefined) { } else if (options === undefined) {
options = {}; options = {};
} }
@ -1279,7 +1307,7 @@ $.contextMenu = function(operation, options) {
if (!o.build && (!o.items || $.isEmptyObject(o.items))) { if (!o.build && (!o.items || $.isEmptyObject(o.items))) {
throw new Error('No Items sepcified'); throw new Error('No Items sepcified');
} }
counter ++; counter++;
o.ns = '.contextMenu' + counter; o.ns = '.contextMenu' + counter;
if (!_hasContext) { if (!_hasContext) {
namespaces[o.selector] = o.ns; namespaces[o.selector] = o.ns;
@ -1364,7 +1392,9 @@ $.contextMenu = function(operation, options) {
$visibleMenu = $('.context-menu-list').filter(':visible'); $visibleMenu = $('.context-menu-list').filter(':visible');
if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) { if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) {
$visibleMenu.trigger('contextmenu:hide', {force: true}); $visibleMenu.trigger('contextmenu:hide', {
force: true
});
} }
try { try {
@ -1373,7 +1403,7 @@ $.contextMenu = function(operation, options) {
} }
delete menus[o.ns]; delete menus[o.ns];
} catch(e) { } catch (e) {
menus[o.ns] = null; menus[o.ns] = null;
} }
@ -1396,7 +1426,9 @@ $.contextMenu = function(operation, options) {
} else if (namespaces[o.selector]) { } else if (namespaces[o.selector]) {
$visibleMenu = $('.context-menu-list').filter(':visible'); $visibleMenu = $('.context-menu-list').filter(':visible');
if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) {
$visibleMenu.trigger('contextmenu:hide', {force: true}); $visibleMenu.trigger('contextmenu:hide', {
force: true
});
} }
try { try {
@ -1405,7 +1437,7 @@ $.contextMenu = function(operation, options) {
} }
delete menus[namespaces[o.selector]]; delete menus[namespaces[o.selector]];
} catch(e) { } catch (e) {
menus[namespaces[o.selector]] = null; menus[namespaces[o.selector]] = null;
} }
@ -1421,7 +1453,7 @@ $.contextMenu = function(operation, options) {
$('menu[type="context"]').each(function() { $('menu[type="context"]').each(function() {
if (this.id) { if (this.id) {
$.contextMenu({ $.contextMenu({
selector: '[contextmenu=' + this.id +']', selector: '[contextmenu=' + this.id + ']',
items: $.contextMenu.fromMenu(this) items: $.contextMenu.fromMenu(this)
}); });
} }
@ -1434,10 +1466,10 @@ $.contextMenu = function(operation, options) {
} }
return this; return this;
}; };
// import values into <input> commands // import values into <input> commands
$.contextMenu.setInputValues = function(opt, data) { $.contextMenu.setInputValues = function(opt, data) {
if (data === undefined) { if (data === undefined) {
data = {}; data = {};
} }
@ -1462,10 +1494,10 @@ $.contextMenu.setInputValues = function(opt, data) {
break; break;
} }
}); });
}; };
// export values from <input> commands // export values from <input> commands
$.contextMenu.getInputValues = function(opt, data) { $.contextMenu.getInputValues = function(opt, data) {
if (data === undefined) { if (data === undefined) {
data = {}; data = {};
} }
@ -1491,15 +1523,15 @@ $.contextMenu.getInputValues = function(opt, data) {
}); });
return data; return data;
}; };
// find <label for="xyz"> // find <label for="xyz">
function inputLabel(node) { function inputLabel(node) {
return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name; return (node.id && $('label[for="' + node.id + '"]').val()) || node.name;
} }
// convert <menu> to items object // convert <menu> to items object
function menuChildren(items, $children, counter) { function menuChildren(items, $children, counter) {
if (!counter) { if (!counter) {
counter = 0; counter = 0;
} }
@ -1530,7 +1562,10 @@ function menuChildren(items, $children, counter) {
switch (nodeName) { switch (nodeName) {
// http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element
case 'menu': case 'menu':
item = {name: $node.attr('label'), items: {}}; item = {
name: $node.attr('label'),
items: {}
};
counter = menuChildren(item.items, $node.children(), counter); counter = menuChildren(item.items, $node.children(), counter);
break; break;
@ -1541,7 +1576,11 @@ function menuChildren(items, $children, counter) {
item = { item = {
name: $node.text(), name: $node.text(),
disabled: !!$node.attr('disabled'), disabled: !!$node.attr('disabled'),
callback: (function(){ return function(){ $node.click(); }; })() callback: (function() {
return function() {
$node.click();
};
})()
}; };
break; break;
@ -1556,7 +1595,11 @@ function menuChildren(items, $children, counter) {
item = { item = {
name: $node.attr('label'), name: $node.attr('label'),
disabled: !!$node.attr('disabled'), disabled: !!$node.attr('disabled'),
callback: (function(){ return function(){ $node.click(); }; })() callback: (function() {
return function() {
$node.click();
};
})()
}; };
break; break;
@ -1634,7 +1677,7 @@ function menuChildren(items, $children, counter) {
selected: $node.val(), selected: $node.val(),
options: {} options: {}
}; };
$node.children().each(function(){ $node.children().each(function() {
item.options[this.value] = $(this).text(); item.options[this.value] = $(this).text();
}); });
break; break;
@ -1652,7 +1695,10 @@ function menuChildren(items, $children, counter) {
break; break;
default: default:
item = {type: 'html', html: $node.clone(true)}; item = {
type: 'html',
html: $node.clone(true)
};
break; break;
} }
@ -1663,24 +1709,24 @@ function menuChildren(items, $children, counter) {
}); });
return counter; return counter;
} }
// convert html5 menu // convert html5 menu
$.contextMenu.fromMenu = function(element) { $.contextMenu.fromMenu = function(element) {
var $this = $(element), var $this = $(element),
items = {}; items = {};
menuChildren(items, $this.children()); menuChildren(items, $this.children());
return items; return items;
}; };
// make defaults accessible // make defaults accessible
$.contextMenu.defaults = defaults; $.contextMenu.defaults = defaults;
$.contextMenu.types = types; $.contextMenu.types = types;
// export internal functions - undocumented, for hacking only! // export internal functions - undocumented, for hacking only!
$.contextMenu.handle = handle; $.contextMenu.handle = handle;
$.contextMenu.op = op; $.contextMenu.op = op;
$.contextMenu.menus = menus; $.contextMenu.menus = menus;
})(jQuery); })(jQuery);