1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots{{>guide-examples-style}}
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<h2>Understanding the problem</h2>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>You want to add a custom context menu to an element, so you add a "contextmenu" event listener to the element. That listener is going to do two basic things:</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>Prevent the display of the browser's context menu</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>Position your custom context menu over top of/relative to the target of the event</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>The code will look something like this:</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Klootsfunction onContextmenu(e) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots if (!contextmenu) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu = new Y.Overlay({
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots bodyContent: "<ul class=\"contextmenu\"><li>Option 1</li><li>Option 2</li><li>Option 3</li></ul>",
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots visible: false,
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots constrain: true
68b1d15dad8af70374cec83a13758dce5621de1dTodd Klootsbtn.on("contextmenu", onContextmenu);
68b1d15dad8af70374cec83a13758dce5621de1dTodd KlootsThis code will work great if the "contextmenu" is triggered via the mouse. However, the "contextmenu" event is one of those device-independent events: can be triggered via the mouse, or the keyboard (on Windows using the Menu key, or the Shift + F10 shortcut). When it's triggered via the keyboard you will run into problems. Here's an overview of the obstacles and inconsistencies by browser + platform:
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<h3>Internet Explorer</h3>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>When the user triggers the "contextmenu" event, the x and y coordinates of the event will be relative to the current position of the mouse cursor. Not useful since the event was fired via the keyboard and the mouse cursor could be anywhere on the screen.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>When the user presses Shift + F10 IE's menubar will gain focus, with the first item ("File") highlighted. To fix that, you'll need to bind a "keydown" listener for Shift + F10 and call e.preventDefault(). That WILL prevent the menubar from gaining focus, but will also prevent the "contextmenu" event from firing when the user presses Shift + F10.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<h3>Firefox on Windows</h3>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>Shift + F10 won't fire the "contextmenu" event, but WILL trigger the display of the browser's context menu.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>If the "contextmenu" event is triggered via the Menu key, the x and y coordinates will be close to the target's bottom left corner.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<h3>Chrome on Windows</h3>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>Both the Menu key and Shift + F10 fire the "contextmenu" event</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>If the "contextmenu" event is triggered via the Menu key, the x and y coordinates will be close to the target's bottom left corner.</li>
e456dceed80992a528fd8dfed0ebedf3598a4adbTodd Kloots<h3>Safari, Chrome and Firefox on the Mac</h3>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>No keyboard shortcut available for triggering the "contextmenu" event, unless the screen reader (VoiceOver) is running, in which case the shortcut is Shift + Ctrl + Alt + M.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>When VoiceOver is running and the user presses Shift + Ctrl + Alt + M, the x and y coordinates will reference the center of the event target.</li>
e456dceed80992a528fd8dfed0ebedf3598a4adbTodd Kloots<h3>Opera</h3>
e456dceed80992a528fd8dfed0ebedf3598a4adbTodd Kloots <li>On Windows, Shift + F10 won't fire the "contextmenu" event, but WILL trigger the display of the browser's context menu.</li>
e456dceed80992a528fd8dfed0ebedf3598a4adbTodd Kloots <li>On Mac, Shift + Command + M won't fire "contextmenu" event, but WILL trigger the display of the browser's context menu.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>Here's a working example. The following button has a custom context menu. Try to invoke it via the keyboard to see the problems yourself:</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<style type="text/css">
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots .contextmenu {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots background: #ccc;
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots list-style-type: none;
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots .contextmenu li {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots white-space: nowrap;
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<button type="button" id="btn-1">I've got a context menu</button>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots YUI().use("overlay", function (Y) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots var btn = Y.one("#btn-1"),
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu;
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots function onContextmenu(e) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots if (!contextmenu) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu = new Y.Overlay({
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots bodyContent: "<ul class=\"contextmenu\"><li>Option 1</li><li>Option 2</li><li>Option 3</li></ul>",
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots visible: false,
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots constrain: true
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots btn.on("contextmenu", onContextmenu);
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<h2>The value of the "contextmenu" synthetic event</h2>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>Returning to the task at hand, as a developer you just want to bind a single "contextmenu" event listener and have it do the right thing regardless of how the event was triggered. This is what the "contextmenu" synthetic event does; it fixes all the aforementioned problems and inconsistencies while maintaining the same signature as a standard "contextmenu" DOM event. Additionally, it provides two bits of sugar:</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>Prevents the display of the browser's context menu. (Since you're likely going to be doing that anyway.)</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots <li>Follows Safari's model such that when the "contextmenu" event is fired via the keyboard, the x and y coordinates of the event will reference the center of the target.</li>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>All that's required to use the "contextmenu" synthetic event is to add "event-contextmenu" to the use() statement.</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd KlootsYUI().use("event-contextmenu", function (Y) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>Here's a working example: The following button has a custom context menu. On Windows the context menu can be invoked by pressing either Menu or using Shift + F10, on the Mac use Shift + Ctrl + Alt + M.</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<button type="button" id="btn-2">I've got a context menu</button>
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots YUI().use("event-contextmenu", "overlay", function (Y) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots var btn = Y.one("#btn-2"),
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu;
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots function onContextmenu(e) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots if (!contextmenu) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu = new Y.Overlay({
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots bodyContent: "<ul class=\"contextmenu\"><li>Option 1</li><li>Option 2</li><li>Option 3</li></ul>",
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots visible: false,
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots constrain: true
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots btn.on("contextmenu", onContextmenu);
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots<p>Here's the code for the example:</p>
68b1d15dad8af70374cec83a13758dce5621de1dTodd KlootsYUI().use("event-contextmenu", "overlay", function (Y) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots var btn = Y.one("#btn-2"),
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu;
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots function onContextmenu(e) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots if (!contextmenu) {
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots contextmenu = new Y.Overlay({
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots bodyContent: "<ul class=\"contextmenu\"><li>Option 1</li><li>Option 2</li><li>Option 3</li></ul>",
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots visible: false,
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots constrain: true
68b1d15dad8af70374cec83a13758dce5621de1dTodd Kloots btn.on("contextmenu", onContextmenu);