PageSourceSearch

https://mail.cheilebicazului-hasmas.ro/js/Default/dyntable/static/js/jquery.contextMenu.js

js cheilebicazului-hasmas.ro collected 2026-09-25 01:05:12 UTC 93,612 bytes, 2,134 lines download raw bytes

1/**
2 * jQuery contextMenu v2.9.0 - Plugin for simple contextMenu handling
3 *
4 * Version: v2.9.0
5 *
6 * Authors: Björn Brala (SWIS.nl), Rodney Rehm, Addy Osmani (patches for FF)
7 * Web: http://swisnl.github.io/jQuery-contextMenu/
8 *
9 * Copyright (c) 2011-2019 SWIS BV and contributors
10 *
11 * Licensed under
12 *   MIT License http://www.opensource.org/licenses/mit-license
13 *
14 * Date: 2019-10-13T13:09:56.900Z
15 */
16
17// jscs:disable
18/* jshint ignore:start */
19(function (factory) {
20    if (typeof define === 'function' && define.amd) {
21        // AMD. Register as anonymous module.
22        define(['jquery'], factory);
23    } else if (typeof exports === 'object') {
24        // Node / CommonJS
25        factory(require('jquery'));
26    } else {
27        // Browser globals.
28        factory(jQuery);
29    }
30})(function ($) {
31
32    'use strict';
33
34    // TODO: -
35    // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
36    // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
37
38    // determine html5 compatibility
39    $.support.htmlMenuitem = ('HTMLMenuItemElement' in window);
40    $.support.htmlCommand = ('HTMLCommandElement' in window);
41    $.support.eventSelectstart = ('onselectstart' in document.documentElement);
42    /* // should the need arise, test for css user-select
43     $.support.cssUserSelect = (function(){
44     var t = false,
45     e = document.createElement('div');
46
47     $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) {
48     var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect',
49     prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select';
50
51     e.style.cssText = prop + ': text;';
52     if (e.style[propCC] == 'text') {
53     t = true;
54     return false;
55     }
56
57     return true;
58     });
59
60     return t;
61     })();
62     */
63
64
65    if (!$.ui || !$.widget) {
66        // duck punch $.cleanData like jQueryUI does to get that remove event
67        $.cleanData = (function (orig) {
68            return function (elems) {
69                var events, elem, i;
70                for (i = 0; elems[i] != null; i++) {
71                    elem = elems[i];
72                    try {
73                        // Only trigger remove when necessary to save time
74                        events = $._data(elem, 'events');
75                        if (events && events.remove) {
76                            $(elem).triggerHandler('remove');
77                        }
78
79                        // Http://bugs.jquery.com/ticket/8235
80                    } catch (e) {
81                    }
82                }
83                orig(elems);
84            };
85        })($.cleanData);
86    }
87    /* jshint ignore:end */
88    // jscs:enable
89
90    var // currently active contextMenu trigger
91        $currentTrigger = null,
92        // is contextMenu initialized with at least one menu?
93        initialized = false,
94        // window handle
95        $win = $(window),
96        // number of registered menus
97        counter = 0,
98        // mapping selector to namespace
99        namespaces = {},
100        // mapping namespace to options
101        menus = {},
102        // custom command type handlers
103        types = {},
104        // default values
105        defaults = {
106            // selector of contextMenu trigger
107            selector: null,
108            // where to append the menu to
109            appendTo: null,
110            // method to trigger context menu ["right", "left", "hover"]
111            trigger: 'right',
112            // hide menu when mouse leaves trigger / menu elements
113            autoHide: false,
114            // ms to wait before showing a hover-triggered context menu
115            delay: 200,
116            // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu
117            // as long as the trigger happened on one of the trigger-element's child nodes
118            reposition: true,
119            // Flag denoting if a second trigger should close the menu, as long as
120            // the trigger happened on one of the trigger-element's child nodes.
121            // This overrides the reposition option.
122            hideOnSecondTrigger: false,
123
124            //ability to select submenu
125            selectableSubMenu: false,
126
127            // Default classname configuration to be able avoid conflicts in frameworks
128            classNames: {
129                hover: 'context-menu-hover', // Item hover
130                disabled: 'context-menu-disabled', // Item disabled
131                visible: 'context-menu-visible', // Item visible
132                notSelectable: 'context-menu-not-selectable', // Item not selectable
133
134                icon: 'context-menu-icon',
135                iconEdit: 'context-menu-icon-edit',
136                iconCut: 'context-menu-icon-cut',
137                iconCopy: 'context-menu-icon-copy',
138                iconPaste: 'context-menu-icon-paste',
139                iconDelete: 'context-menu-icon-delete',
140                iconAdd: 'context-menu-icon-add',
141                iconQuit: 'context-menu-icon-quit',
142                iconLoadingClass: 'context-menu-icon-loading'
143            },
144
145            // determine position to show menu at
146            determinePosition: function ($menu) {
147                // position to the lower middle of the trigger element
148                if ($.ui && $.ui.position) {
149                    // .position() is provided as a jQuery UI utility
150                    // (...and it won't work on hidden elements)
151                    $menu.css('display', 'block').position({
152                        my: 'center top',
153                        at: 'center bottom',
154                        of: this,
155                        offset: '0 5',
156                        collision: 'fit'
157                    }).css('display', 'none');
158                } else {
159                    // determine contextMenu position
160                    var offset = this.offset();
161                    offset.top += this.outerHeight();
162                    offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;
163                    $menu.css(offset);
164                }
165            },
166            // position menu
167            position: function (opt, x, y) {
168                var offset;
169                // determine contextMenu position
170                if (!x && !y) {
171                    opt.determinePosition.call(this, opt.$menu);
172                    return;
173                } else if (x === 'maintain' && y === 'maintain') {
174                    // x and y must not be changed (after re-show on command click)
175                    offset = opt.$menu.position();
176                } else {
177                    // x and y are given (by mouse event)
178                    var offsetParentOffset = opt.$menu.offsetParent().offset();
179                    offset = {top: y - offsetParentOffset.top, left: x -offsetParentOffset.left};
180                }
181
182                // correct offset if viewport demands it
183                var bottom = $win.scrollTop() + $win.height(),
184                    right = $win.scrollLeft() + $win.width(),
185                    height = opt.$menu.outerHeight(),
186                    width = opt.$menu.outerWidth();
187
188                if (offset.top + height > bottom) {
189                    offset.top -= height;
190                }
191
192                if (offset.top < 0) {
193                    offset.top = 0;
194                }
195
196                if (offset.left + width > right) {
197                    offset.left -= width;
198                }
199
200                if (offset.left < 0) {
201                    offset.left = 0;
202                }
203
204                opt.$menu.css(offset);
205            },
206            // position the sub-menu
207            positionSubmenu: function ($menu) {
208                if (typeof $menu === 'undefined') {
209                    // When user hovers over item (which has sub items) handle.focusItem will call this.
210                    // but the submenu does not exist yet if opt.items is a promise. just return, will
211                    // call positionSubmenu after promise is completed.
212                    return;
213                }
214                if ($.ui && $.ui.position) {
215                    // .position() is provided as a jQuery UI utility
216                    // (...and it won't work on hidden elements)
217                    $menu.css('display', 'block').position({
218                        my: 'left top-5',
219                        at: 'right top',
220                        of: this,
221                        collision: 'flipfit fit'
222                    }).css('display', '');
223                } else {
224                    // determine contextMenu position
225                    var offset = {
226                        top: -9,
227                        left: this.outerWidth() - 5
228                    };
229                    $menu.css(offset);
230                }
231            },
232            // offset to add to zIndex
233            zIndex: 1,
234            // show hide animation settings
235            animation: {
236                duration: 50,
237                show: 'slideDown',
238                hide: 'slideUp'
239            },
240            // events
241            events: {
242                preShow: $.noop,
243                show: $.noop,
244                hide: $.noop,
245                activated: $.noop
246            },
247            // default callback
248            callback: null,
249            // list of contextMenu items
250            items: {}
251        },
252        // mouse position for hover activation
253        hoveract = {
254            timer: null,
255            pageX: null,
256            pageY: null
257        },
258        // determine zIndex
259        zindex = function ($t) {
260            var zin = 0,
261                $tt = $t;
262
263            while (true) {
264                zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);
265                $tt = $tt.parent();
266                if (!$tt || !$tt.length || 'html body'.indexOf($tt.prop('nodeName').toLowerCase()) > -1) {
267                    break;
268                }
269            }
270            return zin;
271        },
272        // event handlers
273        handle = {
274            // abort anything
275            abortevent: function (e) {
276                e.preventDefault();
277                e.stopImmediatePropagation();
278            },
279            // contextmenu show dispatcher
280            contextmenu: function (e) {
281                var $this = $(this);
282
283                //Show browser context-menu when preShow returns false
284                if (e.data.events.preShow($this,e) === false) {
285                    return;
286                }
287
288                // disable actual context-menu if we are using the right mouse button as the trigger
289                if (e.data.trigger === 'right') {
290                    e.preventDefault();
291                    e.stopImmediatePropagation();
292                }
293
294                // abort native-triggered events unless we're triggering on right click
295                if ((e.data.trigger !== 'right' && e.data.trigger !== 'demand') && e.originalEvent) {
296                    return;
297                }
298
299                // Let the current contextmenu decide if it should show or not based on its own trigger settings
300                if (typeof e.mouseButton !== 'undefined' && e.data) {
301                    if (!(e.data.trigger === 'left' && e.mouseButton === 0) && !(e.data.trigger === 'right' && e.mouseButton === 2)) {
302                        // Mouse click is not valid.
303                        return;
304                    }
305                }
306
307                // abort event if menu is visible for this trigger
308                if ($this.hasClass('context-menu-active')) {
309                    return;
310                }
311
312                if (!$this.hasClass('context-menu-disabled')) {
313                    // theoretically need to fire a show event at <menu>
314                    // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
315                    // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });
316                    // e.data.$menu.trigger(evt);
317
318                    $currentTrigger = $this;
319                    if (e.data.build) {
320                        var built = e.data.build($currentTrigger, e);
321                        // abort if build() returned false
322                        if (built === false) {
323                            return;
324                        }
325
326                        // dynamically build menu on invocation
327                        e.data = $.extend(true, {}, defaults, e.data, built || {});
328
329                        // abort if there are no items to display
330                        if (!e.data.items || $.isEmptyObject(e.data.items)) {
331                            // Note: jQuery captures and ignores errors from event handlers
332                            if (window.console) {
333                                (console.error || console.log).call(console, 'No items specified to show in contextMenu');
334                            }
335
336                            throw new Error('No Items specified');
337                        }
338
339                        // backreference for custom command type creation
340                        e.data.$trigger = $currentTrigger;
341
342                        op.create(e.data);
343                    }
344                    op.show.call($this, e.data, e.pageX, e.pageY);
345                }
346            },
347            // contextMenu left-click trigger
348            click: function (e) {
349                e.preventDefault();
350                e.stopImmediatePropagation();
351                $(this).trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY}));
352            },
353            // contextMenu right-click trigger
354            mousedown: function (e) {
355                // register mouse down
356                var $this = $(this);
357
358                // hide any previous menus
359                if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {
360                    $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');
361                }
362
363                // activate on right click
364                if (e.button === 2) {
365                    $currentTrigger = $this.data('contextMenuActive', true);
366                }
367            },
368            // contextMenu right-click trigger
369            mouseup: function (e) {
370                // show menu
371                var $this = $(this);
372                if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {
373                    e.preventDefault();
374                    e.stopImmediatePropagation();
375                    $currentTrigger = $this;
376                    $this.trigger($.Event('contextmenu', {data: e.data, pageX: e.pageX, pageY: e.pageY}));
377                }
378
379                $this.removeData('contextMenuActive');
380            },
381            // contextMenu hover trigger
382            mouseenter: function (e) {
383                var $this = $(this),
384                    $related = $(e.relatedTarget),
385                    $document = $(document);
386
387                // abort if we're coming from a menu
388                if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
389                    return;
390                }
391
392                // abort if a menu is shown
393                if ($currentTrigger && $currentTrigger.length) {
394                    return;
395                }
396
397                hoveract.pageX = e.pageX;
398                hoveract.pageY = e.pageY;
399                hoveract.data = e.data;
400                $document.on('mousemove.contextMenuShow', handle.mousemove);
401                hoveract.timer = setTimeout(function () {
402                    hoveract.timer = null;
403                    $document.off('mousemove.contextMenuShow');
404                    $currentTrigger = $this;
405                    $this.trigger($.Event('contextmenu', {
406                        data: hoveract.data,
407                        pageX: hoveract.pageX,
408                        pageY: hoveract.pageY
409                    }));
410                }, e.data.delay);
411            },
412            // contextMenu hover trigger
413            mousemove: function (e) {
414                hoveract.pageX = e.pageX;
415                hoveract.pageY = e.pageY;
416            },
417            // contextMenu hover trigger
418            mouseleave: function (e) {
419                // abort if we're leaving for a menu
420                var $related = $(e.relatedTarget);
421                if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
422                    return;
423                }
424
425                try {
426                    clearTimeout(hoveract.timer);
427                } catch (e) {
428                }
429
430                hoveract.timer = null;
431            },
432            // click on layer to hide contextMenu
433            layerClick: function (e) {
434                var $this = $(this),
435                    root = $this.data('contextMenuRoot'),
436                    button = e.button,
437                    x = e.pageX,
438                    y = e.pageY,
439                    fakeClick = x === undefined,
440                    target,
441                    offset;
442
443                e.preventDefault();
444
445                setTimeout(function () {
446                    // If the click is not real, things break: https://github.com/swisnl/jQuery-contextMenu/issues/132
447                    if(fakeClick){
448                        if (root !== null && typeof root !== 'undefined' && root.$menu !== null  && typeof root.$menu !== 'undefined') {
449                            root.$menu.trigger('contextmenu:hide');
450                        }
451                        return;
452                    }
453
454                    var $window;
455                    var triggerAction = ((root.trigger === 'left' && button === 0) || (root.trigger === 'right' && button === 2));
456
457                    // find the element that would've been clicked, wasn't the layer in the way
458                    if (document.elementFromPoint && root.$layer) {
459                        root.$layer.hide();
460                        target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop());
461
462                        // also need to try and focus this element if we're in a contenteditable area,
463                        // as the layer will prevent the browser mou
463se action we want
464                        if (target.isContentEditable) {
465                            var range = document.createRange(),
466                                sel = window.getSelection();
467                            range.selectNode(target);
468                            range.collapse(true);
469                            sel.removeAllRanges();
470                            sel.addRange(range);
471                        }
472                        $(target).trigger(e);
473                        root.$layer.show();
474                    }
475
476                    if (root.hideOnSecondTrigger && triggerAction && root.$menu !== null && typeof root.$menu !== 'undefined') {
477                      root.$menu.trigger('contextmenu:hide');
478                      return;
479                    }
480
481                    if (root.reposition && triggerAction) {
482                        if (document.elementFromPoint) {
483                            if (root.$trigger.is(target)) {
484                                root.position.call(root.$trigger, root, x, y);
485                                return;
486                            }
487                        } else {
488                            offset = root.$trigger.offset();
489                            $window = $(window);
490                            // while this looks kinda awful, it's the best way to avoid
491                            // unnecessarily calculating any positions
492                            offset.top += $window.scrollTop();
493                            if (offset.top <= e.pageY) {
494                                offset.left += $window.scrollLeft();
495                                if (offset.left <= e.pageX) {
496                                    offset.bottom = offset.top + root.$trigger.outerHeight();
497                                    if (offset.bottom >= e.pageY) {
498                                        offset.right = offset.left + root.$trigger.outerWidth();
499                                        if (offset.right >= e.pageX) {
500                                            // reposition
501                                            root.position.call(root.$trigger, root, x, y);
502                                            return;
503                                        }
504                                    }
505                                }
506                            }
507                        }
508                    }
509
510                    if (target && triggerAction) {
511                        root.$trigger.one('contextmenu:hidden', function () {
512                            $(target).contextMenu({x: x, y: y, button: button});
513                        });
514                    }
515
516                    if (root !== null && typeof root !== 'undefined' && root.$menu !== null  && typeof root.$menu !== 'undefined') {
517                        root.$menu.trigger('contextmenu:hide');
518                    }
519                }, 50);
520            },
521            // key handled :hover
522            keyStop: function (e, opt) {
523                if (!opt.isInput) {
524                    e.preventDefault();
525                }
526
527                e.stopPropagation();
528            },
529            key: function (e) {
530
531                var opt = {};
532
533                // Only get the data from $currentTrigger if it exists
534                if ($currentTrigger) {
535                    opt = $currentTrigger.data('contextMenu') || {};
536                }
537                // If the trigger happen on a element that are above the contextmenu do this
538                if (typeof opt.zIndex === 'undefined') {
539                    opt.zIndex = 0;
540                }
541                var targetZIndex = 0;
542                var getZIndexOfTriggerTarget = function (target) {
543                    if (target.style.zIndex !== '') {
544                        targetZIndex = target.style.zIndex;
545                    } else {
546                        if (target.offsetParent !== null && typeof target.offsetParent !== 'undefined') {
547                            getZIndexOfTriggerTarget(target.offsetParent);
548                        }
549                        else if (target.parentElement !== null && typeof target.parentElement !== 'undefined') {
550                            getZIndexOfTriggerTarget(target.parentElement);
551                        }
552                    }
553                };
554                getZIndexOfTriggerTarget(e.target);
555                // If targetZIndex is heigher then opt.zIndex dont progress any futher.
556                // This is used to make sure that if you are using a dialog with a input / textarea / contenteditable div
557                // and its above the contextmenu it wont steal keys events
558                if (opt.$menu && parseInt(targetZIndex,10) > parseInt(opt.$menu.css("zIndex"),10)) {
559                    return;
560                }
561                switch (e.keyCode) {
562                    case 9:
563                    case 38: // up
564                        handle.keyStop(e, opt);
565                        // if keyCode is [38 (up)] or [9 (tab) with shift]
566                        if (opt.isInput) {
567                            if (e.keyCode === 9 && e.shiftKey) {
568                                e.preventDefault();
569                                if (opt.$selected) {
570                                    opt.$selected.find('input, textarea, select').blur();
571                                }
572                                if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
573                                    opt.$menu.trigger('prevcommand');
574                                }
575                                return;
576                            } else if (e.keyCode === 38 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') {
577                                // checkboxes don't capture this key
578                                e.preventDefault();
579                                return;
580                            }
581                        } else if (e.keyCode !== 9 || e.shiftKey) {
582                            if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
583                                opt.$menu.trigger('prevcommand');
584                            }
585                            return;
586                        }
587                        break;
588                    // omitting break;
589                    // case 9: // tab - reached through omitted break;
590                    case 40: // down
591                        handle.keyStop(e, opt);
592                        if (opt.isInput) {
593                            if (e.keyCode === 9) {
594                                e.preventDefault();
595                                if (opt.$selected) {
596                                    opt.$selected.find('input, textarea, select').blur();
597                                }
598                                if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
599                                    opt.$menu.trigger('nextcommand');
600                                }
601                                return;
602                            } else if (e.keyCode === 40 && opt.$selected.find('input, textarea, select').prop('type') === 'checkbox') {
603                                // checkboxes don't capture this key
604                                e.preventDefault();
605                                return;
606                            }
607                        } else {
608                            if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
609                                opt.$menu.trigger('nextcommand');
610                            }
611                            return;
612                        }
613                        break;
614
615                    case 37: // left
616                        handle.keyStop(e, opt);
617                        if (opt.isInput || !opt.$selected || !opt.$selected.length) {
618                            break;
619                        }
620
621                        if (!opt.$selected.parent().hasClass('context-menu-root')) {
622                            var $parent = opt.$selected.parent().parent();
623                            opt.$selected.trigger('contextmenu:blur');
624                            opt.$selected = $parent;
625                            return;
626                        }
627                        break;
628
629                    case 39: // right
630                        handle.keyStop(e, opt);
631                        if (opt.isInput || !opt.$selected || !opt.$selected.length) {
632                            break;
633                        }
634
635                        var itemdata = opt.$selected.data('contextMenu') || {};
636                        if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {
637                            opt.$selected = null;
638                            itemdata.$selected = null;
639                            itemdata.$menu.trigger('nextcommand');
640                            return;
641                        }
642                        break;
643
644                    case 35: // end
645                    case 36: // home
646                        if (opt.$selected && opt.$selected.find('input, textarea, select').length) {
647                            return;
648                        } else {
649                            (opt.$selected && opt.$selected.parent() || opt.$menu)
650                                .children(':not(.' + opt.classNames.disabled + ', .' + opt.classNames.notSelectable + ')')[e.keyCode === 36 ? 'first' : 'last']()
651                                .trigger('contextmenu:focus');
652                            e.preventDefault();
653                            return;
654                        }
655                        break;
656
657                    case 13: // enter
658                        handle.keyStop(e, opt);
659                        if (opt.isInput) {
660                            if (opt.$selected && !opt.$selected.is('textarea, select')) {
661                                e.preventDefault();
662                                return;
663                            }
664                            break;
665                        }
666                        if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) {
667                            opt.$selected.trigger('mouseup');
668                        }
669                        return;
670
671                    case 32: // space
672                    case 33: // page up
673                    case 34: // page down
674                        // prevent browser from scrolling down while menu is visible
675                        handle.keyStop(e, opt);
676                        return;
677
678                    case 27: // esc
679                        handle.keyStop(e, opt);
680                        if (opt.$menu !== null && typeof opt.$menu !== 'undefined') {
681                            opt.$menu.trigger('contextmenu:hide');
682                        }
683                        return;
684
685                    default: // 0-9, a-z
686                        var k = (String.fromCharCode(e.keyCode)).toUpperCase();
687                        if (opt.accesskeys && opt.accesskeys[k]) {
688                            // according to the specs accesskeys must be invoked immediately
689                            opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu ? 'contextmenu:focus' : 'mouseup');
690                            return;
691                        }
692                        break;
693                }
694                // pass event to selected item,
695                // stop propagation to avoid endless recursion
696                e.stopPropagation();
697                if (typeof opt.$selected !== 'undefined' && opt.$selected !== null) {
698                    opt.$selected.trigger(e);
699                }
700            },
701            // select previous possible command in menu
702            prevItem: function (e) {
703                e.stopPropagation();
704                var opt = $(this).data('contextMenu') || {};
705                var root = $(this).data('contextMenuRoot') || {};
706
707                // obtain currently selected menu
708                if (opt.$selected) {
709                    var $s = opt.$selected;
710                    opt = opt.$selected.parent().data('contextMenu') || {};
711                    opt.$selected = $s;
712                }
713
714                var $children = opt.$menu.children(),
715                    $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last(
715) : opt.$selected.prev(),
716                    $round = $prev;
717
718                // skip disabled or hidden elements
719                while ($prev.hasClass(root.classNames.disabled) || $prev.hasClass(root.classNames.notSelectable) || $prev.is(':hidden')) {
720                    if ($prev.prev().length) {
721                        $prev = $prev.prev();
722                    } else {
723                        $prev = $children.last();
724                    }
725                    if ($prev.is($round)) {
726                        // break endless loop
727                        return;
728                    }
729                }
730
731                // leave current
732                if (opt.$selected) {
733                    handle.itemMouseleave.call(opt.$selected.get(0), e);
734                }
735
736                // activate next
737                handle.itemMouseenter.call($prev.get(0), e);
738
739                // focus input
740                var $input = $prev.find('input, textarea, select');
741                if ($input.length) {
742                    $input.focus();
743                }
744            },
745            // select next possible command in menu
746            nextItem: function (e) {
747                e.stopPropagation();
748                var opt = $(this).data('contextMenu') || {};
749                var root = $(this).data('contextMenuRoot') || {};
750
751                // obtain currently selected menu
752                if (opt.$selected) {
753                    var $s = opt.$selected;
754                    opt = opt.$selected.parent().data('contextMenu') || {};
755                    opt.$selected = $s;
756                }
757
758                var $children = opt.$menu.children(),
759                    $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),
760                    $round = $next;
761
762                // skip disabled
763                while ($next.hasClass(root.classNames.disabled) || $next.hasClass(root.classNames.notSelectable) || $next.is(':hidden')) {
764                    if ($next.next().length) {
765                        $next = $next.next();
766                    } else {
767                        $next = $children.first();
768                    }
769                    if ($next.is($round)) {
770                        // break endless loop
771                        return;
772                    }
773                }
774
775                // leave current
776                if (opt.$selected) {
777                    handle.itemMouseleave.call(opt.$selected.get(0), e);
778                }
779
780                // activate next
781                handle.itemMouseenter.call($next.get(0), e);
782
783                // focus input
784                var $input = $next.find('input, textarea, select');
785                if ($input.length) {
786                    $input.focus();
787                }
788            },
789            // flag that we're inside an input so the key handler can act accordingly
790            focusInput: function () {
791                var $this = $(this).closest('.context-menu-item'),
792                    data = $this.data(),
793                    opt = data.contextMenu,
794                    root = data.contextMenuRoot;
795
796                root.$selected = opt.$selected = $this;
797                root.isInput = opt.isInput = true;
798            },
799            // flag that we're inside an input so the key handler can act accordingly
800            blurInput: function () {
801                var $this = $(this).closest('.context-menu-item'),
802                    data = $this.data(),
803                    opt = data.contextMenu,
804                    root = data.contextMenuRoot;
805
806                root.isInput = opt.isInput = false;
807            },
808            // :hover on menu
809            menuMouseenter: function () {
810                var root = $(this).data().contextMenuRoot;
811                root.hovering = true;
812            },
813            // :hover on menu
814            menuMouseleave: function (e) {
815                var root = $(this).data().contextMenuRoot;
816                if (root.$layer && root.$layer.is(e.relatedTarget)) {
817                    root.hovering = false;
818                }
819            },
820            // :hover done manually so key handling is possible
821            itemMouseenter: function (e) {
822                var $this = $(this),
823                    data = $this.data(),
824                    opt = data.contextMenu,
825                    root = data.contextMenuRoot;
826
827                root.hovering = true;
828
829                // abort if we're re-entering
830                if (e && root.$layer && root.$layer.is(e.relatedTarget)) {
831                    e.preventDefault();
832                    e.stopImmediatePropagation();
833                }
834
835                // make sure only one item is selected
836                (opt.$menu ? opt : root).$menu
837                    .children('.' + root.classNames.hover).trigger('contextmenu:blur')
838                    .children('.hover').trigger('contextmenu:blur');
839
840                if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) {
841                    opt.$selected = null;
842                    return;
843                }
844
845
846                $this.trigger('contextmenu:focus');
847            },
848            // :hover done manually so key handling is possible
849            itemMouseleave: function (e) {
850                var $this = $(this),
851                    data = $this.data(),
852                    opt = data.contextMenu,
853                    root = data.contextMenuRoot;
854
855                if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {
856                    if (typeof root.$selected !== 'undefined' && root.$selected !== null) {
857                        root.$selected.trigger('contextmenu:blur');
858                    }
859                    e.preventDefault();
860                    e.stopImmediatePropagation();
861                    root.$selected = opt.$selected = opt.$node;
862                    return;
863                }
864
865                if(opt && opt.$menu && opt.$menu.hasClass('context-menu-visible')){
866                    return;
867                }
868
869                $this.trigger('contextmenu:blur');
870            },
871            // contextMenu item click
872            itemClick: function (e) {
873                var $this = $(this),
874                    data = $this.data(),
875                    opt = data.contextMenu,
876                    root = data.contextMenuRoot,
877                    key = data.contextMenuKey,
878                    callback;
879
880                // abort if the key is unknown or disabled or is a menu
881                if (!opt.items[key] || $this.is('.' + root.classNames.disabled + ', .context-menu-separator, .' + root.classNames.notSelectable) || ($this.is('.context-menu-submenu') && root.selectableSubMenu === false )) {
882                    return;
883                }
884
885                e.preventDefault();
886                e.stopImmediatePropagation();
887
888                if ($.isFunction(opt.callbacks[key]) && Object.prototype.hasOwnProperty.call(opt.callbacks, key)) {
889                    // item-specific callback
890                    callback = opt.callbacks[key];
891                } else if ($.isFunction(root.callback)) {
892                    // default callback
893                    callback = root.callback;
894                } else {
895                    // no callback, no action
896                    return;
897                }
898
899                // hide menu if callback doesn't stop that
900                if (callback.call(root.$trigger, key, root, e) !== false) {
901                    root.$menu.trigger('contextmenu:hide');
902                } else if (root.$menu.parent().length) {
903                    op.update.call(root.$trigger, root);
904                }
905            },
906            // ignore click events on input elements
907            inputClick: function (e) {
908                e.stopImmediatePropagation();
909            },
910            // hide <menu>
911            hideMenu: function (e, data) {
912                var root = $(this).data('contextMenuRoot');
913                op.hide.call(root.$trigger, root, data && data.force);
914            },
915            // focus <command>
916            focusItem: function (e) {
917                e.stopPropagation();
918                var $this = $(this),
919                    data = $this.data(),
920                    opt = data.contextMenu,
921                    root = data.contextMenuRoot;
922
923                if ($this.hasClass(root.classNames.disabled) || $this.hasClass(root.classNames.notSelectable)) {
924                    return;
925                }
926
927                $this
928                    .addClass([root.classNames.hover, root.classNames.visible].join(' '))
929                    // select other items and included items
930                    .parent().find('.context-menu-item').not($this)
931                    .removeClass(root.classNames.visible)
932                    .filter('.' + root.classNames.hover)
933                    .trigger('contextmenu:blur');
934
935                // remember selected
936                opt.$selected = root.$selected = $this;
937
938
939                if(opt && opt.$node && opt.$node.hasClass('context-menu-submenu')){
940                    opt.$node.addClass(root.classNames.hover);
941                }
942
943                // position sub-menu - do after show so dumb $.ui.position can keep up
944                if (opt.$node) {
945                    root.positionSubmenu.call(opt.$node, opt.$menu);
946                }
947            },
948            // blur <command>
949            blurItem: function (e) {
950                e.stopPropagation();
951                var $this = $(this),
952                    data = $this.data(),
953                    opt = data.contextMenu,
954                    root = data.contextMenuRoot;
955
956                if (opt.autoHide) { // for tablets and touch screens this needs to remain
957                    $this.removeClass(root.classNames.visible);
958                }
959                $this.removeClass(root.classNames.hover);
960                opt.$selected = null;
961            }
962        },
963        // operations
964        op = {
965            show: function (opt, x, y) {
966                var $trigger = $(this),
967                    css = {};
968
969                // hide any open menus
970                $('#context-menu-layer').trigger('mousedown');
971
972                // backreference for callbacks
973                opt.$trigger = $trigger;
974
975                // show event
976                if (opt.events.show.call($trigger, opt) === false) {
977                    $currentTrigger = null;
978                    return;
979                }
980
981                // create or update context menu
982                var hasVisibleItems = op.update.call($trigger, opt);
983                if (hasVisibleItems === false) {
984                    $currentTrigger = null;
985                    return;
986                }
987
988                // position menu
989                opt.position.call($trigger, opt, x, y);
990
991                // make sure we're in front
992                if (opt.zIndex) {
993                    var additionalZValue = opt.zIndex;
994                    // If opt.zIndex is a function, call the function to get the right zIndex.
995                    if (typeof opt.zIndex === 'function') {
996                        additionalZValue = opt.zIndex.call($trigger, opt);
997                    }
998                    css.zIndex = zindex($trigger) + additionalZValue;
999                }
1000
1001                // add layer
1002                op.layer.call(opt.$menu, opt, css.zIndex);
1003
1004                // adjust sub-menu zIndexes
1005                opt.$menu.find('ul').css('zIndex', css.zIndex + 1);
1006
1007                // position and show context menu
1008                opt.$menu.css(css)[opt.animation.show](opt.animation.duration, function () {
1009                    $trigger.trigger('contextmenu:visible');
1010
1011                    op.activated(opt);
1012                    opt.events.activated(opt);
1013                });
1014                // make options available and set state
1015                $trigger
1016                    .data('contextMenu', opt)
1017                    .addClass('context-menu-active');
1018
1019                // register key handler
1020                $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);
1021                // register autoHide handler
1022                if (opt.autoHide) {
1023                    // mouse position handler
1024                    $(document).on('mousemove.contextMenuAutoHide', function (e) {
1025                        // need to capture the offset on mousemove,
1026                        // since the page might've been scrolled since activation
1027                        var pos = $trigger.offset();
1028                        pos.right = pos.left + $trigger.outerWidth();
1029                        pos.bottom = pos.top + $trigger.outerHeight();
1030
1031                        if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {
1032                            /* Additional hover check after short time, you might just miss the edge of the menu */
1033                            setTimeout(function () {
1034                                if (!opt.hovering && opt.$menu !== null && typeof opt.$menu !== 'undefined') {
1035                                    opt.$menu.trigger('contextmenu:hide');
1036                                }
1037                            }, 50);
1038                        }
1039                    });
1040                }
1041            },
1042            hide: function (opt, force) {
1043                var $trigger = $(this);
1044                if (!opt) {
1045                    opt = $trigger.data('contextMenu') || {};
1046                }
1047
1048                // hide event
1049                if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) {
1050                    return;
1051                }
1052
1053                // remove options and revert state
1054                $trigger
1055                    .removeData('contextMenu')
1056                    .removeClass('context-menu-active');
1057
1058                if (opt.$layer) {
1059                    // keep layer for a bit so the contextmenu event can be aborted properly by opera
1060                    setTimeout((function ($layer) {
1061                        return function () {
1062                            $layer.remove();
1063                        };
1064                    })(opt.$layer), 10);
1065
1066                    try {
1067                        delete opt.$layer;
1068                    } catch (e) {
1069                        opt.$layer = null;
1070                    }
1071                }
1072
1073                // remove handle
1074                $currentTrigger = null;
1075                // remove selected
1076                opt.$menu.find('.' + opt.classNames.hover).trigger('contextmenu:blur');
1077                opt.$selected = null;
1078                // collapse all submenus
1079                opt.$menu.find('.' + opt.classNames.visible).removeClass(opt.classNames.visible);
1080                // unregister key and mouse handlers
1081                // $(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705
1082                $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');
1083                // hide menu
1084                if (opt.$menu) {
1085                    opt.$menu[opt.animation.hide](opt.animation.duration, function () {
1086                        // tear down dynamically built menu after animation is completed.
1087                        if (opt.build) {
1088                            opt.$menu.remove();
1089                            $.each(opt, function (key) {
1090                                switch (key) {
1091                                    case 'ns':
1092                                    case 'selector':
1093                                    case 'build':
1094                                    case 'trigger':
1095                                        return true;
1096
1097                                    default:
1098                                        opt[key] = undefined;
1099                                        try {
1100                                            delete opt[key];
1101                                        } catch (e) {
1102                                        }
1103                                        return true;
1104                                }
1105                            });
1106                        }
1107
1108                        setTimeout(function () {
1109                            $trigger.trigger('contextmenu:hidden');
1110                        }, 10);
1111                    });
1112                }
1113            },
1114            create: function (opt, root) {
1115                if (typeof root === 'undefined') {
1116                    root = opt;
1117                }
1118
1119                // create contextMenu
1120                opt.$menu = $('<ul class="context-menu-list"></ul>').addClass(opt.className || '').data({
1121                    'contextMenu': opt,
1122                    'contextMenuRoot': root
1123                });
1124                if(opt.dataAttr){
1125                    $.each(opt.dataAttr, function (key, item) {
1126                        opt.$menu.attr('data-' + opt.key, item);
1127                    });
1128                }
1129
1130                $.each(['callbacks', 'commands', 'inputs'], function (i, k) {
1131                    opt[k] = {};
1132                    if (!root[k]) {
1133                        root[k] = {};
1134                    }
1135                });
1136
1137                if (!root.accesskeys) {
1138                    root.accesskeys = {};
1139                }
1140
1141                function createNameNode(item) {
1142                    var $name = $('<span></span>');
1143                    if (item._accesskey) {
1144                        if (item._beforeAccesskey) {
1145                            $name.append(document.createTextNode(item._beforeAccesskey));
1146                        }
1147                        $('<span></span>')
1148                            .addClass('context-menu-accesskey')
1149                            .text(item._accesskey)
1150                            .appendTo($name);
1151                        if (item._afterAccesskey) {
1152                            $name.append(document.createTextNode(item._afterAccesskey));
1153                        }
1154                    } else {
1155                        if (item.isHtmlName) {
1156                            // restrict use with access keys
1157                            if (typeof item.accesskey !== 'undefined') {
1158                                throw new Error('accesskeys are not compatible with HTML names and cannot be used together in the same item');
1159                            }
1160                            $name.html(item.name);
1161                        } else {
1162                            $name.text(item.name);
1163                        }
1164                    }
1165                    return $name;
1166                }
1167
1168                // create contextMenu items
1169                $.each(opt.items, function (key, item) {
1170                    var $t = $('<li class="context-menu-item"></li>').addClass(item.className || ''),
1171                        $label = null,
1172                        $input = null;
1173
1174                    // iOS needs to see a click-event bound to an element to actually
1175                    // have the TouchEvents infrastructure trigger the click event
1176                    $t.on('click', $.noop);
1177
1178                    // Make old school string seperator a real item so checks wont be
1179                    // akward later.
1180                    // And normalize 'cm_separator' into 'cm_seperator'.
1181                    if (typeof item === 'string' || item.type === 'cm_separator') {
1182                        item = {type: 'cm_seperator'};
1183                    }
1184
1185                    item.$node = $t.data({
1186                        'contextMenu': opt,
1187                        'contextMenuRoot': root,
1188                        'contextMenuKey': key
1189                    });
1190
1191                    // register accesskey
1192                    // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that
1193                    if (typeof item.accesskey !== 'undefined') {
1194                        var aks = splitAccesskey(item.accesskey);
1195                        for (var i = 0, ak; ak = aks[i]; i++) {
1196                            if (!root.accesskeys[ak]) {
1197                                root.accesskeys[ak] = item;
1198                                var matched = item.name.match(new RegExp('^(.*?)(' + ak + ')(.*)$', 'i'));
1199                                if (matched) {
1200                                    item._beforeAccesskey = matched[1];
1201                                    item._accesskey = matched[2];
1202                                    item._afterAccesskey = matched[3];
1203                                }
1204                                break;
1205                            }
1206                        }
1207                    }
1208
1209                    if (item.type && types[item.type]) {
1210                        // run custom type handler
1211                        types[item.type].call($t, item, opt, root);
1212                        // register commands
1213                        $.each([opt, root], function (i, k) {
1214                            k.commands[key] = item;
1215                            // Overwrite only if undefined or the item is appended to the root. This so it
1216                            // doesn't overwrite callbacks of root elements if the name is the same.
1217                            if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) {
1218                                k.callbacks[key] = item.callback;
1219                            }
1220                        });
1221                    } else {
1222                        // add label for input
1223                        if (item.type === 'cm_seperator') {
1224                            $t.addClass('context-menu-separator ' + root.classNames.notSelectable);
1225                        } else if (item.type === 'html') {
1226                            $t.addClass('context-menu-html ' + root.classNames.notSelectable);
1227                        } else if (item.type !== 'sub' && item.type) {
1228                            $label = $('<label></label>').appendTo($t);
1229                            createNameNode(item).appendTo($label);
1230
1231                            $t.addClass('context-menu-input');
1232                            opt.hasTypes = true;
1233                            $.each([opt, root], function (i, k) {
1234                                k.commands[key] = item;
1235                                k.inputs[key] = item;
1236                            });
1237                        } else if (item.items) {
1238                            item.type = 'sub';
1239                        }
1240
1241                        switch (item.type) {
1242                            case 'cm_seperator':
1243                                break;
1244
1245                            case 'text':
1246                                $input = $('<input type="text" value="1" name="" />')
1247                                    .attr('name', 'context-menu-input-' + key)
1248                                    .val(item.value || '')
1249                                    .appendTo($label);
1250                                break;
1251
1252                            case 'textarea':
1253                                $input = $('<textarea name=""></textarea>')
1254                                    .attr('name', 'context-menu-input-' + key)
1255                                    .val(item.value || '')
1256                                    .appendTo($label);
1257
1258                                if (item.height) {
1259                                    $input.height(item.height);
1260                                }
1261                                break;
1262
1263                            case 'checkbox':
1264                                $input = $('<input type="checkbox" value="1" name="" />')
1265                                    .attr('name', 'context-menu-input-' + key)
1266                                    .val(item.value || '')
1267                                    .prop('checked', !!item.selected)
1268                                    .prependTo($label);
1269                                break;
1270
1271                            case 'radio':
1272                                $input = $('<input type="radio" value="1" name="" />')
1273                                    .attr('name', 'context-menu-input-' + item.radio)
1274                                    .val(item.value || '')
1275                                    .prop('checked', !!item.selected)
1276                                    .prependTo($label);
1277                                break;
1278
1279                            case 'select':
1280                                $input = $('<select name=""></select>')
1281                                    .attr('name', 'context-menu-input-' + key)
1282                                    .appendTo($label);
1283                                if (item.options) {
1284                                    $.each(item.options, function (value, text) {
1285                                        $('<option></option>').val(value).text(text).appendTo($input);
1286                                    });
1287                                    $input.val(item.selected);
1288                                }
1289                                break;
1290
1291                            case 'sub':
1292                                createNameNode(item).appendTo($t);
1293                                item.appendTo = item.$node;
1294                                $t.data('contextMenu', item).addClass('context-menu-submenu');
1295                                item.callback = null;
1296
1297                                // If item contains items, and this is a promise, we should create it later
1298                                // check if subitems is of type promise. If it is a promise we need to create
1299                                // it later, after promise has been resolved.
1300                                if ('function' === typeof item.items.then) {
1301                                    // probably a promise, process it, when completed it will create the sub menu's.
1302                                    op.processPromises(item, root, item.items);
1303                                } else {
1304                                    // normal submenu.
1305                                    op.create(item, root);
1306                                }
1307                                break;
1308
1309                            case 'html':
1310                                $(item.html).appendTo($t);
1311                                break;
1312
1313                            default:
1314                                $.each([opt, root], function (i, k) {
1315                                    k.commands[key] = item;
1316                                    // Overwrite only if undefined or the item is appended to the root. This so it
1317                                    // doesn't overwrite callbacks of root elements if the name is the same.
1318                                    if ($.isFunction(item.callback) && (typeof k.callbacks[key] === 'undefined' || typeof opt.type === 'undefined')) {
1319                                        k.callbacks[key] = item.callback;
1320                                    }
1321                                });
1322                                createNameNode(item).appendTo($t);
1323                                break;
1324                        }
1325
1326                        // disable key listener in <input>
1327                        if (item.type && item.type !== 'sub' && item.type !== 'html' && item.type !== 'cm_seperator') {
1328                            $input
1329                                .on('focus', handle.focusInput)
1330                                .on('blur', handle.blurInput);
1331
1332                            if (item.events) {
1333                                $input.on(item.events, opt);
1334                            }
1335                        }
1336
1337                        // add icons
1338                        if (item.icon) {
1339                            if ($.isFunction(item.icon)) {
1340                                item._icon = item.icon.call(this, this, $t, key, item);
1341                            } else {
1342                                if (typeof(item.icon) === 'string' && (
1343                                    item.icon.substring(0, 4) === 'fab '
1344                                    || item.icon.substring(0, 4) === 'fas '
1345                                    || item.icon.substring(0, 4) === 'fad '
1346                                    || item.icon.substring(0, 4) === 'far '
1347                                    || item.icon.substring(0, 4) === 'fal ')
1348                                ) {
1349                                    // to enable font awesome
1350                                    $t.addClass(root.classNames.icon + ' ' + root.classNames.icon + '--fa5');
1351                                    item._icon = $('<i class="' + item.icon + '"></i>');
1352                                } else if (typeof(item.icon) === 'string' && item.icon.substring(0, 3) === 'fa-') {
1353                                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '--fa fa ' + item.icon;
1354                                } else {
1355                                    item._icon = root.classNames.icon + ' ' + root.classNames.icon + '-' + item.icon;
1356                                }
1357                            }
1358
1359                            if(typeof(item._icon) === "string"){
1360                                $t.addClass(item._icon);
1361                            } else {
1362                                $t.prepend(item._icon);
1363                            }
1364                        }
1365                    }
1366
1367                    // cache contained elements
1368                    item.$input = $input;
1369                    item.$label = $label;
1370
1371                    // attach item to menu
1372                    $t.appendTo(opt.$menu);
1373
1374                    // Disable text selection
1375                    if (!opt.hasTypes && $.support.eventSelectstart) {
1376                        // browsers support user-select: none,
1377                        // IE has a special event for text-selection
1378                        // browsers supporting neither will not be preventing text-selection
1379                        $t.on('selectstart.disableTextSelect', handle.abortevent);
1380                    }
1381                });
1382                // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)
1383                if (!opt.$node) {
1384                    opt.$menu.css('display', 'none').addClass('context-menu-root');
1385                }
1386                opt.$menu.appendTo(opt.appendTo || document.body);
1387            },
1388            resize: function ($menu, nested) {
1389                var domMenu;
1390                // determine widths of submenus, as CSS won't grow them automatically
1391                // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100;
1392                // kinda sucks hard...
1393
1394                // determine width of absolutely positioned element
1395                $menu.css({position: 'absolute', display: 'block'});
1396                // don't apply yet, because that would break nested elements' widths
1397                $menu.data('width',
1398                    (domMenu = $menu.get(0)).getBoundingClientRect ?
1399                        Math.ceil(domMenu.getBoundingClientRect().width) :
1400                        $menu.outerWidth() + 1); // outerWidth() returns rounded pixels
1401                // reset styles so they allow nested elements to grow/shrink naturally
1402                $menu.css({
1403                    position: 'static',
1404                    minWidth: '0px',
1405                    maxWidth: '100000px'
1406                });
1407                // identify width of nested menus
1408                $menu.find('> li > ul').each(function () {
1409                    op.resize($(this), true);
1410                });
1411                // reset and apply changes in the end because nested
1412                // elements' widths wouldn't be calculatable otherwise
1413                if (!nested) {
1414                    $menu.find('ul').addBack().css({
1415                        position: '',
1416                        display: '',
1417                        minWidth: '',
1418                        maxWidth: ''
1419                    }).outerWidth(function () {
1420                        return $(this).data('width');
1421                    });
1422                }
1423            },
1424            update: function (opt, root) {
1425                var $trigger = this;
1426                if (typeof root === 'undefined') {
1427                    root = opt;
1428                    op.resize(opt.$menu);
1429                }
1430
1431                var hasVisibleItems = false;
1432
1433                // re-check disabled for each item
1434                opt.$menu.children().each(function () {
1435                    var $item = $(this),
1436                        key = $item.data('contextMenuKey'),
1437                        item = opt.items[key],
1438                        disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true,
1439                        visible;
1440                    if ($.isFunction(item.visible)) {
1441                        visible = item.visible.call($trigger, key, root);
1442                    } else if (typeof item.visible !== 'undefined') {
1443                        visible = item.visible === true;
1444                    } else {
1445                        visible = true;
1446                    }
1447
1448                    if (visible) {
1449                        hasVisibleItems = true;
1450                    }
1451
1452                    $item[visible ? 'show' : 'hide']();
1453
1454                    // dis- / enable item
1455                    $item[disabled ? 'addClass' : 'removeClass'](root.classNames.disabled);
1456
1457                    if ($.isFunction(item.icon)) {
1458                        $item.removeClass(item._icon);
1459                        var iconResult = item.icon.call(this, $trigger, $item, key, item);
1460                        if(typeof(iconResult) === "string"){
1461                            $item.addClass(iconResult);
1462                        } else {
1463                            $item.prepend(iconResult);
1464                        }
1465                    }
1466
1467                    if (item.type) {
1468                        // dis- / enable input elements
1469                        $item.find('input, select, textarea').prop('disabled', disabled);
1470
1471                        // update input states
1472                        switch (item.type) {
1473                            case 'text':
1474                            case 'textarea':
1475                                item.$input.val(item.value || '');
1476                                break;
1477
1478                            case 'checkbox':
1479                            case 'radio':
1480                                item.$input.val(item.value || '').prop('checked', !!item.selected);
1481                                break;
1482
1483                            case 'select':
1484                                item.$input.val((item.selected === 0 ? "0" : item.selected) || '');
1485                                break;
1486                        }
1487                    }
1488
1489                    if (item.$menu) {
1490                        // update sub-menu
1491                        var subMenuHasVisibleItems = op.update.call($trigger, item, root);
1492                        if (subMenuHasVisibleItems) {
1493                            hasVisibleItems = true;
1494                        }
1495                    }
1496                });
1497                return hasVisibleItems;
1498            },
1499            layer: function (opt, zIndex) {
1500                // add transparent layer for click area
1501                // filter and background for Internet Explorer, Issue #23
1502                var $layer = opt.$layer = $('<div id="context-menu-layer"></div>')
1503                    .css({
1504                        height: $win.height(),
1505                        width: $win.width(),
1506                        display: 'block',
1507                        position: 'fixed',
1508                        'z-index': zIndex,
1509                        top: 0,
1510                        left: 0,
1511                        opacity: 0,
1512                        filter: 'alpha(opacity=0)',
1513                        'background-color': '#000'
1514                    })
1515                    .data('contextMenuRoot', opt)
1516                    .insertBefore(this)
1517                    .on('contextmenu', handle.abortevent)
1518                    .on('mousedown', handle.layerClick);
1519
1520                // IE6 doesn't know position:fixed;
1521                if (typeof document.body.style.maxWidth === 'undefined') { // IE6 doesn't support maxWidth
1522                    $layer.css({
1523                        'position': 'absolute',
1524                        'height': $(document).height()
1525                    });
1526                }
1527
1528                return $layer;
1529            },
1530            processPromises: function (opt, root, promise) {
1531                // Start
1532                opt.$node.addClass(root.classNames.iconLoadingClass);
1533
1534                function completedPromise(opt, root, items) {
1535                    // Completed promise (dev called promise.resolve). We now have a list of items which can
1536                    // be used to create the rest of the context menu.
1537                    if (typeof items === 'undefined') {
1538                        // Null result, dev should have checked
1539                        errorPromise(undefined);//own error object
1540                    }
1541                    finishPromiseProcess(opt, root, items);
1542                }
1543
1544                function errorPromise(opt, root, errorItem) {
1545                    // User called promise.reject() with an error item, if not, provide own error item.
1546                    if (typeof errorItem === 'undefined') {
1547                        errorItem = {
1548                            "error": {
1549                                name: "No items and no error item",
1550                                icon: "context-menu-icon context-menu-icon-quit"
1551                            }
1552                        };
1553                        if (window.console) {
1554                            (console.error || console.log).call(console, 'When you reject a promise, provide an "items" object, equal to normal sub-menu items');
1555                        }
1556                    } else if (typeof errorItem === 'string') {
1557                        errorItem = {"error": {name: errorItem}};
1558                    }
1559                    finishPromiseProcess(opt, root, errorItem);
1560                }
1561
1562                function finishPromiseProcess(opt, root, items) {
1563                    if (typeof root.$menu === 'undefined' || !root.$menu.is(':visible')) {
1564                        return;
1565                    }
1566                    opt.$node.removeClass(root.classNames.iconLoadingClass);
1567                    opt.items = items;
1568                    op.create(opt, root, true); // Create submenu
1569                    op.update(opt, root); // Correctly update position if user is already hovered over menu item
1570                    root.positionSubmenu.call(opt.$node, opt.$menu); // positionSubmenu, will only do anything if user already hovered over menu item that just got new subitems.
1571                }
1572
1573                // Wait for promise completion. .then(success, error, notify) (we don't track notify). Bind the opt
1574                // and root to avoid scope problems
1575                promise.then(completedPromise.bind(this, opt, root), errorPromise.bind(this, opt, root));
1576            },
1577            // operation that will run after contextMenu showed on screen
1578            activated: function(opt){
1579                var $menu = opt.$menu;
1580                var $menuOffset = $menu.offset();
1581                var winHeight = $(window).height();
1582                var winScrollTop = $(window).scrollTop();
1583                var menuHeight = $menu.height();
1584                if(menuHeight > winHeight){
1585                    $menu.css({
1586                        'height' : winHeight + 'px',
1587                        'overflow-x': 'hidden',
1588                        'overflow-y': 'auto',
1589                        'top': winScrollTop + 'px'
1590                    });
1591                } else if(($menuOffset.top < winScrollTop) || ($menuOffset.top + menuHeight > winScrollTop + winHeight)){
1592                    $menu.css({
1593                        'top': winScrollTop + 'px'
1594                    });
1595                }
1596            }
1597        };
1598
1599    // split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key
1600    function splitAccesskey(val) {
1601        var t = val.split(/\s+/);
1602        var keys = [];
1603
1604        for (var i = 0, k; k = t[i]; i++) {
1605            k = k.charAt(0).toUpperCase();
1605 // first character only
1606            // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.
1607            // a map to look up already used access keys would be nice
1608            keys.push(k);
1609        }
1610
1611        return keys;
1612    }
1613
1614// handle contextMenu triggers
1615    $.fn.contextMenu = function (operation) {
1616        var $t = this, $o = operation;
1617        if (this.length > 0) {  // this is not a build on demand menu
1618            if (typeof operation === 'undefined') {
1619                this.first().trigger('contextmenu');
1620            } else if (typeof operation.x !== 'undefined' && typeof operation.y !== 'undefined') {
1621                this.first().trigger($.Event('contextmenu', {
1622                    pageX: operation.x,
1623                    pageY: operation.y,
1624                    mouseButton: operation.button
1625                }));
1626            } else if (operation === 'hide') {
1627                var $menu = this.first().data('contextMenu') ? this.first().data('contextMenu').$menu : null;
1628                if ($menu) {
1629                    $menu.trigger('contextmenu:hide');
1630                }
1631            } else if (operation === 'destroy') {
1632                $.contextMenu('destroy', {context: this});
1633            } else if ($.isPlainObject(operation)) {
1634                operation.context = this;
1635                $.contextMenu('create', operation);
1636            } else if (operation) {
1637                this.removeClass('context-menu-disabled');
1638            } else if (!operation) {
1639                this.addClass('context-menu-disabled');
1640            }
1641        } else {
1642            $.each(menus, function () {
1643                if (this.selector === $t.selector) {
1644                    $o.data = this;
1645
1646                    $.extend($o.data, {trigger: 'demand'});
1647                }
1648            });
1649
1650            handle.contextmenu.call($o.target, $o);
1651        }
1652
1653        return this;
1654    };
1655
1656    // manage contextMenu instances
1657    $.contextMenu = function (operation, options) {
1658        if (typeof operation !== 'string') {
1659            options = operation;
1660            operation = 'create';
1661        }
1662
1663        if (typeof options === 'string') {
1664            options = {selector: options};
1665        } else if (typeof options === 'undefined') {
1666            options = {};
1667        }
1668
1669        // merge with default options
1670        var o = $.extend(true, {}, defaults, options || {});
1671        var $document = $(document);
1672        var $context = $document;
1673        var _hasContext = false;
1674
1675        if (!o.context || !o.context.length) {
1676            o.context = document;
1677        } else {
1678            // you never know what they throw at you...
1679            $context = $(o.context).first();
1680            o.context = $context.get(0);
1681            _hasContext = !$(o.context).is(document);
1682        }
1683
1684        switch (operation) {
1685
1686            case 'update':
1687                // Updates visibility and such
1688                if(_hasContext){
1689                    op.update($context);
1690                } else {
1691                    for(var menu in menus){
1692                        if(menus.hasOwnProperty(menu)){
1693                            op.update(menus[menu]);
1694                        }
1695                    }
1696                }
1697                break;
1698
1699            case 'create':
1700                // no selector no joy
1701                if (!o.selector) {
1702                    throw new Error('No selector specified');
1703                }
1704                // make sure internal classes are not bound to
1705                if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {
1706                    throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');
1707                }
1708                if (!o.build && (!o.items || $.isEmptyObject(o.items))) {
1709                    throw new Error('No Items specified');
1710                }
1711                counter++;
1712                o.ns = '.contextMenu' + counter;
1713                if (!_hasContext) {
1714                    namespaces[o.selector] = o.ns;
1715                }
1716                menus[o.ns] = o;
1717
1718                // default to right click
1719                if (!o.trigger) {
1720                    o.trigger = 'right';
1721                }
1722
1723                if (!initialized) {
1724                    var itemClick = o.itemClickEvent === 'click' ? 'click.contextMenu' : 'mouseup.contextMenu';
1725                    var contextMenuItemObj = {
1726                        // 'mouseup.contextMenu': handle.itemClick,
1727                        // 'click.contextMenu': handle.itemClick,
1728                        'contextmenu:focus.contextMenu': handle.focusItem,
1729                        'contextmenu:blur.contextMenu': handle.blurItem,
1730                        'contextmenu.contextMenu': handle.abortevent,
1731                        'mouseenter.contextMenu': handle.itemMouseenter,
1732                        'mouseleave.contextMenu': handle.itemMouseleave
1733                    };
1734                    contextMenuItemObj[itemClick] = handle.itemClick;
1735                    // make sure item click is registered first
1736                    $document
1737                        .on({
1738                            'contextmenu:hide.contextMenu': handle.hideMenu,
1739                            'prevcommand.contextMenu': handle.prevItem,
1740                            'nextcommand.contextMenu': handle.nextItem,
1741                            'contextmenu.contextMenu': handle.abortevent,
1742                            'mouseenter.contextMenu': handle.menuMouseenter,
1743                            'mouseleave.contextMenu': handle.menuMouseleave
1744                        }, '.context-menu-list')
1745                        .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)
1746                        .on(contextMenuItemObj, '.context-menu-item');
1747
1748                    initialized = true;
1749                }
1750
1751                // engage native contextmenu event
1752                $context
1753                    .on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);
1754
1755                if (_hasContext) {
1756                    // add remove hook, just in case
1757                    $context.on('remove' + o.ns, function () {
1758                        $(this).contextMenu('destroy');
1759                    });
1760                }
1761
1762                switch (o.trigger) {
1763                    case 'hover':
1764                        $context
1765                            .on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)
1766                            .on('mouseleave' + o.ns, o.selector, o, handle.mouseleave);
1767                        break;
1768
1769                    case 'left':
1770                        $context.on('click' + o.ns, o.selector, o, handle.click);
1771                        break;
1772				    case 'touchstart':
1773                        $context.on('touchstart' + o.ns, o.selector, o, handle.click);
1774                        break;
1775                    /*
1776                     default:
1777                     // http://www.quirksmode.org/dom/events/contextmenu.html
1778                     $document
1779                     .on('mousedown' + o.ns, o.selector, o, handle.mousedown)
1780                     .on('mouseup' + o.ns, o.selector, o, handle.mouseup);
1781                     break;
1782                     */
1783                }
1784
1785                // create menu
1786                if (!o.build) {
1787                    op.create(o);
1788                }
1789                break;
1790
1791            case 'destroy':
1792                var $visibleMenu;
1793                if (_hasContext) {
1794                    // get proper options
1795                    var context = o.context;
1796                    $.each(menus, function (ns, o) {
1797
1798                        if (!o) {
1799                            return true;
1800                        }
1801
1802                        // Is this menu equest to the context called from
1803                        if (!$(context).is(o.selector)) {
1804                            return true;
1805                        }
1806
1807                        $visibleMenu = $('.context-menu-list').filter(':visible');
1808                        if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) {
1809                            $visibleMenu.trigger('contextmenu:hide', {force: true});
1810                        }
1811
1812                        try {
1813                            if (menus[o.ns].$menu) {
1814                                menus[o.ns].$menu.remove();
1815                            }
1816
1817                            delete menus[o.ns];
1818                        } catch (e) {
1819                            menus[o.ns] = null;
1820                        }
1821
1822                        $(o.context).off(o.ns);
1823
1824                        return true;
1825                    });
1826                } else if (!o.selector) {
1827                    $document.off('.contextMenu .contextMenuAutoHide');
1828                    $.each(menus, function (ns, o) {
1829                        $(o.context).off(o.ns);
1830                    });
1831
1832                    namespaces = {};
1833                    menus = {};
1834                    counter = 0;
1835                    initialized = false;
1836
1837                    $('#context-menu-layer, .context-menu-list').remove();
1838                } else if (namespaces[o.selector]) {
1839                    $visibleMenu = $('.context-menu-list').filter(':visible');
1840                    if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) {
1841                        $visibleMenu.trigger('contextmenu:hide', {force: true});
1842                    }
1843
1844                    try {
1845                        if (menus[namespaces[o.selector]].$menu) {
1846                            menus[namespaces[o.selector]].$menu.remove();
1847                        }
1848
1849                        delete menus[namespaces[o.selector]];
1850                    } catch (e) {
1851                        menus[namespaces[o.selector]] = null;
1852                    }
1853
1854                    $document.off(namespaces[o.selector]);
1855                }
1856                break;
1857
1858            case 'html5':
1859                // if <command> and <menuitem> are not handled by the browser,
1860                // or options was a bool true,
1861                // initialize $.contextMenu for them
1862                if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options === 'boolean' && options)) {
1863                    $('menu[type="context"]').each(function () {
1864                        if (this.id) {
1865                            $.contextMenu({
1866                                selector: '[contextmenu=' + this.id + ']',
1867                                items: $.contextMenu.fromMenu(this)
1868                            });
1869                        }
1870                    }).css('display', 'none');
1871                }
1872                break;
1873
1874            default:
1875                throw new Error('Unknown operation "' + operation + '"');
1876        }
1877
1878        return this;
1879    };
1880
1881// import values into <input> commands
1882    $.contextMenu.setInputValues = function (opt, data) {
1883        if (typeof data === 'undefined') {
1884            data = {};
1885        }
1886
1887        $.each(opt.inputs, function (key, item) {
1888            switch (item.type) {
1889                case 'text':
1890                case 'textarea':
1891                    item.value = data[key] || '';
1892                    break;
1893
1894                case 'checkbox':
1895                    item.selected = data[key] ? true : false;
1896                    break;
1897
1898                case 'radio':
1899                    item.selected = (data[item.radio] || '') === item.value;
1900                    break;
1901
1902                case 'select':
1903                    item.selected = data[key] || '';
1904                    break;
1905            }
1906        });
1907    };
1908
1909// export values from <input> commands
1910    $.contextMenu.getInputValues = function (opt, data) {
1911        if (typeof data === 'undefined') {
1912            data = {};
1913        }
1914
1915        $.each(opt.inputs, function (key, item) {
1916            switch (item.type) {
1917                case 'text':
1918                case 'textarea':
1919                case 'select':
1920                    data[key] = item.$input.val();
1921                    break;
1922
1923                case 'checkbox':
1924                    data[key] = item.$input.prop('checked');
1925                    break;
1926
1927                case 'radio':
1928                    if (item.$input.prop('checked')) {
1929                        data[item.radio] = item.value;
1930                    }
1931                    break;
1932            }
1933        });
1934
1935        return data;
1936    };
1937
1938// find <label for="xyz">
1939    function inputLabel(node) {
1940        return (node.id && $('label[for="' + node.id + '"]').val()) || node.name;
1941    }
1942
1943// convert <menu> to items object
1944    function menuChildren(items, $children, counter) {
1945        if (!counter) {
1946            counter = 0;
1947        }
1948
1949        $children.each(function () {
1950            var $node = $(this),
1951                node = this,
1952                nodeName = this.nodeName.toLowerCase(),
1953                label,
1954                item;
1955
1956            // extract <label><input>
1957            if (nodeName === 'label' && $node.find('input, textarea, select').length) {
1958                label = $node.text();
1959                $node = $node.children().first();
1960                node = $node.get(0);
1961                nodeName = node.nodeName.toLowerCase();
1962            }
1963
1964            /*
1965             * <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.
1966             * Not being the sadistic kind, $.contextMenu only accepts:
1967             * <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.
1968             * Everything else will be imported as an html node, which is not interfaced with contextMenu.
1969             */
1970
1971            // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command
1972            switch (nodeName) {
1973                // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element
1974                case 'menu':
1975                    item = {name: $node.attr('label'), items: {}};
1976                    counter = menuChildren(item.items, $node.children(), counter);
1977                    break;
1978
1979                // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command
1980                case 'a':
1981                // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command
1982                case 'button':
1983                    item = {
1984                        name: $node.text(),
1985                        disabled: !!$node.attr('disabled'),
1986                        callback: (function () {
1987                            return function () {
1988                                $node.get(0).click();
1989                            };
1990                        })()
1991                    };
1992                    break;
1993
1994                // http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command
1995                case 'menuitem':
1996                case 'command':
1997                    switch ($node.attr('type')) {
1998                        case undefined:
1999                        case 'command':
2000                        case 'menuitem':
2001                            item = {
2002                                name: $node.attr('label'),
2003                                disabled: !!$node.attr('disabled'),
2004                                icon: $node.attr('icon'),
2005                                callback: (function () {
2006                                    return function () {
2007                                        $node.get(0).click();
2008                                    };
2009                                })()
2010                            };
2011                            break;
2012
2013                        case 'checkbox':
2014                            item = {
2015                                type: 'checkbox',
2016                                disabled: !!$node.attr('disabled'),
2017                                name: $node.attr('label'),
2018                                selected: !!$node.attr('checked')
2019                            };
2020                            break;
2021                        case 'radio':
2022                            item = {
2023                                type: 'radio',
2024                                disabled: !!$node.attr('disabled'),
2025                                name: $node.attr('label'),
2026                                radio: $node.attr('radiogroup'),
2027                                value: $node.attr('id'),
2028                                selected: !!$node.attr('checked')
2029                            };
2030                            break;
2031
2032                        default:
2033                            item = undefined;
2034                    }
2035                    break;
2036
2037                case 'hr':
2038                    item = '-------';
2039                    break;
2040
2041                case 'input':
2042                    switch ($node.attr('type')) {
2043                        case 'text':
2044                            item = {
2045                                type: 'text',
2046                                name: label || inputLabel(node),
2047                                disabled: !!$node.attr('disabled'),
2048                                value: $node.val()
2049                            };
2050                            break;
2051
2052                        case 'checkbox':
2053                            item = {
2054                                type: 'checkbox',
2055                                name: label || inputLabel(node),
2056                                disabled: !!$node.attr('disabled'),
2057                                selected: !!$node.attr('checked')
2058                            };
2059                            break;
2060
2061                        case 'radio':
2062                            item = {
2063                                type: 'radio',
2064                                name: label || inputLabel(node),
2065                                disabled: !!$node.attr('disabled'),
2066                                radio: !!$node.attr('name'),
2067                                value: $node.val(),
2068                                selected: !!$node.attr('checked')
2069                            };
2070                            break;
2071
2072                        default:
2073                            item = undefined;
2074                            break;
2075                    }
2076                    break;
2077
2078                case 'select':
2079                    item = {
2080                        type: 'select',
2081                        name: label || inputLabel(node),
2082                        disabled: !!$node.attr('disabled'),
2083                        selected: $node.val(),
2084                        options: {}
2085                    };
2086                    $node.children().each(function () {
2087                        item.options[this.value] = $(this).text();
2088                    });
2089                    break;
2090
2091                case 'textarea':
2092                    item = {
2093                        type: 'textarea',
2094                        name: label || inputLabel(node),
2095                        disabled: !!$node.attr('disabled'),
2096                        value: $node.val()
2097                    };
2098                    break;
2099
2100                case 'label':
2101                    break;
2102
2103                default:
2104                    item = {type: 'html', html: $node.clone(true)};
2105                    break;
2106            }
2107
2108            if (item) {
2109                counter++;
2110                items['key' + counter] = item;
2111            }
2112        });
2113
2114        return counter;
2115    }
2116
2117// convert html5 menu
2118    $.contextMenu.fromMenu = function (element) {
2119        var $this = $(element),
2120            items = {};
2121
2122        menuChildren(items, $this.children());
2123
2124        return items;
2125    };
2126
2127// make defaults accessible
2128    $.contextMenu.defaults = defaults;
2129    $.contextMenu.types = types;
2130// export internal functions - undocumented, for hacking only!
2131    $.contextMenu.handle = handle;
2132    $.contextMenu.op = op;
2133    $.contextMenu.menus = menus;
2134});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.