]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/Menu.js.uncompressed.js
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/keys", // keys.F10
11 "dojo/_base/lang", // lang.hitch
13 "dojo/sniff", // has("ie"), has("quirks")
14 "dojo/_base/window", // win.body win.doc.documentElement win.doc.frames
15 "dojo/window", // winUtils.get
19 ], function(require
, array
, declare
, event
, dom
, domAttr
, domGeometry
, domStyle
, keys
, lang
, on
,
20 has
, win
, winUtils
, pm
, DropDownMenu
, ready
){
25 // Back compat w/1.6, remove for 2.0
26 if(has("dijit-legacy-requires")){
28 var requires
= ["dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator"];
29 require(requires
); // use indirection so modules not rolled into a build
33 return declare("dijit.Menu", DropDownMenu
, {
35 // A context menu you can assign to multiple elements
37 constructor: function(/*===== params, srcNodeRef =====*/){
40 // params: Object|null
41 // Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
42 // and functions, typically callbacks like onClick.
43 // The hash can contain any of the widget's properties, excluding read-only properties.
44 // srcNodeRef: DOMNode|String?
45 // If a srcNodeRef (DOM node) is specified:
47 // - use srcNodeRef.innerHTML as my contents
48 // - replace srcNodeRef with my generated DOM tree
53 // targetNodeIds: [const] String[]
54 // Array of dom node ids of nodes to attach to.
55 // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
59 // CSS expression to apply this Menu to descendants of targetNodeIds, rather than to
60 // the nodes specified by targetNodeIds themselves. Useful for applying a Menu to
61 // a range of rows in a table, tree, etc.
63 // The application must require() an appropriate level of dojo/query to handle the selector.
66 // TODO: in 2.0 remove support for multiple targetNodeIds. selector gives the same effect.
67 // So, change targetNodeIds to a targetNodeId: "", remove bindDomNode()/unBindDomNode(), etc.
70 // currentTarget: [readonly] DOMNode
71 // For context menus, set to the current node that the Menu is being displayed for.
72 // Useful so that the menu actions can be tailored according to the node
76 // contextMenuForWindow: [const] Boolean
77 // If true, right clicking anywhere on the window will cause this context menu to open.
78 // If false, must specify targetNodeIds.
79 contextMenuForWindow
: false,
81 // leftClickToOpen: [const] Boolean
82 // If true, menu will open on left click instead of right click, similar to a file menu.
83 leftClickToOpen
: false,
86 // When this menu closes, re-focus the element which had focus before it was opened.
89 postCreate: function(){
90 if(this.contextMenuForWindow
){
91 this.bindDomNode(this.ownerDocumentBody
);
93 // TODO: should have _setTargetNodeIds() method to handle initialization and a possible
94 // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
95 // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
96 array
.forEach(this.targetNodeIds
, this.bindDomNode
, this);
98 this.inherited(arguments
);
102 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el
){
104 // Returns the window reference of the passed iframe
107 return winUtils
.get(this._iframeContentDocument(iframe_el
)) ||
108 // Moz. TODO: is this available when defaultView isn't?
109 this._iframeContentDocument(iframe_el
)['__parent__'] ||
110 (iframe_el
.name
&& win
.doc
.frames
[iframe_el
.name
]) || null; // Window
113 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el
){
115 // Returns a reference to the document object inside iframe_el
118 return iframe_el
.contentDocument
// W3
119 || (iframe_el
.contentWindow
&& iframe_el
.contentWindow
.document
) // IE
120 || (iframe_el
.name
&& win
.doc
.frames
[iframe_el
.name
] && win
.doc
.frames
[iframe_el
.name
].document
)
121 || null; // HTMLDocument
124 bindDomNode: function(/*String|DomNode*/ node
){
126 // Attach menu to given node
127 node
= dom
.byId(node
, this.ownerDocument
);
129 var cn
; // Connect node
131 // Support context menus on iframes. Rather than binding to the iframe itself we need
132 // to bind to the <body> node inside the iframe.
133 if(node
.tagName
.toLowerCase() == "iframe"){
135 window
= this._iframeContentWindow(iframe
);
136 cn
= win
.body(window
.document
);
138 // To capture these events at the top level, attach to <html>, not <body>.
139 // Otherwise right-click context menu just doesn't work.
140 cn
= (node
== win
.body(this.ownerDocument
) ? this.ownerDocument
.documentElement
: node
);
144 // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
150 // Save info about binding in _bindings[], and make node itself record index(+1) into
151 // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
152 // start with a number, which fails on FF/safari.
153 domAttr
.set(node
, "_dijitMenu" + this.id
, this._bindings
.push(binding
));
155 // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
156 // loading yet, in which case we need to wait for the onload event first, and then connect
157 // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
158 // we need to monitor keyboard events in addition to the oncontextmenu event.
159 var doConnects
= lang
.hitch(this, function(cn
){
160 var selector
= this.selector
,
161 delegatedEvent
= selector
?
162 function(eventType
){ return on
.selector(selector
, eventType
); } :
163 function(eventType
){ return eventType
; },
166 // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
167 // rather than shift-F10?
168 on(cn
, delegatedEvent(this.leftClickToOpen
? "click" : "contextmenu"), function(evt
){
169 // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
171 self
._scheduleOpen(this, iframe
, {x
: evt
.pageX
, y
: evt
.pageY
});
173 on(cn
, delegatedEvent("keydown"), function(evt
){
174 if(evt
.shiftKey
&& evt
.keyCode
== keys
.F10
){
176 self
._scheduleOpen(this, iframe
); // no coords - open near target node
181 binding
.connects
= cn
? doConnects(cn
) : [];
184 // Setup handler to [re]bind to the iframe when the contents are initially loaded,
185 // and every time the contents change.
186 // Need to do this b/c we are actually binding to the iframe's <body> node.
187 // Note: can't use connect.connect(), see #9609.
189 binding
.onloadHandler
= lang
.hitch(this, function(){
190 // want to remove old connections, but IE throws exceptions when trying to
191 // access the <body> node because it's already gone, or at least in a state of limbo
193 var window
= this._iframeContentWindow(iframe
);
194 cn
= win
.body(window
.document
)
195 binding
.connects
= doConnects(cn
);
197 if(iframe
.addEventListener
){
198 iframe
.addEventListener("load", binding
.onloadHandler
, false);
200 iframe
.attachEvent("onload", binding
.onloadHandler
);
205 unBindDomNode: function(/*String|DomNode*/ nodeName
){
207 // Detach menu from given node
211 node
= dom
.byId(nodeName
, this.ownerDocument
);
213 // On IE the dom.byId() call will get an exception if the attach point was
214 // the <body> node of an <iframe> that has since been reloaded (and thus the
215 // <body> node is in a limbo state of destruction.
219 // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array
220 var attrName
= "_dijitMenu" + this.id
;
221 if(node
&& domAttr
.has(node
, attrName
)){
222 var bid
= domAttr
.get(node
, attrName
)-1, b
= this._bindings
[bid
], h
;
223 while((h
= b
.connects
.pop())){
227 // Remove listener for iframe onload events
228 var iframe
= b
.iframe
;
230 if(iframe
.removeEventListener
){
231 iframe
.removeEventListener("load", b
.onloadHandler
, false);
233 iframe
.detachEvent("onload", b
.onloadHandler
);
237 domAttr
.remove(node
, attrName
);
238 delete this._bindings
[bid
];
242 _scheduleOpen: function(/*DomNode?*/ target
, /*DomNode?*/ iframe
, /*Object?*/ coords
){
244 // Set timer to display myself. Using a timer rather than displaying immediately solves
247 // 1. IE: without the delay, focus work in "open" causes the system
248 // context menu to appear in spite of stopEvent.
250 // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event
251 // even after a event.stop(e). (Shift-F10 on windows doesn't generate the
252 // oncontextmenu event.)
254 if(!this._openTimer
){
255 this._openTimer
= this.defer(function(){
256 delete this._openTimer
;
266 _openMyself: function(args
){
268 // Internal function for opening myself when the user does a right-click or something similar.
270 // This is an Object containing:
272 // - target: The node that is being clicked
273 // - iframe: If an `<iframe>` is being clicked, iframe points to that iframe
274 // - coords: Put menu at specified x/y position in viewport, or if iframe is
275 // specified, then relative to iframe.
277 // _openMyself() formerly took the event object, and since various code references
278 // evt.target (after connecting to _openMyself()), using an Object for parameters
279 // (so that old code still works).
281 var target
= args
.target
,
282 iframe
= args
.iframe
,
283 coords
= args
.coords
;
285 // To be used by MenuItem event handlers to tell which node the menu was opened on
286 this.currentTarget
= target
;
288 // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard)
289 // then near the node the menu is assigned to.
292 // Specified coordinates are on <body> node of an <iframe>, convert to match main document
293 var ifc
= domGeometry
.position(iframe
, true),
294 window
= this._iframeContentWindow(iframe
),
295 scroll
= domGeometry
.docScroll(window
.document
);
297 var cs
= domStyle
.getComputedStyle(iframe
),
298 tp
= domStyle
.toPixelValue
,
299 left
= (has("ie") && has("quirks") ? 0 : tp(iframe
, cs
.paddingLeft
)) + (has("ie") && has("quirks") ? tp(iframe
, cs
.borderLeftWidth
) : 0),
300 top
= (has("ie") && has("quirks") ? 0 : tp(iframe
, cs
.paddingTop
)) + (has("ie") && has("quirks") ? tp(iframe
, cs
.borderTopWidth
) : 0);
302 coords
.x
+= ifc
.x
+ left
- scroll
.x
;
303 coords
.y
+= ifc
.y
+ top
- scroll
.y
;
306 coords
= domGeometry
.position(target
, true);
312 var prevFocusNode
= this._focusManager
.get("prevNode");
313 var curFocusNode
= this._focusManager
.get("curNode");
314 var savedFocusNode
= !curFocusNode
|| (dom
.isDescendant(curFocusNode
, this.domNode
)) ? prevFocusNode
: curFocusNode
;
316 function closeAndRestoreFocus(){
317 // user has clicked on a menu or popup
318 if(self
.refocus
&& savedFocusNode
){
319 savedFocusNode
.focus();
327 onExecute
: closeAndRestoreFocus
,
328 onCancel
: closeAndRestoreFocus
,
329 orient
: this.isLeftToRight() ? 'L' : 'R'
333 this._onBlur = function(){
334 this.inherited('_onBlur', arguments
);
335 // Usually the parent closes the child widget but if this is a context
336 // menu then there is no parent
338 // don't try to restore focus; user has clicked another part of the screen
339 // and set focus there
344 array
.forEach(this._bindings
, function(b
){ if(b
){ this.unBindDomNode(b
.node
); } }, this);
345 this.inherited(arguments
);