summaryrefslogtreecommitdiff
path: root/lib/dijit/Menu.js
diff options
context:
space:
mode:
authorAndrew Dolgov <[email protected]>2011-11-08 20:40:44 +0400
committerAndrew Dolgov <[email protected]>2011-11-08 20:40:44 +0400
commit81bea17aefb26859f825b9293c7c99192874806e (patch)
treefb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/Menu.js
parent870a70e109ac9e80a88047044530de53d0404ec7 (diff)
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/Menu.js')
-rw-r--r--lib/dijit/Menu.js1009
1 files changed, 700 insertions, 309 deletions
diff --git a/lib/dijit/Menu.js b/lib/dijit/Menu.js
index 83a2ddbad..c5a48660e 100644
--- a/lib/dijit/Menu.js
+++ b/lib/dijit/Menu.js
@@ -1,325 +1,716 @@
/*
- Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved.
+ Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
-if(!dojo._hasResource["dijit.Menu"]){
-dojo._hasResource["dijit.Menu"]=true;
+if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dijit.Menu"] = true;
dojo.provide("dijit.Menu");
dojo.require("dojo.window");
dojo.require("dijit._Widget");
dojo.require("dijit._KeyNavContainer");
dojo.require("dijit._Templated");
-dojo.declare("dijit._MenuBase",[dijit._Widget,dijit._Templated,dijit._KeyNavContainer],{parentMenu:null,popupDelay:500,startup:function(){
-if(this._started){
-return;
-}
-dojo.forEach(this.getChildren(),function(_1){
-_1.startup();
-});
-this.startupKeyNavChildren();
-this.inherited(arguments);
-},onExecute:function(){
-},onCancel:function(_2){
-},_moveToPopup:function(_3){
-if(this.focusedChild&&this.focusedChild.popup&&!this.focusedChild.disabled){
-this.focusedChild._onClick(_3);
-}else{
-var _4=this._getTopMenu();
-if(_4&&_4._isMenuBar){
-_4.focusNext();
-}
-}
-},_onPopupHover:function(_5){
-if(this.currentPopup&&this.currentPopup._pendingClose_timer){
-var _6=this.currentPopup.parentMenu;
-if(_6.focusedChild){
-_6.focusedChild._setSelected(false);
-}
-_6.focusedChild=this.currentPopup.from_item;
-_6.focusedChild._setSelected(true);
-this._stopPendingCloseTimer(this.currentPopup);
-}
-},onItemHover:function(_7){
-if(this.isActive){
-this.focusChild(_7);
-if(this.focusedChild.popup&&!this.focusedChild.disabled&&!this.hover_timer){
-this.hover_timer=setTimeout(dojo.hitch(this,"_openPopup"),this.popupDelay);
-}
-}
-if(this.focusedChild){
-this.focusChild(_7);
-}
-this._hoveredChild=_7;
-},_onChildBlur:function(_8){
-this._stopPopupTimer();
-_8._setSelected(false);
-var _9=_8.popup;
-if(_9){
-this._stopPendingCloseTimer(_9);
-_9._pendingClose_timer=setTimeout(function(){
-_9._pendingClose_timer=null;
-if(_9.parentMenu){
-_9.parentMenu.currentPopup=null;
-}
-dijit.popup.close(_9);
-},this.popupDelay);
-}
-},onItemUnhover:function(_a){
-if(this.isActive){
-this._stopPopupTimer();
-}
-if(this._hoveredChild==_a){
-this._hoveredChild=null;
-}
-},_stopPopupTimer:function(){
-if(this.hover_timer){
-clearTimeout(this.hover_timer);
-this.hover_timer=null;
-}
-},_stopPendingCloseTimer:function(_b){
-if(_b._pendingClose_timer){
-clearTimeout(_b._pendingClose_timer);
-_b._pendingClose_timer=null;
-}
-},_stopFocusTimer:function(){
-if(this._focus_timer){
-clearTimeout(this._focus_timer);
-this._focus_timer=null;
-}
-},_getTopMenu:function(){
-for(var _c=this;_c.parentMenu;_c=_c.parentMenu){
-}
-return _c;
-},onItemClick:function(_d,_e){
-if(typeof this.isShowingNow=="undefined"){
-this._markActive();
-}
-this.focusChild(_d);
-if(_d.disabled){
-return false;
-}
-if(_d.popup){
-this._openPopup();
-}else{
-this.onExecute();
-_d.onClick(_e);
-}
-},_openPopup:function(){
-this._stopPopupTimer();
-var _f=this.focusedChild;
-if(!_f){
-return;
-}
-var _10=_f.popup;
-if(_10.isShowingNow){
-return;
-}
-if(this.currentPopup){
-this._stopPendingCloseTimer(this.currentPopup);
-dijit.popup.close(this.currentPopup);
-}
-_10.parentMenu=this;
-_10.from_item=_f;
-var _11=this;
-dijit.popup.open({parent:this,popup:_10,around:_f.domNode,orient:this._orient||(this.isLeftToRight()?{"TR":"TL","TL":"TR","BR":"BL","BL":"BR"}:{"TL":"TR","TR":"TL","BL":"BR","BR":"BL"}),onCancel:function(){
-_11.focusChild(_f);
-_11._cleanUp();
-_f._setSelected(true);
-_11.focusedChild=_f;
-},onExecute:dojo.hitch(this,"_cleanUp")});
-this.currentPopup=_10;
-_10.connect(_10.domNode,"onmouseenter",dojo.hitch(_11,"_onPopupHover"));
-if(_10.focus){
-_10._focus_timer=setTimeout(dojo.hitch(_10,function(){
-this._focus_timer=null;
-this.focus();
-}),0);
-}
-},_markActive:function(){
-this.isActive=true;
-dojo.addClass(this.domNode,"dijitMenuActive");
-dojo.removeClass(this.domNode,"dijitMenuPassive");
-},onOpen:function(e){
-this.isShowingNow=true;
-this._markActive();
-},_markInactive:function(){
-this.isActive=false;
-dojo.removeClass(this.domNode,"dijitMenuActive");
-dojo.addClass(this.domNode,"dijitMenuPassive");
-},onClose:function(){
-this._stopFocusTimer();
-this._markInactive();
-this.isShowingNow=false;
-this.parentMenu=null;
-},_closeChild:function(){
-this._stopPopupTimer();
-if(this.focusedChild){
-this.focusedChild._setSelected(false);
-this.focusedChild._onUnhover();
-this.focusedChild=null;
-}
-if(this.currentPopup){
-dijit.popup.close(this.currentPopup);
-this.currentPopup=null;
-}
-},_onItemFocus:function(_12){
-if(this._hoveredChild&&this._hoveredChild!=_12){
-this._hoveredChild._onUnhover();
-}
-},_onBlur:function(){
-this._cleanUp();
-this.inherited(arguments);
-},_cleanUp:function(){
-this._closeChild();
-if(typeof this.isShowingNow=="undefined"){
-this._markInactive();
-}
-}});
-dojo.declare("dijit.Menu",dijit._MenuBase,{constructor:function(){
-this._bindings=[];
-},templateString:dojo.cache("dijit","templates/Menu.html","<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" waiRole=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=0>\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),baseClass:"dijitMenu",targetNodeIds:[],contextMenuForWindow:false,leftClickToOpen:false,refocus:true,postCreate:function(){
-if(this.contextMenuForWindow){
-this.bindDomNode(dojo.body());
-}else{
-dojo.forEach(this.targetNodeIds,this.bindDomNode,this);
-}
-var k=dojo.keys,l=this.isLeftToRight();
-this._openSubMenuKey=l?k.RIGHT_ARROW:k.LEFT_ARROW;
-this._closeSubMenuKey=l?k.LEFT_ARROW:k.RIGHT_ARROW;
-this.connectKeyNavHandlers([k.UP_ARROW],[k.DOWN_ARROW]);
-},_onKeyPress:function(evt){
-if(evt.ctrlKey||evt.altKey){
-return;
-}
-switch(evt.charOrCode){
-case this._openSubMenuKey:
-this._moveToPopup(evt);
-dojo.stopEvent(evt);
-break;
-case this._closeSubMenuKey:
-if(this.parentMenu){
-if(this.parentMenu._isMenuBar){
-this.parentMenu.focusPrev();
-}else{
-this.onCancel(false);
-}
-}else{
-dojo.stopEvent(evt);
-}
-break;
-}
-},_iframeContentWindow:function(_13){
-var win=dojo.window.get(this._iframeContentDocument(_13))||this._iframeContentDocument(_13)["__parent__"]||(_13.name&&dojo.doc.frames[_13.name])||null;
-return win;
-},_iframeContentDocument:function(_14){
-var doc=_14.contentDocument||(_14.contentWindow&&_14.contentWindow.document)||(_14.name&&dojo.doc.frames[_14.name]&&dojo.doc.frames[_14.name].document)||null;
-return doc;
-},bindDomNode:function(_15){
-_15=dojo.byId(_15);
-var cn;
-if(_15.tagName.toLowerCase()=="iframe"){
-var _16=_15,win=this._iframeContentWindow(_16);
-cn=dojo.withGlobal(win,dojo.body);
-}else{
-cn=(_15==dojo.body()?dojo.doc.documentElement:_15);
-}
-var _17={node:_15,iframe:_16};
-dojo.attr(_15,"_dijitMenu"+this.id,this._bindings.push(_17));
-var _18=dojo.hitch(this,function(cn){
-return [dojo.connect(cn,this.leftClickToOpen?"onclick":"oncontextmenu",this,function(evt){
-dojo.stopEvent(evt);
-this._scheduleOpen(evt.target,_16,{x:evt.pageX,y:evt.pageY});
-}),dojo.connect(cn,"onkeydown",this,function(evt){
-if(evt.shiftKey&&evt.keyCode==dojo.keys.F10){
-dojo.stopEvent(evt);
-this._scheduleOpen(evt.target,_16);
-}
-})];
-});
-_17.connects=cn?_18(cn):[];
-if(_16){
-_17.onloadHandler=dojo.hitch(this,function(){
-var win=this._iframeContentWindow(_16);
-cn=dojo.withGlobal(win,dojo.body);
-_17.connects=_18(cn);
-});
-if(_16.addEventListener){
-_16.addEventListener("load",_17.onloadHandler,false);
-}else{
-_16.attachEvent("onload",_17.onloadHandler);
-}
-}
-},unBindDomNode:function(_19){
-var _1a;
-try{
-_1a=dojo.byId(_19);
-}
-catch(e){
-return;
-}
-var _1b="_dijitMenu"+this.id;
-if(_1a&&dojo.hasAttr(_1a,_1b)){
-var bid=dojo.attr(_1a,_1b)-1,b=this._bindings[bid];
-dojo.forEach(b.connects,dojo.disconnect);
-var _1c=b.iframe;
-if(_1c){
-if(_1c.removeEventListener){
-_1c.removeEventListener("load",b.onloadHandler,false);
-}else{
-_1c.detachEvent("onload",b.onloadHandler);
-}
-}
-dojo.removeAttr(_1a,_1b);
-delete this._bindings[bid];
-}
-},_scheduleOpen:function(_1d,_1e,_1f){
-if(!this._openTimer){
-this._openTimer=setTimeout(dojo.hitch(this,function(){
-delete this._openTimer;
-this._openMyself({target:_1d,iframe:_1e,coords:_1f});
-}),1);
-}
-},_openMyself:function(_20){
-var _21=_20.target,_22=_20.iframe,_23=_20.coords;
-if(_23){
-if(_22){
-var od=_21.ownerDocument,ifc=dojo.position(_22,true),win=this._iframeContentWindow(_22),_24=dojo.withGlobal(win,"_docScroll",dojo);
-var cs=dojo.getComputedStyle(_22),tp=dojo._toPixelValue,_25=(dojo.isIE&&dojo.isQuirks?0:tp(_22,cs.paddingLeft))+(dojo.isIE&&dojo.isQuirks?tp(_22,cs.borderLeftWidth):0),top=(dojo.isIE&&dojo.isQuirks?0:tp(_22,cs.paddingTop))+(dojo.isIE&&dojo.isQuirks?tp(_22,cs.borderTopWidth):0);
-_23.x+=ifc.x+_25-_24.x;
-_23.y+=ifc.y+top-_24.y;
-}
-}else{
-_23=dojo.position(_21,true);
-_23.x+=10;
-_23.y+=10;
-}
-var _26=this;
-var _27=dijit.getFocus(this);
-function _28(){
-if(_26.refocus){
-dijit.focus(_27);
-}
-dijit.popup.close(_26);
-};
-dijit.popup.open({popup:this,x:_23.x,y:_23.y,onExecute:_28,onCancel:_28,orient:this.isLeftToRight()?"L":"R"});
-this.focus();
-this._onBlur=function(){
-this.inherited("_onBlur",arguments);
-dijit.popup.close(this);
-};
-},uninitialize:function(){
-dojo.forEach(this._bindings,function(b){
-if(b){
-this.unBindDomNode(b.node);
-}
-},this);
-this.inherited(arguments);
-}});
dojo.require("dijit.MenuItem");
dojo.require("dijit.PopupMenuItem");
dojo.require("dijit.CheckedMenuItem");
dojo.require("dijit.MenuSeparator");
+
+
+// "dijit/MenuItem", "dijit/PopupMenuItem", "dijit/CheckedMenuItem", "dijit/MenuSeparator" for Back-compat (TODO: remove in 2.0)
+
+dojo.declare("dijit._MenuBase",
+ [dijit._Widget, dijit._Templated, dijit._KeyNavContainer],
+{
+ // summary:
+ // Base class for Menu and MenuBar
+
+ // parentMenu: [readonly] Widget
+ // pointer to menu that displayed me
+ parentMenu: null,
+
+ // popupDelay: Integer
+ // number of milliseconds before hovering (without clicking) causes the popup to automatically open.
+ popupDelay: 500,
+
+ startup: function(){
+ if(this._started){ return; }
+
+ dojo.forEach(this.getChildren(), function(child){ child.startup(); });
+ this.startupKeyNavChildren();
+
+ this.inherited(arguments);
+ },
+
+ onExecute: function(){
+ // summary:
+ // Attach point for notification about when a menu item has been executed.
+ // This is an internal mechanism used for Menus to signal to their parent to
+ // close them, because they are about to execute the onClick handler. In
+ // general developers should not attach to or override this method.
+ // tags:
+ // protected
+ },
+
+ onCancel: function(/*Boolean*/ closeAll){
+ // summary:
+ // Attach point for notification about when the user cancels the current menu
+ // This is an internal mechanism used for Menus to signal to their parent to
+ // close them. In general developers should not attach to or override this method.
+ // tags:
+ // protected
+ },
+
+ _moveToPopup: function(/*Event*/ evt){
+ // summary:
+ // This handles the right arrow key (left arrow key on RTL systems),
+ // which will either open a submenu, or move to the next item in the
+ // ancestor MenuBar
+ // tags:
+ // private
+
+ if(this.focusedChild && this.focusedChild.popup && !this.focusedChild.disabled){
+ this.focusedChild._onClick(evt);
+ }else{
+ var topMenu = this._getTopMenu();
+ if(topMenu && topMenu._isMenuBar){
+ topMenu.focusNext();
+ }
+ }
+ },
+
+ _onPopupHover: function(/*Event*/ evt){
+ // summary:
+ // This handler is called when the mouse moves over the popup.
+ // tags:
+ // private
+
+ // if the mouse hovers over a menu popup that is in pending-close state,
+ // then stop the close operation.
+ // This can't be done in onItemHover since some popup targets don't have MenuItems (e.g. ColorPicker)
+ if(this.currentPopup && this.currentPopup._pendingClose_timer){
+ var parentMenu = this.currentPopup.parentMenu;
+ // highlight the parent menu item pointing to this popup
+ if(parentMenu.focusedChild){
+ parentMenu.focusedChild._setSelected(false);
+ }
+ parentMenu.focusedChild = this.currentPopup.from_item;
+ parentMenu.focusedChild._setSelected(true);
+ // cancel the pending close
+ this._stopPendingCloseTimer(this.currentPopup);
+ }
+ },
+
+ onItemHover: function(/*MenuItem*/ item){
+ // summary:
+ // Called when cursor is over a MenuItem.
+ // tags:
+ // protected
+
+ // Don't do anything unless user has "activated" the menu by:
+ // 1) clicking it
+ // 2) opening it from a parent menu (which automatically focuses it)
+ if(this.isActive){
+ this.focusChild(item);
+ if(this.focusedChild.popup && !this.focusedChild.disabled && !this.hover_timer){
+ this.hover_timer = setTimeout(dojo.hitch(this, "_openPopup"), this.popupDelay);
+ }
+ }
+ // if the user is mixing mouse and keyboard navigation,
+ // then the menu may not be active but a menu item has focus,
+ // but it's not the item that the mouse just hovered over.
+ // To avoid both keyboard and mouse selections, use the latest.
+ if(this.focusedChild){
+ this.focusChild(item);
+ }
+ this._hoveredChild = item;
+ },
+
+ _onChildBlur: function(item){
+ // summary:
+ // Called when a child MenuItem becomes inactive because focus
+ // has been removed from the MenuItem *and* it's descendant menus.
+ // tags:
+ // private
+ this._stopPopupTimer();
+ item._setSelected(false);
+ // Close all popups that are open and descendants of this menu
+ var itemPopup = item.popup;
+ if(itemPopup){
+ this._stopPendingCloseTimer(itemPopup);
+ itemPopup._pendingClose_timer = setTimeout(function(){
+ itemPopup._pendingClose_timer = null;
+ if(itemPopup.parentMenu){
+ itemPopup.parentMenu.currentPopup = null;
+ }
+ dijit.popup.close(itemPopup); // this calls onClose
+ }, this.popupDelay);
+ }
+ },
+
+ onItemUnhover: function(/*MenuItem*/ item){
+ // summary:
+ // Callback fires when mouse exits a MenuItem
+ // tags:
+ // protected
+
+ if(this.isActive){
+ this._stopPopupTimer();
+ }
+ if(this._hoveredChild == item){ this._hoveredChild = null; }
+ },
+
+ _stopPopupTimer: function(){
+ // summary:
+ // Cancels the popup timer because the user has stop hovering
+ // on the MenuItem, etc.
+ // tags:
+ // private
+ if(this.hover_timer){
+ clearTimeout(this.hover_timer);
+ this.hover_timer = null;
+ }
+ },
+
+ _stopPendingCloseTimer: function(/*dijit._Widget*/ popup){
+ // summary:
+ // Cancels the pending-close timer because the close has been preempted
+ // tags:
+ // private
+ if(popup._pendingClose_timer){
+ clearTimeout(popup._pendingClose_timer);
+ popup._pendingClose_timer = null;
+ }
+ },
+
+ _stopFocusTimer: function(){
+ // summary:
+ // Cancels the pending-focus timer because the menu was closed before focus occured
+ // tags:
+ // private
+ if(this._focus_timer){
+ clearTimeout(this._focus_timer);
+ this._focus_timer = null;
+ }
+ },
+
+ _getTopMenu: function(){
+ // summary:
+ // Returns the top menu in this chain of Menus
+ // tags:
+ // private
+ for(var top=this; top.parentMenu; top=top.parentMenu);
+ return top;
+ },
+
+ onItemClick: function(/*dijit._Widget*/ item, /*Event*/ evt){
+ // summary:
+ // Handle clicks on an item.
+ // tags:
+ // private
+
+ // this can't be done in _onFocus since the _onFocus events occurs asynchronously
+ if(typeof this.isShowingNow == 'undefined'){ // non-popup menu
+ this._markActive();
+ }
+
+ this.focusChild(item);
+
+ if(item.disabled){ return false; }
+
+ if(item.popup){
+ this._openPopup();
+ }else{
+ // before calling user defined handler, close hierarchy of menus
+ // and restore focus to place it was when menu was opened
+ this.onExecute();
+
+ // user defined handler for click
+ item.onClick(evt);
+ }
+ },
+
+ _openPopup: function(){
+ // summary:
+ // Open the popup to the side of/underneath the current menu item
+ // tags:
+ // protected
+
+ this._stopPopupTimer();
+ var from_item = this.focusedChild;
+ if(!from_item){ return; } // the focused child lost focus since the timer was started
+ var popup = from_item.popup;
+ if(popup.isShowingNow){ return; }
+ if(this.currentPopup){
+ this._stopPendingCloseTimer(this.currentPopup);
+ dijit.popup.close(this.currentPopup);
+ }
+ popup.parentMenu = this;
+ popup.from_item = from_item; // helps finding the parent item that should be focused for this popup
+ var self = this;
+ dijit.popup.open({
+ parent: this,
+ popup: popup,
+ around: from_item.domNode,
+ orient: this._orient || (this.isLeftToRight() ?
+ {'TR': 'TL', 'TL': 'TR', 'BR': 'BL', 'BL': 'BR'} :
+ {'TL': 'TR', 'TR': 'TL', 'BL': 'BR', 'BR': 'BL'}),
+ onCancel: function(){ // called when the child menu is canceled
+ // set isActive=false (_closeChild vs _cleanUp) so that subsequent hovering will NOT open child menus
+ // which seems aligned with the UX of most applications (e.g. notepad, wordpad, paint shop pro)
+ self.focusChild(from_item); // put focus back on my node
+ self._cleanUp(); // close the submenu (be sure this is done _after_ focus is moved)
+ from_item._setSelected(true); // oops, _cleanUp() deselected the item
+ self.focusedChild = from_item; // and unset focusedChild
+ },
+ onExecute: dojo.hitch(this, "_cleanUp")
+ });
+
+ this.currentPopup = popup;
+ // detect mouseovers to handle lazy mouse movements that temporarily focus other menu items
+ popup.connect(popup.domNode, "onmouseenter", dojo.hitch(self, "_onPopupHover")); // cleaned up when the popped-up widget is destroyed on close
+
+ if(popup.focus){
+ // If user is opening the popup via keyboard (right arrow, or down arrow for MenuBar),
+ // if the cursor happens to collide with the popup, it will generate an onmouseover event
+ // even though the mouse wasn't moved. Use a setTimeout() to call popup.focus so that
+ // our focus() call overrides the onmouseover event, rather than vice-versa. (#8742)
+ popup._focus_timer = setTimeout(dojo.hitch(popup, function(){
+ this._focus_timer = null;
+ this.focus();
+ }), 0);
+ }
+ },
+
+ _markActive: function(){
+ // summary:
+ // Mark this menu's state as active.
+ // Called when this Menu gets focus from:
+ // 1) clicking it (mouse or via space/arrow key)
+ // 2) being opened by a parent menu.
+ // This is not called just from mouse hover.
+ // Focusing a menu via TAB does NOT automatically set isActive
+ // since TAB is a navigation operation and not a selection one.
+ // For Windows apps, pressing the ALT key focuses the menubar
+ // menus (similar to TAB navigation) but the menu is not active
+ // (ie no dropdown) until an item is clicked.
+ this.isActive = true;
+ dojo.replaceClass(this.domNode, "dijitMenuActive", "dijitMenuPassive");
+ },
+
+ onOpen: function(/*Event*/ e){
+ // summary:
+ // Callback when this menu is opened.
+ // This is called by the popup manager as notification that the menu
+ // was opened.
+ // tags:
+ // private
+
+ this.isShowingNow = true;
+ this._markActive();
+ },
+
+ _markInactive: function(){
+ // summary:
+ // Mark this menu's state as inactive.
+ this.isActive = false; // don't do this in _onBlur since the state is pending-close until we get here
+ dojo.replaceClass(this.domNode, "dijitMenuPassive", "dijitMenuActive");
+ },
+
+ onClose: function(){
+ // summary:
+ // Callback when this menu is closed.
+ // This is called by the popup manager as notification that the menu
+ // was closed.
+ // tags:
+ // private
+
+ this._stopFocusTimer();
+ this._markInactive();
+ this.isShowingNow = false;
+ this.parentMenu = null;
+ },
+
+ _closeChild: function(){
+ // summary:
+ // Called when submenu is clicked or focus is lost. Close hierarchy of menus.
+ // tags:
+ // private
+ this._stopPopupTimer();
+
+ var fromItem = this.focusedChild && this.focusedChild.from_item;
+
+ if(this.currentPopup){
+ // If focus is on my child menu then move focus to me,
+ // because IE doesn't like it when you display:none a node with focus
+ if(dijit._curFocus && dojo.isDescendant(dijit._curFocus, this.currentPopup.domNode)){
+ this.focusedChild.focusNode.focus();
+ }
+ // Close all popups that are open and descendants of this menu
+ dijit.popup.close(this.currentPopup);
+ this.currentPopup = null;
+ }
+
+ if(this.focusedChild){ // unhighlight the focused item
+ this.focusedChild._setSelected(false);
+ this.focusedChild._onUnhover();
+ this.focusedChild = null;
+ }
+ },
+
+ _onItemFocus: function(/*MenuItem*/ item){
+ // summary:
+ // Called when child of this Menu gets focus from:
+ // 1) clicking it
+ // 2) tabbing into it
+ // 3) being opened by a parent menu.
+ // This is not called just from mouse hover.
+ if(this._hoveredChild && this._hoveredChild != item){
+ this._hoveredChild._onUnhover(); // any previous mouse movement is trumped by focus selection
+ }
+ },
+
+ _onBlur: function(){
+ // summary:
+ // Called when focus is moved away from this Menu and it's submenus.
+ // tags:
+ // protected
+ this._cleanUp();
+ this.inherited(arguments);
+ },
+
+ _cleanUp: function(){
+ // summary:
+ // Called when the user is done with this menu. Closes hierarchy of menus.
+ // tags:
+ // private
+
+ this._closeChild(); // don't call this.onClose since that's incorrect for MenuBar's that never close
+ if(typeof this.isShowingNow == 'undefined'){ // non-popup menu doesn't call onClose
+ this._markInactive();
+ }
+ }
+});
+
+dojo.declare("dijit.Menu",
+ dijit._MenuBase,
+ {
+ // summary
+ // A context menu you can assign to multiple elements
+
+ // TODO: most of the code in here is just for context menu (right-click menu)
+ // support. In retrospect that should have been a separate class (dijit.ContextMenu).
+ // Split them for 2.0
+
+ constructor: function(){
+ this._bindings = [];
+ },
+
+ templateString: dojo.cache("dijit", "templates/Menu.html", "<table class=\"dijit dijitMenu dijitMenuPassive dijitReset dijitMenuTable\" role=\"menu\" tabIndex=\"${tabIndex}\" dojoAttachEvent=\"onkeypress:_onKeyPress\" cellspacing=\"0\">\n\t<tbody class=\"dijitReset\" dojoAttachPoint=\"containerNode\"></tbody>\n</table>\n"),
+
+ baseClass: "dijitMenu",
+
+ // targetNodeIds: [const] String[]
+ // Array of dom node ids of nodes to attach to.
+ // Fill this with nodeIds upon widget creation and it becomes context menu for those nodes.
+ targetNodeIds: [],
+
+ // contextMenuForWindow: [const] Boolean
+ // If true, right clicking anywhere on the window will cause this context menu to open.
+ // If false, must specify targetNodeIds.
+ contextMenuForWindow: false,
+
+ // leftClickToOpen: [const] Boolean
+ // If true, menu will open on left click instead of right click, similiar to a file menu.
+ leftClickToOpen: false,
+
+ // refocus: Boolean
+ // When this menu closes, re-focus the element which had focus before it was opened.
+ refocus: true,
+
+ postCreate: function(){
+ if(this.contextMenuForWindow){
+ this.bindDomNode(dojo.body());
+ }else{
+ // TODO: should have _setTargetNodeIds() method to handle initialization and a possible
+ // later set('targetNodeIds', ...) call. There's also a problem that targetNodeIds[]
+ // gets stale after calls to bindDomNode()/unBindDomNode() as it still is just the original list (see #9610)
+ dojo.forEach(this.targetNodeIds, this.bindDomNode, this);
+ }
+ var k = dojo.keys, l = this.isLeftToRight();
+ this._openSubMenuKey = l ? k.RIGHT_ARROW : k.LEFT_ARROW;
+ this._closeSubMenuKey = l ? k.LEFT_ARROW : k.RIGHT_ARROW;
+ this.connectKeyNavHandlers([k.UP_ARROW], [k.DOWN_ARROW]);
+ },
+
+ _onKeyPress: function(/*Event*/ evt){
+ // summary:
+ // Handle keyboard based menu navigation.
+ // tags:
+ // protected
+
+ if(evt.ctrlKey || evt.altKey){ return; }
+
+ switch(evt.charOrCode){
+ case this._openSubMenuKey:
+ this._moveToPopup(evt);
+ dojo.stopEvent(evt);
+ break;
+ case this._closeSubMenuKey:
+ if(this.parentMenu){
+ if(this.parentMenu._isMenuBar){
+ this.parentMenu.focusPrev();
+ }else{
+ this.onCancel(false);
+ }
+ }else{
+ dojo.stopEvent(evt);
+ }
+ break;
+ }
+ },
+
+ // thanks burstlib!
+ _iframeContentWindow: function(/* HTMLIFrameElement */iframe_el){
+ // summary:
+ // Returns the window reference of the passed iframe
+ // tags:
+ // private
+ var win = dojo.window.get(this._iframeContentDocument(iframe_el)) ||
+ // Moz. TODO: is this available when defaultView isn't?
+ this._iframeContentDocument(iframe_el)['__parent__'] ||
+ (iframe_el.name && dojo.doc.frames[iframe_el.name]) || null;
+ return win; // Window
+ },
+
+ _iframeContentDocument: function(/* HTMLIFrameElement */iframe_el){
+ // summary:
+ // Returns a reference to the document object inside iframe_el
+ // tags:
+ // protected
+ var doc = iframe_el.contentDocument // W3
+ || (iframe_el.contentWindow && iframe_el.contentWindow.document) // IE
+ || (iframe_el.name && dojo.doc.frames[iframe_el.name] && dojo.doc.frames[iframe_el.name].document)
+ || null;
+ return doc; // HTMLDocument
+ },
+
+ bindDomNode: function(/*String|DomNode*/ node){
+ // summary:
+ // Attach menu to given node
+ node = dojo.byId(node);
+
+ var cn; // Connect node
+
+ // Support context menus on iframes. Rather than binding to the iframe itself we need
+ // to bind to the <body> node inside the iframe.
+ if(node.tagName.toLowerCase() == "iframe"){
+ var iframe = node,
+ win = this._iframeContentWindow(iframe);
+ cn = dojo.withGlobal(win, dojo.body);
+ }else{
+
+ // To capture these events at the top level, attach to <html>, not <body>.
+ // Otherwise right-click context menu just doesn't work.
+ cn = (node == dojo.body() ? dojo.doc.documentElement : node);
+ }
+
+
+ // "binding" is the object to track our connection to the node (ie, the parameter to bindDomNode())
+ var binding = {
+ node: node,
+ iframe: iframe
+ };
+
+ // Save info about binding in _bindings[], and make node itself record index(+1) into
+ // _bindings[] array. Prefix w/_dijitMenu to avoid setting an attribute that may
+ // start with a number, which fails on FF/safari.
+ dojo.attr(node, "_dijitMenu" + this.id, this._bindings.push(binding));
+
+ // Setup the connections to monitor click etc., unless we are connecting to an iframe which hasn't finished
+ // loading yet, in which case we need to wait for the onload event first, and then connect
+ // On linux Shift-F10 produces the oncontextmenu event, but on Windows it doesn't, so
+ // we need to monitor keyboard events in addition to the oncontextmenu event.
+ var doConnects = dojo.hitch(this, function(cn){
+ return [
+ // TODO: when leftClickToOpen is true then shouldn't space/enter key trigger the menu,
+ // rather than shift-F10?
+ dojo.connect(cn, this.leftClickToOpen ? "onclick" : "oncontextmenu", this, function(evt){
+ // Schedule context menu to be opened unless it's already been scheduled from onkeydown handler
+ dojo.stopEvent(evt);
+ this._scheduleOpen(evt.target, iframe, {x: evt.pageX, y: evt.pageY});
+ }),
+ dojo.connect(cn, "onkeydown", this, function(evt){
+ if(evt.shiftKey && evt.keyCode == dojo.keys.F10){
+ dojo.stopEvent(evt);
+ this._scheduleOpen(evt.target, iframe); // no coords - open near target node
+ }
+ })
+ ];
+ });
+ binding.connects = cn ? doConnects(cn) : [];
+
+ if(iframe){
+ // Setup handler to [re]bind to the iframe when the contents are initially loaded,
+ // and every time the contents change.
+ // Need to do this b/c we are actually binding to the iframe's <body> node.
+ // Note: can't use dojo.connect(), see #9609.
+
+ binding.onloadHandler = dojo.hitch(this, function(){
+ // want to remove old connections, but IE throws exceptions when trying to
+ // access the <body> node because it's already gone, or at least in a state of limbo
+
+ var win = this._iframeContentWindow(iframe);
+ cn = dojo.withGlobal(win, dojo.body);
+ binding.connects = doConnects(cn);
+ });
+ if(iframe.addEventListener){
+ iframe.addEventListener("load", binding.onloadHandler, false);
+ }else{
+ iframe.attachEvent("onload", binding.onloadHandler);
+ }
+ }
+ },
+
+ unBindDomNode: function(/*String|DomNode*/ nodeName){
+ // summary:
+ // Detach menu from given node
+
+ var node;
+ try{
+ node = dojo.byId(nodeName);
+ }catch(e){
+ // On IE the dojo.byId() call will get an exception if the attach point was
+ // the <body> node of an <iframe> that has since been reloaded (and thus the
+ // <body> node is in a limbo state of destruction.
+ return;
+ }
+
+ // node["_dijitMenu" + this.id] contains index(+1) into my _bindings[] array
+ var attrName = "_dijitMenu" + this.id;
+ if(node && dojo.hasAttr(node, attrName)){
+ var bid = dojo.attr(node, attrName)-1, b = this._bindings[bid];
+ dojo.forEach(b.connects, dojo.disconnect);
+
+ // Remove listener for iframe onload events
+ var iframe = b.iframe;
+ if(iframe){
+ if(iframe.removeEventListener){
+ iframe.removeEventListener("load", b.onloadHandler, false);
+ }else{
+ iframe.detachEvent("onload", b.onloadHandler);
+ }
+ }
+
+ dojo.removeAttr(node, attrName);
+ delete this._bindings[bid];
+ }
+ },
+
+ _scheduleOpen: function(/*DomNode?*/ target, /*DomNode?*/ iframe, /*Object?*/ coords){
+ // summary:
+ // Set timer to display myself. Using a timer rather than displaying immediately solves
+ // two problems:
+ //
+ // 1. IE: without the delay, focus work in "open" causes the system
+ // context menu to appear in spite of stopEvent.
+ //
+ // 2. Avoid double-shows on linux, where shift-F10 generates an oncontextmenu event
+ // even after a dojo.stopEvent(e). (Shift-F10 on windows doesn't generate the
+ // oncontextmenu event.)
+
+ if(!this._openTimer){
+ this._openTimer = setTimeout(dojo.hitch(this, function(){
+ delete this._openTimer;
+ this._openMyself({
+ target: target,
+ iframe: iframe,
+ coords: coords
+ });
+ }), 1);
+ }
+ },
+
+ _openMyself: function(args){
+ // summary:
+ // Internal function for opening myself when the user does a right-click or something similar.
+ // args:
+ // This is an Object containing:
+ // * target:
+ // The node that is being clicked
+ // * iframe:
+ // If an <iframe> is being clicked, iframe points to that iframe
+ // * coords:
+ // Put menu at specified x/y position in viewport, or if iframe is
+ // specified, then relative to iframe.
+ //
+ // _openMyself() formerly took the event object, and since various code references
+ // evt.target (after connecting to _openMyself()), using an Object for parameters
+ // (so that old code still works).
+
+ var target = args.target,
+ iframe = args.iframe,
+ coords = args.coords;
+
+ // Get coordinates to open menu, either at specified (mouse) position or (if triggered via keyboard)
+ // then near the node the menu is assigned to.
+ if(coords){
+ if(iframe){
+ // Specified coordinates are on <body> node of an <iframe>, convert to match main document
+ var od = target.ownerDocument,
+ ifc = dojo.position(iframe, true),
+ win = this._iframeContentWindow(iframe),
+ scroll = dojo.withGlobal(win, "_docScroll", dojo);
+
+ var cs = dojo.getComputedStyle(iframe),
+ tp = dojo._toPixelValue,
+ left = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingLeft)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderLeftWidth) : 0),
+ top = (dojo.isIE && dojo.isQuirks ? 0 : tp(iframe, cs.paddingTop)) + (dojo.isIE && dojo.isQuirks ? tp(iframe, cs.borderTopWidth) : 0);
+
+ coords.x += ifc.x + left - scroll.x;
+ coords.y += ifc.y + top - scroll.y;
+ }
+ }else{
+ coords = dojo.position(target, true);
+ coords.x += 10;
+ coords.y += 10;
+ }
+
+ var self=this;
+ var savedFocus = dijit.getFocus(this);
+ function closeAndRestoreFocus(){
+ // user has clicked on a menu or popup
+ if(self.refocus){
+ dijit.focus(savedFocus);
+ }
+ dijit.popup.close(self);
+ }
+ dijit.popup.open({
+ popup: this,
+ x: coords.x,
+ y: coords.y,
+ onExecute: closeAndRestoreFocus,
+ onCancel: closeAndRestoreFocus,
+ orient: this.isLeftToRight() ? 'L' : 'R'
+ });
+ this.focus();
+
+ this._onBlur = function(){
+ this.inherited('_onBlur', arguments);
+ // Usually the parent closes the child widget but if this is a context
+ // menu then there is no parent
+ dijit.popup.close(this);
+ // don't try to restore focus; user has clicked another part of the screen
+ // and set focus there
+ };
+ },
+
+ uninitialize: function(){
+ dojo.forEach(this._bindings, function(b){ if(b){ this.unBindDomNode(b.node); } }, this);
+ this.inherited(arguments);
+ }
+}
+);
+
}