8398c9048d
code was modified slightly, so the code differs from the original downloadable 1.9.5 version
491 lines
13 KiB
JavaScript
491 lines
13 KiB
JavaScript
dojo.provide("dijit.Menu");
|
|
|
|
dojo.require("dijit._Widget");
|
|
dojo.require("dijit._KeyNavContainer");
|
|
dojo.require("dijit._Templated");
|
|
|
|
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();
|
|
}
|
|
}
|
|
},
|
|
|
|
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) tabbing into it
|
|
// 3) 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);
|
|
}
|
|
}
|
|
},
|
|
|
|
_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
|
|
|
|
item._setSelected(false);
|
|
|
|
// Close all popups that are open and descendants of this menu
|
|
dijit.popup.close(item.popup);
|
|
this._stopPopupTimer();
|
|
},
|
|
|
|
onItemUnhover: function(/*MenuItem*/ item){
|
|
// summary:
|
|
// Callback fires when mouse exits a MenuItem
|
|
// tags:
|
|
// protected
|
|
if(this.isActive){
|
|
this._stopPopupTimer();
|
|
}
|
|
},
|
|
|
|
_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;
|
|
}
|
|
},
|
|
|
|
_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(/*Widget*/ item, /*Event*/ evt){
|
|
// summary:
|
|
// Handle clicks on an item.
|
|
// tags:
|
|
// private
|
|
if(item.disabled){ return false; }
|
|
|
|
this.focusChild(item);
|
|
|
|
if(item.popup){
|
|
if(!this.is_open){
|
|
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;
|
|
var popup = from_item.popup;
|
|
|
|
if(popup.isShowingNow){ return; }
|
|
popup.parentMenu = this;
|
|
var self = this;
|
|
dijit.popup.open({
|
|
parent: this,
|
|
popup: popup,
|
|
around: from_item.domNode,
|
|
orient: this._orient || (this.isLeftToRight() ? {'TR': 'TL', 'TL': 'TR'} : {'TL': 'TR', 'TR': 'TL'}),
|
|
onCancel: function(){
|
|
// called when the child menu is canceled
|
|
dijit.popup.close(popup);
|
|
from_item.focus(); // put focus back on my node
|
|
self.currentPopup = null;
|
|
},
|
|
onExecute: dojo.hitch(this, "_onDescendantExecute")
|
|
});
|
|
|
|
this.currentPopup = popup;
|
|
|
|
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)
|
|
setTimeout(dojo.hitch(popup, "focus"), 0);
|
|
}
|
|
},
|
|
|
|
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;
|
|
},
|
|
|
|
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._stopPopupTimer();
|
|
this.parentMenu = null;
|
|
this.isShowingNow = false;
|
|
this.currentPopup = null;
|
|
if(this.focusedChild){
|
|
this._onChildBlur(this.focusedChild);
|
|
this.focusedChild = null;
|
|
}
|
|
},
|
|
|
|
_onFocus: function(){
|
|
// summary:
|
|
// Called when 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.
|
|
// tags:
|
|
// protected
|
|
this.isActive = true;
|
|
dojo.addClass(this.domNode, "dijitMenuActive");
|
|
dojo.removeClass(this.domNode, "dijitMenuPassive");
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_onBlur: function(){
|
|
// summary:
|
|
// Called when focus is moved away from this Menu and it's submenus.
|
|
// tags:
|
|
// protected
|
|
this.isActive = false;
|
|
dojo.removeClass(this.domNode, "dijitMenuActive");
|
|
dojo.addClass(this.domNode, "dijitMenuPassive");
|
|
|
|
// If user blurs/clicks away from a MenuBar (or always visible Menu), then close all popped up submenus etc.
|
|
this.onClose();
|
|
|
|
this.inherited(arguments);
|
|
},
|
|
|
|
_onDescendantExecute: function(){
|
|
// summary:
|
|
// Called when submenu is clicked. Close hierarchy of menus.
|
|
// tags:
|
|
// private
|
|
this.onClose();
|
|
}
|
|
});
|
|
|
|
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 = [];
|
|
},
|
|
|
|
templatePath: dojo.moduleUrl("dijit", "templates/Menu.html"),
|
|
|
|
// 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,
|
|
|
|
// _contextMenuWithMouse: [private] Boolean
|
|
// Used to record mouse and keyboard events to determine if a context
|
|
// menu is being opened with the keyboard or the mouse.
|
|
_contextMenuWithMouse: false,
|
|
|
|
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(/*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 = dijit.getDocumentWindow(dijit.Menu._iframeContentDocument(iframe_el)) ||
|
|
// Moz. TODO: is this available when defaultView isn't?
|
|
dijit.Menu._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);
|
|
|
|
//TODO: this is to support context popups in Editor. Maybe this shouldn't be in dijit.Menu
|
|
var win = dijit.getDocumentWindow(node.ownerDocument);
|
|
if(node.tagName.toLowerCase()=="iframe"){
|
|
win = this._iframeContentWindow(node);
|
|
node = dojo.withGlobal(win, dojo.body);
|
|
}
|
|
|
|
// to capture these events at the top level,
|
|
// attach to document, not body
|
|
var cn = (node == dojo.body() ? dojo.doc : node);
|
|
|
|
node[this.id] = this._bindings.push([
|
|
dojo.connect(cn, (this.leftClickToOpen)?"onclick":"oncontextmenu", this, "_openMyself"),
|
|
dojo.connect(cn, "onkeydown", this, "_contextKey"),
|
|
dojo.connect(cn, "onmousedown", this, "_contextMouse")
|
|
]);
|
|
},
|
|
|
|
unBindDomNode: function(/*String|DomNode*/ nodeName){
|
|
// summary:
|
|
// Detach menu from given node
|
|
var node = dojo.byId(nodeName);
|
|
if(node){
|
|
var bid = node[this.id]-1, b = this._bindings[bid];
|
|
dojo.forEach(b, dojo.disconnect);
|
|
delete this._bindings[bid];
|
|
}
|
|
},
|
|
|
|
_contextKey: function(e){
|
|
// summary:
|
|
// Code to handle popping up editor using F10 key rather than mouse
|
|
// tags:
|
|
// private
|
|
this._contextMenuWithMouse = false;
|
|
if(e.keyCode == dojo.keys.F10){
|
|
dojo.stopEvent(e);
|
|
if(e.shiftKey && e.type=="keydown"){
|
|
// FF: copying the wrong property from e will cause the system
|
|
// context menu to appear in spite of stopEvent. Don't know
|
|
// exactly which properties cause this effect.
|
|
var _e = { target: e.target, pageX: e.pageX, pageY: e.pageY };
|
|
_e.preventDefault = _e.stopPropagation = function(){};
|
|
// IE: without the delay, focus work in "open" causes the system
|
|
// context menu to appear in spite of stopEvent.
|
|
window.setTimeout(dojo.hitch(this, function(){ this._openMyself(_e); }), 1);
|
|
}
|
|
}
|
|
},
|
|
|
|
_contextMouse: function(e){
|
|
// summary:
|
|
// Helper to remember when we opened the context menu with the mouse instead
|
|
// of with the keyboard
|
|
// tags:
|
|
// private
|
|
this._contextMenuWithMouse = true;
|
|
},
|
|
|
|
_openMyself: function(/*Event*/ e){
|
|
// summary:
|
|
// Internal function for opening myself when the user
|
|
// does a right-click or something similar
|
|
// tags:
|
|
// private
|
|
|
|
if(this.leftClickToOpen&&e.button>0){
|
|
return;
|
|
}
|
|
dojo.stopEvent(e);
|
|
|
|
// Get coordinates.
|
|
// if we are opening the menu with the mouse or on safari open
|
|
// the menu at the mouse cursor
|
|
// (Safari does not have a keyboard command to open the context menu
|
|
// and we don't currently have a reliable way to determine
|
|
// _contextMenuWithMouse on Safari)
|
|
var x,y;
|
|
if(dojo.isSafari || this._contextMenuWithMouse){
|
|
x=e.pageX;
|
|
y=e.pageY;
|
|
}else{
|
|
// otherwise open near e.target
|
|
var coords = dojo.coords(e.target, true);
|
|
x = coords.x + 10;
|
|
y = coords.y + 10;
|
|
}
|
|
|
|
var self=this;
|
|
var savedFocus = dijit.getFocus(this);
|
|
function closeAndRestoreFocus(){
|
|
// user has clicked on a menu or popup
|
|
dijit.focus(savedFocus);
|
|
dijit.popup.close(self);
|
|
}
|
|
dijit.popup.open({
|
|
popup: this,
|
|
x: x,
|
|
y: 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.targetNodeIds, this.unBindDomNode, this);
|
|
this.inherited(arguments);
|
|
}
|
|
}
|
|
);
|
|
|
|
// Back-compat (TODO: remove in 2.0)
|
|
dojo.require("dijit.MenuItem");
|
|
dojo.require("dijit.PopupMenuItem");
|
|
dojo.require("dijit.CheckedMenuItem");
|
|
dojo.require("dijit.MenuSeparator");
|
|
|