Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 3dee3e56

Von Moritz Bunkus vor mehr als 11 Jahren hinzugefügt

  • ID 3dee3e56a61f3fdadb9a9608dbfb228a4c1a55ed
  • Vorgänger ecb7a87d
  • Nachfolger e6535205

Neues JS: jquery.contextMenu

Unterschiede anzeigen:

css/jquery.contextMenu.css
1
/*!
2
 * jQuery contextMenu - Plugin for simple contextMenu handling
3
 *
4
 * Version: 1.6.5
5
 *
6
 * Authors: Rodney Rehm, Addy Osmani (patches for FF)
7
 * Web: http://medialize.github.com/jQuery-contextMenu/
8
 *
9
 * Licensed under
10
 *   MIT License http://www.opensource.org/licenses/mit-license
11
 *   GPL v3 http://opensource.org/licenses/GPL-3.0
12
 *
13
 */
14

  
15
.context-menu-list {
16
    margin:0;
17
    padding:0;
18

  
19
    min-width: 120px;
20
    max-width: 250px;
21
    display: inline-block;
22
    position: absolute;
23
    list-style-type: none;
24

  
25
    border: 1px solid #DDD;
26
    background: #EEE;
27

  
28
    -webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
29
       -moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
30
        -ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
31
         -o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
32
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
33

  
34
    font-family: Verdana, Arial, Helvetica, sans-serif;
35
    font-size: 11px;
36
}
37

  
38
.context-menu-item {
39
    padding: 2px 2px 2px 24px;
40
    background-color: #EEE;
41
    position: relative;
42
    -webkit-user-select: none;
43
       -moz-user-select: -moz-none;
44
        -ms-user-select: none;
45
            user-select: none;
46
}
47

  
48
.context-menu-separator {
49
    padding-bottom:0;
50
    border-bottom: 1px solid #DDD;
51
}
52

  
53
.context-menu-item > label > input,
54
.context-menu-item > label > textarea {
55
    -webkit-user-select: text;
56
       -moz-user-select: text;
57
        -ms-user-select: text;
58
            user-select: text;
59
}
60

  
61
.context-menu-item.hover {
62
    cursor: pointer;
63
    background-color: #39F;
64
}
65

  
66
.context-menu-item.disabled {
67
    color: #666;
68
}
69

  
70
.context-menu-input.hover,
71
.context-menu-item.disabled.hover {
72
    cursor: default;
73
    background-color: #EEE;
74
}
75

  
76
.context-menu-submenu:after {
77
    content: ">";
78
    color: #666;
79
    position: absolute;
80
    top: 0;
81
    right: 3px;
82
    z-index: 1;
83
}
84

  
85
/* icons
86
    #protip:
87
    In case you want to use sprites for icons (which I would suggest you do) have a look at
88
    http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
89
    .context-menu-item.icon:before {}
90
 */
91
.context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; }
92
.context-menu-item.icon-edit { background-image: url(../image/jquery.contextMenu/page_white_edit.png); }
93
.context-menu-item.icon-cut { background-image: url(../image/jquery.contextMenu/cut.png); }
94
.context-menu-item.icon-copy { background-image: url(../image/jquery.contextMenu/page_white_copy.png); }
95
.context-menu-item.icon-paste { background-image: url(../image/jquery.contextMenu/page_white_paste.png); }
96
.context-menu-item.icon-delete { background-image: url(../image/jquery.contextMenu/page_white_delete.png); }
97
.context-menu-item.icon-add { background-image: url(../image/jquery.contextMenu/page_white_add.png); }
98
.context-menu-item.icon-quit { background-image: url(../image/jquery.contextMenu/door.png); }
99

  
100
/* vertically align inside labels */
101
.context-menu-input > label > * { vertical-align: top; }
102

  
103
/* position checkboxes and radios as icons */
104
.context-menu-input > label > input[type="checkbox"],
105
.context-menu-input > label > input[type="radio"] {
106
    margin-left: -17px;
107
}
108
.context-menu-input > label > span {
109
    margin-left: 5px;
110
}
111

  
112
.context-menu-input > label,
113
.context-menu-input > label > input[type="text"],
114
.context-menu-input > label > textarea,
115
.context-menu-input > label > select {
116
    display: block;
117
    width: 100%;
118

  
119
    -webkit-box-sizing: border-box;
120
       -moz-box-sizing: border-box;
121
        -ms-box-sizing: border-box;
122
         -o-box-sizing: border-box;
123
            box-sizing: border-box;
124
}
125

  
126
.context-menu-input > label > textarea {
127
    height: 100px;
128
}
129
.context-menu-item > .context-menu-list {
130
    display: none;
131
    /* re-positioned by js */
132
    right: -5px;
133
    top: 5px;
134
}
135

  
136
.context-menu-item.hover > .context-menu-list {
137
    display: block;
138
}
139

  
140
.context-menu-accesskey {
141
    text-decoration: underline;
142
}
js/jquery/jquery.contextMenu.js
1
/*!
2
 * jQuery contextMenu - Plugin for simple contextMenu handling
3
 *
4
 * Version: 1.6.5
5
 *
6
 * Authors: Rodney Rehm, Addy Osmani (patches for FF)
7
 * Web: http://medialize.github.com/jQuery-contextMenu/
8
 *
9
 * Licensed under
10
 *   MIT License http://www.opensource.org/licenses/mit-license
11
 *   GPL v3 http://opensource.org/licenses/GPL-3.0
12
 *
13
 */
14

  
15
(function($, undefined){
16
    
17
    // TODO: -
18
        // ARIA stuff: menuitem, menuitemcheckbox und menuitemradio
19
        // create <menu> structure if $.support[htmlCommand || htmlMenuitem] and !opt.disableNative
20

  
21
// determine html5 compatibility
22
$.support.htmlMenuitem = ('HTMLMenuItemElement' in window);
23
$.support.htmlCommand = ('HTMLCommandElement' in window);
24
$.support.eventSelectstart = ("onselectstart" in document.documentElement);
25
/* // should the need arise, test for css user-select
26
$.support.cssUserSelect = (function(){
27
    var t = false,
28
        e = document.createElement('div');
29
    
30
    $.each('Moz|Webkit|Khtml|O|ms|Icab|'.split('|'), function(i, prefix) {
31
        var propCC = prefix + (prefix ? 'U' : 'u') + 'serSelect',
32
            prop = (prefix ? ('-' + prefix.toLowerCase() + '-') : '') + 'user-select';
33
            
34
        e.style.cssText = prop + ': text;';
35
        if (e.style[propCC] == 'text') {
36
            t = true;
37
            return false;
38
        }
39
        
40
        return true;
41
    });
42
    
43
    return t;
44
})();
45
*/
46

  
47
if (!$.ui || !$.ui.widget) {
48
    // duck punch $.cleanData like jQueryUI does to get that remove event
49
    // https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.widget.js#L16-24
50
    var _cleanData = $.cleanData;
51
    $.cleanData = function( elems ) {
52
        for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
53
            try {
54
                $( elem ).triggerHandler( "remove" );
55
                // http://bugs.jquery.com/ticket/8235
56
            } catch( e ) {}
57
        }
58
        _cleanData( elems );
59
    };
60
}
61

  
62
var // currently active contextMenu trigger
63
    $currentTrigger = null,
64
    // is contextMenu initialized with at least one menu?
65
    initialized = false,
66
    // window handle
67
    $win = $(window),
68
    // number of registered menus
69
    counter = 0,
70
    // mapping selector to namespace
71
    namespaces = {},
72
    // mapping namespace to options
73
    menus = {},
74
    // custom command type handlers
75
    types = {},
76
    // default values
77
    defaults = {
78
        // selector of contextMenu trigger
79
        selector: null,
80
        // where to append the menu to
81
        appendTo: null,
82
        // method to trigger context menu ["right", "left", "hover"]
83
        trigger: "right",
84
        // hide menu when mouse leaves trigger / menu elements
85
        autoHide: false,
86
        // ms to wait before showing a hover-triggered context menu
87
        delay: 200,
88
        // flag denoting if a second trigger should simply move (true) or rebuild (false) an open menu
89
        // as long as the trigger happened on one of the trigger-element's child nodes
90
        reposition: true,
91
        // determine position to show menu at
92
        determinePosition: function($menu) {
93
            // position to the lower middle of the trigger element
94
            if ($.ui && $.ui.position) {
95
                // .position() is provided as a jQuery UI utility
96
                // (...and it won't work on hidden elements)
97
                $menu.css('display', 'block').position({
98
                    my: "center top",
99
                    at: "center bottom",
100
                    of: this,
101
                    offset: "0 5",
102
                    collision: "fit"
103
                }).css('display', 'none');
104
            } else {
105
                // determine contextMenu position
106
                var offset = this.offset();
107
                offset.top += this.outerHeight();
108
                offset.left += this.outerWidth() / 2 - $menu.outerWidth() / 2;
109
                $menu.css(offset);
110
            }
111
        },
112
        // position menu
113
        position: function(opt, x, y) {
114
            var $this = this,
115
                offset;
116
            // determine contextMenu position
117
            if (!x && !y) {
118
                opt.determinePosition.call(this, opt.$menu);
119
                return;
120
            } else if (x === "maintain" && y === "maintain") {
121
                // x and y must not be changed (after re-show on command click)
122
                offset = opt.$menu.position();
123
            } else {
124
                // x and y are given (by mouse event)
125
                offset = {top: y, left: x};
126
            }
127
            
128
            // correct offset if viewport demands it
129
            var bottom = $win.scrollTop() + $win.height(),
130
                right = $win.scrollLeft() + $win.width(),
131
                height = opt.$menu.height(),
132
                width = opt.$menu.width();
133
            
134
            if (offset.top + height > bottom) {
135
                offset.top -= height;
136
            }
137
            
138
            if (offset.left + width > right) {
139
                offset.left -= width;
140
            }
141
            
142
            opt.$menu.css(offset);
143
        },
144
        // position the sub-menu
145
        positionSubmenu: function($menu) {
146
            if ($.ui && $.ui.position) {
147
                // .position() is provided as a jQuery UI utility
148
                // (...and it won't work on hidden elements)
149
                $menu.css('display', 'block').position({
150
                    my: "left top",
151
                    at: "right top",
152
                    of: this,
153
                    collision: "flipfit fit"
154
                }).css('display', '');
155
            } else {
156
                // determine contextMenu position
157
                var offset = {
158
                    top: 0,
159
                    left: this.outerWidth()
160
                };
161
                $menu.css(offset);
162
            }
163
        },
164
        // offset to add to zIndex
165
        zIndex: 1,
166
        // show hide animation settings
167
        animation: {
168
            duration: 50,
169
            show: 'slideDown',
170
            hide: 'slideUp'
171
        },
172
        // events
173
        events: {
174
            show: $.noop,
175
            hide: $.noop
176
        },
177
        // default callback
178
        callback: null,
179
        // list of contextMenu items
180
        items: {}
181
    },
182
    // mouse position for hover activation
183
    hoveract = {
184
        timer: null,
185
        pageX: null,
186
        pageY: null
187
    },
188
    // determine zIndex
189
    zindex = function($t) {
190
        var zin = 0,
191
            $tt = $t;
192

  
193
        while (true) {
194
            zin = Math.max(zin, parseInt($tt.css('z-index'), 10) || 0);
195
            $tt = $tt.parent();
196
            if (!$tt || !$tt.length || "html body".indexOf($tt.prop('nodeName').toLowerCase()) > -1 ) {
197
                break;
198
            }
199
        }
200
        
201
        return zin;
202
    },
203
    // event handlers
204
    handle = {
205
        // abort anything
206
        abortevent: function(e){
207
            e.preventDefault();
208
            e.stopImmediatePropagation();
209
        },
210
        
211
        // contextmenu show dispatcher
212
        contextmenu: function(e) {
213
            var $this = $(this);
214
            
215
            // disable actual context-menu
216
            e.preventDefault();
217
            e.stopImmediatePropagation();
218
            
219
            // abort native-triggered events unless we're triggering on right click
220
            if (e.data.trigger != 'right' && e.originalEvent) {
221
                return;
222
            }
223
            
224
            // abort event if menu is visible for this trigger
225
            if ($this.hasClass('context-menu-active')) {
226
                return;
227
            }
228
            
229
            if (!$this.hasClass('context-menu-disabled')) {
230
                // theoretically need to fire a show event at <menu>
231
                // http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus
232
                // var evt = jQuery.Event("show", { data: data, pageX: e.pageX, pageY: e.pageY, relatedTarget: this });
233
                // e.data.$menu.trigger(evt);
234
                
235
                $currentTrigger = $this;
236
                if (e.data.build) {
237
                    var built = e.data.build($currentTrigger, e);
238
                    // abort if build() returned false
239
                    if (built === false) {
240
                        return;
241
                    }
242
                    
243
                    // dynamically build menu on invocation
244
                    e.data = $.extend(true, {}, defaults, e.data, built || {});
245

  
246
                    // abort if there are no items to display
247
                    if (!e.data.items || $.isEmptyObject(e.data.items)) {
248
                        // Note: jQuery captures and ignores errors from event handlers
249
                        if (window.console) {
250
                            (console.error || console.log)("No items specified to show in contextMenu");
251
                        }
252
                        
253
                        throw new Error('No Items sepcified');
254
                    }
255
                    
256
                    // backreference for custom command type creation
257
                    e.data.$trigger = $currentTrigger;
258
                    
259
                    op.create(e.data);
260
                }
261
                // show menu
262
                op.show.call($this, e.data, e.pageX, e.pageY);
263
            }
264
        },
265
        // contextMenu left-click trigger
266
        click: function(e) {
267
            e.preventDefault();
268
            e.stopImmediatePropagation();
269
            $(this).trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));
270
        },
271
        // contextMenu right-click trigger
272
        mousedown: function(e) {
273
            // register mouse down
274
            var $this = $(this);
275
            
276
            // hide any previous menus
277
            if ($currentTrigger && $currentTrigger.length && !$currentTrigger.is($this)) {
278
                $currentTrigger.data('contextMenu').$menu.trigger('contextmenu:hide');
279
            }
280
            
281
            // activate on right click
282
            if (e.button == 2) {
283
                $currentTrigger = $this.data('contextMenuActive', true);
284
            }
285
        },
286
        // contextMenu right-click trigger
287
        mouseup: function(e) {
288
            // show menu
289
            var $this = $(this);
290
            if ($this.data('contextMenuActive') && $currentTrigger && $currentTrigger.length && $currentTrigger.is($this) && !$this.hasClass('context-menu-disabled')) {
291
                e.preventDefault();
292
                e.stopImmediatePropagation();
293
                $currentTrigger = $this;
294
                $this.trigger($.Event("contextmenu", { data: e.data, pageX: e.pageX, pageY: e.pageY }));
295
            }
296
            
297
            $this.removeData('contextMenuActive');
298
        },
299
        // contextMenu hover trigger
300
        mouseenter: function(e) {
301
            var $this = $(this),
302
                $related = $(e.relatedTarget),
303
                $document = $(document);
304
            
305
            // abort if we're coming from a menu
306
            if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
307
                return;
308
            }
309
            
310
            // abort if a menu is shown
311
            if ($currentTrigger && $currentTrigger.length) {
312
                return;
313
            }
314
            
315
            hoveract.pageX = e.pageX;
316
            hoveract.pageY = e.pageY;
317
            hoveract.data = e.data;
318
            $document.on('mousemove.contextMenuShow', handle.mousemove);
319
            hoveract.timer = setTimeout(function() {
320
                hoveract.timer = null;
321
                $document.off('mousemove.contextMenuShow');
322
                $currentTrigger = $this;
323
                $this.trigger($.Event("contextmenu", { data: hoveract.data, pageX: hoveract.pageX, pageY: hoveract.pageY }));
324
            }, e.data.delay );
325
        },
326
        // contextMenu hover trigger
327
        mousemove: function(e) {
328
            hoveract.pageX = e.pageX;
329
            hoveract.pageY = e.pageY;
330
        },
331
        // contextMenu hover trigger
332
        mouseleave: function(e) {
333
            // abort if we're leaving for a menu
334
            var $related = $(e.relatedTarget);
335
            if ($related.is('.context-menu-list') || $related.closest('.context-menu-list').length) {
336
                return;
337
            }
338
            
339
            try {
340
                clearTimeout(hoveract.timer);
341
            } catch(e) {}
342
            
343
            hoveract.timer = null;
344
        },
345
        
346
        // click on layer to hide contextMenu
347
        layerClick: function(e) {
348
            var $this = $(this),
349
                root = $this.data('contextMenuRoot'),
350
                mouseup = false,
351
                button = e.button,
352
                x = e.pageX,
353
                y = e.pageY,
354
                target, 
355
                offset,
356
                selectors;
357
                
358
            e.preventDefault();
359
            e.stopImmediatePropagation();
360
            
361
            setTimeout(function() {
362
                var $window, hideshow, possibleTarget;
363
                var triggerAction = ((root.trigger == 'left' && button === 0) || (root.trigger == 'right' && button === 2));
364
                
365
                // find the element that would've been clicked, wasn't the layer in the way
366
                if (document.elementFromPoint) {
367
                    root.$layer.hide();
368
                    target = document.elementFromPoint(x - $win.scrollLeft(), y - $win.scrollTop());
369
                    root.$layer.show();
370
                }
371
                
372
                if (root.reposition && triggerAction) {
373
                    if (document.elementFromPoint) {
374
                        if (root.$trigger.is(target) || root.$trigger.has(target).length) {
375
                            root.position.call(root.$trigger, root, x, y);
376
                            return;
377
                        }
378
                    } else {
379
                        offset = root.$trigger.offset();
380
                        $window = $(window);
381
                        // while this looks kinda awful, it's the best way to avoid
382
                        // unnecessarily calculating any positions
383
                        offset.top += $window.scrollTop();
384
                        if (offset.top <= e.pageY) {
385
                            offset.left += $window.scrollLeft();
386
                            if (offset.left <= e.pageX) {
387
                                offset.bottom = offset.top + root.$trigger.outerHeight();
388
                                if (offset.bottom >= e.pageY) {
389
                                    offset.right = offset.left + root.$trigger.outerWidth();
390
                                    if (offset.right >= e.pageX) {
391
                                        // reposition
392
                                        root.position.call(root.$trigger, root, x, y);
393
                                        return;
394
                                    }
395
                                }
396
                            }
397
                        }
398
                    }
399
                }
400
                
401
                if (target && triggerAction) {
402
                    root.$trigger.one('contextmenu:hidden', function() {
403
                        $(target).contextMenu({x: x, y: y});
404
                    });
405
                }
406

  
407
                root.$menu.trigger('contextmenu:hide');
408
            }, 50);
409
        },
410
        // key handled :hover
411
        keyStop: function(e, opt) {
412
            if (!opt.isInput) {
413
                e.preventDefault();
414
            }
415
            
416
            e.stopPropagation();
417
        },
418
        key: function(e) {
419
            var opt = $currentTrigger.data('contextMenu') || {};
420

  
421
            switch (e.keyCode) {
422
                case 9:
423
                case 38: // up
424
                    handle.keyStop(e, opt);
425
                    // if keyCode is [38 (up)] or [9 (tab) with shift]
426
                    if (opt.isInput) {
427
                        if (e.keyCode == 9 && e.shiftKey) {
428
                            e.preventDefault();
429
                            opt.$selected && opt.$selected.find('input, textarea, select').blur();
430
                            opt.$menu.trigger('prevcommand');
431
                            return;
432
                        } else if (e.keyCode == 38 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {
433
                            // checkboxes don't capture this key
434
                            e.preventDefault();
435
                            return;
436
                        }
437
                    } else if (e.keyCode != 9 || e.shiftKey) {
438
                        opt.$menu.trigger('prevcommand');
439
                        return;
440
                    }
441
                    // omitting break;
442
                    
443
                // case 9: // tab - reached through omitted break;
444
                case 40: // down
445
                    handle.keyStop(e, opt);
446
                    if (opt.isInput) {
447
                        if (e.keyCode == 9) {
448
                            e.preventDefault();
449
                            opt.$selected && opt.$selected.find('input, textarea, select').blur();
450
                            opt.$menu.trigger('nextcommand');
451
                            return;
452
                        } else if (e.keyCode == 40 && opt.$selected.find('input, textarea, select').prop('type') == 'checkbox') {
453
                            // checkboxes don't capture this key
454
                            e.preventDefault();
455
                            return;
456
                        }
457
                    } else {
458
                        opt.$menu.trigger('nextcommand');
459
                        return;
460
                    }
461
                    break;
462
                
463
                case 37: // left
464
                    handle.keyStop(e, opt);
465
                    if (opt.isInput || !opt.$selected || !opt.$selected.length) {
466
                        break;
467
                    }
468
                
469
                    if (!opt.$selected.parent().hasClass('context-menu-root')) {
470
                        var $parent = opt.$selected.parent().parent();
471
                        opt.$selected.trigger('contextmenu:blur');
472
                        opt.$selected = $parent;
473
                        return;
474
                    }
475
                    break;
476
                    
477
                case 39: // right
478
                    handle.keyStop(e, opt);
479
                    if (opt.isInput || !opt.$selected || !opt.$selected.length) {
480
                        break;
481
                    }
482
                    
483
                    var itemdata = opt.$selected.data('contextMenu') || {};
484
                    if (itemdata.$menu && opt.$selected.hasClass('context-menu-submenu')) {
485
                        opt.$selected = null;
486
                        itemdata.$selected = null;
487
                        itemdata.$menu.trigger('nextcommand');
488
                        return;
489
                    }
490
                    break;
491
                
492
                case 35: // end
493
                case 36: // home
494
                    if (opt.$selected && opt.$selected.find('input, textarea, select').length) {
495
                        return;
496
                    } else {
497
                        (opt.$selected && opt.$selected.parent() || opt.$menu)
498
                            .children(':not(.disabled, .not-selectable)')[e.keyCode == 36 ? 'first' : 'last']()
499
                            .trigger('contextmenu:focus');
500
                        e.preventDefault();
501
                        return;
502
                    }
503
                    break;
504
                    
505
                case 13: // enter
506
                    handle.keyStop(e, opt);
507
                    if (opt.isInput) {
508
                        if (opt.$selected && !opt.$selected.is('textarea, select')) {
509
                            e.preventDefault();
510
                            return;
511
                        }
512
                        break;
513
                    }
514
                    opt.$selected && opt.$selected.trigger('mouseup');
515
                    return;
516
                    
517
                case 32: // space
518
                case 33: // page up
519
                case 34: // page down
520
                    // prevent browser from scrolling down while menu is visible
521
                    handle.keyStop(e, opt);
522
                    return;
523
                    
524
                case 27: // esc
525
                    handle.keyStop(e, opt);
526
                    opt.$menu.trigger('contextmenu:hide');
527
                    return;
528
                    
529
                default: // 0-9, a-z
530
                    var k = (String.fromCharCode(e.keyCode)).toUpperCase();
531
                    if (opt.accesskeys[k]) {
532
                        // according to the specs accesskeys must be invoked immediately
533
                        opt.accesskeys[k].$node.trigger(opt.accesskeys[k].$menu
534
                            ? 'contextmenu:focus'
535
                            : 'mouseup'
536
                        );
537
                        return;
538
                    }
539
                    break;
540
            }
541
            // pass event to selected item, 
542
            // stop propagation to avoid endless recursion
543
            e.stopPropagation();
544
            opt.$selected && opt.$selected.trigger(e);
545
        },
546

  
547
        // select previous possible command in menu
548
        prevItem: function(e) {
549
            e.stopPropagation();
550
            var opt = $(this).data('contextMenu') || {};
551

  
552
            // obtain currently selected menu
553
            if (opt.$selected) {
554
                var $s = opt.$selected;
555
                opt = opt.$selected.parent().data('contextMenu') || {};
556
                opt.$selected = $s;
557
            }
558
            
559
            var $children = opt.$menu.children(),
560
                $prev = !opt.$selected || !opt.$selected.prev().length ? $children.last() : opt.$selected.prev(),
561
                $round = $prev;
562
            
563
            // skip disabled
564
            while ($prev.hasClass('disabled') || $prev.hasClass('not-selectable')) {
565
                if ($prev.prev().length) {
566
                    $prev = $prev.prev();
567
                } else {
568
                    $prev = $children.last();
569
                }
570
                if ($prev.is($round)) {
571
                    // break endless loop
572
                    return;
573
                }
574
            }
575
            
576
            // leave current
577
            if (opt.$selected) {
578
                handle.itemMouseleave.call(opt.$selected.get(0), e);
579
            }
580
            
581
            // activate next
582
            handle.itemMouseenter.call($prev.get(0), e);
583
            
584
            // focus input
585
            var $input = $prev.find('input, textarea, select');
586
            if ($input.length) {
587
                $input.focus();
588
            }
589
        },
590
        // select next possible command in menu
591
        nextItem: function(e) {
592
            e.stopPropagation();
593
            var opt = $(this).data('contextMenu') || {};
594

  
595
            // obtain currently selected menu
596
            if (opt.$selected) {
597
                var $s = opt.$selected;
598
                opt = opt.$selected.parent().data('contextMenu') || {};
599
                opt.$selected = $s;
600
            }
601

  
602
            var $children = opt.$menu.children(),
603
                $next = !opt.$selected || !opt.$selected.next().length ? $children.first() : opt.$selected.next(),
604
                $round = $next;
605

  
606
            // skip disabled
607
            while ($next.hasClass('disabled') || $next.hasClass('not-selectable')) {
608
                if ($next.next().length) {
609
                    $next = $next.next();
610
                } else {
611
                    $next = $children.first();
612
                }
613
                if ($next.is($round)) {
614
                    // break endless loop
615
                    return;
616
                }
617
            }
618
            
619
            // leave current
620
            if (opt.$selected) {
621
                handle.itemMouseleave.call(opt.$selected.get(0), e);
622
            }
623
            
624
            // activate next
625
            handle.itemMouseenter.call($next.get(0), e);
626
            
627
            // focus input
628
            var $input = $next.find('input, textarea, select');
629
            if ($input.length) {
630
                $input.focus();
631
            }
632
        },
633
        
634
        // flag that we're inside an input so the key handler can act accordingly
635
        focusInput: function(e) {
636
            var $this = $(this).closest('.context-menu-item'),
637
                data = $this.data(),
638
                opt = data.contextMenu,
639
                root = data.contextMenuRoot;
640

  
641
            root.$selected = opt.$selected = $this;
642
            root.isInput = opt.isInput = true;
643
        },
644
        // flag that we're inside an input so the key handler can act accordingly
645
        blurInput: function(e) {
646
            var $this = $(this).closest('.context-menu-item'),
647
                data = $this.data(),
648
                opt = data.contextMenu,
649
                root = data.contextMenuRoot;
650

  
651
            root.isInput = opt.isInput = false;
652
        },
653
        
654
        // :hover on menu
655
        menuMouseenter: function(e) {
656
            var root = $(this).data().contextMenuRoot;
657
            root.hovering = true;
658
        },
659
        // :hover on menu
660
        menuMouseleave: function(e) {
661
            var root = $(this).data().contextMenuRoot;
662
            if (root.$layer && root.$layer.is(e.relatedTarget)) {
663
                root.hovering = false;
664
            }
665
        },
666
        
667
        // :hover done manually so key handling is possible
668
        itemMouseenter: function(e) {
669
            var $this = $(this),
670
                data = $this.data(),
671
                opt = data.contextMenu,
672
                root = data.contextMenuRoot;
673
            
674
            root.hovering = true;
675

  
676
            // abort if we're re-entering
677
            if (e && root.$layer && root.$layer.is(e.relatedTarget)) {
678
                e.preventDefault();
679
                e.stopImmediatePropagation();
680
            }
681

  
682
            // make sure only one item is selected
683
            (opt.$menu ? opt : root).$menu
684
                .children('.hover').trigger('contextmenu:blur');
685

  
686
            if ($this.hasClass('disabled') || $this.hasClass('not-selectable')) {
687
                opt.$selected = null;
688
                return;
689
            }
690
            
691
            $this.trigger('contextmenu:focus');
692
        },
693
        // :hover done manually so key handling is possible
694
        itemMouseleave: function(e) {
695
            var $this = $(this),
696
                data = $this.data(),
697
                opt = data.contextMenu,
698
                root = data.contextMenuRoot;
699

  
700
            if (root !== opt && root.$layer && root.$layer.is(e.relatedTarget)) {
701
                root.$selected && root.$selected.trigger('contextmenu:blur');
702
                e.preventDefault();
703
                e.stopImmediatePropagation();
704
                root.$selected = opt.$selected = opt.$node;
705
                return;
706
            }
707
            
708
            $this.trigger('contextmenu:blur');
709
        },
710
        // contextMenu item click
711
        itemClick: function(e) {
712
            var $this = $(this),
713
                data = $this.data(),
714
                opt = data.contextMenu,
715
                root = data.contextMenuRoot,
716
                key = data.contextMenuKey,
717
                callback;
718

  
719
            // abort if the key is unknown or disabled or is a menu
720
            if (!opt.items[key] || $this.is('.disabled, .context-menu-submenu, .context-menu-separator, .not-selectable')) {
721
                return;
722
            }
723

  
724
            e.preventDefault();
725
            e.stopImmediatePropagation();
726

  
727
            if ($.isFunction(root.callbacks[key]) && Object.prototype.hasOwnProperty.call(root.callbacks, key)) {
728
                // item-specific callback
729
                callback = root.callbacks[key];
730
            } else if ($.isFunction(root.callback)) {
731
                // default callback
732
                callback = root.callback;                
733
            } else {
734
                // no callback, no action
735
                return;
736
            }
737

  
738
            // hide menu if callback doesn't stop that
739
            if (callback.call(root.$trigger, key, root) !== false) {
740
                root.$menu.trigger('contextmenu:hide');
741
            } else if (root.$menu.parent().length) {
742
                op.update.call(root.$trigger, root);
743
            }
744
        },
745
        // ignore click events on input elements
746
        inputClick: function(e) {
747
            e.stopImmediatePropagation();
748
        },
749
        
750
        // hide <menu>
751
        hideMenu: function(e, data) {
752
            var root = $(this).data('contextMenuRoot');
753
            op.hide.call(root.$trigger, root, data && data.force);
754
        },
755
        // focus <command>
756
        focusItem: function(e) {
757
            e.stopPropagation();
758
            var $this = $(this),
759
                data = $this.data(),
760
                opt = data.contextMenu,
761
                root = data.contextMenuRoot;
762

  
763
            $this.addClass('hover')
764
                .siblings('.hover').trigger('contextmenu:blur');
765
            
766
            // remember selected
767
            opt.$selected = root.$selected = $this;
768
            
769
            // position sub-menu - do after show so dumb $.ui.position can keep up
770
            if (opt.$node) {
771
                root.positionSubmenu.call(opt.$node, opt.$menu);
772
            }
773
        },
774
        // blur <command>
775
        blurItem: function(e) {
776
            e.stopPropagation();
777
            var $this = $(this),
778
                data = $this.data(),
779
                opt = data.contextMenu,
780
                root = data.contextMenuRoot;
781
            
782
            $this.removeClass('hover');
783
            opt.$selected = null;
784
        }
785
    },
786
    // operations
787
    op = {
788
        show: function(opt, x, y) {
789
            var $trigger = $(this),
790
                offset,
791
                css = {};
792

  
793
            // hide any open menus
794
            $('#context-menu-layer').trigger('mousedown');
795

  
796
            // backreference for callbacks
797
            opt.$trigger = $trigger;
798

  
799
            // show event
800
            if (opt.events.show.call($trigger, opt) === false) {
801
                $currentTrigger = null;
802
                return;
803
            }
804

  
805
            // create or update context menu
806
            op.update.call($trigger, opt);
807
            
808
            // position menu
809
            opt.position.call($trigger, opt, x, y);
810

  
811
            // make sure we're in front
812
            if (opt.zIndex) {
813
                css.zIndex = zindex($trigger) + opt.zIndex;
814
            }
815
            
816
            // add layer
817
            op.layer.call(opt.$menu, opt, css.zIndex);
818
            
819
            // adjust sub-menu zIndexes
820
            opt.$menu.find('ul').css('zIndex', css.zIndex + 1);
821
            
822
            // position and show context menu
823
            opt.$menu.css( css )[opt.animation.show](opt.animation.duration, function() {
824
                $trigger.trigger('contextmenu:visible');
825
            });
826
            // make options available and set state
827
            $trigger
828
                .data('contextMenu', opt)
829
                .addClass("context-menu-active");
830
            
831
            // register key handler
832
            $(document).off('keydown.contextMenu').on('keydown.contextMenu', handle.key);
833
            // register autoHide handler
834
            if (opt.autoHide) {
835
                // mouse position handler
836
                $(document).on('mousemove.contextMenuAutoHide', function(e) {
837
                    // need to capture the offset on mousemove,
838
                    // since the page might've been scrolled since activation
839
                    var pos = $trigger.offset();
840
                    pos.right = pos.left + $trigger.outerWidth();
841
                    pos.bottom = pos.top + $trigger.outerHeight();
842
                    
843
                    if (opt.$layer && !opt.hovering && (!(e.pageX >= pos.left && e.pageX <= pos.right) || !(e.pageY >= pos.top && e.pageY <= pos.bottom))) {
844
                        // if mouse in menu...
845
                        opt.$menu.trigger('contextmenu:hide');
846
                    }
847
                });
848
            }
849
        },
850
        hide: function(opt, force) {
851
            var $trigger = $(this);
852
            if (!opt) {
853
                opt = $trigger.data('contextMenu') || {};
854
            }
855
            
856
            // hide event
857
            if (!force && opt.events && opt.events.hide.call($trigger, opt) === false) {
858
                return;
859
            }
860
            
861
            // remove options and revert state
862
            $trigger
863
                .removeData('contextMenu')
864
                .removeClass("context-menu-active");
865
            
866
            if (opt.$layer) {
867
                // keep layer for a bit so the contextmenu event can be aborted properly by opera
868
                setTimeout((function($layer) {
869
                    return function(){
870
                        $layer.remove();
871
                    };
872
                })(opt.$layer), 10);
873
                
874
                try {
875
                    delete opt.$layer;
876
                } catch(e) {
877
                    opt.$layer = null;
878
                }
879
            }
880
            
881
            // remove handle
882
            $currentTrigger = null;
883
            // remove selected
884
            opt.$menu.find('.hover').trigger('contextmenu:blur');
885
            opt.$selected = null;
886
            // unregister key and mouse handlers
887
            //$(document).off('.contextMenuAutoHide keydown.contextMenu'); // http://bugs.jquery.com/ticket/10705
888
            $(document).off('.contextMenuAutoHide').off('keydown.contextMenu');
889
            // hide menu
890
            opt.$menu && opt.$menu[opt.animation.hide](opt.animation.duration, function (){
891
                // tear down dynamically built menu after animation is completed.
892
                if (opt.build) {
893
                    opt.$menu.remove();
894
                    $.each(opt, function(key, value) {
895
                        switch (key) {
896
                            case 'ns':
897
                            case 'selector':
898
                            case 'build':
899
                            case 'trigger':
900
                                return true;
901

  
902
                            default:
903
                                opt[key] = undefined;
904
                                try {
905
                                    delete opt[key];
906
                                } catch (e) {}
907
                                return true;
908
                        }
909
                    });
910
                }
911
                
912
                setTimeout(function() {
913
                    $trigger.trigger('contextmenu:hidden');
914
                }, 10);
915
            });
916
        },
917
        create: function(opt, root) {
918
            if (root === undefined) {
919
                root = opt;
920
            }
921
            // create contextMenu
922
            opt.$menu = $('<ul class="context-menu-list"></ul>').addClass(opt.className || "").data({
923
                'contextMenu': opt,
924
                'contextMenuRoot': root
925
            });
926
            
927
            $.each(['callbacks', 'commands', 'inputs'], function(i,k){
928
                opt[k] = {};
929
                if (!root[k]) {
930
                    root[k] = {};
931
                }
932
            });
933
            
934
            root.accesskeys || (root.accesskeys = {});
935
            
936
            // create contextMenu items
937
            $.each(opt.items, function(key, item){
938
                var $t = $('<li class="context-menu-item"></li>').addClass(item.className || ""),
939
                    $label = null,
940
                    $input = null;
941
                
942
                // iOS needs to see a click-event bound to an element to actually
943
                // have the TouchEvents infrastructure trigger the click event
944
                $t.on('click', $.noop);
945
                
946
                item.$node = $t.data({
947
                    'contextMenu': opt,
948
                    'contextMenuRoot': root,
949
                    'contextMenuKey': key
950
                });
951
                
952
                // register accesskey
953
                // NOTE: the accesskey attribute should be applicable to any element, but Safari5 and Chrome13 still can't do that
954
                if (item.accesskey) {
955
                    var aks = splitAccesskey(item.accesskey);
956
                    for (var i=0, ak; ak = aks[i]; i++) {
957
                        if (!root.accesskeys[ak]) {
958
                            root.accesskeys[ak] = item;
959
                            item._name = item.name.replace(new RegExp('(' + ak + ')', 'i'), '<span class="context-menu-accesskey">$1</span>');
960
                            break;
961
                        }
962
                    }
963
                }
964
                
965
                if (typeof item == "string") {
966
                    $t.addClass('context-menu-separator not-selectable');
967
                } else if (item.type && types[item.type]) {
968
                    // run custom type handler
969
                    types[item.type].call($t, item, opt, root);
970
                    // register commands
971
                    $.each([opt, root], function(i,k){
972
                        k.commands[key] = item;
973
                        if ($.isFunction(item.callback)) {
974
                            k.callbacks[key] = item.callback;
975
                        }
976
                    });
977
                } else {
978
                    // add label for input
979
                    if (item.type == 'html') {
980
                        $t.addClass('context-menu-html not-selectable');
981
                    } else if (item.type) {
982
                        $label = $('<label></label>').appendTo($t);
983
                        $('<span></span>').html(item._name || item.name).appendTo($label);
984
                        $t.addClass('context-menu-input');
985
                        opt.hasTypes = true;
986
                        $.each([opt, root], function(i,k){
987
                            k.commands[key] = item;
988
                            k.inputs[key] = item;
989
                        });
990
                    } else if (item.items) {
991
                        item.type = 'sub';
992
                    }
993
                
994
                    switch (item.type) {
995
                        case 'text':
996
                            $input = $('<input type="text" value="1" name="" value="">')
997
                                .attr('name', 'context-menu-input-' + key)
998
                                .val(item.value || "")
999
                                .appendTo($label);
1000
                            break;
1001
                    
1002
                        case 'textarea':
1003
                            $input = $('<textarea name=""></textarea>')
1004
                                .attr('name', 'context-menu-input-' + key)
1005
                                .val(item.value || "")
1006
                                .appendTo($label);
1007

  
1008
                            if (item.height) {
1009
                                $input.height(item.height);
1010
                            }
1011
                            break;
1012

  
1013
                        case 'checkbox':
1014
                            $input = $('<input type="checkbox" value="1" name="" value="">')
1015
                                .attr('name', 'context-menu-input-' + key)
1016
                                .val(item.value || "")
1017
                                .prop("checked", !!item.selected)
1018
                                .prependTo($label);
1019
                            break;
1020

  
1021
                        case 'radio':
1022
                            $input = $('<input type="radio" value="1" name="" value="">')
1023
                                .attr('name', 'context-menu-input-' + item.radio)
1024
                                .val(item.value || "")
1025
                                .prop("checked", !!item.selected)
1026
                                .prependTo($label);
1027
                            break;
1028
                    
1029
                        case 'select':
1030
                            $input = $('<select name="">')
1031
                                .attr('name', 'context-menu-input-' + key)
1032
                                .appendTo($label);
1033
                            if (item.options) {
1034
                                $.each(item.options, function(value, text) {
1035
                                    $('<option></option>').val(value).text(text).appendTo($input);
1036
                                });
1037
                                $input.val(item.selected);
1038
                            }
1039
                            break;
1040
                        
1041
                        case 'sub':
1042
                            // FIXME: shouldn't this .html() be a .text()?
1043
                            $('<span></span>').html(item._name || item.name).appendTo($t);
1044
                            item.appendTo = item.$node;
1045
                            op.create(item, root);
1046
                            $t.data('contextMenu', item).addClass('context-menu-submenu');
1047
                            item.callback = null;
1048
                            break;
1049
                        
1050
                        case 'html':
1051
                            $(item.html).appendTo($t);
1052
                            break;
1053
                        
1054
                        default:
1055
                            $.each([opt, root], function(i,k){
1056
                                k.commands[key] = item;
1057
                                if ($.isFunction(item.callback)) {
1058
                                    k.callbacks[key] = item.callback;
1059
                                }
1060
                            });
1061
                            // FIXME: shouldn't this .html() be a .text()?
1062
                            $('<span></span>').html(item._name || item.name || "").appendTo($t);
1063
                            break;
1064
                    }
1065
                    
1066
                    // disable key listener in <input>
1067
                    if (item.type && item.type != 'sub' && item.type != 'html') {
1068
                        $input
1069
                            .on('focus', handle.focusInput)
1070
                            .on('blur', handle.blurInput);
1071
                        
1072
                        if (item.events) {
1073
                            $input.on(item.events, opt);
1074
                        }
1075
                    }
1076
                
1077
                    // add icons
1078
                    if (item.icon) {
1079
                        $t.addClass("icon icon-" + item.icon);
1080
                    }
1081
                }
1082
                
1083
                // cache contained elements
1084
                item.$input = $input;
1085
                item.$label = $label;
1086

  
1087
                // attach item to menu
1088
                $t.appendTo(opt.$menu);
1089
                
1090
                // Disable text selection
1091
                if (!opt.hasTypes && $.support.eventSelectstart) {
1092
                    // browsers support user-select: none, 
1093
                    // IE has a special event for text-selection
1094
                    // browsers supporting neither will not be preventing text-selection
1095
                    $t.on('selectstart.disableTextSelect', handle.abortevent);
1096
                }
1097
            });
1098
            // attach contextMenu to <body> (to bypass any possible overflow:hidden issues on parents of the trigger element)
1099
            if (!opt.$node) {
1100
                opt.$menu.css('display', 'none').addClass('context-menu-root');
1101
            }
1102
            opt.$menu.appendTo(opt.appendTo || document.body);
1103
        },
1104
        resize: function($menu, nested) {
1105
            // determine widths of submenus, as CSS won't grow them automatically
1106
            // position:absolute within position:absolute; min-width:100; max-width:200; results in width: 100;
1107
            // kinda sucks hard...
1108

  
1109
            // determine width of absolutely positioned element
1110
            $menu.css({position: 'absolute', display: 'block'});
1111
            // don't apply yet, because that would break nested elements' widths
1112
            // add a pixel to circumvent word-break issue in IE9 - #80
1113
            $menu.data('width', Math.ceil($menu.width()) + 1);
1114
            // reset styles so they allow nested elements to grow/shrink naturally
1115
            $menu.css({
1116
                position: 'static',
1117
                minWidth: '0px',
1118
                maxWidth: '100000px'
1119
            });
1120
            // identify width of nested menus
1121
            $menu.find('> li > ul').each(function() {
1122
                op.resize($(this), true);
1123
            });
1124
            // reset and apply changes in the end because nested
1125
            // elements' widths wouldn't be calculatable otherwise
1126
            if (!nested) {
1127
                $menu.find('ul').andSelf().css({
1128
                    position: '', 
1129
                    display: '',
1130
                    minWidth: '',
1131
                    maxWidth: ''
1132
                }).width(function() {
1133
                    return $(this).data('width');
1134
                });
1135
            }
1136
        },
1137
        update: function(opt, root) {
1138
            var $trigger = this;
1139
            if (root === undefined) {
1140
                root = opt;
1141
                op.resize(opt.$menu);
1142
            }
1143
            // re-check disabled for each item
1144
            opt.$menu.children().each(function(){
1145
                var $item = $(this),
1146
                    key = $item.data('contextMenuKey'),
1147
                    item = opt.items[key],
1148
                    disabled = ($.isFunction(item.disabled) && item.disabled.call($trigger, key, root)) || item.disabled === true;
1149

  
1150
                // dis- / enable item
1151
                $item[disabled ? 'addClass' : 'removeClass']('disabled');
1152
                
1153
                if (item.type) {
1154
                    // dis- / enable input elements
1155
                    $item.find('input, select, textarea').prop('disabled', disabled);
1156
                    
1157
                    // update input states
1158
                    switch (item.type) {
1159
                        case 'text':
1160
                        case 'textarea':
1161
                            item.$input.val(item.value || "");
1162
                            break;
1163
                            
1164
                        case 'checkbox':
1165
                        case 'radio':
1166
                            item.$input.val(item.value || "").prop('checked', !!item.selected);
1167
                            break;
1168
                            
1169
                        case 'select':
1170
                            item.$input.val(item.selected || "");
1171
                            break;
1172
                    }
1173
                }
1174
                
1175
                if (item.$menu) {
1176
                    // update sub-menu
1177
                    op.update.call($trigger, item, root);
1178
                }
1179
            });
1180
        },
1181
        layer: function(opt, zIndex) {
1182
            // add transparent layer for click area
1183
            // filter and background for Internet Explorer, Issue #23
1184
            var $layer = opt.$layer = $('<div id="context-menu-layer" style="position:fixed; z-index:' + zIndex + '; top:0; left:0; opacity: 0; filter: alpha(opacity=0); background-color: #000;"></div>')
1185
                .css({height: $win.height(), width: $win.width(), display: 'block'})
1186
                .data('contextMenuRoot', opt)
1187
                .insertBefore(this)
1188
                .on('contextmenu', handle.abortevent)
1189
                .on('mousedown', handle.layerClick);
1190
            
1191
            // IE6 doesn't know position:fixed;
1192
            if (!$.support.fixedPosition) {
1193
                $layer.css({
1194
                    'position' : 'absolute',
1195
                    'height' : $(document).height()
1196
                });
1197
            }
1198
            
1199
            return $layer;
1200
        }
1201
    };
1202

  
1203
// split accesskey according to http://www.whatwg.org/specs/web-apps/current-work/multipage/editing.html#assigned-access-key
1204
function splitAccesskey(val) {
1205
    var t = val.split(/\s+/),
1206
        keys = [];
1207
        
1208
    for (var i=0, k; k = t[i]; i++) {
1209
        k = k[0].toUpperCase(); // first character only
1210
        // theoretically non-accessible characters should be ignored, but different systems, different keyboard layouts, ... screw it.
1211
        // a map to look up already used access keys would be nice
1212
        keys.push(k);
1213
    }
1214
    
1215
    return keys;
1216
}
1217

  
1218
// handle contextMenu triggers
1219
$.fn.contextMenu = function(operation) {
1220
    if (operation === undefined) {
1221
        this.first().trigger('contextmenu');
1222
    } else if (operation.x && operation.y) {
1223
        this.first().trigger($.Event("contextmenu", {pageX: operation.x, pageY: operation.y}));
1224
    } else if (operation === "hide") {
1225
        var $menu = this.data('contextMenu').$menu;
1226
        $menu && $menu.trigger('contextmenu:hide');
1227
    } else if (operation === "destroy") {
1228
        $.contextMenu("destroy", {context: this});
1229
    } else if ($.isPlainObject(operation)) {
1230
        operation.context = this;
1231
        $.contextMenu("create", operation);
1232
    } else if (operation) {
1233
        this.removeClass('context-menu-disabled');
1234
    } else if (!operation) {
1235
        this.addClass('context-menu-disabled');
1236
    }
1237
    
1238
    return this;
1239
};
1240

  
1241
// manage contextMenu instances
1242
$.contextMenu = function(operation, options) {
1243
    if (typeof operation != 'string') {
1244
        options = operation;
1245
        operation = 'create';
1246
    }
1247
    
1248
    if (typeof options == 'string') {
1249
        options = {selector: options};
1250
    } else if (options === undefined) {
1251
        options = {};
1252
    }
1253
    
1254
    // merge with default options
1255
    var o = $.extend(true, {}, defaults, options || {});
1256
    var $document = $(document);
1257
    var $context = $document;
1258
    var _hasContext = false;
1259
    
1260
    if (!o.context || !o.context.length) {
1261
        o.context = document;
1262
    } else {
1263
        // you never know what they throw at you...
1264
        $context = $(o.context).first();
1265
        o.context = $context.get(0);
1266
        _hasContext = o.context !== document;
1267
    }
1268
    
1269
    switch (operation) {
1270
        case 'create':
1271
            // no selector no joy
1272
            if (!o.selector) {
1273
                throw new Error('No selector specified');
1274
            }
1275
            // make sure internal classes are not bound to
1276
            if (o.selector.match(/.context-menu-(list|item|input)($|\s)/)) {
1277
                throw new Error('Cannot bind to selector "' + o.selector + '" as it contains a reserved className');
1278
            }
1279
            if (!o.build && (!o.items || $.isEmptyObject(o.items))) {
1280
                throw new Error('No Items sepcified');
1281
            }
1282
            counter ++;
1283
            o.ns = '.contextMenu' + counter;
1284
            if (!_hasContext) {
1285
                namespaces[o.selector] = o.ns;
1286
            }
1287
            menus[o.ns] = o;
1288
            
1289
            // default to right click
1290
            if (!o.trigger) {
1291
                o.trigger = 'right';
1292
            }
1293
            
1294
            if (!initialized) {
1295
                // make sure item click is registered first
1296
                $document
1297
                    .on({
1298
                        'contextmenu:hide.contextMenu': handle.hideMenu,
1299
                        'prevcommand.contextMenu': handle.prevItem,
1300
                        'nextcommand.contextMenu': handle.nextItem,
1301
                        'contextmenu.contextMenu': handle.abortevent,
1302
                        'mouseenter.contextMenu': handle.menuMouseenter,
1303
                        'mouseleave.contextMenu': handle.menuMouseleave
1304
                    }, '.context-menu-list')
1305
                    .on('mouseup.contextMenu', '.context-menu-input', handle.inputClick)
1306
                    .on({
1307
                        'mouseup.contextMenu': handle.itemClick,
1308
                        'contextmenu:focus.contextMenu': handle.focusItem,
1309
                        'contextmenu:blur.contextMenu': handle.blurItem,
1310
                        'contextmenu.contextMenu': handle.abortevent,
1311
                        'mouseenter.contextMenu': handle.itemMouseenter,
1312
                        'mouseleave.contextMenu': handle.itemMouseleave
1313
                    }, '.context-menu-item');
... Dieser Diff wurde abgeschnitten, weil er die maximale Anzahl anzuzeigender Zeilen überschreitet.

Auch abrufbar als: Unified diff