diff --git a/loleaflet/.eslintignore b/loleaflet/.eslintignore index c7d0ae184..6f11545b6 100644 --- a/loleaflet/.eslintignore +++ b/loleaflet/.eslintignore @@ -5,4 +5,3 @@ # # This is way too ugly for eslint... **/js/jquery.mCustomScrollbar.js -**/js/jquery.contextMenu.js diff --git a/loleaflet/Makefile.am b/loleaflet/Makefile.am index 0cb26b4a0..4c09cc590 100644 --- a/loleaflet/Makefile.am +++ b/loleaflet/Makefile.am @@ -91,6 +91,7 @@ NODE_MODULES_JS =\ node_modules/hammerjs/hammer.min.js \ node_modules/jquery/dist/jquery.js \ node_modules/jquery-mousewheel/jquery.mousewheel.js \ + node_modules/jquery-contextmenu/dist/jquery.contextMenu.js \ node_modules/jquery-ui/jquery-ui.js \ node_modules/smartmenus/dist/jquery.smartmenus.js \ node_modules/autolinker/dist/Autolinker.js \ @@ -100,7 +101,6 @@ NODE_MODULES_JS =\ node_modules/@braintree/sanitize-url/dist.js LOLEAFLET_LIBS_JS =\ - jquery.contextMenu.js \ jquery.mCustomScrollbar.js \ w2ui-1.5.rc1.js diff --git a/loleaflet/js/jquery.contextMenu.js b/loleaflet/js/jquery.contextMenu.js deleted file mode 100644 index 881ee67f0..000000000 --- a/loleaflet/js/jquery.contextMenu.js +++ /dev/null @@ -1,1928 +0,0 @@ -/*! - * jQuery contextMenu v2.2.3 - Plugin for simple contextMenu handling - * - * Version: v2.2.3 - * - * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF) - * Web: http://swisnl.github.io/jQuery-contextMenu/ - * - * Copyright (c) 2011-2016 SWIS BV and contributors - * - * Licensed under - * MIT License http://www.opensource.org/licenses/mit-license - * GPL v3 http://opensource.org/licenses/GPL-3.0 - * - * Date: 2016-07-17T19:36:02.968Z - */ - -(function (factory) { - if (typeof define === 'function' && define.amd) { - // AMD. Register as anonymous module. - define(['jquery'], factory); - } else if (typeof exports === 'object') { - // Node / CommonJS - factory(require('jquery')); - } else { - // Browser globals. - factory(jQuery); - } -})(function ($) { - - 'use strict'; - - // TODO: - - // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio - // create structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative - - // determine html5 compatibility - $.support.htmlMenuitem = ('HTMLMenuItemElement' in window); - $.support.htmlCommand = ('HTMLCommandElement' in window); - $.support.eventSelectstart = ('onselectstart' in document.documentElement); - /* // should the need arise, test for css user-select - $.support.cssUserSelect = (function(){ - var t = false, - e = document.createElement('div'); - - $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) { - var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect', - prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select'; - - e.style.cssText = prop + ': text;'; - if (e.style[propCC] == 'text') { - t = true; - return false; - } - - return true; - }); - - return t; - })(); - */ - - /* jshint ignore:start */ - if (!$.ui || !$.widget) { - // duck punch $.cleanData like jQueryUI does to get that remove event - $.cleanData = (function (orig) { - return function (elems) { - var events, elem, i; - for (i = 0; elems[i] != null; i++) { - elem = elems[i]; - try { - // Only trigger remove when necessary to save time - events = $._data(elem, 'events'); - if (events && events.remove) { - $(elem).triggerHandler('remove'); - } - - // Http://bugs.jquery.com/ticket/8235 - } catch (e) {} - } - orig(elems); - }; - })($.cleanData); - } - /* jshint ignore:end */ - - var // currently active contextMenu trigger - $currentTrigger = null, - // is contextMenu initialized with at least one menu? - initialized = false, - // window handle - $win = $(window), - // number of registered menus - counter = 0, - // mapping selector to namespace - namespaces = {}, - // mapping namespace to options - menus = {}, - // custom command type handlers - types = {}, - // default values - defaults = { - // selector of contextMenu trigger - selector: null, - // where to append the menu to - appendTo: null, - // method to trigger context menu ["right", "left", "hover"] - trigger: 'right', - // hide menu when mouse leaves trigger / menu elements - autoHide: false, - // ms to wait before showing a hover-triggered context menu - delay: 200, - // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu - // as long as the trigger happened on one of the trigger-element's child nodes - reposition: true, - - // Default classname configuration to be able avoid conflicts in frameworks - classNames : { - - hover: 'context-menu-hover', // Item hover - disabled: 'context-menu-disabled', // Item disabled - visible: 'context-menu-visible', // Item visible - notSelectable: 'context-menu-not-selectable', // Item not selectable - - icon: 'context-menu-icon', - iconEdit: 'context-menu-icon-edit', - iconCut: 'context-menu-icon-cut', - iconCopy: 'context-menu-icon-copy', - iconPaste: 'context-menu-icon-paste', - iconDelete: 'context-menu-icon-delete', - iconAdd: 'context-menu-icon-add', - iconQuit: 'context-menu-icon-quit' - }, - - // determine position to show menu at - determinePosition: function ($menu) { - // position to the lower middle of the trigger element - if ($.ui && $.ui.position) { - // .position() is provided as a jQuery UI utility - // (...and it won't work on hidden elements) - $menu.css('display', 'block').position({ - my: 'center top', - at: 'center bottom', - of: this, - offset: '0 5', - collision: 'fit' - }).css('display', 'none'); - } else { - // determine contextMenu position - var offset = this.offset(); - offset.top += this.outerHeight(); - offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2; - $menu.css(offset); - } - }, - // position menu - position: function (opt, x, y) { - var offset; - // determine contextMenu position - if (!x && !y) { - opt.determinePosition.call(this, opt.$menu); - return; - } else if (x === 'maintain' && y === 'maintain') { - // x and y must not be changed (after re-show on command click) - offset = opt.$menu.position(); - } else { - // x and y are given (by mouse event) - offset = {top: y, left: x}; - } - - // correct offset if viewport demands it - var bottom = $win.scrollTop() + $win.height(), - right = $win.scrollLeft() + $win.width(), - height = opt.$menu.outerHeight(), - width = opt.$menu.outerWidth(); - - if (offset.top + height > bottom) { - offset.top -= height; - } - - if (offset.top < 0) { - offset.top = 0; - } - - if (offset.left + width > right) { - offset.left -= width; - } - - if (offset.left < 0) { - offset.left = 0; - } - - opt.$menu.css(offset); - }, - // position the sub-menu - positionSubmenu: function ($menu) { - if ($.ui && $.ui.position) { - // .position() is provided as a jQuery UI utility - // (...and it won't work on hidden elements) - $menu.css('display', 'block').position({ - my: 'left top', - at: 'right top', - of: this, - collision: 'flipfit fit' - }).css('display', ''); - } else { - // determine contextMenu position - var offset = { - top: 0, - left: this.outerWidth() - }; - $menu.css(offset); - } - }, - // offset to add to zIndex - zIndex: 1, - // show hide animation settings - animation: { - duration: 50, - show: 'slideDown', - hide: 'slideUp' - }, - // events - events: { - show: $.noop, - hide: $.noop - }, - // default callback - callback: null, - // list of contextMenu items - items: {} - }, - // mouse position for hover activation - hoveract = { - timer: null, - pageX: null, - pageY: null - }, - // determine zIndex - zindex = function ($t) { - var zin = 0, - $tt = $t; - - while (true) { - zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0); - $tt = $tt.parent(); - if (!$tt || !$tt.length || 'html body'.indexOf($tt.prop('nodeName').toLowerCase()) > -1) { - break; - } - } - return zin; - }, - // event handlers - handle = { - // abort anything - abortevent: function (e) { - e.preventDefault(); - e.stopImmediatePropagation(); - }, - // contextmenu show dispatcher - contextmenu: function (e) { - var $this = $(this); - - // disable actual context-menu if we are using the right mouse button as the trigger - if (e.data.trigger === 'right') { - e.preventDefault(); - e.stopImmediatePropagation(); - } - - // abort native-triggered events unless we're triggering on right click - if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) { - return; - } - - // Let the current contextmenu decide if it should show or not based on its own trigger settings - if (e.mouseButton !== undefined && e.data) { - if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) { - // Mouse click is not valid. - return; - } - } - - // abort event if menu is visible for this trigger - if ($this.hasClass('context-menu-active')) { - return; - } - - if (!$this.hasClass('context-menu-disabled')) { - // theoretically need to fire a show event at - // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus - // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this }); - // e.data.$menu.trigger(evt); - - $currentTrigger = $this; - if (e.data.build) { - var built = e.data.build($currentTrigger, e); - // abort if build() returned false - if (built === false) { - return; - } - - // dynamically build menu on invocation - e.data = $.extend(true, {}, defaults, e.data, built || {}); - - // abort if there are no items to display - if (!e.data.items || $.isEmptyObject(e.data.items)) { - // Note: jQuery captures and ignores errors from event handlers - if (window.console) { - (console.error || console.log).call(console, 'No items specified to show in contextMenu'); - } - - throw new Error('No Items specified'); - } - - // backreference for custom command type creation - e.data.$trigger = $currentTrigger; - - op.create(e.data); - } - var showMenu = false; - for (var item in e.data.items) { - if (e.data.items.hasOwnProperty(item)) { - var visible; - if ($.isFunction(e.data.items[item].visible)) { - visible = e.data.items[item].visible.call($(e.currentTarget), item, e.data); - } else if (typeof item.visible !== 'undefined') { - visible = e.data.items[item].visible === true; - } else { - visible = true; - } - if (visible) { - showMenu = true; - } - } - } - if (showMenu) { - // show menu - op.show.call($this, e.data, e.pageX, e.pageY); - } - } - }, - // contextMenu left-click trigger - click: function (e) { - e.preventDefault(); - e.stopImmediatePropagation(); - $(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY})); - }, - // contextMenu right-click trigger - mousedown: function (e) { - // register mouse down - var $this = $(this); - - // hide any previous menus - if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) { - $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide'); - } - - // activate on right click - if (e.button === 2) { - $currentTrigger = $this.data('contextMenuActive', true); - } - }, - // contextMenu right-click trigger - mouseup: function (e) { - // show menu - var $this = $(this); - if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) { - e.preventDefault(); - e.stopImmediatePropagation(); - $currentTrigger = $this; - $this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY})); - } - - $this.removeData('contextMenuActive'); - }, - // contextMenu hover trigger - mouseenter: function (e) { - var $this = $(this), - $related = $(e.relatedTarget), - $document = $(document); - - // abort if we're coming from a menu - if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { - return; - } - - // abort if a menu is shown - if ($currentTrigger && $currentTrigger.length) { - return; - } - - hoveract.pageX = e.pageX; - hoveract.pageY = e.pageY; - hoveract.data = e.data; - $document.on('mousemove.contextMenuShow', handle.mousemove); - hoveract.timer = setTimeout(function () { - hoveract.timer = null; - $document.off('mousemove.contextMenuShow'); - $currentTrigger = $this; - $this.trigger($.Event('contextmenu', { - data: hoveract.data, - pageX: hoveract.pageX, - pageY: hoveract.pageY - })); - }, e.data.delay); - }, - // contextMenu hover trigger - mousemove: function (e) { - hoveract.pageX = e.pageX; - hoveract.pageY = e.pageY; - }, - // contextMenu hover trigger - mouseleave: function (e) { - // abort if we're leaving for a menu - var $related = $(e.relatedTarget); - if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) { - return; - } - - try { - clearTimeout(hoveract.timer); - } catch (e) { - } - - hoveract.timer = null; - }, - // click on layer to hide contextMenu - layerClick: function (e) { - var $this = $(this), - root = $this.data('contextMenuRoot'), - button = e.button, - x = e.pageX, - y = e.pageY, - target, - offset; - - e.preventDefault(); - e.stopImmediatePropagation(); - - setTimeout(function () { - var $window; - var triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2)); - - // find the element that would've been clicked, wasn't the layer in the way - if (document.elementFromPoint && root.$layer) { - root.$layer.hide(); - target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop()); - root.$layer.show(); - } - - if (root.reposition && triggerAction) { - if (document.elementFromPoint) { - if (root.$trigger.is(target) || root.$trigger.has(target).length) { - root.position.call(root.$trigger, root, x, y); - return; - } - } else { - offset = root.$trigger.offset(); - $window = $(window); - // while this looks kinda awful, it's the best way to avoid - // unnecessarily calculating any positions - offset.top += $window.scrollTop(); - if (offset.top <= e.pageY) { - offset.left += $window.scrollLeft(); - if (offset.left <= e.pageX) { - offset.bottom = offset.top + root.$trigger.outerHeight(); - if (offset.bottom >= e.pageY) { - offset.right = offset.left + root.$trigger.outerWidth(); - if (offset.right >= e.pageX) { - // reposition - root.position.call(root.$trigger, root, x, y); - return; - } - } - } - } - } - } - - if (target && triggerAction) { - root.$trigger.one('contextmenu:hidden', function () { - $(target).contextMenu({ x: x, y: y, button: button }); - }); - } - - if (root != null && root.$menu != null) { - root.$menu.trigger('contextmenu:hide'); - } - }, 50); - }, - // key handled :hover - keyStop: function (e, opt) { - if (!opt.isInput) { - e.preventDefault(); - } - - e.stopPropagation(); - }, - key: function (e) { - - var opt = {}; - - // Only get the data from $currentTrigger if it exists - if ($currentTrigger) { - opt = $currentTrigger.data('contextMenu') || {}; - } - // If the trigger happen on a element that are above the contextmenu do this - if (opt.zIndex === undefined) { - opt.zIndex = 0; - } - var targetZIndex = 0; - var getZIndexOfTriggerTarget = function (target) { - if (target.style.zIndex !== '') { - targetZIndex = target.style.zIndex; - } else { - if (target.offsetParent !== null && target.offsetParent !== undefined) { - getZIndexOfTriggerTarget(target.offsetParent); - } - else if (target.parentElement !== null && target.parentElement !== undefined) { - getZIndexOfTriggerTarget(target.parentElement); - } - } - }; - getZIndexOfTriggerTarget(e.target); - // If targetZIndex is heigher then opt.zIndex dont progress any futher. - // This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div - // and its above the contextmenu it wont steal keys events - if (targetZIndex > opt.zIndex) { - return; - } - switch (e.keyCode) { - case 9: - case 38: // up - handle.keyStop(e, opt); - // if keyCode is [38 (up)] or [9 (tab) with shift] - if (opt.isInput) { - if (e.keyCode === 9 && e.shiftKey) { - e.preventDefault(); - if(opt.$selected) { - opt.$selected.find('input, textarea, select').blur(); - } - opt.$menu.trigger('prevcommand'); - return; - } else if (e.keyCode === 38 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') { - // checkboxes don't capture this key - e.preventDefault(); - return; - } - } else if (e.keyCode !== 9 || e.shiftKey) { - opt.$menu.trigger('prevcommand'); - return; - } - break; - // omitting break; - // case 9: // tab - reached through omitted break; - case 40: // down - handle.keyStop(e, opt); - if (opt.isInput) { - if (e.keyCode === 9) { - e.preventDefault(); - if(opt.$selected) { - opt.$selected.find('input, textarea, select').blur(); - } - opt.$menu.trigger('nextcommand'); - return; - } else if (e.keyCode === 40 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') { - // checkboxes don't capture this key - e.preventDefault(); - return; - } - } else { - opt.$menu.trigger('nextcommand'); - return; - } - break; - - case 37: // left - handle.keyStop(e, opt); - if (opt.isInput || !opt.$selected || !opt.$selected.length) { - break; - } - - if (!opt.$selected.parent().hasClass('context-menu-root')) { - var $parent = opt.$selected.parent().parent(); - opt.$selected.trigger('contextmenu:blur'); - opt.$selected = $parent; - return; - } - break; - - case 39: // right - handle.keyStop(e, opt); - if (opt.isInput || !opt.$selected || !opt.$selected.length) { - break; - } - - var itemdata = opt.$selected.data('contextMenu') || {}; - if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) { - opt.$selected = null; - itemdata.$selected = null; - itemdata.$menu.trigger('nextcommand'); - return; - } - break; - - case 35: // end - case 36: // home - if (opt.$selected && opt.$selected.find('input, textarea, select').length) { - return; - } else { - (opt.$selected && opt.$selected.parent() || opt.$menu) - .children(':not(.' + opt.classNames.disabled + ', .' + opt.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']() - .trigger('contextmenu:focus'); - e.preventDefault(); - return; - } - break; - - case 13: // enter - handle.keyStop(e, opt); - if (opt.isInput) { - if (opt.$selected && !opt.$selected.is('textarea, select')) { - e.preventDefault(); - return; - } - break; - } - if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) { - opt.$selected.trigger('mouseup'); - } - return; - - case 32: // space - case 33: // page up - case 34: // page down - // prevent browser from scrolling down while menu is visible - handle.keyStop(e, opt); - return; - - case 27: // esc - handle.keyStop(e, opt); - opt.$menu.trigger('contextmenu:hide'); - return; - - default: // 0-9, a-z - var k = (String.fromCharCode(e.keyCode)).toUpperCase(); - if (opt.accesskeys && opt.accesskeys[k]) { - // according to the specs accesskeys must be invoked immediately - opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup'); - return; - } - break; - } - // pass event to selected item, - // stop propagation to avoid endless recursion - e.stopPropagation(); - if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) { - opt.$selected.trigger(e); - } - }, - // select previous possible command in menu - prevItem: function (e) { - e.stopPropagation(); - var opt = $(this).data('contextMenu') || {}; - var root = $(this).data('contextMenuRoot') || {}; - - // obtain currently selected menu - if (opt.$selected) { - var $s = opt.$selected; - opt = opt.$selected.parent().data('contextMenu') || {}; - opt.$selected = $s; - } - - var $children = opt.$menu.children(), - $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(), - $round = $prev; - - // skip disabled or hidden elements - while ($prev.hasClass(root.classNames.disabled) || $prev.hasClass(root.classNames.notSelectable) || $prev.is(':hidden')) { - if ($prev.prev().length) { - $prev = $prev.prev(); - } else { - $prev = $children.last(); - } - if ($prev.is($round)) { - // break endless loop - return; - } - } - - // leave current - if (opt.$selected) { - handle.itemMouseleave.call(opt.$selected.get(0), e); - } - - // activate next - handle.itemMouseenter.call($prev.get(0), e); - - // focus input - var $input = $prev.find('input, textarea, select'); - if ($input.length) { - $input.focus(); - } - }, - // select next possible command in menu - nextItem: function (e) { - e.stopPropagation(); - var opt = $(this).data('contextMenu') || {}; - var root = $(this).data('contextMenuRoot') || {}; - - // obtain currently selected menu - if (opt.$selected) { - var $s = opt.$selected; - opt = opt.$selected.parent().data('contextMenu') || {}; - opt.$selected = $s; - } - - var $children = opt.$menu.children(), - $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(), - $round = $next; - - // skip disabled - while ($next.hasClass(root.classNames.disabled) || $next.hasClass(root.classNames.notSelectable) || $next.is(':hidden')) { - if ($next.next().length) { - $next = $next.next(); - } else { - $next = $children.first(); - } - if ($next.is($round)) { - // break endless loop - return; - } - } - - // leave current - if (opt.$selected) { - handle.itemMouseleave.call(opt.$selected.get(0), e); - } - - // activate next - handle.itemMouseenter.call($next.get(0), e); - - // focus input - var $input = $next.find('input, textarea, select'); - if ($input.length) { - $input.focus(); - } - }, - // flag that we're inside an input so the key handler can act accordingly - focusInput: function () { - var $this = $(this).closest('.context-menu-item'), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot; - - root.$selected = opt.$selected = $this; - root.isInput = opt.isInput = true; - }, - // flag that we're inside an input so the key handler can act accordingly - blurInput: function () { - var $this = $(this).closest('.context-menu-item'), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot; - - root.isInput = opt.isInput = false; - }, - // :hover on menu - menuMouseenter: function () { - var root = $(this).data().contextMenuRoot; - root.hovering = true; - }, - // :hover on menu - menuMouseleave: function (e) { - var root = $(this).data().contextMenuRoot; - if (root.$layer && root.$layer.is(e.relatedTarget)) { - root.hovering = false; - } - }, - // :hover done manually so key handling is possible - itemMouseenter: function (e) { - var $this = $(this), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot; - - root.hovering = true; - - // abort if we're re-entering - if (e && root.$layer && root.$layer.is(e.relatedTarget)) { - e.preventDefault(); - e.stopImmediatePropagation(); - } - - // make sure only one item is selected - (opt.$menu ? opt : root).$menu - .children('.' + root.classNames.hover).trigger('contextmenu:blur') - .children('.hover').trigger('contextmenu:blur'); - - if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) { - opt.$selected = null; - return; - } - - $this.trigger('contextmenu:focus'); - }, - // :hover done manually so key handling is possible - itemMouseleave: function (e) { - var $this = $(this), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot; - - if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) { - if (typeof root.$selected !== 'undefined' && root.$selected !== null) { - root.$selected.trigger('contextmenu:blur'); - } - e.preventDefault(); - e.stopImmediatePropagation(); - root.$selected = opt.$selected = opt.$node; - return; - } - - $this.trigger('contextmenu:blur'); - }, - // contextMenu item click - itemClick: function (e) { - var $this = $(this), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot, - key = data.contextMenuKey, - callback; - - // abort if the key is unknown or disabled or is a menu - if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-submenu, .context-menu-separator, .' + root.classNames.notSelectable)) { - return; - } - - e.preventDefault(); - e.stopImmediatePropagation(); - - if ($.isFunction(opt.callbacks[key]) && Object.prototype.hasOwnProperty.call(opt.callbacks, key)) { - // item-specific callback - callback = opt.callbacks[key]; - } else if ($.isFunction(root.callback)) { - // default callback - callback = root.callback; - } else { - // no callback, no action - return; - } - - // hide menu if callback doesn't stop that - if (callback.call(root.$trigger, key, root) !== false) { - root.$menu.trigger('contextmenu:hide'); - } else if (root.$menu.parent().length) { - op.update.call(root.$trigger, root); - } - }, - // ignore click events on input elements - inputClick: function (e) { - e.stopImmediatePropagation(); - }, - // hide - hideMenu: function (e, data) { - var root = $(this).data('contextMenuRoot'); - op.hide.call(root.$trigger, root, data && data.force); - }, - // focus - focusItem: function (e) { - e.stopPropagation(); - var $this = $(this), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot; - - if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) { - return; - } - - $this - .addClass([root.classNames.hover, root.classNames.visible].join(' ')) - // select other items and included items - .parent().find('.context-menu-item').not($this) - .removeClass(root.classNames.visible) - .filter('.' + root.classNames.hover) - .trigger('contextmenu:blur'); - - // remember selected - opt.$selected = root.$selected = $this; - - // position sub-menu - do after show so dumb $.ui.position can keep up - if (opt.$node) { - root.positionSubmenu.call(opt.$node, opt.$menu); - } - }, - // blur - blurItem: function (e) { - e.stopPropagation(); - var $this = $(this), - data = $this.data(), - opt = data.contextMenu, - root = data.contextMenuRoot; - - if (opt.autoHide) { // for tablets and touch screens this needs to remain - $this.removeClass(root.classNames.visible); - } - $this.removeClass(root.classNames.hover); - opt.$selected = null; - } - }, - // operations - op = { - show: function (opt, x, y) { - var $trigger = $(this), - css = {}; - - // hide any open menus - $('#context-menu-layer').trigger('mousedown'); - - // backreference for callbacks - opt.$trigger = $trigger; - - // show event - if (opt.events.show.call($trigger, opt) === false) { - $currentTrigger = null; - return; - } - - // create or update context menu - op.update.call($trigger, opt); - - // position menu - opt.position.call($trigger, opt, x, y); - - // make sure we're in front - if (opt.zIndex) { - var additionalZValue = opt.zIndex; - // If opt.zIndex is a function, call the function to get the right zIndex. - if (typeof opt.zIndex === 'function') { - additionalZValue = opt.zIndex.call($trigger, opt); - } - css.zIndex = zindex($trigger) + additionalZValue; - } - - // add layer - op.layer.call(opt.$menu, opt, css.zIndex); - - // adjust sub-menu zIndexes - opt.$menu.find('ul').css('zIndex', css.zIndex + 1); - - // position and show context menu - opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () { - $trigger.trigger('contextmenu:visible'); - }); - // make options available and set state - $trigger - .data('contextMenu', opt) - .addClass('context-menu-active'); - - // register key handler - $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key); - // register autoHide handler - if (opt.autoHide) { - // mouse position handler - $(document).on('mousemove.contextMenuAutoHide', function (e) { - // need to capture the offset on mousemove, - // since the page might've been scrolled since activation - var pos = $trigger.offset(); - pos.right = pos.left + $trigger.outerWidth(); - pos.bottom = pos.top + $trigger.outerHeight(); - - if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) { - /* Additional hover check after short time, you might just miss the edge of the menu */ - setTimeout(function () { - if (!opt.hovering && opt.$menu != null) { opt.$menu.trigger('contextmenu:hide'); } - }, 50); - } - }); - } - }, - hide: function (opt, force) { - var $trigger = $(this); - if (!opt) { - opt = $trigger.data('contextMenu') || {}; - } - - // hide event - if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) { - return; - } - - // remove options and revert state - $trigger - .removeData('contextMenu') - .removeClass('context-menu-active'); - - if (opt.$layer) { - // keep layer for a bit so the contextmenu event can be aborted properly by opera - setTimeout((function ($layer) { - return function () { - $layer.remove(); - }; - })(opt.$layer), 10); - - try { - delete opt.$layer; - } catch (e) { - opt.$layer = null; - } - } - - // remove handle - $currentTrigger = null; - // remove selected - opt.$menu.find('.' + opt.classNames.hover).trigger('contextmenu:blur'); - opt.$selected = null; - // collapse all submenus - opt.$menu.find('.' + opt.classNames.visible).removeClass(opt.classNames.visible); - // unregister key and mouse handlers - // $(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705 - $(document).off('.contextMenuAutoHide').off('keydown.contextMenu'); - // hide menu - if(opt.$menu){ - opt.$menu[opt.animation.hide](opt.animation.duration, function () { - // tear down dynamically built menu after animation is completed. - if (opt.build) { - opt.$menu.remove(); - $.each(opt, function (key) { - switch (key) { - case 'ns': - case 'selector': - case 'build': - case 'trigger': - return true; - - default: - opt[key] = undefined; - try { - delete opt[key]; - } catch (e) { - } - return true; - } - }); - } - - setTimeout(function () { - $trigger.trigger('contextmenu:hidden'); - }, 10); - }); - } - }, - create: function (opt, root) { - if (root === undefined) { - root = opt; - } - // create contextMenu - opt.$menu = $('
    ').addClass(opt.className || '').data({ - 'contextMenu': opt, - 'contextMenuRoot': root - }); - - $.each(['callbacks', 'commands', 'inputs'], function (i, k) { - opt[k] = {}; - if (!root[k]) { - root[k] = {}; - } - }); - - if(!root.accesskeys){ - root.accesskeys = {}; - } - - function createNameNode(item) { - var $name = $(''); - if (item._accesskey) { - if (item._beforeAccesskey) { - $name.append(document.createTextNode(item._beforeAccesskey)); - } - $('') - .addClass('context-menu-accesskey') - .text(item._accesskey) - .appendTo($name); - if (item._afterAccesskey) { - $name.append(document.createTextNode(item._afterAccesskey)); - } - } else { - if (item.isHtmlName) { - // restrict use with access keys - if (typeof item.accesskey !== 'undefined') { - throw new Error('accesskeys are not compatible with HTML names and cannot be used together in the same item'); - } - $name.html(item.name); - } else { - $name.text(item.name); - } - } - return $name; - } - - // create contextMenu items - $.each(opt.items, function (key, item) { - var $t = $('
  • ').addClass(item.className || ''), - $label = null, - $input = null; - - // iOS needs to see a click-event bound to an element to actually - // have the TouchEvents infrastructure trigger the click event - $t.on('click', $.noop); - - // Make old school string seperator a real item so checks wont be - // akward later. - // And normalize 'cm_separator' into 'cm_seperator'. - if (typeof item === 'string' || item.type === 'cm_separator') { - item = { type : 'cm_seperator' }; - } - - item.$node = $t.data({ - 'contextMenu': opt, - 'contextMenuRoot': root, - 'contextMenuKey': key - }); - - // register accesskey - // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that - if (typeof item.accesskey !== 'undefined') { - var aks = splitAccesskey(item.accesskey); - for (var i = 0, ak; ak = aks[i]; i++) { - if (!root.accesskeys[ak]) { - root.accesskeys[ak] = item; - var matched = item.name.match(new RegExp('^(.*?)(' + ak + ')(.*)$', 'i')); - if (matched) { - item._beforeAccesskey = matched[1]; - item._accesskey = matched[2]; - item._afterAccesskey = matched[3]; - } - break; - } - } - } - - if (item.type && types[item.type]) { - // run custom type handler - types[item.type].call($t, item, opt, root); - // register commands - $.each([opt, root], function (i, k) { - k.commands[key] = item; - // Overwrite only if undefined or the item is appended to the root. This so it - // doesn't overwrite callbacks of root elements if the name is the same. - if ($.isFunction(item.callback) && (k.callbacks[key] === undefined || opt.type === undefined)) { - k.callbacks[key] = item.callback; - } - }); - } else { - // add label for input - if (item.type === 'cm_seperator') { - $t.addClass('context-menu-separator ' + root.classNames.notSelectable); - } else if (item.type === 'html') { - $t.addClass('context-menu-html ' + root.classNames.notSelectable); - } else if (item.type) { - $label = $('').appendTo($t); - createNameNode(item).appendTo($label); - - $t.addClass('context-menu-input'); - opt.hasTypes = true; - $.each([opt, root], function (i, k) { - k.commands[key] = item; - k.inputs[key] = item; - }); - } else if (item.items) { - item.type = 'sub'; - } - - switch (item.type) { - case 'cm_seperator': - break; - - case 'text': - $input = $('') - .attr('name', 'context-menu-input-' + key) - .val(item.value || '') - .appendTo($label); - break; - - case 'textarea': - $input = $('') - .attr('name', 'context-menu-input-' + key) - .val(item.value || '') - .appendTo($label); - - if (item.height) { - $input.height(item.height); - } - break; - - case 'checkbox': - $input = $('') - .attr('name', 'context-menu-input-' + key) - .val(item.value || '') - .prop('checked', !!item.selected) - .prependTo($label); - break; - - case 'radio': - $input = $('') - .attr('name', 'context-menu-input-' + item.radio) - .val(item.value || '') - .prop('checked', !!item.selected) - .prependTo($label); - break; - - case 'select': - $input = $(' - if (item.type && item.type !== 'sub' && item.type !== 'html' && item.type !== 'cm_seperator') { - $input - .on('focus', handle.focusInput) - .on('blur', handle.blurInput); - - if (item.events) { - $input.on(item.events, opt); - } - } - - // add icons - if (item.icon) { - if ($.isFunction(item.icon)) { - item._icon = item.icon.call(this, this, $t, key, item); - } else { - if ( typeof(item.icon) === 'string' && item.icon.substring(0,3) == 'fa-' ) { - // to enable font awesome - item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon; - } else { - item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon; - } - } - $t.addClass(item._icon); - } - } - - // cache contained elements - item.$input = $input; - item.$label = $label; - - // attach item to menu - $t.appendTo(opt.$menu); - - // Disable text selection - if (!opt.hasTypes && $.support.eventSelectstart) { - // browsers support user-select: none, - // IE has a special event for text-selection - // browsers supporting neither will not be preventing text-selection - $t.on('selectstart.disableTextSelect', handle.abortevent); - } - }); - // attach contextMenu to (to bypass any possible overflow:hidden issues on parents of the trigger element) - if (!opt.$node) { - opt.$menu.css('display', 'none').addClass('context-menu-root'); - } - opt.$menu.appendTo(opt.appendTo || document.body); - }, - resize: function ($menu, nested) { - var domMenu; - // determine widths of submenus, as CSS won't grow them automatically - // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100; - // kinda sucks hard... - - // determine width of absolutely positioned element - $menu.css({position: 'absolute', display: 'block'}); - // don't apply yet, because that would break nested elements' widths - $menu.data('width', - (domMenu = $menu.get(0)).getBoundingClientRect ? - Math.ceil(domMenu.getBoundingClientRect().width) : - $menu.outerWidth() + 1); // outerWidth() returns rounded pixels - // reset styles so they allow nested elements to grow/shrink naturally - $menu.css({ - position: 'static', - minWidth: '0px', - maxWidth: '100000px' - }); - // identify width of nested menus - $menu.find('> li > ul').each(function () { - op.resize($(this), true); - }); - // reset and apply changes in the end because nested - // elements' widths wouldn't be calculatable otherwise - if (!nested) { - $menu.find('ul').addBack().css({ - position: '', - display: '', - minWidth: '', - maxWidth: '' - }).outerWidth(function () { - return $(this).data('width'); - }); - } - }, - update: function (opt, root) { - var $trigger = this; - if (root === undefined) { - root = opt; - op.resize(opt.$menu); - } - // re-check disabled for each item - opt.$menu.children().each(function () { - var $item = $(this), - key = $item.data('contextMenuKey'), - item = opt.items[key], - disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true, - visible; - if ($.isFunction(item.visible)) { - visible = item.visible.call($trigger, key, root); - } else if (typeof item.visible !== 'undefined') { - visible = item.visible === true; - } else { - visible = true; - } - $item[visible ? 'show' : 'hide'](); - - // dis- / enable item - $item[disabled ? 'addClass' : 'removeClass'](root.classNames.disabled); - - if ($.isFunction(item.icon)) { - $item.removeClass(item._icon); - item._icon = item.icon.call(this, $trigger, $item, key, item); - $item.addClass(item._icon); - } - - if (item.type) { - // dis- / enable input elements - $item.find('input, select, textarea').prop('disabled', disabled); - - // update input states - switch (item.type) { - case 'text': - case 'textarea': - item.$input.val(item.value || ''); - break; - - case 'checkbox': - case 'radio': - item.$input.val(item.value || '').prop('checked', !!item.selected); - break; - - case 'select': - item.$input.val(item.selected || ''); - break; - } - } - - if (item.$menu) { - // update sub-menu - op.update.call($trigger, item, root); - } - }); - }, - layer: function (opt, zIndex) { - // add transparent layer for click area - // filter and background for Internet Explorer, Issue #23 - var $layer = opt.$layer = $('
    ') - .css({height: $win.height(), width: $win.width(), display: 'block'}) - .data('contextMenuRoot', opt) - .insertBefore(this) - .on('contextmenu', handle.abortevent) - .on('mousedown', handle.layerClick); - - // IE6 doesn't know position:fixed; - if (document.body.style.maxWidth === undefined) { // IE6 doesn't support maxWidth - $layer.css({ - 'position': 'absolute', - 'height': $(document).height() - }); - } - - return $layer; - } - }; - - // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key - function splitAccesskey(val) { - var t = val.split(/\s+/), - keys = []; - - for (var i = 0, k; k = t[i]; i++) { - k = k.charAt(0).toUpperCase(); // first character only - // 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 - keys.push(k); - } - - return keys; - } - -// handle contextMenu triggers - $.fn.contextMenu = function (operation) { - var $t = this, $o = operation; - if (this.length > 0) { // this is not a build on demand menu - if (operation === undefined) { - this.first().trigger('contextmenu'); - } else if (operation.x !== undefined && operation.y !== undefined) { - this.first().trigger($.Event('contextmenu', { pageX: operation.x, pageY: operation.y, mouseButton: operation.button })); - } else if (operation === 'hide') { - var $menu = this.first().data('contextMenu') ? this.first().data('contextMenu').$menu : null; - if($menu){ - $menu.trigger('contextmenu:hide'); - } - } else if (operation === 'destroy') { - $.contextMenu('destroy', {context: this}); - } else if ($.isPlainObject(operation)) { - operation.context = this; - $.contextMenu('create', operation); - } else if (operation) { - this.removeClass('context-menu-disabled'); - } else if (!operation) { - this.addClass('context-menu-disabled'); - } - } else { - $.each(menus, function () { - if (this.selector === $t.selector) { - $o.data = this; - - $.extend($o.data, {trigger: 'demand'}); - } - }); - - handle.contextmenu.call($o.target, $o); - } - - return this; - }; - - // manage contextMenu instances - $.contextMenu = function (operation, options) { - if (typeof operation !== 'string') { - options = operation; - operation = 'create'; - } - - if (typeof options === 'string') { - options = {selector: options}; - } else if (options === undefined) { - options = {}; - } - - // merge with default options - var o = $.extend(true, {}, defaults, options || {}); - var $document = $(document); - var $context = $document; - var _hasContext = false; - - if (!o.context || !o.context.length) { - o.context = document; - } else { - // you never know what they throw at you... - $context = $(o.context).first(); - o.context = $context.get(0); - _hasContext = o.context !== document; - } - - switch (operation) { - case 'create': - // no selector no joy - if (!o.selector) { - throw new Error('No selector specified'); - } - // make sure internal classes are not bound to - if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) { - throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className'); - } - if (!o.build && (!o.items || $.isEmptyObject(o.items))) { - throw new Error('No Items specified'); - } - counter++; - o.ns = '.contextMenu' + counter; - if (!_hasContext) { - namespaces[o.selector] = o.ns; - } - menus[o.ns] = o; - - // default to right click - if (!o.trigger) { - o.trigger = 'right'; - } - - if (!initialized) { - var itemClick = o.itemClickEvent === 'click' ? 'click.contextMenu' : 'mouseup.contextMenu'; - var contextMenuItemObj = { - // 'mouseup.contextMenu': handle.itemClick, - // 'click.contextMenu': handle.itemClick, - 'contextmenu:focus.contextMenu': handle.focusItem, - 'contextmenu:blur.contextMenu': handle.blurItem, - 'contextmenu.contextMenu': handle.abortevent, - 'mouseenter.contextMenu': handle.itemMouseenter, - 'mouseleave.contextMenu': handle.itemMouseleave - }; - contextMenuItemObj[itemClick] = handle.itemClick; - // make sure item click is registered first - $document - .on({ - 'contextmenu:hide.contextMenu': handle.hideMenu, - 'prevcommand.contextMenu': handle.prevItem, - 'nextcommand.contextMenu': handle.nextItem, - 'contextmenu.contextMenu': handle.abortevent, - 'mouseenter.contextMenu': handle.menuMouseenter, - 'mouseleave.contextMenu': handle.menuMouseleave - }, '.context-menu-list') - .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick) - .on(contextMenuItemObj, '.context-menu-item'); - - initialized = true; - } - - // engage native contextmenu event - $context - .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu); - - if (_hasContext) { - // add remove hook, just in case - $context.on('remove' + o.ns, function () { - $(this).contextMenu('destroy'); - }); - } - - switch (o.trigger) { - case 'hover': - $context - .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter) - .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave); - break; - - case 'left': - $context.on('click' + o.ns, o.selector, o, handle.click); - break; - /* - default: - // http://www.quirksmode.org/dom/events/contextmenu.html - $document - .on('mousedown' + o.ns, o.selector, o, handle.mousedown) - .on('mouseup' + o.ns, o.selector, o, handle.mouseup); - break; - */ - } - - // create menu - if (!o.build) { - op.create(o); - } - break; - - case 'destroy': - var $visibleMenu; - if (_hasContext) { - // get proper options - var context = o.context; - $.each(menus, function (ns, o) { - if (o.context !== context) { - return true; - } - - $visibleMenu = $('.context-menu-list').filter(':visible'); - if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) { - $visibleMenu.trigger('contextmenu:hide', {force: true}); - } - - try { - if (menus[o.ns].$menu) { - menus[o.ns].$menu.remove(); - } - - delete menus[o.ns]; - } catch (e) { - menus[o.ns] = null; - } - - $(o.context).off(o.ns); - - return true; - }); - } else if (!o.selector) { - $document.off('.contextMenu .contextMenuAutoHide'); - $.each(menus, function (ns, o) { - $(o.context).off(o.ns); - }); - - namespaces = {}; - menus = {}; - counter = 0; - initialized = false; - - $('#context-menu-layer, .context-menu-list').remove(); - } else if (namespaces[o.selector]) { - $visibleMenu = $('.context-menu-list').filter(':visible'); - if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) { - $visibleMenu.trigger('contextmenu:hide', {force: true}); - } - - try { - if (menus[namespaces[o.selector]].$menu) { - menus[namespaces[o.selector]].$menu.remove(); - } - - delete menus[namespaces[o.selector]]; - } catch (e) { - menus[namespaces[o.selector]] = null; - } - - $document.off(namespaces[o.selector]); - } - break; - - case 'html5': - // if or are not handled by the browser, - // or options was a bool true, - // initialize $.contextMenu for them - if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options === 'boolean' && options)) { - $('menu[type="context"]').each(function () { - if (this.id) { - $.contextMenu({ - selector: '[contextmenu=' + this.id + ']', - items: $.contextMenu.fromMenu(this) - }); - } - }).css('display', 'none'); - } - break; - - default: - throw new Error('Unknown operation "' + operation + '"'); - } - - return this; - }; - -// import values into commands - $.contextMenu.setInputValues = function (opt, data) { - if (data === undefined) { - data = {}; - } - - $.each(opt.inputs, function (key, item) { - switch (item.type) { - case 'text': - case 'textarea': - item.value = data[key] || ''; - break; - - case 'checkbox': - item.selected = data[key] ? true : false; - break; - - case 'radio': - item.selected = (data[item.radio] || '') === item.value; - break; - - case 'select': - item.selected = data[key] || ''; - break; - } - }); - }; - -// export values from commands - $.contextMenu.getInputValues = function (opt, data) { - if (data === undefined) { - data = {}; - } - - $.each(opt.inputs, function (key, item) { - switch (item.type) { - case 'text': - case 'textarea': - case 'select': - data[key] = item.$input.val(); - break; - - case 'checkbox': - data[key] = item.$input.prop('checked'); - break; - - case 'radio': - if (item.$input.prop('checked')) { - data[item.radio] = item.value; - } - break; - } - }); - - return data; - }; - -// find