Skip to content

Commit 1ba0b9e

Browse files
committed
beforeOpen event accepts deferred as return value
Close mar10#81
1 parent da26cea commit 1ba0b9e

File tree

4 files changed

+237
-9
lines changed

4 files changed

+237
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# 1.7.1-0 / Unreleased
22
* [FEATURE] #80 setEntry() supports creating nested menus
3+
* [FEATURE] #81 beforeOpen event accepts deferred as return value
34
* [BUGFIX] entry data attached to parentLi instead of `<a>`
45
* Use jscs
56

README.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ $("#container").bind("contextmenuselect", function(event, ui) {
370370
371371
372372
# Tips and Tricks
373-
### Add right-aligned shortcut hints
373+
### [Howto] Add right-aligned shortcut hints
374374
375375
Simply add a tag of your choice to the title (for example `<kbd>`)
376376
```js
@@ -389,6 +389,55 @@ and make it right aligned via CSS:
389389
```
390390
391391
392+
### [Howto] Modify the menu using an asynchronous request
393+
394+
395+
```js
396+
$(document).contextmenu({
397+
...
398+
beforeOpen: function(event, ui) {
399+
// Immediate menu changes
400+
$(document).contextmenu("setEntry", "test", "(loading...)");
401+
// Menu opens, then we submit a request and wait for the resonse
402+
$.ajax({
403+
...
404+
}).done(function(data) {
405+
// Modify the menu from the ajax response. The menu will updated
406+
// while open
407+
$(document).contextmenu("setEntry", "test", {
408+
title: "New entry", cmd: "test",
409+
children: [ ... ]
410+
});
411+
});
412+
},
413+
```
414+
415+
Alternively we can delay the opening until the response arrives:
416+
```js
417+
$(document).contextmenu({
418+
...
419+
beforeOpen: function(event, ui) {
420+
var dfd = new $.Deferred();
421+
422+
$.ajax({
423+
...
424+
}).done(function(data) {
425+
// Modify the menu from the ajax response. The menu will be opened
426+
// afterwards
427+
$(document).contextmenu("setEntry", "test", {
428+
title: "New entry", cmd: "test",
429+
children: [ ... ]
430+
});
431+
dfd.resolve(); // Notify about finished response
432+
});
433+
434+
// Return a promise to delay opening until an async response becomes
435+
// available
436+
ui.result = dfd.promise();
437+
},
438+
```
439+
440+
392441
# Credits
393442
394443
Thanks to all [contributors](https://github.com/mar10/jquery-ui-contextmenu/contributors).

jquery.ui-contextmenu.js

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,28 +171,43 @@ $.widget("moogle.contextmenu", {
171171
}));
172172
},
173173
/** Open popup (called on 'contextmenu' event). */
174-
_openMenu: function(event) {
175-
var opts = this.options,
174+
_openMenu: function(event, recursive) {
175+
var res, promise,
176+
opts = this.options,
176177
posOption = opts.position,
177178
self = this,
178179
manualTrigger = !!event.isTrigger,
179180
ui = { menu: this.$menu, target: $(event.target),
180-
extraData: event.extraData, originalEvent: event };
181+
extraData: event.extraData, originalEvent: event,
182+
result: null };
181183

182184
if ( !opts.autoTrigger && !manualTrigger ) {
183185
// ignore browser's `contextmenu` events
184186
return;
185187
}
186-
this.currentTarget = event.target;
187188

188189
// Prevent browser from opening the system context menu
189190
event.preventDefault();
190191

191-
if ( this._trigger("beforeOpen", event, ui) === false ) {
192-
this.currentTarget = null;
193-
return false;
192+
if ( !recursive ) {
193+
res = this._trigger("beforeOpen", event, ui);
194+
promise = (ui.result && $.isFunction(ui.result.promise)) ? ui.result : null;
195+
ui.result = null;
196+
if ( res === false ) {
197+
// this.currentTarget = null;
198+
return false;
199+
} else if ( promise ) {
200+
// Handler returned a Deferred or Promise. Delay menu open until
201+
// the promise is resolved
202+
promise.done(function() {
203+
self._openMenu(event, true);
204+
});
205+
return false;
206+
}
207+
ui.menu = this.$menu; // Might have changed in beforeOpen
194208
}
195-
ui.menu = this.$menu; // Might have changed in beforeOpen
209+
this.currentTarget = event.target;
210+
196211
// Register global event handlers that close the dropdown-menu
197212
$(document).bind("keydown" + this.eventNamespace, function(event) {
198213
if ( event.which === $.ui.keyCode.ESCAPE ) {

test/issue-80-async.html

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
5+
<title>Triage - jquery.ui-contextmenu.js</title>
6+
7+
<link href="//code.jquery.com/ui/1.11.2/themes/smoothness/jquery-ui.css" type="text/css" rel="stylesheet" />
8+
<script src="//code.jquery.com/jquery-1.11.1.min.js" type="text/javascript"></script>
9+
<script src="//code.jquery.com/ui/1.11.2/jquery-ui.min.js" type="text/javascript"></script>
10+
<!-- Finally this plugin itself -->
11+
<script src="../jquery.ui-contextmenu.js" type="text/javascript"></script>
12+
13+
<style type="text/css">
14+
15+
body{
16+
font-family: "Trebuchet MS", "Helvetica", "Arial", "Verdana", "sans-serif";
17+
font-size: .8em;
18+
/* Prevent tablets from selecting text on taphold, etc:
19+
Note:
20+
If only the potential menu trigger elements should be protected, simply
21+
use the 'preventSelect: true' option.
22+
But we disable it more globally for tablet pc's, because the whole line
23+
or paragraph will still be selected otherwise.
24+
*/
25+
-webkit-user-select: none;
26+
-khtml-user-select: none;
27+
-moz-user-select: none;
28+
-ms-user-select: none;
29+
user-select: none;
30+
}
31+
32+
/* Only for the demo */
33+
.hasmenu {
34+
border: 1px solid #008;
35+
margin: 3px;
36+
padding: 5px;
37+
width: 30px;
38+
}
39+
div.sample {
40+
padding: 5px;
41+
margin: 5px;
42+
}
43+
44+
/* Optionally define a fixed width for menus */
45+
.ui-menu {
46+
width: 220px;
47+
}
48+
/* Allow to use <kbd> elements inside the title to define shortcut hints. */
49+
.ui-menu kbd {
50+
padding-left: 1em;
51+
float: right;
52+
}
53+
54+
/* Define a custom icon */
55+
.ui-icon.custom-icon-firefox {
56+
background-image: url(application_firefox.gif);
57+
background-position: 0 0;
58+
}
59+
</style>
60+
61+
62+
<script type="text/javascript">
63+
$(function(){
64+
65+
var CLIPBOARD = "";
66+
67+
$(document).contextmenu({
68+
delegate: ".hasmenu",
69+
preventContextMenuForPopup: true,
70+
preventSelect: true,
71+
taphold: true,
72+
menu: [
73+
{title: "Cut <kbd>Ctrl+X</kbd>", cmd: "cut", uiIcon: "ui-icon-scissors"},
74+
{title: "Copy <kbd>Ctrl+C</kbd>", cmd: "copy", uiIcon: "ui-icon-copy"},
75+
{title: "Paste <kbd>Ctrl+V</kbd>", cmd: "paste", uiIcon: "ui-icon-clipboard", disabled: true },
76+
{title: "Test", cmd: "test" },
77+
{title: "----"},
78+
{title: "More", children: [
79+
{title: "Sub 1 (callback)", action: function(event, ui) { alert("action callback sub1");} },
80+
{title: "Edit <kbd>[F2]</kbd>", cmd: "sub2"},
81+
]}
82+
],
83+
// Implement the beforeOpen callback to dynamically change the entries
84+
beforeOpen: function(event, ui) {
85+
var $menu = ui.menu,
86+
$target = ui.target,
87+
extraData = ui.extraData, // passed when menu was opened by call to open()
88+
dfd = new $.Deferred();
89+
90+
// console.log("beforeOpen", event, ui, event.originalEvent.type);
91+
$(document)
92+
.contextmenu("setEntry", "copy", "Copy '" + $target.text() + "'")
93+
.contextmenu("setEntry", "paste", "Paste" + (CLIPBOARD ? " '" + CLIPBOARD + "'" : ""))
94+
.contextmenu("setEntry", "test", "(loading...)");
95+
96+
setTimeout(function(){
97+
$(document)
98+
.contextmenu("setEntry", "test", {
99+
title: "test dynamic", cmd: "test",
100+
children: [
101+
{title: "dyn sub 1", cmd: "test_1"},
102+
{title: "dyn sub 2", cmd: "test_2"}
103+
]
104+
});
105+
dfd.resolve();
106+
}, 2000);
107+
108+
// Optionally return false, to prevent opening the menu now
109+
// or return a promise to delay opening until an async response becomes
110+
// available
111+
ui.result = dfd.promise();
112+
console.log("beforeOpen", event, ui, event.originalEvent.type);
113+
return;
114+
},
115+
focus: function(event, ui) {
116+
var menuId = ui.item.find(">a").attr("href");
117+
$("#info").text("focus " + menuId);
118+
// console.log("focus", ui.item);
119+
},
120+
blur: function(event, ui) {
121+
$("#info").text("");
122+
// console.log("blur", ui.item);
123+
},
124+
open: function(event, ui) {
125+
// alert("open on " + ui.target.text());
126+
},
127+
select: function(event, ui) {
128+
alert("select " + ui.cmd + " on " + ui.target.text());
129+
}
130+
});
131+
132+
/* Open and close an existing menu without programatically. */
133+
134+
$("#btnTest1").click(function(){
135+
// Trigger popup menu on the first target element
136+
$(document).contextmenu("open", $(".hasmenu:first"), {foo: "bar"});
137+
setTimeout(function(){
138+
$(document).contextmenu("close");
139+
}, 2000);
140+
});
141+
});
142+
</script>
143+
</head>
144+
145+
<body class="example">
146+
<h1>Triage jquery.ui-contextmenu.js</h1>
147+
148+
<p>Right-click in an element to open the context menu:</p>
149+
<div class="sample">
150+
<span class="hasmenu">AAA</span>
151+
<span class="hasmenu">BBB</span>
152+
<span class="hasmenu">CCC</span>
153+
</div>
154+
155+
<button id="btnTest1">Test 1</button>
156+
157+
<ul id="options2" class="ui-helper-hidden">
158+
<li data-command="action2"><span class="ui-icon ui-icon-heart"></span>Action 2
159+
<li data-command="action3" class="ui-state-disabled">Action 3
160+
</ul>
161+
162+
</body>
163+
</html>

0 commit comments

Comments
 (0)