2010-11-15 08:39:52 +01:00
|
|
|
/*
|
2011-11-08 17:40:44 +01:00
|
|
|
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
|
2010-11-15 08:39:52 +01:00
|
|
|
Available via Academic Free License >= 2.1 OR the modified BSD license.
|
|
|
|
see: http://dojotoolkit.org/license for details
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2011-11-08 17:40:44 +01:00
|
|
|
if(!dojo._hasResource["dijit.Menu"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
|
dojo._hasResource["dijit.Menu"] = true;
|
2010-11-15 08:39:52 +01:00
|
|
|
dojo.provide("dijit.Menu");
|
|
|
|
dojo.require("dojo.window");
|
|
|
|
dojo.require("dijit._Widget");
|
|
|
|
dojo.require("dijit._KeyNavContainer");
|
|
|
|
dojo.require("dijit._Templated");
|
|
|
|
dojo.require("dijit.MenuItem");
|
|
|
|
dojo.require("dijit.PopupMenuItem");
|
|
|
|
dojo.require("dijit.CheckedMenuItem");
|
|
|
|
dojo.require("dijit.MenuSeparator");
|
2011-11-08 17:40:44 +01:00
|
|
|
|
|
|
|
|
|
|
|
// "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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2010-11-15 08:39:52 +01:00
|
|
|
}
|