1 if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dijit.Menu"] = true;
3 dojo.provide("dijit.Menu");
5 dojo.require("dijit._Widget");
6 dojo.require("dijit._Container");
7 dojo.require("dijit._Templated");
9 dojo.declare("dijit.Menu",
10 [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
13 // A context menu you can assign to multiple elements
15 constructor: function(){
20 '<table class="dijit dijitMenu dijitReset dijitMenuTable" waiRole="menu" dojoAttachEvent="onkeypress:_onKeyPress">' +
21 '<tbody class="dijitReset" dojoAttachPoint="containerNode"></tbody>'+
24 // targetNodeIds: String[]
25 // Array of dom node ids of nodes to attach to.
26 // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
29 // contextMenuForWindow: Boolean
30 // if true, right clicking anywhere on the window will cause this context menu to open;
31 // if false, must specify targetNodeIds
32 contextMenuForWindow: false,
34 // leftClickToOpen: Boolean
35 // If true, menu will open on left click instead of right click, similiar to a file menu.
36 leftClickToOpen: false,
39 // pointer to menu that displayed me
42 // popupDelay: Integer
43 // number of milliseconds before hovering (without clicking) causes the popup to automatically open
46 // _contextMenuWithMouse: Boolean
47 // used to record mouse and keyboard events to determine if a context
48 // menu is being opened with the keyboard or the mouse
49 _contextMenuWithMouse: false,
51 postCreate: function(){
52 if(this.contextMenuForWindow){
53 this.bindDomNode(dojo.body());
55 dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
57 this.connectKeyNavHandlers([dojo.keys.UP_ARROW], [dojo.keys.DOWN_ARROW]);
61 if(this._started){ return; }
63 dojo.forEach(this.getChildren(), function(child){ child.startup(); });
64 this.startupKeyNavChildren();
66 this.inherited(arguments);
69 onExecute: function(){
70 // summary: attach point for notification about when a menu item has been executed
73 onCancel: function(/*Boolean*/ closeAll){
74 // summary: attach point for notification about when the user cancels the current menu
77 _moveToPopup: function(/*Event*/ evt){
78 if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
79 this.focusedChild._onClick(evt);
83 _onKeyPress: function(/*Event*/ evt){
84 // summary: Handle keyboard based menu navigation.
85 if(evt.ctrlKey || evt.altKey){ return; }
88 case dojo.keys.RIGHT_ARROW:
89 this._moveToPopup(evt);
92 case dojo.keys.LEFT_ARROW:
102 onItemHover: function(/*MenuItem*/ item){
103 // summary: Called when cursor is over a MenuItem
104 this.focusChild(item);
106 if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
107 this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay);
111 _onChildBlur: function(item){
112 // summary: Close all popups that are open and descendants of this menu
113 dijit.popup.close(item.popup);
115 this._stopPopupTimer();
118 onItemUnhover: function(/*MenuItem*/ item){
119 // summary: Callback fires when mouse exits a MenuItem
122 _stopPopupTimer: function(){
123 if(this.hover_timer){
124 clearTimeout(this.hover_timer);
125 this.hover_timer = null;
129 _getTopMenu: function(){
130 for(var top=this; top.parentMenu; top=top.parentMenu);
134 onItemClick: function(/*Widget*/ item, /*Event*/ evt){
135 // summary: user defined function to handle clicks on an item
136 if(item.disabled){ return false; }
143 // before calling user defined handler, close hierarchy of menus
144 // and restore focus to place it was when menu was opened
147 // user defined handler for click
153 _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
155 // Returns the window reference of the passed iframe
156 var win = dijit.getDocumentWindow(dijit.Menu._iframeContentDocument(iframe_el)) ||
157 // Moz. TODO: is this available when defaultView isn't?
158 dijit.Menu._iframeContentDocument(iframe_el)['__parent__'] ||
159 (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
160 return win; // Window
163 _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
165 // Returns a reference to the document object inside iframe_el
166 var doc = iframe_el.contentDocument // W3
167 || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
168 || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
170 return doc; // HTMLDocument
173 bindDomNode: function(/*String|DomNode*/ node){
174 // summary: attach menu to given node
175 node = dojo.byId(node);
177 //TODO: this is to support context popups in Editor. Maybe this shouldn't be in dijit.Menu
178 var win = dijit.getDocumentWindow(node.ownerDocument);
179 if(node.tagName.toLowerCase()=="iframe"){
180 win = this._iframeContentWindow(node);
181 node = dojo.withGlobal(win, dojo.body);
184 // to capture these events at the top level,
185 // attach to document, not body
186 var cn = (node == dojo.body() ? dojo.doc : node);
188 node[this.id] = this._bindings.push([
189 dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, "_openMyself"),
190 dojo.connect(cn, "onkeydown", this, "_contextKey"),
191 dojo.connect(cn, "onmousedown", this, "_contextMouse")
195 unBindDomNode: function(/*String|DomNode*/ nodeName){
196 // summary: detach menu from given node
197 var node = dojo.byId(nodeName);
199 var bid = node[this.id]-1, b = this._bindings[bid];
200 dojo.forEach(b, dojo.disconnect);
201 delete this._bindings[bid];
205 _contextKey: function(e){
206 this._contextMenuWithMouse = false;
207 if(e.keyCode == dojo.keys.F10){
209 if(e.shiftKey && e.type=="keydown"){
210 // FF: copying the wrong property from e will cause the system
211 // context menu to appear in spite of stopEvent. Don't know
212 // exactly which properties cause this effect.
213 var _e = { target: e.target, pageX: e.pageX, pageY: e.pageY };
214 _e.preventDefault = _e.stopPropagation = function(){};
215 // IE: without the delay, focus work in "open" causes the system
216 // context menu to appear in spite of stopEvent.
217 window.setTimeout(dojo.hitch(this, function(){ this._openMyself(_e); }), 1);
222 _contextMouse: function(e){
223 this._contextMenuWithMouse = true;
226 _openMyself: function(/*Event*/ e){
228 // Internal function for opening myself when the user
229 // does a right-click or something similar
231 if(this.leftClickToOpen&&e.button>0){
237 // if we are opening the menu with the mouse or on safari open
238 // the menu at the mouse cursor
239 // (Safari does not have a keyboard command to open the context menu
240 // and we don't currently have a reliable way to determine
241 // _contextMenuWithMouse on Safari)
243 if(dojo.isSafari || this._contextMenuWithMouse){
247 // otherwise open near e.target
248 var coords = dojo.coords(e.target, true);
254 var savedFocus = dijit.getFocus(this);
255 function closeAndRestoreFocus(){
256 // user has clicked on a menu or popup
257 dijit.focus(savedFocus);
258 dijit.popup.close(self);
264 onExecute: closeAndRestoreFocus,
265 onCancel: closeAndRestoreFocus,
266 orient: this.isLeftToRight() ? 'L' : 'R'
270 this._onBlur = function(){
271 this.inherited('_onBlur', arguments);
272 // Usually the parent closes the child widget but if this is a context
273 // menu then there is no parent
274 dijit.popup.close(this);
275 // don't try to restore focus; user has clicked another part of the screen
276 // and set focus there
280 onOpen: function(/*Event*/ e){
281 // summary: Open menu relative to the mouse
282 this.isShowingNow = true;
286 // summary: callback when this menu is closed
287 this._stopPopupTimer();
288 this.parentMenu = null;
289 this.isShowingNow = false;
290 this.currentPopup = null;
291 if(this.focusedChild){
292 this._onChildBlur(this.focusedChild);
293 this.focusedChild = null;
297 _openPopup: function(){
298 // summary: open the popup to the side of the current menu item
299 this._stopPopupTimer();
300 var from_item = this.focusedChild;
301 var popup = from_item.popup;
303 if(popup.isShowingNow){ return; }
304 popup.parentMenu = this;
309 around: from_item.arrowCell,
310 orient: this.isLeftToRight() ? {'TR': 'TL', 'TL': 'TR'} : {'TL': 'TR', 'TR': 'TL'},
311 onCancel: function(){
312 // called when the child menu is canceled
313 dijit.popup.close(popup);
314 from_item.focus(); // put focus back on my node
315 self.currentPopup = null;
319 this.currentPopup = popup;
326 uninitialize: function(){
327 dojo.forEach(this.targetNodeIds, this.unBindDomNode, this);
328 this.inherited(arguments);
333 dojo.declare("dijit.MenuItem",
334 [dijit._Widget, dijit._Templated, dijit._Contained],
336 // summary: A line item in a Menu Widget
339 // icon, label, and expand arrow (BiDi-dependent) indicating sub-menu
341 '<tr class="dijitReset dijitMenuItem" '
342 +'dojoAttachEvent="onmouseenter:_onHover,onmouseleave:_onUnhover,ondijitclick:_onClick">'
343 +'<td class="dijitReset"><div class="dijitMenuItemIcon ${iconClass}" dojoAttachPoint="iconNode"></div></td>'
344 +'<td tabIndex="-1" class="dijitReset dijitMenuItemLabel" dojoAttachPoint="containerNode,focusNode" waiRole="menuitem"></td>'
345 +'<td class="dijitReset" dojoAttachPoint="arrowCell">'
346 +'<div class="dijitMenuExpand" dojoAttachPoint="expand" style="display:none">'
347 +'<span class="dijitInline dijitArrowNode dijitMenuExpandInner">+</span>'
357 // class to apply to div in button to make it display an icon
361 // if true, the menu item is disabled
362 // if false, the menu item is enabled
365 postCreate: function(){
366 dojo.setSelectable(this.domNode, false);
367 this.setDisabled(this.disabled);
369 this.setLabel(this.label);
373 _onHover: function(){
374 // summary: callback when mouse is moved onto menu item
375 this.getParent().onItemHover(this);
378 _onUnhover: function(){
379 // summary: callback when mouse is moved off of menu item
381 // if we are unhovering the currently selected item
383 this.getParent().onItemUnhover(this);
386 _onClick: function(evt){
387 this.getParent().onItemClick(this, evt);
391 onClick: function(/*Event*/ evt){
392 // summary: User defined function to handle clicks
396 dojo.addClass(this.domNode, 'dijitMenuItemHover');
398 dijit.focus(this.containerNode);
400 // this throws on IE (at least) in some scenarios
405 dojo.removeClass(this.domNode, 'dijitMenuItemHover');
408 setLabel: function(/*String*/ value){
409 this.containerNode.innerHTML=this.label=value;
412 setDisabled: function(/*Boolean*/ value){
413 // summary: enable or disable this menu item
414 this.disabled = value;
415 dojo[value ? "addClass" : "removeClass"](this.domNode, 'dijitMenuItemDisabled');
416 dijit.setWaiState(this.containerNode, 'disabled', value ? 'true' : 'false');
420 dojo.declare("dijit.PopupMenuItem",
423 _fillContent: function(){
424 // summary: The innerHTML contains both the menu item text and a popup widget
425 // description: the first part holds the menu item text and the second part is the popup
427 // | <div dojoType="dijit.PopupMenuItem">
428 // | <span>pick me</span>
429 // | <popup> ... </popup>
432 var nodes = dojo.query("*", this.srcNodeRef);
433 dijit.PopupMenuItem.superclass._fillContent.call(this, nodes[0]);
435 // save pointer to srcNode so we can grab the drop down widget after it's instantiated
436 this.dropDownContainer = this.srcNodeRef;
441 if(this._started){ return; }
442 this.inherited(arguments);
444 // we didn't copy the dropdown widget from the this.srcNodeRef, so it's in no-man's
445 // land now. move it to dojo.doc.body.
447 var node = dojo.query("[widgetId]", this.dropDownContainer)[0];
448 this.popup = dijit.byNode(node);
450 dojo.body().appendChild(this.popup.domNode);
452 this.popup.domNode.style.display="none";
453 dojo.addClass(this.expand, "dijitMenuExpandEnabled");
454 dojo.style(this.expand, "display", "");
455 dijit.setWaiState(this.containerNode, "haspopup", "true");
458 destroyDescendants: function(){
460 this.popup.destroyRecursive();
463 this.inherited(arguments);
467 dojo.declare("dijit.MenuSeparator",
468 [dijit._Widget, dijit._Templated, dijit._Contained],
470 // summary: A line between two menu items
472 templateString: '<tr class="dijitMenuSeparator"><td colspan=3>'
473 +'<div class="dijitMenuSeparatorTop"></div>'
474 +'<div class="dijitMenuSeparatorBottom"></div>'
477 postCreate: function(){
478 dojo.setSelectable(this.domNode, false);
481 isFocusable: function(){
482 // summary: over ride to always return false
483 return false; // Boolean