jquery_context_menu-rails4 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +1 -0
- data/README.md +25 -0
- data/jquery_context_menu-rails.gemspec +28 -0
- data/lib/jquery_context_menu-rails.rb +1 -0
- data/lib/jquery_context_menu/rails.rb +6 -0
- data/lib/jquery_context_menu/rails/engine.rb +6 -0
- data/lib/jquery_context_menu/rails/version.rb +6 -0
- data/vendor/assets/images/cut.png +0 -0
- data/vendor/assets/images/door.png +0 -0
- data/vendor/assets/images/page_white_add.png +0 -0
- data/vendor/assets/images/page_white_copy.png +0 -0
- data/vendor/assets/images/page_white_delete.png +0 -0
- data/vendor/assets/images/page_white_edit.png +0 -0
- data/vendor/assets/images/page_white_paste.png +0 -0
- data/vendor/assets/javascripts/jquery.contextMenu.js +1686 -0
- data/vendor/assets/javascripts/jquery.ui.position.js +497 -0
- data/vendor/assets/stylesheets/jquery.contextMenu.css +142 -0
- metadata +140 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
OTEyYmFmMzZjZjFmMWIyYzJjODk2YzdlMzBhYjRiNjM1OGExZGJiNg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmVmODQ5ZDc0NGY1YTM0MDdlOGU1MTI4OTVlMDQ3NDg2MGY1ZjdiZg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NWFhMjRmMzU1Y2ExNzUzNTM5MWFlZDE1ZGRhZjczYzkxYmM1YWY0MzVhNDhh
|
10
|
+
ZDQwNjIwMjE2NjA3YTU3ZDE1ZTU1NzQ2MTE4MDZkNzQ2OTY4NWRkNjg0YjBj
|
11
|
+
MTYyNWE3ZjBhMTM4NWQ2MTc1Yzk1YmExOWUxNmNiYTcxNjc3ZTU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NDFjMzViMTc2NmIyYjI0MGJmNGQ4NWYyOGY4NmI2ODIwYjhjMjZiZjY2ZDNl
|
14
|
+
OThlMzIzMDU4ZmRkNzZjZTkwZDVhZjdhYjhiOWMwOWQwOTliMDc5YzBmY2Fh
|
15
|
+
YjUwZTk1N2UzYWI2ZTFjY2I1NDEwZTIyOWQwNGY4NDJiZWEyYzI=
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
jquery_context_menu-rails-0.0.1.gem
|
data/README.md
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# Ruby Gem for jQuery contextMenu Plug-In #
|
2
|
+
|
3
|
+
This gem wraps the jQuery context menu plug-in (https://github.com/medialize/jQuery-contextMenu) for convenient use in Rails 3. jQuery-contextMenu is a jQuery plug-in that pops up a menu when the user right clicks on an element to which the menu is assigned.
|
4
|
+
|
5
|
+
## Usage ##
|
6
|
+
|
7
|
+
To include the plug-in in your rails application, modify your Gemfile to includ the following:
|
8
|
+
|
9
|
+
gem "jquery_context_menu-rails"
|
10
|
+
|
11
|
+
Next add the following line to app/assets/javascripts/application.js:
|
12
|
+
|
13
|
+
//= require jquery.contextMenu.js
|
14
|
+
|
15
|
+
And, in app/assets/stylesheets/application.css.scss add:
|
16
|
+
|
17
|
+
//= require jquery.contextMenu.css
|
18
|
+
|
19
|
+
These additions will make the jQuery-contextMenu plug-in available on all pages. If you want to limit it to a subset of pages, require the .js and .css files in the appropriate Rails Javascript and CSS files.
|
20
|
+
|
21
|
+
For documentation on how to use the context menu, please see the original jQuery-contextMenu site.
|
22
|
+
|
23
|
+
## License ##
|
24
|
+
|
25
|
+
$.contextMenu is published under the [MIT license](http://www.opensource.org/licenses/mit-license) and [GPL v3](http://opensource.org/licenses/GPL-3.0), as is this package.
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jquery_context_menu/rails/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "jquery_context_menu-rails4"
|
8
|
+
s.version = JQueryContextMenu::Rails::VERSION
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.authors = ["Harssh Sshrivastava","Bill Dieter"]
|
11
|
+
s.email = ["harssh122@gmail.com","dieter@acm.org"]
|
12
|
+
s.homepage = "https://github.com/harssh/jquery_context_menu-rails"
|
13
|
+
s.summary = "Use jQuery-contextMenu with Rails 3 and 4"
|
14
|
+
s.description = "This gem provides jQuery-contextMenufor your Rails application. (jQuery-contextMenu source code is at https://github.com/medialize/jQuery-contextMenu.git)"
|
15
|
+
s.license = "MIT"
|
16
|
+
|
17
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
18
|
+
s.add_development_dependency "rake"
|
19
|
+
|
20
|
+
|
21
|
+
s.add_dependency "railties", ">= 3.2.0", "< 5.0"
|
22
|
+
s.add_dependency "jquery-rails"
|
23
|
+
s.add_dependency 'jquery-ui-rails'
|
24
|
+
|
25
|
+
s.files = `git ls-files`.split("\n")
|
26
|
+
s.executables = `git ls-files`.split("\n").select{|f| f =~ /^bin/}
|
27
|
+
s.require_path = 'lib'
|
28
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'jquery_context_menu/rails'
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,1686 @@
|
|
1
|
+
/*!
|
2
|
+
* jQuery contextMenu - Plugin for simple contextMenu handling
|
3
|
+
*
|
4
|
+
* Version: git-master
|
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 specified');
|
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 specified');
|
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');
|
1314
|
+
|
1315
|
+
initialized = true;
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
// engage native contextmenu event
|
1319
|
+
$context
|
1320
|
+
.on('contextmenu' + o.ns, o.selector, o, handle.contextmenu);
|
1321
|
+
|
1322
|
+
if (_hasContext) {
|
1323
|
+
// add remove hook, just in case
|
1324
|
+
$context.on('remove' + o.ns, function() {
|
1325
|
+
$(this).contextMenu("destroy");
|
1326
|
+
});
|
1327
|
+
}
|
1328
|
+
|
1329
|
+
switch (o.trigger) {
|
1330
|
+
case 'hover':
|
1331
|
+
$context
|
1332
|
+
.on('mouseenter' + o.ns, o.selector, o, handle.mouseenter)
|
1333
|
+
.on('mouseleave' + o.ns, o.selector, o, handle.mouseleave);
|
1334
|
+
break;
|
1335
|
+
|
1336
|
+
case 'left':
|
1337
|
+
$context.on('click' + o.ns, o.selector, o, handle.click);
|
1338
|
+
break;
|
1339
|
+
/*
|
1340
|
+
default:
|
1341
|
+
// http://www.quirksmode.org/dom/events/contextmenu.html
|
1342
|
+
$document
|
1343
|
+
.on('mousedown' + o.ns, o.selector, o, handle.mousedown)
|
1344
|
+
.on('mouseup' + o.ns, o.selector, o, handle.mouseup);
|
1345
|
+
break;
|
1346
|
+
*/
|
1347
|
+
}
|
1348
|
+
|
1349
|
+
// create menu
|
1350
|
+
if (!o.build) {
|
1351
|
+
op.create(o);
|
1352
|
+
}
|
1353
|
+
break;
|
1354
|
+
|
1355
|
+
case 'destroy':
|
1356
|
+
var $visibleMenu;
|
1357
|
+
if (_hasContext) {
|
1358
|
+
// get proper options
|
1359
|
+
var context = o.context;
|
1360
|
+
$.each(menus, function(ns, o) {
|
1361
|
+
if (o.context !== context) {
|
1362
|
+
return true;
|
1363
|
+
}
|
1364
|
+
|
1365
|
+
$visibleMenu = $('.context-menu-list').filter(':visible');
|
1366
|
+
if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is($(o.context).find(o.selector))) {
|
1367
|
+
$visibleMenu.trigger('contextmenu:hide', {force: true});
|
1368
|
+
}
|
1369
|
+
|
1370
|
+
try {
|
1371
|
+
if (menus[o.ns].$menu) {
|
1372
|
+
menus[o.ns].$menu.remove();
|
1373
|
+
}
|
1374
|
+
|
1375
|
+
delete menus[o.ns];
|
1376
|
+
} catch(e) {
|
1377
|
+
menus[o.ns] = null;
|
1378
|
+
}
|
1379
|
+
|
1380
|
+
$(o.context).off(o.ns);
|
1381
|
+
|
1382
|
+
return true;
|
1383
|
+
});
|
1384
|
+
} else if (!o.selector) {
|
1385
|
+
$document.off('.contextMenu .contextMenuAutoHide');
|
1386
|
+
$.each(menus, function(ns, o) {
|
1387
|
+
$(o.context).off(o.ns);
|
1388
|
+
});
|
1389
|
+
|
1390
|
+
namespaces = {};
|
1391
|
+
menus = {};
|
1392
|
+
counter = 0;
|
1393
|
+
initialized = false;
|
1394
|
+
|
1395
|
+
$('#context-menu-layer, .context-menu-list').remove();
|
1396
|
+
} else if (namespaces[o.selector]) {
|
1397
|
+
$visibleMenu = $('.context-menu-list').filter(':visible');
|
1398
|
+
if ($visibleMenu.length && $visibleMenu.data().contextMenuRoot.$trigger.is(o.selector)) {
|
1399
|
+
$visibleMenu.trigger('contextmenu:hide', {force: true});
|
1400
|
+
}
|
1401
|
+
|
1402
|
+
try {
|
1403
|
+
if (menus[namespaces[o.selector]].$menu) {
|
1404
|
+
menus[namespaces[o.selector]].$menu.remove();
|
1405
|
+
}
|
1406
|
+
|
1407
|
+
delete menus[namespaces[o.selector]];
|
1408
|
+
} catch(e) {
|
1409
|
+
menus[namespaces[o.selector]] = null;
|
1410
|
+
}
|
1411
|
+
|
1412
|
+
$document.off(namespaces[o.selector]);
|
1413
|
+
}
|
1414
|
+
break;
|
1415
|
+
|
1416
|
+
case 'html5':
|
1417
|
+
// if <command> or <menuitem> are not handled by the browser,
|
1418
|
+
// or options was a bool true,
|
1419
|
+
// initialize $.contextMenu for them
|
1420
|
+
if ((!$.support.htmlCommand && !$.support.htmlMenuitem) || (typeof options == "boolean" && options)) {
|
1421
|
+
$('menu[type="context"]').each(function() {
|
1422
|
+
if (this.id) {
|
1423
|
+
$.contextMenu({
|
1424
|
+
selector: '[contextmenu=' + this.id +']',
|
1425
|
+
items: $.contextMenu.fromMenu(this)
|
1426
|
+
});
|
1427
|
+
}
|
1428
|
+
}).css('display', 'none');
|
1429
|
+
}
|
1430
|
+
break;
|
1431
|
+
|
1432
|
+
default:
|
1433
|
+
throw new Error('Unknown operation "' + operation + '"');
|
1434
|
+
}
|
1435
|
+
|
1436
|
+
return this;
|
1437
|
+
};
|
1438
|
+
|
1439
|
+
// import values into <input> commands
|
1440
|
+
$.contextMenu.setInputValues = function(opt, data) {
|
1441
|
+
if (data === undefined) {
|
1442
|
+
data = {};
|
1443
|
+
}
|
1444
|
+
|
1445
|
+
$.each(opt.inputs, function(key, item) {
|
1446
|
+
switch (item.type) {
|
1447
|
+
case 'text':
|
1448
|
+
case 'textarea':
|
1449
|
+
item.value = data[key] || "";
|
1450
|
+
break;
|
1451
|
+
|
1452
|
+
case 'checkbox':
|
1453
|
+
item.selected = data[key] ? true : false;
|
1454
|
+
break;
|
1455
|
+
|
1456
|
+
case 'radio':
|
1457
|
+
item.selected = (data[item.radio] || "") == item.value ? true : false;
|
1458
|
+
break;
|
1459
|
+
|
1460
|
+
case 'select':
|
1461
|
+
item.selected = data[key] || "";
|
1462
|
+
break;
|
1463
|
+
}
|
1464
|
+
});
|
1465
|
+
};
|
1466
|
+
|
1467
|
+
// export values from <input> commands
|
1468
|
+
$.contextMenu.getInputValues = function(opt, data) {
|
1469
|
+
if (data === undefined) {
|
1470
|
+
data = {};
|
1471
|
+
}
|
1472
|
+
|
1473
|
+
$.each(opt.inputs, function(key, item) {
|
1474
|
+
switch (item.type) {
|
1475
|
+
case 'text':
|
1476
|
+
case 'textarea':
|
1477
|
+
case 'select':
|
1478
|
+
data[key] = item.$input.val();
|
1479
|
+
break;
|
1480
|
+
|
1481
|
+
case 'checkbox':
|
1482
|
+
data[key] = item.$input.prop('checked');
|
1483
|
+
break;
|
1484
|
+
|
1485
|
+
case 'radio':
|
1486
|
+
if (item.$input.prop('checked')) {
|
1487
|
+
data[item.radio] = item.value;
|
1488
|
+
}
|
1489
|
+
break;
|
1490
|
+
}
|
1491
|
+
});
|
1492
|
+
|
1493
|
+
return data;
|
1494
|
+
};
|
1495
|
+
|
1496
|
+
// find <label for="xyz">
|
1497
|
+
function inputLabel(node) {
|
1498
|
+
return (node.id && $('label[for="'+ node.id +'"]').val()) || node.name;
|
1499
|
+
}
|
1500
|
+
|
1501
|
+
// convert <menu> to items object
|
1502
|
+
function menuChildren(items, $children, counter) {
|
1503
|
+
if (!counter) {
|
1504
|
+
counter = 0;
|
1505
|
+
}
|
1506
|
+
|
1507
|
+
$children.each(function() {
|
1508
|
+
var $node = $(this),
|
1509
|
+
node = this,
|
1510
|
+
nodeName = this.nodeName.toLowerCase(),
|
1511
|
+
label,
|
1512
|
+
item;
|
1513
|
+
|
1514
|
+
// extract <label><input>
|
1515
|
+
if (nodeName == 'label' && $node.find('input, textarea, select').length) {
|
1516
|
+
label = $node.text();
|
1517
|
+
$node = $node.children().first();
|
1518
|
+
node = $node.get(0);
|
1519
|
+
nodeName = node.nodeName.toLowerCase();
|
1520
|
+
}
|
1521
|
+
|
1522
|
+
/*
|
1523
|
+
* <menu> accepts flow-content as children. that means <embed>, <canvas> and such are valid menu items.
|
1524
|
+
* Not being the sadistic kind, $.contextMenu only accepts:
|
1525
|
+
* <command>, <menuitem>, <hr>, <span>, <p> <input [text, radio, checkbox]>, <textarea>, <select> and of course <menu>.
|
1526
|
+
* Everything else will be imported as an html node, which is not interfaced with contextMenu.
|
1527
|
+
*/
|
1528
|
+
|
1529
|
+
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#concept-command
|
1530
|
+
switch (nodeName) {
|
1531
|
+
// http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#the-menu-element
|
1532
|
+
case 'menu':
|
1533
|
+
item = {name: $node.attr('label'), items: {}};
|
1534
|
+
counter = menuChildren(item.items, $node.children(), counter);
|
1535
|
+
break;
|
1536
|
+
|
1537
|
+
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-a-element-to-define-a-command
|
1538
|
+
case 'a':
|
1539
|
+
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-button-element-to-define-a-command
|
1540
|
+
case 'button':
|
1541
|
+
item = {
|
1542
|
+
name: $node.text(),
|
1543
|
+
disabled: !!$node.attr('disabled'),
|
1544
|
+
callback: (function(){ return function(){ $node.click(); }; })()
|
1545
|
+
};
|
1546
|
+
break;
|
1547
|
+
|
1548
|
+
// http://www.whatwg.org/specs/web-apps/current-work/multipage/commands.html#using-the-command-element-to-define-a-command
|
1549
|
+
|
1550
|
+
case 'menuitem':
|
1551
|
+
case 'command':
|
1552
|
+
switch ($node.attr('type')) {
|
1553
|
+
case undefined:
|
1554
|
+
case 'command':
|
1555
|
+
case 'menuitem':
|
1556
|
+
item = {
|
1557
|
+
name: $node.attr('label'),
|
1558
|
+
disabled: !!$node.attr('disabled'),
|
1559
|
+
callback: (function(){ return function(){ $node.click(); }; })()
|
1560
|
+
};
|
1561
|
+
break;
|
1562
|
+
|
1563
|
+
case 'checkbox':
|
1564
|
+
item = {
|
1565
|
+
type: 'checkbox',
|
1566
|
+
disabled: !!$node.attr('disabled'),
|
1567
|
+
name: $node.attr('label'),
|
1568
|
+
selected: !!$node.attr('checked')
|
1569
|
+
};
|
1570
|
+
break;
|
1571
|
+
|
1572
|
+
case 'radio':
|
1573
|
+
item = {
|
1574
|
+
type: 'radio',
|
1575
|
+
disabled: !!$node.attr('disabled'),
|
1576
|
+
name: $node.attr('label'),
|
1577
|
+
radio: $node.attr('radiogroup'),
|
1578
|
+
value: $node.attr('id'),
|
1579
|
+
selected: !!$node.attr('checked')
|
1580
|
+
};
|
1581
|
+
break;
|
1582
|
+
|
1583
|
+
default:
|
1584
|
+
item = undefined;
|
1585
|
+
}
|
1586
|
+
break;
|
1587
|
+
|
1588
|
+
case 'hr':
|
1589
|
+
item = '-------';
|
1590
|
+
break;
|
1591
|
+
|
1592
|
+
case 'input':
|
1593
|
+
switch ($node.attr('type')) {
|
1594
|
+
case 'text':
|
1595
|
+
item = {
|
1596
|
+
type: 'text',
|
1597
|
+
name: label || inputLabel(node),
|
1598
|
+
disabled: !!$node.attr('disabled'),
|
1599
|
+
value: $node.val()
|
1600
|
+
};
|
1601
|
+
break;
|
1602
|
+
|
1603
|
+
case 'checkbox':
|
1604
|
+
item = {
|
1605
|
+
type: 'checkbox',
|
1606
|
+
name: label || inputLabel(node),
|
1607
|
+
disabled: !!$node.attr('disabled'),
|
1608
|
+
selected: !!$node.attr('checked')
|
1609
|
+
};
|
1610
|
+
break;
|
1611
|
+
|
1612
|
+
case 'radio':
|
1613
|
+
item = {
|
1614
|
+
type: 'radio',
|
1615
|
+
name: label || inputLabel(node),
|
1616
|
+
disabled: !!$node.attr('disabled'),
|
1617
|
+
radio: !!$node.attr('name'),
|
1618
|
+
value: $node.val(),
|
1619
|
+
selected: !!$node.attr('checked')
|
1620
|
+
};
|
1621
|
+
break;
|
1622
|
+
|
1623
|
+
default:
|
1624
|
+
item = undefined;
|
1625
|
+
break;
|
1626
|
+
}
|
1627
|
+
break;
|
1628
|
+
|
1629
|
+
case 'select':
|
1630
|
+
item = {
|
1631
|
+
type: 'select',
|
1632
|
+
name: label || inputLabel(node),
|
1633
|
+
disabled: !!$node.attr('disabled'),
|
1634
|
+
selected: $node.val(),
|
1635
|
+
options: {}
|
1636
|
+
};
|
1637
|
+
$node.children().each(function(){
|
1638
|
+
item.options[this.value] = $(this).text();
|
1639
|
+
});
|
1640
|
+
break;
|
1641
|
+
|
1642
|
+
case 'textarea':
|
1643
|
+
item = {
|
1644
|
+
type: 'textarea',
|
1645
|
+
name: label || inputLabel(node),
|
1646
|
+
disabled: !!$node.attr('disabled'),
|
1647
|
+
value: $node.val()
|
1648
|
+
};
|
1649
|
+
break;
|
1650
|
+
|
1651
|
+
case 'label':
|
1652
|
+
break;
|
1653
|
+
|
1654
|
+
default:
|
1655
|
+
item = {type: 'html', html: $node.clone(true)};
|
1656
|
+
break;
|
1657
|
+
}
|
1658
|
+
|
1659
|
+
if (item) {
|
1660
|
+
counter++;
|
1661
|
+
items['key' + counter] = item;
|
1662
|
+
}
|
1663
|
+
});
|
1664
|
+
|
1665
|
+
return counter;
|
1666
|
+
}
|
1667
|
+
|
1668
|
+
// convert html5 menu
|
1669
|
+
$.contextMenu.fromMenu = function(element) {
|
1670
|
+
var $this = $(element),
|
1671
|
+
items = {};
|
1672
|
+
|
1673
|
+
menuChildren(items, $this.children());
|
1674
|
+
|
1675
|
+
return items;
|
1676
|
+
};
|
1677
|
+
|
1678
|
+
// make defaults accessible
|
1679
|
+
$.contextMenu.defaults = defaults;
|
1680
|
+
$.contextMenu.types = types;
|
1681
|
+
// export internal functions - undocumented, for hacking only!
|
1682
|
+
$.contextMenu.handle = handle;
|
1683
|
+
$.contextMenu.op = op;
|
1684
|
+
$.contextMenu.menus = menus;
|
1685
|
+
|
1686
|
+
})(jQuery);
|