]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/Menu.js.uncompressed.js
make precache_headlines_idle() start slower
[tt-rss.git] / lib / dijit / Menu.js.uncompressed.js
CommitLineData
1354d172
AD
1define("dijit/Menu", [
2 "require",
3 "dojo/_base/array", // array.forEach
4 "dojo/_base/declare", // declare
5 "dojo/_base/event", // event.stop
6 "dojo/dom", // dom.byId dom.isDescendant
7 "dojo/dom-attr", // domAttr.get domAttr.set domAttr.has domAttr.remove
8 "dojo/dom-geometry", // domStyle.getComputedStyle domGeometry.position
9 "dojo/dom-style", // domStyle.getComputedStyle
10 "dojo/_base/kernel",
11 "dojo/keys", // keys.F10
12 "dojo/_base/lang", // lang.hitch
13 "dojo/on",
14 "dojo/_base/sniff", // has("ie"), has("quirks")
15 "dojo/_base/window", // win.body win.doc.documentElement win.doc.frames win.withGlobal
16 "dojo/window", // winUtils.get
17 "./popup",
18 "./DropDownMenu",
19 "dojo/ready"
20], function(require, array, declare, event, dom, domAttr, domGeometry, domStyle, kernel, keys, lang, on,
21 has, win, winUtils, pm, DropDownMenu, ready){
22
23/*=====
24 var DropDownMenu = dijit.DropDownMenu;
25=====*/
26
27// module:
28// dijit/Menu
29// summary:
30// Includes dijit.Menu widget and base class dijit._MenuBase
31
32// Back compat w/1.6, remove for 2.0
33if(!kernel.isAsync){
34 ready(0, function(){
35 var requires = ["dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator"];
36 require(requires); // use indirection so modules not rolled into a build
37 });
38}
39
40return declare("dijit.Menu", DropDownMenu, {
41 // summary:
42 // A context menu you can assign to multiple elements
43
44 constructor: function(){
45 this._bindings = [];
46 },
47
48 // targetNodeIds: [const] String[]
49 // Array of dom node ids of nodes to attach to.
50 // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
51 targetNodeIds: [],
52
53 // contextMenuForWindow: [const] Boolean
54 // If true, right clicking anywhere on the window will cause this context menu to open.
55 // If false, must specify targetNodeIds.
56 contextMenuForWindow: false,
57
58 // leftClickToOpen: [const] Boolean
59 // If true, menu will open on left click instead of right click, similar to a file menu.
60 leftClickToOpen: false,
61
62 // refocus: Boolean
63 // When this menu closes, re-focus the element which had focus before it was opened.
64 refocus: true,
65
66 postCreate: function(){
67 if(this.contextMenuForWindow){
68 this.bindDomNode(win.body());
69 }else{
70 // TODO: should have _setTargetNodeIds() method to handle initialization and a possible
71 // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
72 // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
73 array.forEach(this.targetNodeIds, this.bindDomNode, this);
74 }
75 this.inherited(arguments);
76 },
77
78 // thanks burstlib!
79 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
80 // summary:
81 // Returns the window reference of the passed iframe
82 // tags:
83 // private
84 return winUtils.get(this._iframeContentDocument(iframe_el)) ||
85 // Moz. TODO: is this available when defaultView isn't?
86 this._iframeContentDocument(iframe_el)['__parent__'] ||
87 (iframe_el.name && win.doc.frames[iframe_el.name]) || null; // Window
88 },
89
90 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
91 // summary:
92 // Returns a reference to the document object inside iframe_el
93 // tags:
94 // protected
95 return iframe_el.contentDocument // W3
96 || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
97 || (iframe_el.name && win.doc.frames[iframe_el.name] && win.doc.frames[iframe_el.name].document)
98 || null; // HTMLDocument
99 },
100
101 bindDomNode: function(/*String|DomNode*/ node){
102 // summary:
103 // Attach menu to given node
104 node = dom.byId(node);
105
106 var cn; // Connect node
107
108 // Support context menus on iframes. Rather than binding to the iframe itself we need
109 // to bind to the <body> node inside the iframe.
110 if(node.tagName.toLowerCase() == "iframe"){
111 var iframe = node,
112 window = this._iframeContentWindow(iframe);
113 cn = win.withGlobal(window, win.body);
114 }else{
115
116 // To capture these events at the top level, attach to <html>, not <body>.
117 // Otherwise right-click context menu just doesn't work.
118 cn = (node == win.body() ? win.doc.documentElement : node);
119 }
120
121
122 // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
123 var binding = {
124 node: node,
125 iframe: iframe
126 };
127
128 // Save info about binding in _bindings[], and make node itself record index(+1) into
129 // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
130 // start with a number, which fails on FF/safari.
131 domAttr.set(node, "_dijitMenu" + this.id, this._bindings.push(binding));
132
133 // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
134 // loading yet, in which case we need to wait for the onload event first, and then connect
135 // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
136 // we need to monitor keyboard events in addition to the oncontextmenu event.
137 var doConnects = lang.hitch(this, function(cn){
138 return [
139 // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
140 // rather than shift-F10?
141 on(cn, this.leftClickToOpen ? "click" : "contextmenu", lang.hitch(this, function(evt){
142 // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
143 event.stop(evt);
144 this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY});
145 })),
146 on(cn, "keydown", lang.hitch(this, function(evt){
147 if(evt.shiftKey && evt.keyCode == keys.F10){
148 event.stop(evt);
149 this._scheduleOpen(evt.target, iframe); // no coords - open near target node
150 }
151 }))
152 ];
153 });
154 binding.connects = cn ? doConnects(cn) : [];
155
156 if(iframe){
157 // Setup handler to [re]bind to the iframe when the contents are initially loaded,
158 // and every time the contents change.
159 // Need to do this b/c we are actually binding to the iframe's <body> node.
160 // Note: can't use connect.connect(), see #9609.
161
162 binding.onloadHandler = lang.hitch(this, function(){
163 // want to remove old connections, but IE throws exceptions when trying to
164 // access the <body> node because it's already gone, or at least in a state of limbo
165
166 var window = this._iframeContentWindow(iframe);
167 cn = win.withGlobal(window, win.body);
168 binding.connects = doConnects(cn);
169 });
170 if(iframe.addEventListener){
171 iframe.addEventListener("load", binding.onloadHandler, false);
172 }else{
173 iframe.attachEvent("onload", binding.onloadHandler);
174 }
175 }
176 },
177
178 unBindDomNode: function(/*String|DomNode*/ nodeName){
179 // summary:
180 // Detach menu from given node
181
182 var node;
183 try{
184 node = dom.byId(nodeName);
185 }catch(e){
186 // On IE the dom.byId() call will get an exception if the attach point was
187 // the <body> node of an <iframe> that has since been reloaded (and thus the
188 // <body> node is in a limbo state of destruction.
189 return;
190 }
191
192 // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array
193 var attrName = "_dijitMenu" + this.id;
194 if(node && domAttr.has(node, attrName)){
195 var bid = domAttr.get(node, attrName)-1, b = this._bindings[bid], h;
196 while(h = b.connects.pop()){
197 h.remove();
198 }
199
200 // Remove listener for iframe onload events
201 var iframe = b.iframe;
202 if(iframe){
203 if(iframe.removeEventListener){
204 iframe.removeEventListener("load", b.onloadHandler, false);
205 }else{
206 iframe.detachEvent("onload", b.onloadHandler);
207 }
208 }
209
210 domAttr.remove(node, attrName);
211 delete this._bindings[bid];
212 }
213 },
214
215 _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){
216 // summary:
217 // Set timer to display myself. Using a timer rather than displaying immediately solves
218 // two problems:
219 //
220 // 1. IE: without the delay, focus work in "open" causes the system
221 // context menu to appear in spite of stopEvent.
222 //
223 // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event
224 // even after a event.stop(e). (Shift-F10 on windows doesn't generate the
225 // oncontextmenu event.)
226
227 if(!this._openTimer){
228 this._openTimer = setTimeout(lang.hitch(this, function(){
229 delete this._openTimer;
230 this._openMyself({
231 target: target,
232 iframe: iframe,
233 coords: coords
234 });
235 }), 1);
236 }
237 },
238
239 _openMyself: function(args){
240 // summary:
241 // Internal function for opening myself when the user does a right-click or something similar.
242 // args:
243 // This is an Object containing:
244 // * target:
245 // The node that is being clicked
246 // * iframe:
247 // If an <iframe> is being clicked, iframe points to that iframe
248 // * coords:
249 // Put menu at specified x/y position in viewport, or if iframe is
250 // specified, then relative to iframe.
251 //
252 // _openMyself() formerly took the event object, and since various code references
253 // evt.target (after connecting to _openMyself()), using an Object for parameters
254 // (so that old code still works).
255
256 var target = args.target,
257 iframe = args.iframe,
258 coords = args.coords;
259
260 // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard)
261 // then near the node the menu is assigned to.
262 if(coords){
263 if(iframe){
264 // Specified coordinates are on <body> node of an <iframe>, convert to match main document
265 var ifc = domGeometry.position(iframe, true),
266 window = this._iframeContentWindow(iframe),
267 scroll = win.withGlobal(window, "_docScroll", dojo);
268
269 var cs = domStyle.getComputedStyle(iframe),
270 tp = domStyle.toPixelValue,
271 left = (has("ie") && has("quirks") ? 0 : tp(iframe, cs.paddingLeft)) + (has("ie") && has("quirks") ? tp(iframe, cs.borderLeftWidth) : 0),
272 top = (has("ie") && has("quirks") ? 0 : tp(iframe, cs.paddingTop)) + (has("ie") && has("quirks") ? tp(iframe, cs.borderTopWidth) : 0);
273
274 coords.x += ifc.x + left - scroll.x;
275 coords.y += ifc.y + top - scroll.y;
276 }
277 }else{
278 coords = domGeometry.position(target, true);
279 coords.x += 10;
280 coords.y += 10;
281 }
282
283 var self=this;
284 var prevFocusNode = this._focusManager.get("prevNode");
285 var curFocusNode = this._focusManager.get("curNode");
286 var savedFocusNode = !curFocusNode || (dom.isDescendant(curFocusNode, this.domNode)) ? prevFocusNode : curFocusNode;
287
288 function closeAndRestoreFocus(){
289 // user has clicked on a menu or popup
290 if(self.refocus && savedFocusNode){
291 savedFocusNode.focus();
292 }
293 pm.close(self);
294 }
295 pm.open({
296 popup: this,
297 x: coords.x,
298 y: coords.y,
299 onExecute: closeAndRestoreFocus,
300 onCancel: closeAndRestoreFocus,
301 orient: this.isLeftToRight() ? 'L' : 'R'
302 });
303 this.focus();
304
305 this._onBlur = function(){
306 this.inherited('_onBlur', arguments);
307 // Usually the parent closes the child widget but if this is a context
308 // menu then there is no parent
309 pm.close(this);
310 // don't try to restore focus; user has clicked another part of the screen
311 // and set focus there
312 };
313 },
314
315 uninitialize: function(){
316 array.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
317 this.inherited(arguments);
318 }
319});
320
321});