You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by he...@apache.org on 2006/11/13 23:55:14 UTC

svn commit: r474551 [36/49] - in /struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo: ./ src/ src/alg/ src/animation/ src/cal/ src/charting/ src/charting/svg/ src/charting/vml/ src/collections/ src/crypto/ src/data/ src/data/cs...

Modified: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js?view=diff&rev=474551&r1=474550&r2=474551
==============================================================================
--- struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js (original)
+++ struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js Mon Nov 13 14:54:45 2006
@@ -1,5 +1,5 @@
 /*
-	Copyright (c) 2004-2005, The Dojo Foundation
+	Copyright (c) 2004-2006, The Dojo Foundation
 	All Rights Reserved.
 
 	Licensed under the Academic Free License version 2.1 or above OR the
@@ -9,370 +9,335 @@
 */
 
 dojo.provide("dojo.widget.Menu2");
-dojo.provide("dojo.widget.html.Menu2");
-dojo.provide("dojo.widget.PopupMenu2");
-dojo.provide("dojo.widget.MenuItem2");
-
-dojo.require("dojo.html");
-dojo.require("dojo.style");
-dojo.require("dojo.event.*");
-dojo.require("dojo.widget.*");
-dojo.require("dojo.widget.HtmlWidget");
-
-
-dojo.widget.PopupMenu2 = function(){
-	dojo.widget.HtmlWidget.call(this);
-	this.items = [];	// unused???
-	this.targetNodeIds = []; // fill this with nodeIds upon widget creation and it becomes context menu for those nodes
-	this.queueOnAnimationFinish = [];
-}
-
-dojo.inherits(dojo.widget.PopupMenu2, dojo.widget.HtmlWidget);
-
-dojo.lang.extend(dojo.widget.PopupMenu2, {
-	widgetType: "PopupMenu2",
-	isContainer: true,
 
+dojo.require("dojo.widget.PopupContainer");
+
+// summary
+//	provides a menu that can be used as a context menu (typically shown by right-click),
+//	or as the drop down on a DropDownButton, ComboButton, etc.
+dojo.widget.defineWidget(
+	"dojo.widget.PopupMenu2",
+	dojo.widget.PopupContainer,
+	function(){
+		this.targetNodeIds = []; // fill this with nodeIds upon widget creation and it becomes context menu for those nodes
+	
+		this.eventNames =  {
+			open: ""
+		};
+	},
+{
 	snarfChildDomOutput: true,
 
-	currentSubmenu: null,
-	currentSubmenuTrigger: null,
-	parentMenu: null,
-	isShowing: false,
-	menuX: 0,
-	menuY: 0,
-	menuWidth: 0,
-	menuHeight: 0,
-	menuIndex: 0,
-
-	domNode: null,
-	containerNode: null,
-
-	templateString: '<div><div dojoAttachPoint="containerNode"></div></div>',
-	templateCssPath: dojo.uri.dojoUri("src/widget/templates/HtmlMenu2.css"),
-
-	itemHeight: 18,
-	iconGap: 1,
-	accelGap: 10,
-	submenuGap: 2,
-	finalGap: 5,
-	submenuIconSize: 4,
-	separatorHeight: 9,
+	// String
+	//	if "default" event names are based on widget id, otherwise user must define
+	//	TODO: write real documentation about the events
+	eventNaming: "default",
+
+	templateString: '<table class="dojoPopupMenu2" border=0 cellspacing=0 cellpadding=0 style="display: none;"><tbody dojoAttachPoint="containerNode"></tbody></table>',
+	templateCssPath: dojo.uri.dojoUri("src/widget/templates/Menu2.css"),
+	templateCssString: "",
+
+	// Integer
+	//	number of milliseconds before hovering (without clicking) causes the submenu to automatically open
 	submenuDelay: 500,
+	
+	// Integer
+	//	a submenu usually appears to the right, but slightly overlapping, it's parent menu;
+	//	this controls the number of pixels the two menus overlap.
 	submenuOverlap: 5,
+	
+	// Boolean
+	//	if true, right clicking anywhere on the window will cause this context menu to open;
+	//	if false, must specify targetNodeIds
 	contextMenuForWindow: false,
-	openEvent: null,
 
-	submenuIconSrc: dojo.uri.dojoUri("src/widget/templates/images/submenu_off.gif").toString(),
-	submenuIconOnSrc: dojo.uri.dojoUri("src/widget/templates/images/submenu_on.gif").toString(),
+	// Array
+	//	Array of dom node ids of nodes to attach to
+	targetNodeIds: [],
+
+	initialize: function(args, frag) {
+		if (this.eventNaming == "default") {
+			for (var eventName in this.eventNames) {
+				this.eventNames[eventName] = this.widgetId+"/"+eventName;
+			}
+		}
+	},
 
 	postCreate: function(){
-
-		dojo.html.addClass(this.domNode, 'dojoPopupMenu2');
-		dojo.html.addClass(this.containerNode, 'dojoPopupMenu2Client');
-
-		this.domNode.style.left = '-9999px'
-		this.domNode.style.top = '-9999px'
-
 		if (this.contextMenuForWindow){
-			var doc = document.documentElement  || dojo.html.body();
-			dojo.event.connect(doc, "oncontextmenu", this, "onOpen");
+			var doc = dojo.body();
+			this.bindDomNode(doc);
 		} else if ( this.targetNodeIds.length > 0 ){
-			for(var i=0; i<this.targetNodeIds.length; i++){
-				this.bindDomNode(this.targetNodeIds[i]);
-			}
+			dojo.lang.forEach(this.targetNodeIds, this.bindDomNode, this);
 		}
 
-		this.layoutMenuSoon();
+		this._subscribeSubitemsOnOpen();
+	},
+
+	_subscribeSubitemsOnOpen: function() {
+		var subItems = this.getChildrenOfType(dojo.widget.MenuItem2);
+
+		for(var i=0; i<subItems.length; i++) {
+			dojo.event.topic.subscribe(this.eventNames.open, subItems[i], "menuOpen")
+		}
 	},
 
-	// get open event for current menu
 	getTopOpenEvent: function() {
+		// summary: get event that initially caused current chain of menus to open
 		var menu = this;
-		while (menu.parent){ menu = menu.parent; }
-		return menu.openEvent;
+		while (menu.parentPopup){ menu = menu.parentPopup; }
+		return menu.openEvent;	// Event
 	},
 
-	// attach menu to given node
-	bindDomNode: function(node){
-		dojo.event.connect(dojo.byId(node), "oncontextmenu", this, "onOpen");
-	},
+	bindDomNode: function(/*String|DomNode*/ node){
+		// summary: attach menu to given node
+		node = dojo.byId(node);
 
-	// detach menu from given node
-	unBindDomNode: function(node){
-		dojo.event.kwDisconnect({
-			srcObj:     dojo.byId(node),
+		var win = dojo.html.getElementWindow(node);
+		if(dojo.html.isTag(node,'iframe') == 'iframe'){
+			win = dojo.html.iframeContentWindow(node);
+			node = dojo.withGlobal(win, dojo.body);
+		}
+		// fixes node so that it supports oncontextmenu if not natively supported, Konqueror, Opera more?
+		dojo.widget.Menu2.OperaAndKonqFixer.fixNode(node);
+
+		dojo.event.kwConnect({
+			srcObj:     node,
 			srcFunc:    "oncontextmenu",
 			targetObj:  this,
 			targetFunc: "onOpen",
 			once:       true
 		});
-	},
 
-	layoutMenuSoon: function(){
-
-		dojo.lang.setTimeout(this, "layoutMenu", 0);
+		//normal connect does not work if document.designMode is on in FF, use addListener instead
+		if(dojo.render.html.moz && win.document.designMode.toLowerCase() == 'on'){
+			dojo.event.browser.addListener(node, "contextmenu", dojo.lang.hitch(this, "onOpen"));
+		}
+		dojo.widget.PopupManager.registerWin(win);
 	},
 
-	layoutMenu: function(){
-
-		// determine menu width
-
-		var max_label_w = 0;
-		var max_accel_w = 0;
-
-		for(var i=0; i<this.children.length; i++){
+	unBindDomNode: function(/*String|DomNode*/ nodeName){
+		// summary: detach menu from given node
+		var node = dojo.byId(nodeName);
+		dojo.event.kwDisconnect({
+			srcObj:     node,
+			srcFunc:    "oncontextmenu",
+			targetObj:  this,
+			targetFunc: "onOpen",
+			once:       true
+		});
 
-			if (this.children[i].getLabelWidth){
+		// cleans a fixed node, konqueror and opera
+		dojo.widget.Menu2.OperaAndKonqFixer.cleanNode(node);
+	},
 
-				max_label_w = Math.max(max_label_w, this.children[i].getLabelWidth());
-			}
+	_moveToNext: function(/*Event*/ evt){
+		this._highlightOption(1);
+		return true; //do not pass to parent menu
+	},
 
-			if (dojo.lang.isFunction(this.children[i].getAccelWidth)){
+	_moveToPrevious: function(/*Event*/ evt){
+		this._highlightOption(-1);
+		return true; //do not pass to parent menu
+	},
 
-				max_accel_w = Math.max(max_accel_w, this.children[i].getAccelWidth());
+	_moveToParentMenu: function(/*Event*/ evt){
+		if(this._highlighted_option && this.parentPopup){
+			//only process event in the focused menu
+			//and its immediate parentPopup to support
+			//MenuBar2
+			if(evt._menu2UpKeyProcessed){
+				return true; //do not pass to parent menu
+			}else{
+				this._highlighted_option.onUnhover();
+				this.closeSubpopup();
+				evt._menu2UpKeyProcessed = true;
 			}
 		}
-
-		if( isNaN(max_label_w) || isNaN(max_accel_w) ){
-			// Browser needs some more time to calculate sizes
-			this.layoutMenuSoon();
-			return;
-		}
-
-		var clientLeft = dojo.style.getPixelValue(this.domNode, "padding-left", true) + dojo.style.getPixelValue(this.containerNode, "padding-left", true);
-		var clientTop  = dojo.style.getPixelValue(this.domNode, "padding-top", true)  + dojo.style.getPixelValue(this.containerNode, "padding-top", true);
-
-		if( isNaN(clientLeft) || isNaN(clientTop) ){
-			// Browser needs some more time to calculate sizes
-			this.layoutMenuSoon();
-			return;
-		}
-
-		var y = clientTop;
-		var max_item_width = 0;
-
-		for(var i=0; i<this.children.length; i++){
-
-			var ch = this.children[i];
-
-			ch.layoutItem(max_label_w, max_accel_w);
-
-			ch.topPosition = y;
-
-			y += dojo.style.getOuterHeight(ch.domNode);
-			max_item_width = Math.max(max_item_width, dojo.style.getOuterWidth(ch.domNode));
-		}
-
-		dojo.style.setContentWidth(this.containerNode, max_item_width);
-		dojo.style.setContentHeight(this.containerNode, y-clientTop);
-
-		dojo.style.setContentWidth(this.domNode, dojo.style.getOuterWidth(this.containerNode));
-		dojo.style.setContentHeight(this.domNode, dojo.style.getOuterHeight(this.containerNode));
-
-		this.menuWidth = dojo.style.getOuterWidth(this.domNode);
-		this.menuHeight = dojo.style.getOuterHeight(this.domNode);
+		return false;
 	},
 
-	open: function(x, y, parentMenu, explodeSrc){
-
-		// NOTE: alex:
-		//	this couldn't have possibly worked. this.open wound up calling
-		//	this.close, which called open...etc..
-		if (this.isShowing){ /* this.close(); */ return; }
-
-		if ( !parentMenu ) {
-			// record whenever a top level menu is opened
-			dojo.widget.html.Menu2Manager.opened(this, explodeSrc);
-		}
-
-		//dojo.debug("open called for animation "+this.animationInProgress)
-
-		// if I click  right button and menu is opened, then it gets 2 commands: close -> open
-		// so close enables animation and next "open" is put to queue to occur at new location
-		if(this.animationInProgress){
-			this.queueOnAnimationFinish.push(this.open, arguments);
-			return;
+	_moveToChildMenu: function(/*Event*/ evt){
+		if(this._highlighted_option && this._highlighted_option.submenuId){
+			this._highlighted_option._onClick(true);
+			return true; //do not pass to parent menu
+		}
+		return false;
+	},
+
+	_selectCurrentItem: function(/*Event*/ evt){
+		if(this._highlighted_option){
+			this._highlighted_option._onClick();
+			return true;
+		}
+		return false;
+	},
+
+	processKey: function(/*Event*/ evt){
+		// summary
+		//	callback to process key strokes
+		//	return true to stop the event being processed by the
+		//	parent popupmenu
+
+		if(evt.ctrlKey || evt.altKey || !evt.key){ return false; }
+
+		var rval = false;
+		switch(evt.key){
+ 			case evt.KEY_DOWN_ARROW:
+				rval = this._moveToNext(evt);
+				break;
+			case evt.KEY_UP_ARROW:
+				rval = this._moveToPrevious(evt);
+				break;
+			case evt.KEY_RIGHT_ARROW:
+				rval = this._moveToChildMenu(evt);
+				break;
+			case evt.KEY_LEFT_ARROW:
+				rval = this._moveToParentMenu(evt);
+				break;
+			case " ": //fall through
+			case evt.KEY_ENTER: 
+				if(rval = this._selectCurrentItem(evt)){
+					break;
+				}
+				//fall through
+			case evt.KEY_ESCAPE:
+				dojo.widget.PopupManager.currentMenu.close();
+				rval = true;
+				break;
+		}
+
+		return rval;
+	},
+
+	_findValidItem: function(dir, curItem){
+		if(curItem){
+			curItem = dir>0 ? curItem.getNextSibling() : curItem.getPreviousSibling();
+		}
+
+		for(var i=0; i < this.children.length; ++i){
+			if(!curItem){
+				curItem = dir>0 ? this.children[0] : this.children[this.children.length-1];
+			}
+			//find next/previous visible menu item, not including separators
+			if(curItem.onHover && curItem.isShowing()){
+				return curItem;
+			}
+			curItem = dir>0 ? curItem.getNextSibling() : curItem.getPreviousSibling();
 		}
-
-		var viewport = dojo.html.getViewportSize();
-		var scrolloffset = dojo.html.getScrollOffset();
-
-		var clientRect = {
-			'left'  : scrolloffset[0],
-			'right' : scrolloffset[0] + viewport[0],
-			'top'   : scrolloffset[1],
-			'bottom': scrolloffset[1] + viewport[1]
-		};
-
-		if (parentMenu){
-			// submenu is opening
-
-			if (x + this.menuWidth > clientRect.right){ x = x - (this.menuWidth + parentMenu.menuWidth - (2 * this.submenuOverlap)); }
-
-			if (y + this.menuHeight > clientRect.bottom){ y = y -
-			(this.menuHeight - (this.itemHeight + 5)); } // TODO: why 5?
-
+	},
+	
+	_highlightOption: function(dir){
+		var item;
+		// || !this._highlighted_option.parentNode
+		if((!this._highlighted_option)){
+			item = this._findValidItem(dir);
 		}else{
-			// top level menu is opening
-			x+=scrolloffset[0];
-			y+=scrolloffset[1];
-			explodeSrc[0] += scrolloffset[0];
-			explodeSrc[1] += scrolloffset[1];
-
-			if (x < clientRect.left){ x = clientRect.left; }
-			if (x + this.menuWidth > clientRect.right){ x = x - this.menuWidth; }
-
-			if (y < clientRect.top){ y = clientRect.top; }
-			if (y + this.menuHeight > clientRect.bottom){ y = y - this.menuHeight; }
+			item = this._findValidItem(dir, this._highlighted_option);
 		}
+		if(item){
+			if(this._highlighted_option) {
+				this._highlighted_option.onUnhover();
+			}
+			item.onHover();
+			dojo.html.scrollIntoView(item.domNode);
+			// navigate into the item table and select the first caption tag
+			try {
+				var node = dojo.html.getElementsByClass("dojoMenuItem2Label", item.domNode)[0];
+				node.focus();
+			} catch(e) { }
+		}
+	},
 
-		this.parentMenu = parentMenu;
-		this.explodeSrc = explodeSrc;
-		this.menuIndex = parentMenu ? parentMenu.menuIndex + 1 : 1;
-
-		this.menuX = x;
-		this.menuY = y;
-
-		// move the menu into position but make it invisible
-		// (because when menus are initially constructed they are visible but off-screen)
-		this.domNode.style.zIndex = 10 + this.menuIndex;
-		this.domNode.style.left = x + 'px';
-		this.domNode.style.top = y + 'px';
-		this.domNode.style.display='none';
-		this.domNode.style.position='absolute';
-
-		// then use the user defined method to display it
-		this.show();
-
-		this.isShowing = true;
+	onItemClick: function(/*Widget*/ item) {
+		// summary: user defined function to handle clicks on an item
 	},
 
-	close: function(){
-		// If we are in the process of opening the menu and we are asked to close it,
-		// we should really cancel the current animation, but for simplicity we will
-		// just ignore the request
+	close: function(/*Boolean*/ force){
+		// summary: close the menu
 		if(this.animationInProgress){
-			this.queueOnAnimationFinish.push(this.close, []);
+			dojo.widget.PopupMenu2.superclass.close.apply(this, arguments);
 			return;
 		}
 
-		this.closeSubmenu();
-		this.hide();
-		this.isShowing = false;
-		dojo.widget.html.Menu2Manager.closed(this);
-	},
-
-	onShow: function() {
-		dojo.widget.HtmlWidget.prototype.onShow.call(this);
-		this.processQueue();
-	},
-
-	// do events from queue
-	processQueue: function() {
-		if (!this.queueOnAnimationFinish.length) return;
-
-		var func = this.queueOnAnimationFinish.shift();
-		var args = this.queueOnAnimationFinish.shift();
-
-		func.apply(this, args);
-	},
-
-	onHide: function() {
-		dojo.widget.HtmlWidget.prototype.onHide.call(this);
-
-		this.processQueue();
-	},
-
-
-	closeAll: function(){
-
-		if (this.parentMenu){
-			this.parentMenu.closeAll();
-		}else{
-			this.close();
+		if(this._highlighted_option){
+			this._highlighted_option.onUnhover();
 		}
+
+		dojo.widget.PopupMenu2.superclass.close.apply(this, arguments);
 	},
 
-	closeSubmenu: function(){
-		if (this.currentSubmenu == null){ return; }
+	closeSubpopup: function(force){
+		// summary: close the currently displayed submenu
+		if (this.currentSubpopup == null){ return; }
 
-		this.currentSubmenu.close();
-		this.currentSubmenu = null;
+		this.currentSubpopup.close(force);
+		this.currentSubpopup = null;
 
 		this.currentSubmenuTrigger.is_open = false;
-		this.currentSubmenuTrigger.closedSubmenu();
+		this.currentSubmenuTrigger._closedSubmenu(force);
 		this.currentSubmenuTrigger = null;
 	},
 
-	openSubmenu: function(submenu, from_item){
-
-		var our_x = dojo.style.getPixelValue(this.domNode, 'left');
-		var our_y = dojo.style.getPixelValue(this.domNode, 'top');
-		var our_w = dojo.style.getOuterWidth(this.domNode);
-		var item_y = from_item.topPosition;
-
-		var x = our_x + our_w - this.submenuOverlap;
-		var y = our_y + item_y;
-
-		this.currentSubmenu = submenu;
-		this.currentSubmenu.open(x, y, this, from_item.domNode);
+	_openSubmenu: function(submenu, from_item){
+		// summary: open the menu to the right of the current menu item
+		var fromPos = dojo.html.getAbsolutePosition(from_item.domNode, true);
+		var our_w = dojo.html.getMarginBox(this.domNode).width;
+		var x = fromPos.x + our_w - this.submenuOverlap;
+		var y = fromPos.y;
+
+		//the following is set in open, so we do not need it
+		//this.currentSubpopup = submenu;
+		submenu.open(x, y, this, from_item.domNode);
 
 		this.currentSubmenuTrigger = from_item;
 		this.currentSubmenuTrigger.is_open = true;
 	},
 
-	onOpen: function(e){
+	onOpen: function(/*Event*/ e){
+		// summary: callback when menu is opened
 		this.openEvent = e;
-
-		//dojo.debugShallow(e);
-		this.open(e.clientX, e.clientY, null, [e.clientX, e.clientY]);
-
-		if(e["preventDefault"]){
-			e.preventDefault();
+		if(e["target"]){
+			this.openedForWindow = dojo.html.getElementWindow(e.target);
+		}else{
+			this.openedForWindow = null;
 		}
-	},
-
-	isPointInMenu: function(x, y){
-
-		if (x < this.menuX){ return 0; }
-		if (x > this.menuX + this.menuWidth){ return 0; }
+		var x = e.pageX, y = e.pageY;
 
-		if (y < this.menuY){ return 0; }
-		if (y > this.menuY + this.menuHeight){ return 0; }
+		var win = dojo.html.getElementWindow(e.target);
+		var iframe = win._frameElement || win.frameElement;
+		if(iframe){
+			var cood = dojo.html.abs(iframe, true);
+			x += cood.x - dojo.withGlobal(win, dojo.html.getScroll).left;
+			y += cood.y - dojo.withGlobal(win, dojo.html.getScroll).top;
+		}
+		this.open(x, y, null, [x, y]);
 
-		return 1;
+		e.preventDefault();
+		e.stopPropagation();
 	}
 });
 
-
-dojo.widget.MenuItem2 = function(){
-	dojo.widget.HtmlWidget.call(this);
-}
-
-dojo.inherits(dojo.widget.MenuItem2, dojo.widget.HtmlWidget);
-
-dojo.lang.extend(dojo.widget.MenuItem2, {
-	widgetType: "MenuItem2",
+// summary
+//	A line item in a Menu2
+dojo.widget.defineWidget(
+	"dojo.widget.MenuItem2",
+	dojo.widget.HtmlWidget,
+	function(){
+		this.eventNames = {
+			engage: ""
+		};
+	},
+{
+	// Make 4 columns
+	//   icon, label, accelerator-key, and right-arrow indicating sub-menu
 	templateString:
-			 '<div class="dojoMenuItem2">'
-			+'<div dojoAttachPoint="iconNode" class="dojoMenuItem2Icon"></div>'
-			+'<span dojoAttachPoint="labelNode" class="dojoMenuItem2Label"><span><span></span></span></span>'
-			+'<span dojoAttachPoint="accelNode" class="dojoMenuItem2Accel"><span><span></span></span></span>'
-			+'<div dojoAttachPoint="submenuNode" class="dojoMenuItem2Submenu"></div>'
-			+'<div dojoAttachPoint="targetNode" class="dojoMenuItem2Target" dojoAttachEvent="onMouseOver: onHover; onMouseOut: onUnhover; onClick;">&nbsp;</div>'
-			+'</div>',
-
-	//
-	// nodes
-	//
-
-	domNode: null,
-	iconNode: null,
-	labelNode: null,
-	accelNode: null,
-	submenuNode: null,
-	targetNode: null,
+		 '<tr class="dojoMenuItem2" dojoAttachEvent="onMouseOver: onHover; onMouseOut: onUnhover; onClick: _onClick; onKey:onKey;">'
+		+'<td><div class="${this.iconClass}" style="${this.iconStyle}"></div></td>'
+		+'<td tabIndex="-1" class="dojoMenuItem2Label">${this.caption}</td>'
+		+'<td class="dojoMenuItem2Accel">${this.accelKey}</td>'
+		+'<td><div class="dojoMenuItem2Submenu" style="display:${this.arrowDisplay};"></div></td>'
+		+'</tr>',
 
 	//
 	// internal settings
@@ -382,322 +347,419 @@
 	hover_timer: null,
 	is_open: false,
 	topPosition: 0,
-	is_disabled: false,
 
 	//
 	// options
 	//
 
+	// String
+	//	text of the menu item
 	caption: 'Untitled',
+	
+	// String
+	//	accelerator key (not supported yet!)
 	accelKey: '',
+	
+	// String
+	//	path to icon to display to the left of the menu text
 	iconSrc: '',
+	
+	// String
+	//	CSS class name to use for menu item (if CSS class specifies a background image then iconSrc is not necessary)
+	iconClass: 'dojoMenuItem2Icon',
+	
+	// String
+	//	widget ID of Menu2 widget to open when this menu item is clicked
 	submenuId: '',
-	isDisabled: false,
+	
+	// Boolean
+	//	if true, this menu item cannot be selected
+	disabled: false,
+	
+	// String
+	//	event names for announcing when menu item is clicked.
+	//	if "default", then use the default name, based on the widget ID
+	eventNaming: "default",
+	
+	// String
+	//	CSS class for menu item when it's hovered over
+	highlightClass: 'dojoMenuItem2Hover',
 
+	postMixInProperties: function(){
+		this.iconStyle="";
+		if (this.iconSrc){
+			if ((this.iconSrc.toLowerCase().substring(this.iconSrc.length-4) == ".png") && (dojo.render.html.ie55 || dojo.render.html.ie60)){
+				this.iconStyle="filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this.iconSrc+"', sizingMethod='image')";
+			}else{
+				this.iconStyle="background-image: url("+this.iconSrc+")";
+			}
+		}
+		this.arrowDisplay = this.submenuId ? 'block' : 'none';
+		dojo.widget.MenuItem2.superclass.postMixInProperties.apply(this, arguments);
+	},
 
-	postCreate: function(){
-
+	fillInTemplate: function(){
 		dojo.html.disableSelection(this.domNode);
 
-		if (this.isDisabled){
+		if (this.disabled){
 			this.setDisabled(true);
 		}
 
-		this.labelNode.childNodes[0].appendChild(document.createTextNode(this.caption));
-		this.accelNode.childNodes[0].appendChild(document.createTextNode(this.accelKey));
-
-		this.labelShadowNode = this.labelNode.childNodes[0].childNodes[0];
-		this.accelShadowNode = this.accelNode.childNodes[0].childNodes[0];
-
-		this.labelShadowNode.appendChild(document.createTextNode(this.caption));
-		this.accelShadowNode.appendChild(document.createTextNode(this.accelKey));
-	},
-
-	layoutItem: function(label_w, accel_w){
-
-		var x_label = this.parent.itemHeight + this.parent.iconGap;
-		var x_accel = x_label + label_w + this.parent.accelGap;
-		var x_submu = x_accel + accel_w + this.parent.submenuGap;
-		var total_w = x_submu + this.parent.submenuIconSize + this.parent.finalGap;
-
-
-		this.iconNode.style.left = '0px';
-		this.iconNode.style.top = '0px';
-
-
-		if (this.iconSrc){
-
-			if ((this.iconSrc.toLowerCase().substring(this.iconSrc.length-4) == ".png") && (dojo.render.html.ie)){
-
-				this.iconNode.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='"+this.iconSrc+"', sizingMethod='image')";
-				this.iconNode.style.backgroundImage = '';
-			}else{
-				this.iconNode.style.backgroundImage = 'url('+this.iconSrc+')';
+		if (this.eventNaming == "default") {
+			for (var eventName in this.eventNames) {
+				this.eventNames[eventName] = this.widgetId+"/"+eventName;
 			}
-		}else{
-			this.iconNode.style.backgroundImage = '';
 		}
-
-		dojo.style.setOuterWidth(this.iconNode, this.parent.itemHeight);
-		dojo.style.setOuterHeight(this.iconNode, this.parent.itemHeight);
-
-		dojo.style.setOuterHeight(this.labelNode, this.parent.itemHeight);
-		dojo.style.setOuterHeight(this.accelNode, this.parent.itemHeight);
-
-		dojo.style.setContentWidth(this.domNode, total_w);
-		dojo.style.setContentHeight(this.domNode, this.parent.itemHeight);
-
-		this.labelNode.style.left = x_label + 'px';
-		this.accelNode.style.left = x_accel + 'px';
-		this.submenuNode.style.left = x_submu + 'px';
-
-		dojo.style.setOuterWidth(this.submenuNode, this.parent.submenuIconSize);
-		dojo.style.setOuterHeight(this.submenuNode, this.parent.itemHeight);
-
-		this.submenuNode.style.display = this.submenuId ? 'block' : 'none';
-		this.submenuNode.style.backgroundImage = 'url('+this.parent.submenuIconSrc+')';
-
-		dojo.style.setOuterWidth(this.targetNode, total_w);
-		dojo.style.setOuterHeight(this.targetNode, this.parent.itemHeight);
 	},
 
 	onHover: function(){
+		// summary: callback when mouse is moved onto menu item
+
+		//this is to prevent some annoying behavior when both mouse and keyboard are used
+		this.onUnhover();
 
 		if (this.is_hovering){ return; }
 		if (this.is_open){ return; }
 
-		this.parent.closeSubmenu();
-		this.highlightItem();
-
-		if (this.is_hovering){ this.stopSubmenuTimer(); }
-		this.is_hovering = 1;
-		this.startSubmenuTimer();
+		if(this.parent._highlighted_option){
+			this.parent._highlighted_option.onUnhover();
+		}
+		this.parent.closeSubpopup();
+		this.parent._highlighted_option = this;
+		dojo.widget.PopupManager.setFocusedMenu(this.parent);
+
+		this._highlightItem();
+
+		if (this.is_hovering){ this._stopSubmenuTimer(); }
+		this.is_hovering = true;
+		this._startSubmenuTimer();
 	},
 
 	onUnhover: function(){
+		// summary: callback when mouse is moved off of menu item
+		if(!this.is_open){ this._unhighlightItem(); }
 
-		if (!this.is_open){ this.unhighlightItem(); }
+		this.is_hovering = false;
 
-		this.is_hovering = 0;
-		this.stopSubmenuTimer();
-	},
+		this.parent._highlighted_option = null;
 
-	onClick: function(){
+		if(this.parent.parentPopup){
+			dojo.widget.PopupManager.setFocusedMenu(this.parent.parentPopup);
+		}
 
-		if (this.is_disabled){ return; }
+		this._stopSubmenuTimer();
+	},
 
-		if (this.submenuId){
+	_onClick: function(focus){
+		// summary: internal function for clicks
+		var displayingSubMenu = false;
+		if (this.disabled){ return false; }
 
+		if (this.submenuId){
 			if (!this.is_open){
-				this.stopSubmenuTimer();
-				this.openSubmenu();
+				this._stopSubmenuTimer();
+				this._openSubmenu();
 			}
-
+			displayingSubMenu = true;
 		}else{
+			// for some browsers the onMouseOut doesn't get called (?), so call it manually
+			this.onUnhover(); //only onUnhover when no submenu is available
+			this.parent.closeAll(true);
+		}
 
-			this.parent.closeAll();
+		// user defined handler for click
+		this.onClick();
+
+		dojo.event.topic.publish(this.eventNames.engage, this);
+
+		if(displayingSubMenu && focus){
+			dojo.widget.getWidgetById(this.submenuId)._highlightOption(1);
 		}
+		return;
 	},
 
-	highlightItem: function(){
-
-		dojo.html.addClass(this.domNode, 'dojoMenuItem2Hover');
-		this.submenuNode.style.backgroundImage = 'url('+this.parent.submenuIconOnSrc+')';
+	onClick: function() {
+		// summary
+		//	User defined function to handle clicks
+		//	this default function call the parent
+		//	menu's onItemClick
+		this.parent.onItemClick(this);
 	},
 
-	unhighlightItem: function(){
+	_highlightItem: function(){
+		dojo.html.addClass(this.domNode, this.highlightClass);
+	},
 
-		dojo.html.removeClass(this.domNode, 'dojoMenuItem2Hover');
-		this.submenuNode.style.backgroundImage = 'url('+this.parent.submenuIconSrc+')';
+	_unhighlightItem: function(){
+		dojo.html.removeClass(this.domNode, this.highlightClass);
 	},
 
-	startSubmenuTimer: function(){
-		this.stopSubmenuTimer();
+	_startSubmenuTimer: function(){
+		this._stopSubmenuTimer();
 
-		if (this.is_disabled){ return; }
+		if (this.disabled){ return; }
 
 		var self = this;
-		var closure = function(){ return function(){ self.openSubmenu(); } }();
+		var closure = function(){ return function(){ self._openSubmenu(); } }();
 
-		this.hover_timer = window.setTimeout(closure, this.parent.submenuDelay);
+		this.hover_timer = dojo.lang.setTimeout(closure, this.parent.submenuDelay);
 	},
 
-	stopSubmenuTimer: function(){
+	_stopSubmenuTimer: function(){
 		if (this.hover_timer){
-			window.clearTimeout(this.hover_timer);
+			dojo.lang.clearTimeout(this.hover_timer);
 			this.hover_timer = null;
 		}
 	},
 
-	openSubmenu: function(){
+	_openSubmenu: function(){
+		if (this.disabled){ return; }
+
 		// first close any other open submenu
-		this.parent.closeSubmenu();
+		this.parent.closeSubpopup();
 
 		var submenu = dojo.widget.getWidgetById(this.submenuId);
 		if (submenu){
-
-			this.parent.openSubmenu(submenu, this);
+			this.parent._openSubmenu(submenu, this);
 		}
-
-		//dojo.debug('open submenu for item '+this.widgetId);
 	},
 
-	closedSubmenu: function(){
-
+	_closedSubmenu: function(){
 		this.onUnhover();
 	},
 
-	setDisabled: function(value){
-
-		if (value == this.is_disabled){ return; }
+	setDisabled: function(/*Boolean*/ value){
+		// summary: enable or disable this menu item
+		this.disabled = value;
 
-		this.is_disabled = value;
-
-		if (this.is_disabled){
+		if (this.disabled){
 			dojo.html.addClass(this.domNode, 'dojoMenuItem2Disabled');
 		}else{
 			dojo.html.removeClass(this.domNode, 'dojoMenuItem2Disabled');
 		}
 	},
 
-	getLabelWidth: function(){
-
-		var node = this.labelNode.childNodes[0];
-
-		return dojo.style.getOuterWidth(node);
+	enable: function(){
+		// summary: enable this menu item so user can click it
+		this.setDisabled(false);
 	},
 
-	getAccelWidth: function(){
-
-		var node = this.accelNode.childNodes[0];
+	disable: function(){
+		// summary: disable this menu item so user can't click it
+		this.setDisabled(true);
+	},
 
-		return dojo.style.getOuterWidth(node);
+	menuOpen: function(message) {
+		// summary: callback when menu is opened
+		// TODO: I don't see anyone calling this menu item
 	}
-});
 
+});
 
-dojo.widget.MenuSeparator2 = function(){
-	dojo.widget.HtmlWidget.call(this);
-}
-
-dojo.inherits(dojo.widget.MenuSeparator2, dojo.widget.HtmlWidget);
-
-dojo.lang.extend(dojo.widget.MenuSeparator2, {
-	widgetType: "MenuSeparator2",
-
-	domNode: null,
-	topNode: null,
-	bottomNode: null,
-
-	templateString: '<div>'
-			+'<div dojoAttachPoint="topNode"></div>'
-			+'<div dojoAttachPoint="bottomNode"></div>'
-			+'</div>',
+// summary
+//	A line between two menu items
+dojo.widget.defineWidget(
+	"dojo.widget.MenuSeparator2",
+	dojo.widget.HtmlWidget,
+{
+	templateString: '<tr class="dojoMenuSeparator2"><td colspan=4>'
+			+'<div class="dojoMenuSeparator2Top"></div>'
+			+'<div class="dojoMenuSeparator2Bottom"></div>'
+			+'</td></tr>',
 
 	postCreate: function(){
-
-		dojo.html.addClass(this.domNode, 'dojoMenuSeparator2');
-		dojo.html.addClass(this.topNode, 'dojoMenuSeparator2Top');
-		dojo.html.addClass(this.bottomNode, 'dojoMenuSeparator2Bottom');
-
 		dojo.html.disableSelection(this.domNode);
-
-		this.layoutItem();
-	},
-
-	layoutItem: function(label_w, accel_w){
-
-		var full_width = this.parent.itemHeight
-				+ this.parent.iconGap
-				+ label_w
-				+ this.parent.accelGap
-				+ accel_w
-				+ this.parent.submenuGap
-				+ this.parent.submenuIconSize
-				+ this.parent.finalGap;
-
-		if (isNaN(full_width)){ return; }
-
-		dojo.style.setContentHeight(this.domNode, this.parent.separatorHeight);
-		dojo.style.setContentWidth(this.domNode, full_width);
 	}
 });
 
-//
-// the menu manager makes sure we don't have several menus
-// open at once. the root menu in an opening sequence calls
-// opened(). when a root menu closes it calls closed(). then
-// everything works. lovely.
-//
-
-dojo.widget.html.Menu2Manager = new function(){
-
-	this.currentMenu = null;
-	this.currentButton = null;		// button that opened current menu (if any)
-	this.focusNode = null;
-
-	dojo.event.connect(document, 'onmousedown', this, 'onClick');
-	dojo.event.connect(window, "onscroll", this, "onClick");
-
-	this.closed = function(menu){
-		if (this.currentMenu == menu){
-			this.currentMenu = null;
-			this.currentButton = null;
+// summary
+//	A menu bar, listing menu choices horizontally, like the "File" menu in most desktop applications
+dojo.widget.defineWidget(
+	"dojo.widget.MenuBar2",
+	dojo.widget.PopupMenu2,
+{
+	menuOverlap: 2,
+
+	templateString: '<div class="dojoMenuBar2" tabIndex="0"><table class="dojoMenuBar2Client"><tr dojoAttachPoint="containerNode"></tr></table></div>',
+
+	close: function(force){
+		if(this._highlighted_option){
+			this._highlighted_option.onUnhover();
+		}
+
+		this.closeSubpopup(force);
+	},
+
+	processKey: function(/*Event*/ evt){
+		if(evt.ctrlKey || evt.altKey){ return false; }
+
+		if (!dojo.html.hasClass(evt.target,"dojoMenuBar2")) { return false; }
+		var rval = false;
+
+		switch(evt.key){
+ 			case evt.KEY_DOWN_ARROW:
+				rval = this._moveToChildMenu(evt);
+				break;
+			case evt.KEY_UP_ARROW:
+				rval = this._moveToParentMenu(evt);
+				break;
+			case evt.KEY_RIGHT_ARROW:
+				rval = this._moveToNext(evt);
+				break;
+			case evt.KEY_LEFT_ARROW:
+				rval = this._moveToPrevious(evt);
+				break;
+			default:
+				rval = 	dojo.widget.MenuBar2.superclass.processKey.apply(this, arguments);
+				break;
 		}
-	};
 
-	this.opened = function(menu, button){
-		if (menu == this.currentMenu){ return; }
-
-		if (this.currentMenu){
-			this.currentMenu.close();
-		}
-
-		this.currentMenu = menu;
-		this.currentButton = button;
-	};
-
-	this.onClick = function(e){
-
-		if (!this.currentMenu){ return; }
-
-		var scrolloffset = dojo.html.getScrollOffset();
-
-		var x = e.clientX + scrolloffset[0];
-		var y = e.clientY + scrolloffset[1];
-
-		var m = this.currentMenu;
+		return rval;
+	},
 
-		// starting from the base menu, perform a hit test
-		// and exit when one succeeds
+	postCreate: function(){
+		dojo.widget.MenuBar2.superclass.postCreate.apply(this, arguments);
+		dojo.widget.PopupManager.opened(this);
+		this.isShowingNow = true;
+	},
+
+	/*
+	 * override PopupMenu2 to open the submenu below us rather than to our right
+	 */
+	_openSubmenu: function(submenu, from_item){
+		var fromPos = dojo.html.getAbsolutePosition(from_item.domNode, true);
+		var ourPos = dojo.html.getAbsolutePosition(this.domNode, true);
+		var our_h = dojo.html.getBorderBox(this.domNode).height;
+		var x = fromPos.x;
+		var y = ourPos.y + our_h - this.menuOverlap;
 
-		while (m){
+		submenu.open(x, y, this, from_item.domNode);
 
-			if (m.isPointInMenu(x, y)){
+		this.currentSubmenuTrigger = from_item;
+		this.currentSubmenuTrigger.is_open = true;
+	}
+});
 
-				return;
-			}
+// summary
+//	Item in a Menu2Bar
+dojo.widget.defineWidget(
+	"dojo.widget.MenuBarItem2",
+	dojo.widget.MenuItem2,
+{
+	templateString:
+		 '<td class="dojoMenuBarItem2" dojoAttachEvent="onMouseOver: onHover; onMouseOut: onUnhover; onClick: _onClick;">'
+		+'<span>${this.caption}</span>'
+		+'</td>',
 
-			m = m.currentSubmenu;
-		}
+	highlightClass: 'dojoMenuBarItem2Hover',
 
-		// Also, if user clicked the button that opened this menu, then
-		// that button will send the menu a close() command, so this code
-		// shouldn't try to close the menu.  Closing twice messes up animation.
-		if (this.currentButton && dojo.html.overElement(this.currentButton, e)){
-			return;
+	setDisabled: function(value){
+		this.disabled = value;
+		if (this.disabled){
+			dojo.html.addClass(this.domNode, 'dojoMenuBarItem2Disabled');
+		}else{
+			dojo.html.removeClass(this.domNode, 'dojoMenuBarItem2Disabled');
 		}
+	}
+});
 
-		// the click didn't fall within the open menu tree
-		// so close it
-
-		this.currentMenu.close();
-	};
-}
-
-
-// make it a tag
-dojo.widget.tags.addParseTreeHandler("dojo:PopupMenu2");
-dojo.widget.tags.addParseTreeHandler("dojo:MenuItem2");
-dojo.widget.tags.addParseTreeHandler("dojo:MenuSeparator2");
 
+// ************************** make contextmenu work in konqueror and opera *********************
+dojo.widget.Menu2.OperaAndKonqFixer = new function(){
+ 	var implement = true;
+ 	var delfunc = false;
+
+ 	/** 	dom event check
+ 	*
+ 	*	make a event and dispatch it and se if it calls function below,
+ 	*	if it indeed is supported and we dont need to implement our own
+ 	*/
+
+ 	// gets called if we have support for oncontextmenu
+ 	if (!dojo.lang.isFunction(dojo.doc().oncontextmenu)){
+ 		dojo.doc().oncontextmenu = function(){
+ 			implement = false;
+ 			delfunc = true;
+ 		}
+ 	}
+
+ 	if (dojo.doc().createEvent){ // moz, safari has contextmenu event, need to do livecheck on this env.
+ 		try {
+ 			var e = dojo.doc().createEvent("MouseEvents");
+ 			e.initMouseEvent("contextmenu", 1, 1, dojo.global(), 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
+ 			dojo.doc().dispatchEvent(e);
+ 		} catch (e) {/* assume not supported */}
+ 	} else {
+ 		// IE no need to implement custom contextmenu
+ 		implement = false;
+ 	}
+
+ 	// clear this one if it wasn't there before
+ 	if (delfunc){
+ 		delete dojo.doc().oncontextmenu;
+ 	}
+ 	/***** end dom event check *****/
+
+
+ 	/**
+ 	*	this fixes a dom node by attaching a custom oncontextmenu function that gets called when apropriate
+ 	*	@param	node	a dom node
+ 	*
+ 	*	no returns
+ 	*/
+ 	this.fixNode = function(node){
+ 		if (implement){
+ 			// attach stub oncontextmenu function
+ 			if (!dojo.lang.isFunction(node.oncontextmenu)){
+ 				node.oncontextmenu = function(e){/*stub*/}
+ 			}
+
+ 			// attach control function for oncontextmenu
+ 			if (dojo.render.html.opera){
+ 				// opera
+ 				// listen to ctrl-click events
+ 				node._menufixer_opera = function(e){
+ 					if (e.ctrlKey){
+ 						this.oncontextmenu(e);
+ 					}
+ 				};
+
+ 				dojo.event.connect(node, "onclick", node, "_menufixer_opera");
+
+ 			} else {
+ 				// konqueror
+ 				// rightclick, listen to mousedown events
+ 				node._menufixer_konq = function(e){
+ 					if (e.button==2 ){
+ 						e.preventDefault(); // need to prevent browsers menu
+ 						this.oncontextmenu(e);
+ 					}
+ 				};
+
+ 				dojo.event.connect(node, "onmousedown", node, "_menufixer_konq");
+ 			}
+ 		}
+ 	}
+
+ 	/**
+ 	*	this cleans up a fixed node, prevent memoryleak?
+ 	*	@param node	node to clean
+ 	*
+ 	*	no returns
+ 	*/
+ 	this.cleanNode = function(node){
+ 		if (implement){
+ 			// checks needed if we gets a non fixed node
+ 			if (node._menufixer_opera){
+ 				dojo.event.disconnect(node, "onclick", node, "_menufixer_opera");
+ 				delete node._menufixer_opera;
+ 			} else if(node._menufixer_konq){
+ 				dojo.event.disconnect(node, "onmousedown", node, "_menufixer_konq");
+ 				delete node._menufixer_konq;
+ 			}
+ 			if (node.oncontextmenu){
+ 				delete node.oncontextmenu;
+ 			}
+ 		}
+ 	}
+};
\ No newline at end of file

Added: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js?view=auto&rev=474551
==============================================================================
--- struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js (added)
+++ struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js Mon Nov 13 14:54:45 2006
@@ -0,0 +1,188 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.MonthlyCalendar");
+dojo.require("dojo.date.common");
+dojo.require("dojo.date.format");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.DatePicker");
+dojo.require("dojo.event.*");
+dojo.require("dojo.html.*");
+dojo.require("dojo.experimental");
+
+dojo.experimental("dojo.widget.MonthlyCalendar");
+
+dojo.widget.defineWidget(
+	"dojo.widget.MonthlyCalendar",
+	dojo.widget.DatePicker,
+	{
+		dayWidth: 'wide',
+
+		templatePath: dojo.uri.dojoUri("src/widget/templates/MonthlyCalendar.html"),
+		templateCssPath: dojo.uri.dojoUri("src/widget/templates/MonthlyCalendar.css"),
+
+		initializer: function(){
+			this.iCalendars = [];
+		},
+
+		/*
+		cache: function(){
+		},
+		*/
+
+		addCalendar: function(/* dojo.iCalendar */ cal) {
+			dojo.debug("Adding Calendar");
+			this.iCalendars.push(cal);
+			dojo.debug("Starting init");
+			this.initUI();
+			dojo.debug("done init");
+		},
+
+		createDayContents: function(node,mydate) {
+			dojo.html.removeChildren(node);
+			node.appendChild(document.createTextNode(mydate.getDate()));	
+				for(var x=0; x<this.iCalendars.length; x++) {
+					var evts = this.iCalendars[x].getEvents(mydate);
+					if ((dojo.lang.isArray(evts)) && (evts.length>0)) {
+					for(var y=0;y<evts.length;y++) {
+						var el = document.createElement("div");
+						dojo.html.addClass(el, "dojoMonthlyCalendarEvent");          
+						el.appendChild(document.createTextNode(evts[y].summary.value));
+						el.width = dojo.html.getContentBox(node).width;
+						node.appendChild(el);
+					}
+				}
+			}
+		},
+
+		initUI: function() {
+			var dayLabels = dojo.date.getNames('days', this.dayWidth, 'standAlone', this.lang);
+			var dayLabelNodes = this.dayLabelsRow.getElementsByTagName("td");
+			for(var i=0; i<7; i++) {
+				dayLabelNodes.item(i).innerHTML = dayLabels[i];
+			}
+
+			this.selectedIsUsed = false;
+			this.currentIsUsed = false;
+			var currentClassName = "";
+			var previousDate = new Date();
+			var calendarNodes = this.calendarDatesContainerNode.getElementsByTagName("td");
+			var currentCalendarNode;
+			// set hours of date such that there is no chance of rounding error due to 
+			// time change in local time zones
+			previousDate.setHours(8);
+			var nextDate = new Date(this.firstSaturday.year, this.firstSaturday.month, this.firstSaturday.date, 8);
+			var lastDay = new Date(this.firstSaturday.year, this.firstSaturday.month, this.firstSaturday.date + 42, 8);
+			
+			if (this.iCalendars.length > 0) {
+				for (var x=0; x<this.iCalendars.length;x++) {
+					this.iCalendars[x].preComputeRecurringEvents(lastDay);
+				}
+			}
+
+			if(this.firstSaturday.date < 7) {
+				// this means there are days to show from the previous month
+				var dayInWeek = 6;
+				for (var i=this.firstSaturday.date; i>0; i--) {
+					currentCalendarNode = calendarNodes.item(dayInWeek);
+					this.createDayContents(currentCalendarNode, nextDate);
+					
+					dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "current"));
+					dayInWeek--;
+					previousDate = nextDate;
+					nextDate = this.incrementDate(nextDate, false);
+				}
+				for(var i=dayInWeek; i>-1; i--) {
+					currentCalendarNode = calendarNodes.item(i);
+
+					this.createDayContents(currentCalendarNode, nextDate);
+
+					dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "previous"));
+					previousDate = nextDate;
+					nextDate = this.incrementDate(nextDate, false);				
+				}
+			} else {
+				nextDate.setDate(1);
+				for(var i=0; i<7; i++) {
+					currentCalendarNode = calendarNodes.item(i);
+					this.createDayContents(currentCalendarNode, nextDate);
+					dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "current"));
+					previousDate = nextDate;
+					nextDate = this.incrementDate(nextDate, true);				
+				}
+			}
+			previousDate.setDate(this.firstSaturday.date);
+			previousDate.setMonth(this.firstSaturday.month);
+			previousDate.setFullYear(this.firstSaturday.year);
+			nextDate = this.incrementDate(previousDate, true);
+			var count = 7;
+			currentCalendarNode = calendarNodes.item(count);
+			while((nextDate.getMonth() == previousDate.getMonth()) && (count<42)) {
+				this.createDayContents(currentCalendarNode, nextDate);
+				dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "current"));
+				currentCalendarNode = calendarNodes.item(++count);
+				previousDate = nextDate;
+				nextDate = this.incrementDate(nextDate, true);
+			}
+			
+			while(count < 42) {
+				this.createDayContents(currentCalendarNode, nextDate);
+				dojo.html.setClass(currentCalendarNode, this.getDateClassName(nextDate, "next"));
+				currentCalendarNode = calendarNodes.item(++count);
+				previousDate = nextDate;
+				nextDate = this.incrementDate(nextDate, true);
+			}
+			this.setMonthLabel(this.firstSaturday.month);
+			this.setYearLabels(this.firstSaturday.year);
+		}	
+	}
+);
+
+dojo.widget.MonthlyCalendar.util= new function() {
+
+	this.toRfcDate = function(jsDate) {
+		if(!jsDate) {
+			jsDate = this.today;
+		}
+		var year = jsDate.getFullYear();
+		var month = jsDate.getMonth() + 1;
+		if (month < 10) {
+			month = "0" + month.toString();
+		}
+		var date = jsDate.getDate();
+		if (date < 10) {
+			date = "0" + date.toString();
+		}
+		// because this is a date picker and not a time picker, we treat time 
+		// as zero
+		return year + "-" + month + "-" + date + "T00:00:00+00:00";
+	}
+	
+	this.fromRfcDate = function(rfcDate) {
+		var tempDate = rfcDate.split("-");
+		if(tempDate.length < 3) {
+			return new Date();
+		}
+		// fullYear, month, date
+		return new Date(parseInt(tempDate[0]), (parseInt(tempDate[1], 10) - 1), parseInt(tempDate[2].substr(0,2), 10));
+	}
+
+//Note: redundant with dojo.widget.DatePicker.util	
+	this.initFirstSaturday = function(month, year) {
+		if(!month) {
+			month = this.date.getMonth();
+		}
+		if(!year) {
+			year = this.date.getFullYear();
+		}
+		var firstOfMonth = new Date(year, month, 1);
+		return {year: year, month: month, date: 7 - firstOfMonth.getDay()};
+	}
+}

Propchange: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js?view=auto&rev=474551
==============================================================================
--- struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js (added)
+++ struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js Mon Nov 13 14:54:45 2006
@@ -0,0 +1,387 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.PageContainer");
+
+dojo.require("dojo.lang.func");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.event.*");
+dojo.require("dojo.html.selection");
+
+// A PageContainer is a container that has multiple children, but shows only
+// one child at a time (like looking at the pages in a book one by one).
+//
+// Publishes topics <widgetId>-addChild, <widgetId>-removeChild, and <widgetId>-selectChild
+//
+// Can be base class for container, Wizard, Show, etc.
+dojo.widget.defineWidget("dojo.widget.PageContainer", dojo.widget.HtmlWidget, {
+	isContainer: true,
+
+	// Boolean
+	//  if true, change the size of my currently displayed child to match my size
+	doLayout: true,
+
+	templateString: "<div dojoAttachPoint='containerNode'></div>",
+
+	// String
+	//   id of the initially shown page
+	selectedChild: "",
+
+	fillInTemplate: function(args, frag) {
+		// Copy style info from input node to output node
+		var source = this.getFragNodeRef(frag);
+		dojo.html.copyStyle(this.domNode, source);
+		dojo.widget.PageContainer.superclass.fillInTemplate.apply(this, arguments);
+	},
+
+	postCreate: function(args, frag) {
+		if(this.children.length){
+			// Setup each page panel
+			dojo.lang.forEach(this.children, this._setupChild, this);
+
+			// Figure out which child to initially display
+			var initialChild;
+			if(this.selectedChild){
+				this.selectChild(this.selectedChild);
+			}else{
+				for(var i=0; i<this.children.length; i++){
+					if(this.children[i].selected){
+						this.selectChild(this.children[i]);
+						break;
+					}
+				}
+				if(!this.selectedChildWidget){
+					this.selectChild(this.children[0]);
+				}
+			}
+		}
+	},
+
+	addChild: function(child){
+		dojo.widget.PageContainer.superclass.addChild.apply(this, arguments);
+		this._setupChild(child);
+
+		// in case the page labels have overflowed from one line to two lines
+		this.onResized();
+
+		// if this is the first child, then select it
+		if(!this.selectedChildWidget){
+			this.selectChild(child);
+		}
+	},
+
+	_setupChild: function(page){
+		// Summary: Add the given child to this page container
+
+		page.hide();
+
+		// publish the addChild event for panes added via addChild(), and the original panes too
+		dojo.event.topic.publish(this.widgetId+"-addChild", page);
+	},
+
+	removeChild: function(/* Widget */page){
+		dojo.widget.PageContainer.superclass.removeChild.apply(this, arguments);
+
+		// If we are being destroyed than don't run the code below (to select another page), because we are deleting
+		// every page one by one
+		if(this._beingDestroyed){ return; }
+
+		// this will notify any tablists to remove a button; do this first because it may affect sizing
+		dojo.event.topic.publish(this.widgetId+"-removeChild", page);
+
+		if (this.selectedChildWidget === page) {
+			this.selectedChildWidget = undefined;
+			if (this.children.length > 0) {
+				this.selectChild(this.children[0], true);
+			}
+		}
+	},
+
+	selectChild: function(/* Widget */ page, /* Widget */ callingWidget){
+		// summary
+		//	Show the given widget (which must be one of my children)
+		page = dojo.widget.byId(page);
+		this.correspondingPageButton = callingWidget;
+
+		// Deselect old page and select new one
+		if(this.selectedChildWidget){
+			this._hideChild(this.selectedChildWidget);
+		}
+		this.selectedChildWidget = page;
+		this._showChild(page);
+		page.isFirstChild = (page == this.children[0]);
+		page.isLastChild = (page == this.children[this.children.length-1]);
+		dojo.event.topic.publish(this.widgetId+"-selectChild", page);
+	},
+
+	forward: function(){
+		// Summary: advance to next page
+		var index = dojo.lang.find(this.children, this.selectedChildWidget);
+		this.selectChild(this.children[index+1]);
+	},
+
+	back: function(){
+		// Summary: go back to previous page
+		var index = dojo.lang.find(this.children, this.selectedChildWidget);
+		this.selectChild(this.children[index-1]);
+	},
+
+	onResized: function(){
+		// Summary: called when any page is shown, to make it fit the container correctly
+		if(this.doLayout && this.selectedChildWidget){
+			with(this.selectedChildWidget.domNode.style){
+				top = dojo.html.getPixelValue(this.containerNode, "padding-top", true);
+				left = dojo.html.getPixelValue(this.containerNode, "padding-left", true);
+			}
+			var content = dojo.html.getContentBox(this.containerNode);
+			this.selectedChildWidget.resizeTo(content.width, content.height);
+		}
+	},
+
+	_showChild: function(page) {
+		// size the current page (in case this is the first time it's being shown, or I have been resized)
+		if(this.doLayout){
+			var content = dojo.html.getContentBox(this.containerNode);
+			page.resizeTo(content.width, content.height);
+		}
+
+		page.selected=true;
+		page.show();
+	},
+
+	_hideChild: function(page) {
+		page.selected=false;
+		page.hide();
+	},
+
+	closeChild: function(page) {
+		// summary
+		//	callback when user clicks the [X] to remove a page
+		//	if onClose() returns true then remove and destroy the childd
+		var remove = page.onClose(this, page);
+		if(remove) {
+			this.removeChild(page);
+			// makes sure we can clean up executeScripts in ContentPane onUnLoad
+			page.destroy();
+		}
+	},
+
+	destroy: function(){
+		this._beingDestroyed = true;
+		dojo.event.topic.destroy(this.widgetId+"-addChild");
+		dojo.event.topic.destroy(this.widgetId+"-removeChild");
+		dojo.event.topic.destroy(this.widgetId+"-selectChild");
+		dojo.widget.PageContainer.superclass.destroy.apply(this, arguments);
+	}
+});
+
+
+// PageController - set of buttons to select the page in a page list
+// When intialized, the PageController monitors the container, and whenever a page is
+// added or deleted updates itself accordingly.
+dojo.widget.defineWidget(
+    "dojo.widget.PageController",
+    dojo.widget.HtmlWidget,
+	{
+		templateString: "<span wairole='tablist' dojoAttachEvent='onKey'></span>",
+		isContainer: true,
+
+		// String
+		//	the id of the page container that I point to
+		containerId: "",
+
+		// String
+		//	the name of the button widget to create to correspond to each page
+		buttonWidget: "PageButton",
+
+		// String
+		//	Class name to apply to the top dom node
+		"class": "dojoPageController",
+
+		fillInTemplate: function() {
+			dojo.html.addClass(this.domNode, this["class"]);  // "class" is a reserved word in JS
+			dojo.widget.wai.setAttr(this.domNode, "waiRole", "role", "tablist");
+		},
+
+		postCreate: function(){
+			this.pane2button = {};		// mapping from panes to buttons
+
+			// If children have already been added to the page container then create buttons for them
+			var container = dojo.widget.byId(this.containerId);
+			if(container){
+				dojo.lang.forEach(container.children, this.onAddChild, this);
+			}
+
+			dojo.event.topic.subscribe(this.containerId+"-addChild", this, "onAddChild");
+			dojo.event.topic.subscribe(this.containerId+"-removeChild", this, "onRemoveChild");
+			dojo.event.topic.subscribe(this.containerId+"-selectChild", this, "onSelectChild");
+		},
+
+		destroy: function(){
+			dojo.event.topic.unsubscribe(this.containerId+"-addChild", this, "onAddChild");
+			dojo.event.topic.unsubscribe(this.containerId+"-removeChild", this, "onRemoveChild");
+			dojo.event.topic.unsubscribe(this.containerId+"-selectChild", this, "onSelectChild");
+			dojo.widget.PageController.superclass.destroy.apply(this, arguments);
+		},
+
+		onAddChild: function(/* Widget */ page){
+			// summary
+			//   Called whenever a page is added to the container.
+			//   Create button corresponding to the page.
+			var button = dojo.widget.createWidget(this.buttonWidget,
+				{
+					label: page.label,
+					closeButton: page.closable
+				});
+			this.addChild(button);
+			this.domNode.appendChild(button.domNode);
+			this.pane2button[page]=button;
+			page.controlButton = button;	// this value might be overwritten if two tabs point to same container
+
+			var _this = this;
+			dojo.event.connect(button, "onClick", function(){ _this.onButtonClick(page); });
+			dojo.event.connect(button, "onCloseButtonClick", function(){ _this.onCloseButtonClick(page); });
+		},
+
+		onRemoveChild: function(/* Widget */ page){
+			// summary
+			//   Called whenever a page is removed from the container.
+			//   Remove the button corresponding to the page.
+			if(this._currentChild == page){ this._currentChild = null; }
+			var button = this.pane2button[page];
+			if(button){
+				button.destroy();
+			}
+			this.pane2button[page] = null;
+		},
+
+		onSelectChild: function(/*Widget*/ page){
+			// Summary
+			//	Called when a page has been selected in the PageContainer, either by me or by another PageController
+			if(this._currentChild){
+				var oldButton=this.pane2button[this._currentChild];
+				oldButton.clearSelected();
+			}
+			var newButton=this.pane2button[page];
+			newButton.setSelected();
+			this._currentChild=page;
+		},
+
+		onButtonClick: function(/*Widget*/ page){
+			// summary
+			//   Called whenever one of my child buttons is pressed in an attempt to select a page
+			var container = dojo.widget.byId(this.containerId);	// TODO: do this via topics?
+			container.selectChild(page, false, this);
+		},
+
+		onCloseButtonClick: function(/*Widget*/ page){
+			// summary
+			//   Called whenever one of my child buttons [X] is pressed in an attempt to close a page
+			var container = dojo.widget.byId(this.containerId);
+			container.closeChild(page);
+		},
+
+		onKey: function(evt){
+			// summary:
+			//   Handle keystrokes on the page list, for advancing to next/previous button
+
+			if( (evt.keyCode == evt.KEY_RIGHT_ARROW)||
+				(evt.keyCode == evt.KEY_LEFT_ARROW) ){
+				var current = 0;
+				var next = null;	// the next button to focus on
+				
+				// find currently focused button in children array
+				var current = dojo.lang.find(this.children, this.pane2button[this._currentChild]);
+				
+				// pick next button to focus on
+				if(evt.keyCode == evt.KEY_RIGHT_ARROW){
+					next = this.children[ (current+1) % this.children.length ]; 
+				}else{ // is LEFT_ARROW
+					next = this.children[ (current+ (this.children.length-1)) % this.children.length ];
+				}
+				
+				dojo.event.browser.stopEvent(evt);
+				next.onClick();
+			}
+		}
+	}
+);
+
+// PageButton (the thing you click to select or delete a page)
+dojo.widget.defineWidget("dojo.widget.PageButton", dojo.widget.HtmlWidget,
+{
+	templateString: "<span class='item'>" +
+						"<span dojoAttachEvent='onClick' dojoAttachPoint='titleNode' class='selectButton'>${this.label}</span>" +
+						"<span dojoAttachEvent='onClick:onCloseButtonClick' class='closeButton'>[X]</span>" +
+					"</span>",
+
+	// String
+	//  Name to print on the button
+	label: "foo",
+	
+	// Boolean
+	//	true iff we should also print a close icon to destroy corresponding page
+	closeButton: false,
+
+	onClick: function(){
+		// summary
+		//  Basically this is the attach point PageController listens to, to select the page
+		this.focus();
+	},
+
+	onCloseButtonMouseOver: function(){
+		// summary
+		//	The close button changes color a bit when you mouse over	
+		dojo.html.addClass(this.closeButtonNode, "closeHover");
+	},
+
+	onCloseButtonMouseOut: function(){
+		// summary
+		// 	Revert close button to normal color on mouse out
+		dojo.html.removeClass(this.closeButtonNode, "closeHover");
+	},
+
+	onCloseButtonClick: function(evt){
+		// summary
+		//	Handle clicking the close button for this tab
+	},
+	
+	setSelected: function(){
+		// summary
+		//	This is run whenever the page corresponding to this button has been selected
+		dojo.html.addClass(this.domNode, "current");
+		this.titleNode.setAttribute("tabIndex","0");
+	},
+	
+	clearSelected: function(){
+		// summary
+		//	This function is run whenever the page corresponding to this button has been deselected (and another page has been shown)
+		dojo.html.removeClass(this.domNode, "current");
+		this.titleNode.setAttribute("tabIndex","-1");
+	},
+
+	focus: function(){
+		// summary
+		//	This will focus on the this button (for accessibility you need to do this when the button is selected)
+		if(this.titleNode.focus){	// mozilla 1.7 doesn't have focus() func
+			this.titleNode.focus();
+		}
+	}
+});
+
+// These arguments can be specified for the children of a PageContainer.
+// Since any widget can be specified as a PageContainer child, mix them
+// into the base widget class.  (This is a hack, but it's effective.)
+dojo.lang.extend(dojo.widget.Widget, {
+	label: "",
+	selected: false,	// is this tab currently selected?
+	closable: false,	// true if user can close this tab pane
+	onClose: function(){ return true; }	// callback if someone tries to close the child, child will be closed if func returns true
+});

Propchange: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Parse.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Parse.js?view=diff&rev=474551&r1=474550&r2=474551
==============================================================================
--- struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Parse.js (original)
+++ struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/Parse.js Mon Nov 13 14:54:45 2006
@@ -1,5 +1,5 @@
 /*
-	Copyright (c) 2004-2005, The Dojo Foundation
+	Copyright (c) 2004-2006, The Dojo Foundation
 	All Rights Reserved.
 
 	Licensed under the Academic Free License version 2.1 or above OR the
@@ -9,110 +9,119 @@
 */
 
 dojo.provide("dojo.widget.Parse");
-
 dojo.require("dojo.widget.Manager");
-dojo.require("dojo.string");
 dojo.require("dojo.dom");
 
-dojo.widget.Parse = function(fragment) {
+//
+// dojoML parser should be moved out of 'widget', codifying the difference between a 'component'
+// and a 'widget'. A 'component' being anything that can be generated from a tag.
+//
+// a particular dojoML tag would be handled by a registered tagHandler with a hook for a default handler
+// if the widget system is loaded, a widget builder would be attach itself as the default handler
+// 
+// widget tags are no longer registered themselves:
+// they are now arbitrarily namespaced, so we cannot register them all, and the non-prefixed portions 
+// are no longer guaranteed unique 
+// 
+// therefore dojo.widget.tags should go with this parser code out of the widget module
+//
+
+dojo.widget.Parse = function(fragment){
 	this.propertySetsList = [];
 	this.fragment = fragment;
-
-	/*	createComponents recurses over a raw JavaScript object structure,
-			and calls the corresponding handler for its normalized tagName if it exists
-	*/
-	this.createComponents = function(fragment, parentComp){
-		var djTags = dojo.widget.tags;
-		var returnValue = [];
-		// this allows us to parse without having to include the parent
-		// it is commented out as it currently breaks the existing mechanism for
-		// adding widgets programmatically.  Once that is fixed, this can be used
-		/*if( (fragment["tagName"])&&
-			(fragment != fragment["nodeRef"])){
-			var tn = new String(fragment["tagName"]);
-			// we split so that you can declare multiple
-			// non-destructive widgets from the same ctor node
-			var tna = tn.split(";");
-			for(var x=0; x<tna.length; x++){
-				var ltn = dojo.text.trim(tna[x]).toLowerCase();
-				if(djTags[ltn]){
-					fragment.tagName = ltn;
-					returnValue.push(djTags[ltn](fragment, this, parentComp, count++));
-				}else{
-					if(ltn.substr(0, 5)=="dojo:"){
-						dj_debug("no tag handler registed for type: ", ltn);
-					}
-				}
-			}
-		}*/
-		for(var item in fragment){
-			var built = false;
-			// if we have items to parse/create at this level, do it!
-			try{
-				if( fragment[item] && (fragment[item]["tagName"])&&
-					(fragment[item] != fragment["nodeRef"])){
-					var tn = new String(fragment[item]["tagName"]);
-					// we split so that you can declare multiple
-					// non-destructive widgets from the same ctor node
-					var tna = tn.split(";");
-					for(var x=0; x<tna.length; x++){
-						var ltn = dojo.string.trim(tna[x]).toLowerCase();
-						if(djTags[ltn]){
+	
+	this.createComponents = function(frag, parentComp){
+		var comps = [];
+		var built = false;
+		// if we have items to parse/create at this level, do it!
+		try{
+			if((frag)&&(frag["tagName"])&&(frag!=frag["nodeRef"])){
+				
+				// these are in fact, not ever for widgets per-se anymore, 
+				// but for other markup elements (aka components)
+				var djTags = dojo.widget.tags;
+				
+				// we split so that you can declare multiple 
+				// non-destructive components from the same ctor node
+				var tna = String(frag["tagName"]).split(";");
+				for(var x=0; x<tna.length; x++){
+					var ltn = (tna[x].replace(/^\s+|\s+$/g, "")).toLowerCase();
+					// FIXME: unsure what this does
+					frag.tagName = ltn;
+					if(djTags[ltn]){
+						built = true;
+						var ret = djTags[ltn](frag, this, parentComp, frag["index"]);
+						comps.push(ret);
+					} else {
+						// we require a namespace prefix, default to dojo:
+						if (ltn.indexOf(":") == -1){
+							ltn = "dojo:"+ltn;
+						}
+						// FIXME: handling failure condition correctly?
+						//var ret = djTags[ltn](frag, this, parentComp, frag["index"]);
+						var ret = dojo.widget.buildWidgetFromParseTree(ltn, frag, this, parentComp, frag["index"]);
+						if (ret) {
 							built = true;
-							// var tic = new Date();
-							fragment[item].tagName = ltn;
-							var ret = djTags[ltn](fragment[item], this, parentComp, fragment[item]["index"])
-							returnValue.push(ret);
-						}else{
-							if((dojo.lang.isString(ltn))&&(ltn.substr(0, 5)=="dojo:")){
-								dojo.debug("no tag handler registed for type: ", ltn);
-							}
+							comps.push(ret);
 						}
 					}
 				}
-			}catch(e){
-				dojo.debug("fragment creation error:", e);
-				// throw(e);
-				// IE is such a bitch sometimes
 			}
+		}catch(e){
+			dojo.debug("dojo.widget.Parse: error:" + e);
+			// note, commenting out the next line is breaking several widgets for me
+			// throw e;
+			// IE is such a bitch sometimes
+		}
+		// if there's a sub-frag, build widgets from that too
+		if(!built){
+			comps = comps.concat(this.createSubComponents(frag, parentComp));
+		}
+		return comps;
+	}
 
-			// if there's a sub-frag, build widgets from that too
-			if( (!built) && (typeof fragment[item] == "object")&&
-				(fragment[item] != fragment.nodeRef)&&
-				(fragment[item] != fragment["tagName"])){
-				returnValue.push(this.createComponents(fragment[item], parentComp));
+	/*	createSubComponents recurses over a raw JavaScript object structure,
+			and calls the corresponding handler for its normalized tagName if it exists
+	*/
+	this.createSubComponents = function(fragment, parentComp){
+		var frag, comps = [];
+		for(var item in fragment){
+			frag = fragment[item];
+			if ((frag)&&(typeof frag == "object")&&(frag!=fragment.nodeRef)&&(frag!=fragment["tagName"])){
+				comps = comps.concat(this.createComponents(frag, parentComp));
 			}
 		}
-		return returnValue;
+		return comps;
 	}
 
 	/*  parsePropertySets checks the top level of a raw JavaScript object
 			structure for any propertySets.  It stores an array of references to 
 			propertySets that it finds.
 	*/
-	this.parsePropertySets = function(fragment) {
+	this.parsePropertySets = function(fragment){
 		return [];
+		/*
 		var propertySets = [];
 		for(var item in fragment){
-			if(	(fragment[item]["tagName"] == "dojo:propertyset") ) {
+			if((fragment[item]["tagName"] == "dojo:propertyset")){
 				propertySets.push(fragment[item]);
 			}
 		}
 		// FIXME: should we store these propertySets somewhere for later retrieval
 		this.propertySetsList.push(propertySets);
 		return propertySets;
+		*/
 	}
 	
 	/*  parseProperties checks a raw JavaScript object structure for
 			properties, and returns an array of properties that it finds.
 	*/
-	this.parseProperties = function(fragment) {
+	this.parseProperties = function(fragment){
 		var properties = {};
 		for(var item in fragment){
 			// FIXME: need to check for undefined?
 			// case: its a tagName or nodeRef
-			if((fragment[item] == fragment["tagName"])||
-				(fragment[item] == fragment.nodeRef)){
+			if((fragment[item] == fragment["tagName"])||(fragment[item] == fragment.nodeRef)){
 				// do nothing
 			}else{
 				if((fragment[item]["tagName"])&&
@@ -121,7 +130,7 @@
 					// so do something else
 					// FIXME: needs to be a better/stricter check
 					// TODO: handle xlink:href for external property sets
-				}else if((fragment[item][0])&&(fragment[item][0].value!="")){
+				}else if((fragment[item][0])&&(fragment[item][0].value!="")&&(fragment[item][0].value!=null)){
 					try{
 						// FIXME: need to allow more than one provider
 						if(item.toLowerCase() == "dataprovider") {
@@ -137,7 +146,15 @@
 						}
 					}catch(e){ dojo.debug(e); }
 				}
-			}
+				switch(item.toLowerCase()){
+				case "checked":
+				case "disabled":
+					if (typeof properties[item] != "boolean"){ 
+						properties[item] = true;
+					}
+				break;
+				}
+			} 
 		}
 		return properties;
 	}
@@ -145,7 +162,7 @@
 	/* getPropertySetById returns the propertySet that matches the provided id
 	*/
 	
-	this.getDataProvider = function(objRef, dataUrl) {
+	this.getDataProvider = function(objRef, dataUrl){
 		// FIXME: this is currently sync.  To make this async, we made need to move 
 		//this step into the widget ctor, so that it is loaded when it is needed 
 		// to populate the widget
@@ -179,6 +196,7 @@
 		for(var x=0; x < this.propertySetsList.length; x++){
 			var cpl = this.propertySetsList[x];
 			var cpcc = cpl["componentClass"]||cpl["componentType"]||null;
+			var propertySetId = this.propertySetsList[x]["id"][0].value;
 			if((cpcc)&&(propertySetId == cpcc[0].value)){
 				propertySets.push(cpl);
 			}
@@ -199,7 +217,7 @@
 			// FIXME: need a better test to see if this is local or external
 			// FIXME: doesn't handle nested propertySets, or propertySets that
 			// 		  just contain information about css documents, etc.
-			for(propertySetId in propertyProviderIds){
+			for(var propertySetId in propertyProviderIds){
 				if((propertySetId.indexOf("..")==-1)&&(propertySetId.indexOf("://")==-1)){
 					// get a reference to a propertySet within the current parsed structure
 					var propertySet = this.getPropertySetById(propertySetId);
@@ -225,21 +243,19 @@
 		componentName is the expected dojo widget name, i.e. Button of ContextMenu
 
 		properties is an object of name value pairs
+		ns is the namespace of the widget.  Defaults to "dojo"
 	*/
-	this.createComponentFromScript = function(nodeRef, componentName, properties){
-		var ltn = "dojo:" + componentName.toLowerCase();
+	this.createComponentFromScript = function(nodeRef, componentName, properties, ns){
+		properties.fastMixIn = true;			
+		// FIXME: we pulled it apart and now we put it back together ... 
+		var ltn = (ns || "dojo") + ":" + componentName.toLowerCase();
 		if(dojo.widget.tags[ltn]){
-			properties.fastMixIn = true;
 			return [dojo.widget.tags[ltn](properties, this, null, null, properties)];
-		}else{
-			if(ltn.substr(0, 5)=="dojo:"){
-				dojo.debug("no tag handler registed for type: ", ltn);
-			}
 		}
+		return [dojo.widget.buildWidgetFromParseTree(ltn, properties, this, null, null, properties)];
 	}
 }
 
-
 dojo.widget._parser_collection = {"dojo": new dojo.widget.Parse() };
 dojo.widget.getParser = function(name){
 	if(!name){ name = "dojo"; }
@@ -252,37 +268,47 @@
 /**
  * Creates widget.
  *
- * @param name     The name of the widget to create
+ * @param name     The name of the widget to create with optional namespace prefix,
+ *                 e.g."ns:widget", namespace defaults to "dojo".
  * @param props    Key-Value pairs of properties of the widget
- * @param refNode  If the last argument is specified this node is used as
- *                 a reference for inserting this node into a DOM tree else
- *                 it beomces the domNode
+ * @param refNode  If the position argument is specified, this node is used as
+ *                 a reference for inserting this node into a DOM tree; else
+ *                 the widget becomes the domNode
  * @param position The position to insert this widget's node relative to the
  *                 refNode argument
  * @return The new Widget object
  */
- 
-dojo.widget.createWidget = function (name, props, refNode, position) {
 
-	function fromScript (placeKeeperNode, name, props) {
+dojo.widget.createWidget = function(name, props, refNode, position){
+	var isNode = false;
+	var isNameStr = (typeof name == "string");
+	if(isNameStr){
+		var pos = name.indexOf(":");
+		var ns = (pos > -1) ? name.substring(0,pos) : "dojo";
+		if(pos > -1){ name = name.substring(pos+1); }
 		var lowerCaseName = name.toLowerCase();
-		var namespacedName = "dojo:" + lowerCaseName;
+		var namespacedName = ns + ":" + lowerCaseName;
+		isNode = (dojo.byId(name) && (!dojo.widget.tags[namespacedName])); 
+	}
+
+	if((arguments.length == 1) && ((isNode)||(!isNameStr))){
+		// we got a DOM node 
+		var xp = new dojo.xml.Parse(); 
+		// FIXME: we should try to find the parent! 
+		var tn = (isNode) ? dojo.byId(name) : name; 
+		return dojo.widget.getParser().createComponents(xp.parseElement(tn, null, true))[0]; 
+	}
+	
+	function fromScript(placeKeeperNode, name, props, ns){
 		props[namespacedName] = { 
 			dojotype: [{value: lowerCaseName}],
 			nodeRef: placeKeeperNode,
 			fastMixIn: true
 		};
-		return dojo.widget.getParser().createComponentFromScript(
-			placeKeeperNode, name, props, true);
+		props.ns = ns;
+		return dojo.widget.getParser().createComponentFromScript(placeKeeperNode, name, props, ns);
 	}
 
-	if (typeof name != "string" && typeof props == "string") {
-		dojo.deprecated("dojo.widget.createWidget", 
-			"argument order is now of the form " +
-			"dojo.widget.createWidget(NAME, [PROPERTIES, [REFERENCENODE, [POSITION]]])");
-		return fromScript(name, props, refNode);
-	}
-	
 	props = props||{};
 	var notRef = false;
 	var tn = null;
@@ -294,27 +320,27 @@
 		notRef = true;
 		refNode = tn;
 		if(h){
-			dojo.html.body().appendChild(refNode);
+			dojo.body().appendChild(refNode);
 		}
 	}else if(position){
 		dojo.dom.insertAtPosition(tn, refNode, position);
 	}else{ // otherwise don't replace, but build in-place
 		tn = refNode;
 	}
-	var widgetArray = fromScript(tn, name, props);
-	if (!widgetArray[0] || typeof widgetArray[0].widgetType == "undefined") {
+	var widgetArray = fromScript(tn, name.toLowerCase(), props, ns);
+	if(	(!widgetArray)||(!widgetArray[0])||
+		(typeof widgetArray[0].widgetType == "undefined") ){
 		throw new Error("createWidget: Creation of \"" + name + "\" widget failed.");
 	}
-	if (notRef) {
-		if (widgetArray[0].domNode.parentNode) {
-			widgetArray[0].domNode.parentNode.removeChild(widgetArray[0].domNode);
+	try{
+		if(notRef){
+			if(widgetArray[0].domNode.parentNode){
+				widgetArray[0].domNode.parentNode.removeChild(widgetArray[0].domNode);
+			}
 		}
+	}catch(e){
+		/* squelch for Safari */
+		dojo.debug(e);
 	}
 	return widgetArray[0]; // just return the widget
-}
- 
-dojo.widget.fromScript = function(name, props, refNode, position){
-	dojo.deprecated("dojo.widget.fromScript", " use " +
-		"dojo.widget.createWidget instead");
-	return dojo.widget.createWidget(name, props, refNode, position);
 }

Added: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PopupContainer.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PopupContainer.js?view=auto&rev=474551
==============================================================================
--- struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PopupContainer.js (added)
+++ struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PopupContainer.js Mon Nov 13 14:54:45 2006
@@ -0,0 +1,452 @@
+/*
+	Copyright (c) 2004-2006, The Dojo Foundation
+	All Rights Reserved.
+
+	Licensed under the Academic Free License version 2.1 or above OR the
+	modified BSD license. For more information on Dojo licensing, see:
+
+		http://dojotoolkit.org/community/licensing.shtml
+*/
+
+dojo.provide("dojo.widget.PopupContainer");
+
+dojo.require("dojo.html.style");
+dojo.require("dojo.html.layout");
+dojo.require("dojo.html.selection");
+dojo.require("dojo.html.iframe");
+dojo.require("dojo.event.*");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.HtmlWidget");
+
+// summary:
+//		PopupContainerBase is the mixin class which provide popup behaviors:
+//		it can open in a given position x,y or around a given node.
+//		In addition, it handles animation and IE bleed through workaround.
+// description:
+//		This class can not be used standalone: it should be mixed-in to a
+//		dojo.widget.HtmlWidget. Use PopupContainer instead if you want a
+//		a standalone popup widget
+dojo.declare(
+	"dojo.widget.PopupContainerBase",
+	null,
+	function(){
+		this.queueOnAnimationFinish = [];
+	},
+{
+	isContainer: true,
+	templateString: '<div dojoAttachPoint="containerNode" style="display:none;position:absolute;" class="dojoPopupContainer" ></div>',
+
+	// Boolean: whether this popup is shown
+	isShowingNow: false,
+
+	// Widget: the shown sub popup if any
+	currentSubpopup: null,
+
+	// Int: the minimal popup zIndex
+	beginZIndex: 1000,
+
+	// Widget: parent popup widget
+	parentPopup: null,
+	// Widget: parent Widget
+	parent: null,
+	// Int: level of sub popup
+	popupIndex: 0,
+
+	// dojo.html.boxSizing: which bounding box to use for open aroundNode. By default use BORDER box of the aroundNode
+	aroundBox: dojo.html.boxSizing.BORDER_BOX,
+
+	// Object: in which window, the open() is triggered
+	openedForWindow: null,
+
+	processKey: function(/*Event*/evt){
+		// summary: key event handler
+		return false;
+	},
+
+	applyPopupBasicStyle: function(){
+		// summary: apply necessary css rules to the top domNode
+		// description:
+		//		this function should be called in sub class where a custom
+		//		templateString/templateStringPath is used (see Tooltip widget)
+		with(this.domNode.style){
+			display = 'none';
+			position = 'absolute';
+		}
+	},
+
+	aboutToShow: function() {
+		// summary: connect to this stub to modify the content of the popup
+	},
+
+	open: function(/*Integer*/x, /*Integer*/y, /*DomNode*/parent, /*Object*/explodeSrc, /*String?*/orient, /*Array?*/padding){
+		// summary:
+		//		Open the popup at position (x,y), relative to dojo.body()
+	 	//		Or open(node, parent, explodeSrc, aroundOrient) to open
+	 	//		around node
+		if (this.isShowingNow){ return; }
+
+		this.aboutToShow();
+
+		// if I click right button and menu is opened, then it gets 2 commands: close -> open
+		// so close enables animation and next "open" is put to queue to occur at new location
+		if(this.animationInProgress){
+			this.queueOnAnimationFinish.push(this.open, arguments);
+			return;
+		}
+
+		// save this so that the focus can be returned
+		this.parent = parent;
+
+		var around = false, node, aroundOrient;
+		if(typeof x == 'object'){
+			node = x;
+			aroundOrient = explodeSrc;
+			explodeSrc = parent;
+			parent = y;
+			around = true;
+		}
+
+		// for unknown reasons even if the domNode is attached to the body in postCreate(),
+		// it's not attached here, so have to attach it here.
+		dojo.body().appendChild(this.domNode);
+
+		// if explodeSrc isn't specified then explode from my parent widget
+		explodeSrc = explodeSrc || parent["domNode"] || [];
+
+		//keep track of parent popup to decided whether this is a top level popup
+		var parentPopup = null;
+		this.isTopLevel = true;
+		while(parent){
+			if(parent !== this && (parent.setOpenedSubpopup != undefined && parent.applyPopupBasicStyle != undefined)){
+				parentPopup = parent;
+				this.isTopLevel = false;
+				parentPopup.setOpenedSubpopup(this);
+				break;
+			}
+			parent = parent.parent;
+		}
+
+		this.parentPopup = parentPopup;
+		this.popupIndex = parentPopup ? parentPopup.popupIndex + 1 : 1;
+
+		if(this.isTopLevel){
+			var button = dojo.html.isNode(explodeSrc) ? explodeSrc : null;
+			dojo.widget.PopupManager.opened(this, button);
+		}
+
+		//Store the current selection and restore it before the action for a menu item
+		//is executed. This is required as clicking on an menu item deselects current selection
+		if(this.isTopLevel && !dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){
+			this._bookmark = dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.getBookmark);
+		}else{
+			this._bookmark = null;
+		}
+
+		//convert explodeSrc from format [x, y] to
+		//{left: x, top: y, width: 0, height: 0} which is the new
+		//format required by dojo.html.toCoordinateObject
+		if(explodeSrc instanceof Array){
+			explodeSrc = {left: explodeSrc[0], top: explodeSrc[1], width: 0, height: 0};
+		}
+
+		// display temporarily, and move into position, then hide again
+		with(this.domNode.style){
+			display="";
+			zIndex = this.beginZIndex + this.popupIndex;
+		}
+
+		if(around){
+			this.move(node, padding, aroundOrient);
+		}else{
+			this.move(x, y, padding, orient);
+		}
+		this.domNode.style.display="none";
+
+		this.explodeSrc = explodeSrc;
+
+		// then use the user defined method to display it
+		this.show();
+
+		this.isShowingNow = true;
+	},
+
+	// TODOC: move(node, padding, aroundOrient) how to do this?
+	move: function(/*Int*/x, /*Int*/y, /*Integer?*/padding, /*String?*/orient){
+		// summary: calculate where to place the popup
+
+		var around = (typeof x == "object");
+		if(around){
+			var aroundOrient=padding;
+			var node=x;
+			padding=y;
+			if(!aroundOrient){ //By default, attempt to open above the aroundNode, or below
+				aroundOrient = {'BL': 'TL', 'TL': 'BL'};
+			}
+			dojo.html.placeOnScreenAroundElement(this.domNode, node, padding, this.aroundBox, aroundOrient);
+		}else{
+			if(!orient){ orient = 'TL,TR,BL,BR';}
+			dojo.html.placeOnScreen(this.domNode, x, y, padding, true, orient);
+		}
+	},
+
+	close: function(/*Boolean?*/force){
+		// summary: hide the popup
+		if(force){
+			this.domNode.style.display="none";
+		}
+
+		// If we are in the process of opening the menu and we are asked to close it
+		if(this.animationInProgress){
+			this.queueOnAnimationFinish.push(this.close, []);
+			return;
+		}
+
+		this.closeSubpopup(force);
+		this.hide();
+		if(this.bgIframe){
+			this.bgIframe.hide();
+			this.bgIframe.size({left: 0, top: 0, width: 0, height: 0});
+		}
+		if(this.isTopLevel){
+			dojo.widget.PopupManager.closed(this);
+		}
+		this.isShowingNow = false;
+		// return focus to the widget that opened the menu
+		try {
+			this.parent.domNode.focus();
+		} catch(e) {}
+
+		//do not need to restore if current selection is not empty
+		//(use keyboard to select a menu item)
+		if(this._bookmark && dojo.withGlobal(this.openedForWindow||dojo.global(), dojo.html.selection.isCollapsed)){
+			if(this.openedForWindow){
+				this.openedForWindow.focus()
+			}
+			dojo.withGlobal(this.openedForWindow||dojo.global(), "moveToBookmark", dojo.html.selection, [this._bookmark]);
+		}
+		this._bookmark = null;
+	},
+
+	closeAll: function(/*Boolean?*/force){
+		// summary: hide all popups including sub ones
+		if (this.parentPopup){
+			this.parentPopup.closeAll(force);
+		}else{
+			this.close(force);
+		}
+	},
+
+	setOpenedSubpopup: function(/*Widget*/popup) {
+		// summary: used by sub popup to set currentSubpopup in the parent popup
+		this.currentSubpopup = popup;
+	},
+
+	closeSubpopup: function(/*Boolean?*/force) {
+		// summary: close opened sub popup
+		if(this.currentSubpopup == null){ return; }
+
+		this.currentSubpopup.close(force);
+		this.currentSubpopup = null;
+	},
+
+	onShow: function() {
+		dojo.widget.PopupContainer.superclass.onShow.apply(this, arguments);
+		// With some animation (wipe), after close, the size of the domnode is 0
+		// and next time when shown, the open() function can not determine
+		// the correct place to popup, so we store the opened size here and
+		// set it after close (in function onHide())
+		this.openedSize={w: this.domNode.style.width, h: this.domNode.style.height};
+		// prevent IE bleed through
+		if(dojo.render.html.ie){
+			if(!this.bgIframe){
+				this.bgIframe = new dojo.html.BackgroundIframe();
+				this.bgIframe.setZIndex(this.domNode);
+			}
+
+			this.bgIframe.size(this.domNode);
+			this.bgIframe.show();
+		}
+		this.processQueue();
+	},
+
+	processQueue: function() {
+		// summary: do events from queue
+		if (!this.queueOnAnimationFinish.length) return;
+
+		var func = this.queueOnAnimationFinish.shift();
+		var args = this.queueOnAnimationFinish.shift();
+
+		func.apply(this, args);
+	},
+
+	onHide: function() {
+		dojo.widget.HtmlWidget.prototype.onHide.call(this);
+
+		//restore size of the domnode, see comment in
+		//function onShow()
+		if(this.openedSize){
+			with(this.domNode.style){
+				width=this.openedSize.w;
+				height=this.openedSize.h;
+			}
+		}
+
+		this.processQueue();
+	}
+});
+
+// summary: dojo.widget.PopupContainer is the widget version of dojo.widget.PopupContainerBase
+dojo.widget.defineWidget(
+	"dojo.widget.PopupContainer",
+	[dojo.widget.HtmlWidget, dojo.widget.PopupContainerBase], {});
+
+
+// summary:
+//		the popup manager makes sure we don't have several popups
+//		open at once. the root popup in an opening sequence calls
+//		opened(). when a root menu closes it calls closed(). then
+//		everything works. lovely.
+dojo.widget.PopupManager = new function(){
+	this.currentMenu = null;
+	this.currentButton = null;		// button that opened current menu (if any)
+	this.currentFocusMenu = null;	// the (sub)menu which receives key events
+	this.focusNode = null;
+	this.registeredWindows = [];
+
+	this.registerWin = function(/*Window*/win){
+		// summary: register a window so that when clicks/scroll in it, the popup can be closed automatically
+		if(!win.__PopupManagerRegistered)
+		{
+			dojo.event.connect(win.document, 'onmousedown', this, 'onClick');
+			dojo.event.connect(win, "onscroll", this, "onClick");
+			dojo.event.connect(win.document, "onkey", this, 'onKey');
+			win.__PopupManagerRegistered = true;
+			this.registeredWindows.push(win);
+		}
+	};
+
+	/*
+
+	*/
+	this.registerAllWindows = function(/*Window*/targetWindow){
+		// summary:
+		//		This function register all the iframes and the top window,
+		//		so that whereever the user clicks in the page, the popup
+		//		menu will be closed
+		//		In case you add an iframe after onload event, please call
+		//		dojo.widget.PopupManager.registerWin manually
+
+		//starting from window.top, clicking everywhere in this page
+		//should close popup menus
+		if(!targetWindow) { //see comment below
+			targetWindow = dojo.html.getDocumentWindow(window.top && window.top.document || window.document);
+		}
+
+		this.registerWin(targetWindow);
+
+		for (var i = 0; i < targetWindow.frames.length; i++){
+			try{
+				//do not remove  dojo.html.getDocumentWindow, see comment in it
+				var win = dojo.html.getDocumentWindow(targetWindow.frames[i].document);
+				if(win){
+					this.registerAllWindows(win);
+				}
+			}catch(e){ /* squelch error for cross domain iframes */ }
+		}
+	};
+
+	this.unRegisterWin = function(/*Window*/win){
+		// summary: remove listeners on the registered window
+		if(win.__PopupManagerRegistered)
+		{
+			dojo.event.disconnect(win.document, 'onmousedown', this, 'onClick');
+			dojo.event.disconnect(win, "onscroll", this, "onClick");
+			dojo.event.disconnect(win.document, "onkey", this, 'onKey');
+			win.__PopupManagerRegistered = false;
+		}
+	};
+
+	this.unRegisterAllWindows = function(){
+		// summary: remove listeners on all the registered windows
+		for(var i=0;i<this.registeredWindows.length;++i){
+			this.unRegisterWin(this.registeredWindows[i]);
+		}
+		this.registeredWindows = [];
+	};
+
+	dojo.addOnLoad(this, "registerAllWindows");
+	dojo.addOnUnload(this, "unRegisterAllWindows");
+
+	this.closed = function(/*Widget*/menu){
+		// summary: notify the manager that menu is closed
+		if (this.currentMenu == menu){
+			this.currentMenu = null;
+			this.currentButton = null;
+			this.currentFocusMenu = null;
+		}
+	};
+
+	this.opened = function(/*Widget*/menu, /*DomNode*/button){
+		// summary: sets the current opened popup
+		if (menu == this.currentMenu){ return; }
+
+		if (this.currentMenu){
+			this.currentMenu.close();
+		}
+
+		this.currentMenu = menu;
+		this.currentFocusMenu = menu;
+		this.currentButton = button;
+	};
+
+	this.setFocusedMenu = function(/*Widget*/menu){
+		// summary:
+		// 		Set the current focused popup, This is used by popups which supports keyboard navigation
+		this.currentFocusMenu = menu;
+	};
+
+	this.onKey = function(/*Event*/e){
+		if (!e.key) { return; }
+		if(!this.currentMenu || !this.currentMenu.isShowingNow){ return; }
+
+		var m = this.currentFocusMenu;
+		while (m){
+			if(m.processKey(e)){
+				e.preventDefault();
+				e.stopPropagation();
+				break;
+			}
+			m = m.parentPopup;
+		}
+	},
+
+	this.onClick = function(/*Event*/e){
+		if (!this.currentMenu){ return; }
+
+		var scrolloffset = dojo.html.getScroll().offset;
+
+		// starting from the base menu, perform a hit test
+		// and exit when one succeeds
+
+		var m = this.currentMenu;
+
+		while (m){
+			if(dojo.html.overElement(m.domNode, e) || dojo.html.isDescendantOf(e.target, m.domNode)){
+				return;
+			}
+			m = m.currentSubpopup;
+		}
+
+		// Also, if user clicked the button that opened this menu, then
+		// that button will send the menu a close() command, so this code
+		// shouldn't try to close the menu.  Closing twice messes up animation.
+		if (this.currentButton && dojo.html.overElement(this.currentButton, e)){
+			return;
+		}
+
+		// the click didn't fall within the open menu tree
+		// so close it
+
+		this.currentMenu.close();
+	};
+}
\ No newline at end of file

Propchange: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/src/widget/PopupContainer.js
------------------------------------------------------------------------------
    svn:eol-style = native