You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by mu...@apache.org on 2007/03/03 06:49:21 UTC

svn commit: r514083 [34/49] - in /struts/struts2/trunk/plugins/dojo: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/struts2/ src/main/java/org/apache/struts2/components/ src/main/java/org/apache/s...

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InlineEditBox.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,212 @@
+/*
+	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.InlineEditBox");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.event.*");
+dojo.require("dojo.lfx.*");
+dojo.require("dojo.gfx.color");
+dojo.require("dojo.string");
+dojo.require("dojo.html.*");
+dojo.require("dojo.html.layout");
+
+dojo.widget.defineWidget(
+	"dojo.widget.InlineEditBox",
+	dojo.widget.HtmlWidget,
+	function(){
+		// mutable objects need to be in constructor to give each instance its own copy
+		this.history = [];
+	},
+{
+	templatePath: dojo.uri.dojoUri("src/widget/templates/InlineEditBox.html"),
+	templateCssPath: dojo.uri.dojoUri("src/widget/templates/InlineEditBox.css"),
+
+	form: null,
+	editBox: null,
+	edit: null,
+	text: null,
+	textarea: null,
+	submitButton: null,
+	cancelButton: null,
+	mode: "text",
+	name: "",
+	
+	minWidth: 100, //px. minimum width of edit box
+	minHeight: 200, //px. minimum width of edit box, if it's a TA
+
+	editing: false,
+	textValue: "",
+	defaultText: "",
+	doFade: false,
+	
+	onSave: function(newValue, oldValue, name){},
+	onUndo: function(value){},
+
+	postCreate: function(args, frag){
+		// put original node back in the document, and attach handlers
+		// which hide it and display the editor
+		this.editable = this.getFragNodeRef(frag);
+		dojo.html.insertAfter(this.editable, this.form);
+		dojo.event.connect(this.editable, "onmouseover", this, "onMouseOver");
+		dojo.event.connect(this.editable, "onmouseout", this, "onMouseOut");
+		dojo.event.connect(this.editable, "onclick", this, "beginEdit");
+
+		this.textValue = dojo.string.trim(this.editable.innerHTML);
+		if(dojo.string.trim(this.textValue).length == 0){
+			this.editable.innerHTML = this.defaultText;
+		}		
+	},
+	
+	onMouseOver: function(){
+		if(!this.editing){
+			if (!this.isEnabled){
+				dojo.html.addClass(this.editable, "editableRegionDisabled");
+			} else {
+				dojo.html.addClass(this.editable, "editableRegion");
+				if(this.mode == "textarea"){
+					dojo.html.addClass(this.editable, "editableTextareaRegion");
+				}
+			}
+		}
+		
+		this.mouseover();
+	},
+	
+	mouseover: function(e){
+		// TODO: How do we deprecate a function without going into overkill with debug statements?
+		// dojo.deprecated("onMouseOver should be used instead of mouseover to listen for mouse events");
+	},
+	
+	onMouseOut: function(){
+		if(!this.editing){
+			dojo.html.removeClass(this.editable, "editableRegion");
+			dojo.html.removeClass(this.editable, "editableTextareaRegion");
+			dojo.html.removeClass(this.editable, "editableRegionDisabled");
+		}
+		
+		this.mouseout();
+	},
+	
+	mouseout: function(e){
+		// dojo.deprecated("onMouseOut should be used instead of mouseout to listen for mouse events");
+	},
+
+	// When user clicks the text, then start editing.
+	// Hide the text and display the form instead.
+	beginEdit: function(e){
+		if(this.editing || !this.isEnabled){ return; }
+		this.onMouseOut();
+		this.editing = true;
+
+		// setup the form's <input> or <textarea> field, as specified by mode
+		var ee = this[this.mode.toLowerCase()];
+		ee.value = dojo.string.trim(this.textValue);
+		ee.style.fontSize = dojo.html.getStyle(this.editable, "font-size");
+		ee.style.fontWeight = dojo.html.getStyle(this.editable, "font-weight");
+		ee.style.fontStyle = dojo.html.getStyle(this.editable, "font-style");
+		var bb = dojo.html.getBorderBox(this.editable);
+		ee.style.width = Math.max(bb.width, this.minWidth) + "px";
+		if(this.mode.toLowerCase()=="textarea"){
+			ee.style.display = "block";
+			ee.style.height = Math.max(bb.height, this.minHeight) + "px";
+		} else {
+			ee.style.display = "";
+		}
+
+		// show the edit form and hide the read only version of the text
+		this.form.style.display = "";
+		this.editable.style.display = "none";
+
+		ee.focus();
+		ee.select();
+		this.submitButton.disabled = true;
+	},
+
+	saveEdit: function(e){
+		e.preventDefault();
+		e.stopPropagation();
+		var ee = this[this.mode.toLowerCase()];
+		if((this.textValue != ee.value)&&
+			(dojo.string.trim(ee.value) != "")){
+			this.doFade = true;
+			this.history.push(this.textValue);
+			this.onSave(ee.value, this.textValue, this.name);
+			this.textValue = ee.value;
+			this.editable.innerHTML = "";
+			var textNode = document.createTextNode( this.textValue );
+			this.editable.appendChild( textNode );
+		}else{
+			this.doFade = false;
+		}
+		this.finishEdit(e);
+	},
+
+	cancelEdit: function(e){
+		if(!this.editing){ return false; }
+		this.editing = false;
+		this.form.style.display="none";
+		this.editable.style.display = "";
+		return true;
+	},
+
+	finishEdit: function(e){
+		if(!this.cancelEdit(e)){ return; }
+		if(this.doFade) {
+			dojo.lfx.highlight(this.editable, dojo.gfx.color.hex2rgb("#ffc"), 700).play(300);
+		}
+		this.doFade = false;
+	},
+	
+	setText: function(txt){
+		// sets the text without informing the server
+		txt = "" + txt;
+		var tt = dojo.string.trim(txt);
+		this.textValue = tt
+		this.editable.innerHTML = tt;
+	},
+
+	undo: function(){
+		if(this.history.length > 0){
+			var curValue = this.textValue;
+			var value = this.history.pop();
+			this.editable.innerHTML = value;
+			this.textValue = value;
+			this.onUndo(value);
+			this.onSave(value, curValue, this.name);
+		}
+	},
+
+	checkForValueChange: function(){
+		var ee = this[this.mode.toLowerCase()];
+		if((this.textValue != ee.value)&&
+			(dojo.string.trim(ee.value) != "")){
+			this.submitButton.disabled = false;
+		}
+	},
+	
+	disable: function(){
+		this.submitButton.disabled = true;
+		this.cancelButton.disabled = true;
+		var ee = this[this.mode.toLowerCase()];
+		ee.disabled = true;
+		
+		dojo.widget.Widget.prototype.disable.call(this);
+	},
+	
+	enable: function(){
+		this.checkForValueChange();
+		this.cancelButton.disabled = false;
+		var ee = this[this.mode.toLowerCase()];
+		ee.disabled = false;
+		
+		dojo.widget.Widget.prototype.enable.call(this);
+	}
+});

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/IntegerTextbox.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,66 @@
+/*
+	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.IntegerTextbox");
+
+dojo.require("dojo.widget.ValidationTextbox");
+dojo.require("dojo.validate.common");
+
+/*
+  ****** IntegerTextbox ******
+
+  A subclass of ValidationTextbox.
+  Over-rides isValid/isInRange to test for integer input.
+  Has 4 new properties that can be specified as attributes in the markup.
+
+  @attr signed     The leading plus-or-minus sign. Can be true or false, default is either.
+  @attr separator  The character used as the thousands separator.  Default is no separator.
+  @attr min  Minimum signed value.  Default is -Infinity
+  @attr max  Maximum signed value.  Default is +Infinity
+*/
+dojo.widget.defineWidget(
+	"dojo.widget.IntegerTextbox",
+	dojo.widget.ValidationTextbox,
+	{
+		mixInProperties: function(localProperties, frag){
+			// First initialize properties in super-class.
+			dojo.widget.IntegerTextbox.superclass.mixInProperties.apply(this, arguments);
+	
+			// Get properties from markup attributes, and assign to flags object.
+			if((localProperties.signed == "true")||
+				(localProperties.signed == "always")){
+				this.flags.signed = true;
+			}else if((localProperties.signed == "false")||
+					(localProperties.signed == "never")){
+				this.flags.signed = false;
+				this.flags.min = 0;
+			}else{
+				this.flags.signed = [ true, false ]; // optional
+			}
+			if(localProperties.separator){ 
+				this.flags.separator = localProperties.separator;
+			}
+			if(localProperties.min){ 
+				this.flags.min = parseInt(localProperties.min);
+			}
+			if(localProperties.max){ 
+				this.flags.max = parseInt(localProperties.max);
+			}
+		},
+
+		// Over-ride for integer validation
+		isValid: function(){
+			return dojo.validate.isInteger(this.textbox.value, this.flags);
+		},
+		isInRange: function(){
+			return dojo.validate.isInRange(this.textbox.value, this.flags);
+		}
+	}
+);

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/InternetTextbox.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,168 @@
+/*
+	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.InternetTextbox");
+
+dojo.require("dojo.widget.ValidationTextbox");
+dojo.require("dojo.validate.web");
+
+dojo.widget.defineWidget(
+	"dojo.widget.IpAddressTextbox",
+	dojo.widget.ValidationTextbox,
+	{
+		// summary:  A Textbox which tests for a valid IP address
+		// description:  Can specify formats for ipv4 or ipv6 as attributes in the markup.
+		//
+		// allowDottedDecimal  true or false, default is true.
+		// allowDottedHex      true or false, default is true.
+		// allowDottedOctal    true or false, default is true.
+		// allowDecimal        true or false, default is true.
+		// allowHex            true or false, default is true.
+		// allowIPv6           true or false, default is true.
+		// allowHybrid         true or false, default is true.
+
+		mixInProperties: function(/*Object*/localProperties){
+			// summary: see dojo.widget.Widget
+
+			// First initialize properties in super-class.
+			dojo.widget.IpAddressTextbox.superclass.mixInProperties.apply(this, arguments);
+
+			// Get properties from markup attributes, and assign to flags object.
+			if(localProperties.allowdotteddecimal){ 
+				this.flags.allowDottedDecimal = (localProperties.allowdotteddecimal == "true");
+			}
+			if(localProperties.allowdottedhex){ 
+				this.flags.allowDottedHex = (localProperties.allowdottedhex == "true");
+			}
+			if(localProperties.allowdottedoctal){ 
+				this.flags.allowDottedOctal = (localProperties.allowdottedoctal == "true");
+			}
+			if(localProperties.allowdecimal){ 
+				this.flags.allowDecimal = (localProperties.allowdecimal == "true");
+			}
+			if(localProperties.allowhex){ 
+				this.flags.allowHex = (localProperties.allowhex == "true");
+			}
+			if(localProperties.allowipv6){ 
+				this.flags.allowIPv6 = (localProperties.allowipv6 == "true");
+			}
+			if(localProperties.allowhybrid){ 
+				this.flags.allowHybrid = (localProperties.allowhybrid == "true");
+			}
+		},
+
+		isValid: function(){ 
+			// summary: see dojo.widget.ValidationTextbox
+			return dojo.validate.isIpAddress(this.textbox.value, this.flags);
+		}
+	}
+);
+
+dojo.widget.defineWidget(
+	"dojo.widget.UrlTextbox",
+	dojo.widget.IpAddressTextbox,
+	{
+		// summary:  A Textbox which tests for a valid URL
+		// scheme        Can be true or false.  If omitted the scheme is optional.
+		// allowIP       Allow an IP address for hostname.  Default is true.
+		// allowLocal    Allow the host to be "localhost".  Default is false.
+		// allowCC       Allow 2 letter country code domains.  Default is true.
+		// allowGeneric  Allow generic domains.  Can be true or false, default is true.
+
+		mixInProperties: function(/*Object*/localProperties){
+			// summary: see dojo.widget.Widget
+
+			// First initialize properties in super-class.
+			dojo.widget.UrlTextbox.superclass.mixInProperties.apply(this, arguments);
+
+			// Get properties from markup attributes, and assign to flags object.
+			if ( localProperties.scheme ) { 
+				this.flags.scheme = ( localProperties.scheme == "true" );
+			}
+			if ( localProperties.allowip ) { 
+				this.flags.allowIP = ( localProperties.allowip == "true" );
+			}
+			if ( localProperties.allowlocal ) { 
+				this.flags.allowLocal = ( localProperties.allowlocal == "true" );
+			}
+			if ( localProperties.allowcc ) { 
+				this.flags.allowCC = ( localProperties.allowcc == "true" );
+			}
+			if ( localProperties.allowgeneric ) { 
+				this.flags.allowGeneric = ( localProperties.allowgeneric == "true" );
+			}
+		},
+
+		isValid: function(){ 
+			// summary: see dojo.widget.ValidationTextbox
+			return dojo.validate.isUrl(this.textbox.value, this.flags);
+		}
+	}
+);
+
+//FIXME: DOC: need more consistent explanation on whether attributes are inherited from the parent.  Some make sense, some don't?
+
+dojo.widget.defineWidget(
+	"dojo.widget.EmailTextbox",
+	dojo.widget.UrlTextbox,
+	{
+		// summary:  A Textbox which tests for a valid email address
+		// description:
+		//  Can use all markup attributes/properties of UrlTextbox except scheme.
+		//
+		// allowCruft: Allow address like <ma...@yahoo.com>.  Default is false.
+
+		mixInProperties: function(/*Object*/localProperties){
+			// summary: see dojo.widget.Widget
+
+			// First initialize properties in super-class.
+			dojo.widget.EmailTextbox.superclass.mixInProperties.apply(this, arguments);
+	
+			// Get properties from markup attributes, and assign to flags object.
+			if(localProperties.allowcruft){ 
+				this.flags.allowCruft = (localProperties.allowcruft == "true");
+			}
+		},
+
+		isValid: function(){
+			// summary: see dojo.widget.ValidationTextbox
+			return dojo.validate.isEmailAddress(this.textbox.value, this.flags);
+		}
+	}
+);
+
+//TODO: perhaps combine with EmailTextbox?
+dojo.widget.defineWidget(
+	"dojo.widget.EmailListTextbox",
+	dojo.widget.EmailTextbox,
+	{
+		// summary:  A Textbox which tests for a list of valid email addresses
+		//
+		// listSeparator:  The character used to separate email addresses.  
+		//    Default is ";", ",", "\n" or " "
+
+		mixInProperties: function(/*Object*/localProperties){
+			// summary: see dojo.widget.Widget
+
+			// First initialize properties in super-class.
+			dojo.widget.EmailListTextbox.superclass.mixInProperties.apply(this, arguments);
+	
+			// Get properties from markup attributes, and assign to flags object.
+			if(localProperties.listseparator){ 
+				this.flags.listSeparator = localProperties.listseparator;
+			}
+		},
+
+		isValid: function(){
+			// summary: see dojo.widget.ValidationTextbox
+			return dojo.validate.isEmailAddressList(this.textbox.value, this.flags);
+		}
+	}
+);

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LayoutContainer.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,97 @@
+/*
+	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.LayoutContainer");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.html.layout");
+
+// summary
+//	Provides Delphi-style panel layout semantics.
+//
+// details
+//	A LayoutContainer is a box with a specified size (like style="width: 500px; height: 500px;"),
+//	that contains children widgets marked with "layoutAlign" of "left", "right", "bottom", "top", and "client".
+//	It takes it's children marked as left/top/bottom/right, and lays them out along the edges of the box,
+//	and then it takes the child marked "client" and puts it into the remaining space in the middle.
+//
+//  Left/right positioning is similar to CSS's "float: left" and "float: right",
+//	and top/bottom positioning would be similar to "float: top" and "float: bottom", if there were such
+//	CSS.
+//
+//	Note that there can only be one client element, but there can be multiple left, right, top,
+//	or bottom elements.
+//
+// usage
+//	<style>
+//		html, body{ height: 100%; width: 100%; }
+//	</style>
+//	<div dojoType="LayoutContainer" style="width: 100%; height: 100%">
+//		<div dojoType="ContentPane" layoutAlign="top">header text</div>
+//		<div dojoType="ContentPane" layoutAlign="left" style="width: 200px;">table of contents</div>
+//		<div dojoType="ContentPane" layoutAlign="client">client area</div>
+//	</div>
+dojo.widget.defineWidget(
+	"dojo.widget.LayoutContainer",
+	dojo.widget.HtmlWidget,
+{
+	isContainer: true,
+
+	// String
+	//	- If the value is "top-bottom", then LayoutContainer will first position the "top" and "bottom" aligned elements,
+	//	to and then put the left and right aligned elements in the remaining space, between the top and the bottom elements.
+	//	It aligns the client element at the very end, in the remaining space.
+	//
+	//	- If the value is "left-right", then it first positions the "left" and "right" elements, and then puts the
+	//	"top" and "bottom" elements in the remaining space, between the left and the right elements.
+	//	It aligns the client element at the very end, in the remaining space.
+	//
+	//	- If the value is "none", then it will lay out each child in the natural order the children occur in.
+	//	Basically each child is laid out into the "remaining space", where "remaining space" is initially
+	//	the content area of this widget, but is reduced to a smaller rectangle each time a child is added.
+	//	
+	layoutChildPriority: 'top-bottom',
+
+	postCreate: function(){
+		dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+	},
+
+	addChild: function(child, overrideContainerNode, pos, ref, insertIndex){
+		dojo.widget.LayoutContainer.superclass.addChild.call(this, child, overrideContainerNode, pos, ref, insertIndex);
+		dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+	},
+
+	removeChild: function(pane){
+		dojo.widget.LayoutContainer.superclass.removeChild.call(this,pane);
+		dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+	},
+
+	onResized: function(){
+		dojo.widget.html.layout(this.domNode, this.children, this.layoutChildPriority);
+	},
+
+	show: function(){
+		// If this node was created while display=="none" then it
+		// hasn't been laid out yet.  Do that now.
+		this.domNode.style.display="";
+		this.checkSize();
+		this.domNode.style.display="none";
+		this.domNode.style.visibility="";
+
+		dojo.widget.LayoutContainer.superclass.show.call(this);
+	}
+});
+
+// This argument can be specified for the children of a LayoutContainer.
+// Since any widget can be specified as a LayoutContainer child, mix it
+// into the base widget class.  (This is a hack, but it's effective.)
+dojo.lang.extend(dojo.widget.Widget, {
+	layoutAlign: 'none'
+});

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/LinkPane.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,42 @@
+/*
+	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.LinkPane");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.ContentPane");
+dojo.require("dojo.html.style");
+
+// summary
+//	LinkPane is just a ContentPane that loads data remotely (via the href attribute),
+//	and has markup similar to an anchor.  The anchor's body (the words between <a> and </a>)
+//	become the label of the widget (used for TabContainer, AccordionContainer, etc.)
+// usage
+//	<a href="foo.html">my label</a>
+dojo.widget.defineWidget(
+	"dojo.widget.LinkPane",
+	dojo.widget.ContentPane,
+{
+	// I'm using a template because the user may specify the input as
+	// <a href="foo.html">label</a>, in which case we need to get rid of the
+	// <a> because we don't want a link.
+	templateString: '<div class="dojoLinkPane"></div>',
+
+	fillInTemplate: function(args, frag){
+		var source = this.getFragNodeRef(frag);
+
+		// If user has specified node contents, they become the label
+		// (the link must be plain text)
+		this.label += source.innerHTML;
+
+		var source = this.getFragNodeRef(frag);
+		dojo.html.copyStyle(this.domNode, source);
+	}
+});

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Manager.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,363 @@
+/*
+	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.Manager");
+dojo.require("dojo.lang.array");
+dojo.require("dojo.lang.func");
+dojo.require("dojo.event.*");
+
+// Manager class
+dojo.widget.manager = new function(){
+	this.widgets = [];
+	this.widgetIds = [];
+	
+	// map of widgetId-->widget for widgets without parents (top level widgets)
+	this.topWidgets = {};
+
+	var widgetTypeCtr = {};
+	var renderPrefixCache = [];
+
+	this.getUniqueId = function (widgetType) {
+		var widgetId;
+		do{
+			widgetId = widgetType + "_" + (widgetTypeCtr[widgetType] != undefined ?
+			++widgetTypeCtr[widgetType] : widgetTypeCtr[widgetType] = 0);
+		}while(this.getWidgetById(widgetId));
+		return widgetId;
+	}
+
+	this.add = function(widget){
+		//dojo.profile.start("dojo.widget.manager.add");
+		this.widgets.push(widget);
+		// Opera9 uses ID (caps)
+		if(!widget.extraArgs["id"]){
+			widget.extraArgs["id"] = widget.extraArgs["ID"];
+		}
+		// FIXME: the rest of this method is very slow!
+		if(widget.widgetId == ""){
+			if(widget["id"]){
+				widget.widgetId = widget["id"];
+			}else if(widget.extraArgs["id"]){
+				widget.widgetId = widget.extraArgs["id"];
+			}else{
+				widget.widgetId = this.getUniqueId(widget.widgetType);
+			}
+		}
+		if(this.widgetIds[widget.widgetId]){
+			dojo.debug("widget ID collision on ID: "+widget.widgetId);
+		}
+		this.widgetIds[widget.widgetId] = widget;
+		// Widget.destroy already calls removeById(), so we don't need to
+		// connect() it here
+		//dojo.profile.end("dojo.widget.manager.add");
+	}
+
+	this.destroyAll = function(){
+		for(var x=this.widgets.length-1; x>=0; x--){
+			try{
+				// this.widgets[x].destroyChildren();
+				this.widgets[x].destroy(true);
+				delete this.widgets[x];
+			}catch(e){ }
+		}
+	}
+
+	// FIXME: we should never allow removal of the root widget until all others
+	// are removed!
+	this.remove = function(widgetIndex){
+		if(dojo.lang.isNumber(widgetIndex)){
+			var tw = this.widgets[widgetIndex].widgetId;
+			delete this.widgetIds[tw];
+			this.widgets.splice(widgetIndex, 1);
+		}else{
+			this.removeById(widgetIndex);
+		}
+	}
+	
+	// FIXME: suboptimal performance
+	this.removeById = function(id) {
+		if(!dojo.lang.isString(id)){
+			id = id["widgetId"];
+			if(!id){ dojo.debug("invalid widget or id passed to removeById"); return; }
+		}
+		for (var i=0; i<this.widgets.length; i++){
+			if(this.widgets[i].widgetId == id){
+				this.remove(i);
+				break;
+			}
+		}
+	}
+
+	this.getWidgetById = function(id){
+		if(dojo.lang.isString(id)){
+			return this.widgetIds[id];
+		}
+		return id;
+	}
+
+	this.getWidgetsByType = function(type){
+		var lt = type.toLowerCase();
+		var getType = (type.indexOf(":") < 0 ? 
+			function(x) { return x.widgetType.toLowerCase(); } :
+			function(x) { return x.getNamespacedType(); }
+		);
+		var ret = [];
+		dojo.lang.forEach(this.widgets, function(x){
+			if(getType(x) == lt){ret.push(x);}
+		});
+		return ret;
+	}
+
+	this.getWidgetsByFilter = function(unaryFunc, onlyOne){
+		var ret = [];
+		dojo.lang.every(this.widgets, function(x){
+			if(unaryFunc(x)){
+				ret.push(x);
+				if(onlyOne){return false;}
+			}
+			return true;
+		});
+		return (onlyOne ? ret[0] : ret);
+	}
+
+	this.getAllWidgets = function() {
+		return this.widgets.concat();
+	}
+
+	//	added, trt 2006-01-20
+	this.getWidgetByNode = function(/* DOMNode */ node){
+		var w=this.getAllWidgets();
+		node = dojo.byId(node);
+		for(var i=0; i<w.length; i++){
+			if(w[i].domNode==node){
+				return w[i];
+			}
+		}
+		return null;
+	}
+
+	// shortcuts, baby
+	this.byId = this.getWidgetById;
+	this.byType = this.getWidgetsByType;
+	this.byFilter = this.getWidgetsByFilter;
+	this.byNode = this.getWidgetByNode;
+
+	// map of previousally discovered implementation names to constructors
+	var knownWidgetImplementations = {};
+
+	// support manually registered widget packages
+	var widgetPackages = ["dojo.widget"];
+	for (var i=0; i<widgetPackages.length; i++) {
+		// convenience for checking if a package exists (reverse lookup)
+		widgetPackages[widgetPackages[i]] = true;
+	}
+
+	this.registerWidgetPackage = function(pname) {
+		if(!widgetPackages[pname]){
+			widgetPackages[pname] = true;
+			widgetPackages.push(pname);
+		}
+	}
+	
+	this.getWidgetPackageList = function() {
+		return dojo.lang.map(widgetPackages, function(elt) { return(elt!==true ? elt : undefined); });
+	}
+	
+	this.getImplementation = function(widgetName, ctorObject, mixins, ns){
+		// try and find a name for the widget
+		var impl = this.getImplementationName(widgetName, ns);
+		if(impl){ 
+			// var tic = new Date();
+			var ret = ctorObject ? new impl(ctorObject) : new impl();
+			// dojo.debug(new Date() - tic);
+			return ret;
+		}
+	}
+
+	function buildPrefixCache() {
+		for(var renderer in dojo.render){
+			if(dojo.render[renderer]["capable"] === true){
+				var prefixes = dojo.render[renderer].prefixes;
+				for(var i=0; i<prefixes.length; i++){
+					renderPrefixCache.push(prefixes[i].toLowerCase());
+				}
+			}
+		}
+		// make sure we don't HAVE to prefix widget implementation names
+		// with anything to get them to render
+		//renderPrefixCache.push("");
+		// empty prefix is included automatically
+	}
+	
+	var findImplementationInModule = function(lowerCaseWidgetName, module){
+		if(!module){return null;}
+		for(var i=0, l=renderPrefixCache.length, widgetModule; i<=l; i++){
+			widgetModule = (i<l ? module[renderPrefixCache[i]] : module);
+			if(!widgetModule){continue;}
+			for(var name in widgetModule){
+				if(name.toLowerCase() == lowerCaseWidgetName){
+					return widgetModule[name];
+				}
+			}
+		}
+		return null;
+	}
+
+	var findImplementation = function(lowerCaseWidgetName, moduleName){
+		// locate registered widget module
+		var module = dojo.evalObjPath(moduleName, false);
+		// locate a widget implementation in the registered module for our current rendering environment
+		return (module ? findImplementationInModule(lowerCaseWidgetName, module) : null);
+	}
+
+	this.getImplementationName = function(widgetName, ns){
+		/*
+		 * Locate an implementation (constructor) for 'widgetName' in namespace 'ns' 
+		 * widgetNames are case INSENSITIVE
+		 * 
+		 * 1. Return value from implementation cache, if available, for quick turnaround.
+		 * 2. Locate a namespace registration for 'ns'
+		 * 3. If no namespace found, register the conventional one (ns.widget)
+		 * 4. Allow the namespace resolver (if any) to load a module for this widget.
+		 * 5. Permute the widget name and capable rendering prefixes to locate, cache, and return 
+		 *    an appropriate widget implementation.
+		 * 6. If no implementation is found, attempt to load the namespace manifest,
+		 *    and then look again for an implementation to cache and return.
+		 * 7. Use the deprecated widgetPackages registration system to attempt to locate the widget
+		 * 8. Fail
+		 */
+		var lowerCaseWidgetName = widgetName.toLowerCase();
+
+		// default to dojo namespace
+		ns=ns||"dojo";
+		// use cache if available
+		var imps = knownWidgetImplementations[ns] || (knownWidgetImplementations[ns]={});
+		//if(!knownWidgetImplementations[ns]){knownWidgetImplementations[ns]={};}
+		var impl = imps[lowerCaseWidgetName];
+		if(impl){
+			return impl;
+		}
+		
+		// (one time) store a list of the render prefixes we are capable of rendering
+		if(!renderPrefixCache.length){
+			buildPrefixCache();
+		}
+
+		// lookup namespace
+		var nsObj = dojo.ns.get(ns);
+		if(!nsObj){
+			// default to <ns>.widget by convention
+			dojo.ns.register(ns, ns + '.widget');
+			nsObj = dojo.ns.get(ns);
+		}
+		
+		// allow the namespace to resolve the widget module
+		if(nsObj){nsObj.resolve(widgetName);}
+
+		// locate a widget implementation in the registered module for our current rendering environment
+		impl = findImplementation(lowerCaseWidgetName, nsObj.module);
+		if(impl){return(imps[lowerCaseWidgetName] = impl)};
+
+		// try to load a manifest to resolve this implemenation
+		nsObj = dojo.ns.require(ns);
+		if((nsObj)&&(nsObj.resolver)){
+			nsObj.resolve(widgetName);
+			impl = findImplementation(lowerCaseWidgetName, nsObj.module);
+			if(impl){return(imps[lowerCaseWidgetName] = impl)};
+		}
+	
+		// this is an error condition under new rules
+		dojo.deprecated('dojo.widget.Manager.getImplementationName', 
+			'Could not locate widget implementation for "' + widgetName + '" in "' + nsObj.module + '" registered to namespace "' + nsObj.name + '". '										
+			+ "Developers must specify correct namespaces for all non-Dojo widgets", "0.5");
+
+		// backward compat: if the user has not specified any namespace and their widget is not in dojo.widget.*
+		// search registered widget packages [sic]
+		// note: registerWidgetPackage itself is now deprecated 
+		for(var i=0; i<widgetPackages.length; i++){
+			impl = findImplementation(lowerCaseWidgetName, widgetPackages[i]);
+			if(impl){return(imps[lowerCaseWidgetName] = impl)};
+		}
+		
+		throw new Error('Could not locate widget implementation for "' + widgetName + '" in "' + nsObj.module + '" registered to namespace "' + nsObj.name + '"');
+	}
+
+	// FIXME: does it even belong in this module?
+	// NOTE: this method is implemented by DomWidget.js since not all
+	// hostenv's would have an implementation.
+	/*this.getWidgetFromPrimitive = function(baseRenderType){
+		dojo.unimplemented("dojo.widget.manager.getWidgetFromPrimitive");
+	}
+
+	this.getWidgetFromEvent = function(nativeEvt){
+		dojo.unimplemented("dojo.widget.manager.getWidgetFromEvent");
+	}*/
+
+	// Catch window resize events and notify top level widgets
+	this.resizing=false;
+	this.onWindowResized = function(){
+		if(this.resizing){
+			return;	// duplicate event
+		}
+		try{
+			this.resizing=true;
+			for(var id in this.topWidgets){
+				var child = this.topWidgets[id];
+				if(child.checkSize ){
+					child.checkSize();
+				}
+			}
+		}catch(e){
+		}finally{
+			this.resizing=false;
+		}
+	}
+	if(typeof window != "undefined") {
+		dojo.addOnLoad(this, 'onWindowResized');							// initial sizing
+		dojo.event.connect(window, 'onresize', this, 'onWindowResized');	// window resize
+	}
+
+	// FIXME: what else?
+};
+
+(function(){
+	var dw = dojo.widget;
+	var dwm = dw.manager;
+	var h = dojo.lang.curry(dojo.lang, "hitch", dwm);
+	var g = function(oldName, newName){
+		dw[(newName||oldName)] = h(oldName);
+	}
+	// copy the methods from the default manager (this) to the widget namespace
+	g("add", "addWidget");
+	g("destroyAll", "destroyAllWidgets");
+	g("remove", "removeWidget");
+	g("removeById", "removeWidgetById");
+	g("getWidgetById");
+	g("getWidgetById", "byId");
+	g("getWidgetsByType");
+	g("getWidgetsByFilter");
+	g("getWidgetsByType", "byType");
+	g("getWidgetsByFilter", "byFilter");
+	g("getWidgetByNode", "byNode");
+	dw.all = function(n){
+		var widgets = dwm.getAllWidgets.apply(dwm, arguments);
+		if(arguments.length > 0) {
+			return widgets[n];
+		}
+		return widgets;
+	}
+	g("registerWidgetPackage");
+	g("getImplementation", "getWidgetImplementation");
+	g("getImplementationName", "getWidgetImplementationName");
+
+	dw.widgets = dwm.widgets;
+	dw.widgetIds = dwm.widgetIds;
+	dw.root = dwm.root;
+})();

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/Menu2.js Fri Mar  2 21:48:54 2007
@@ -0,0 +1,765 @@
+/*
+	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.Menu2");
+
+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,
+
+	// 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,
+
+	// 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(){
+		if (this.contextMenuForWindow){
+			var doc = dojo.body();
+			this.bindDomNode(doc);
+		} else if ( this.targetNodeIds.length > 0 ){
+			dojo.lang.forEach(this.targetNodeIds, this.bindDomNode, this);
+		}
+
+		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")
+		}
+	},
+
+	getTopOpenEvent: function() {
+		// summary: get event that initially caused current chain of menus to open
+		var menu = this;
+		while (menu.parentPopup){ menu = menu.parentPopup; }
+		return menu.openEvent;	// Event
+	},
+
+	bindDomNode: function(/*String|DomNode*/ node){
+		// summary: attach menu to given node
+		node = 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
+		});
+
+		//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);
+	},
+
+	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
+		});
+
+		// cleans a fixed node, konqueror and opera
+		dojo.widget.Menu2.OperaAndKonqFixer.cleanNode(node);
+	},
+
+	_moveToNext: function(/*Event*/ evt){
+		this._highlightOption(1);
+		return true; //do not pass to parent menu
+	},
+
+	_moveToPrevious: function(/*Event*/ evt){
+		this._highlightOption(-1);
+		return true; //do not pass to parent menu
+	},
+
+	_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;
+			}
+		}
+		return false;
+	},
+
+	_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();
+		}
+	},
+	
+	_highlightOption: function(dir){
+		var item;
+		// || !this._highlighted_option.parentNode
+		if((!this._highlighted_option)){
+			item = this._findValidItem(dir);
+		}else{
+			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) { }
+		}
+	},
+
+	onItemClick: function(/*Widget*/ item) {
+		// summary: user defined function to handle clicks on an item
+	},
+
+	close: function(/*Boolean*/ force){
+		// summary: close the menu
+		if(this.animationInProgress){
+			dojo.widget.PopupMenu2.superclass.close.apply(this, arguments);
+			return;
+		}
+
+		if(this._highlighted_option){
+			this._highlighted_option.onUnhover();
+		}
+
+		dojo.widget.PopupMenu2.superclass.close.apply(this, arguments);
+	},
+
+	closeSubpopup: function(force){
+		// summary: close the currently displayed submenu
+		if (this.currentSubpopup == null){ return; }
+
+		this.currentSubpopup.close(force);
+		this.currentSubpopup = null;
+
+		this.currentSubmenuTrigger.is_open = false;
+		this.currentSubmenuTrigger._closedSubmenu(force);
+		this.currentSubmenuTrigger = null;
+	},
+
+	_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(/*Event*/ e){
+		// summary: callback when menu is opened
+		this.openEvent = e;
+		if(e["target"]){
+			this.openedForWindow = dojo.html.getElementWindow(e.target);
+		}else{
+			this.openedForWindow = null;
+		}
+		var x = e.pageX, y = e.pageY;
+
+		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]);
+
+		e.preventDefault();
+		e.stopPropagation();
+	}
+});
+
+// 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:
+		 '<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
+	//
+
+	is_hovering: false,
+	hover_timer: null,
+	is_open: false,
+	topPosition: 0,
+
+	//
+	// 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: '',
+	
+	// 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);
+	},
+
+	fillInTemplate: function(){
+		dojo.html.disableSelection(this.domNode);
+
+		if (this.disabled){
+			this.setDisabled(true);
+		}
+
+		if (this.eventNaming == "default") {
+			for (var eventName in this.eventNames) {
+				this.eventNames[eventName] = this.widgetId+"/"+eventName;
+			}
+		}
+	},
+
+	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; }
+
+		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(); }
+
+		this.is_hovering = false;
+
+		this.parent._highlighted_option = null;
+
+		if(this.parent.parentPopup){
+			dojo.widget.PopupManager.setFocusedMenu(this.parent.parentPopup);
+		}
+
+		this._stopSubmenuTimer();
+	},
+
+	_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();
+			}
+			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);
+		}
+
+		// 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;
+	},
+
+	onClick: function() {
+		// summary
+		//	User defined function to handle clicks
+		//	this default function call the parent
+		//	menu's onItemClick
+		this.parent.onItemClick(this);
+	},
+
+	_highlightItem: function(){
+		dojo.html.addClass(this.domNode, this.highlightClass);
+	},
+
+	_unhighlightItem: function(){
+		dojo.html.removeClass(this.domNode, this.highlightClass);
+	},
+
+	_startSubmenuTimer: function(){
+		this._stopSubmenuTimer();
+
+		if (this.disabled){ return; }
+
+		var self = this;
+		var closure = function(){ return function(){ self._openSubmenu(); } }();
+
+		this.hover_timer = dojo.lang.setTimeout(closure, this.parent.submenuDelay);
+	},
+
+	_stopSubmenuTimer: function(){
+		if (this.hover_timer){
+			dojo.lang.clearTimeout(this.hover_timer);
+			this.hover_timer = null;
+		}
+	},
+
+	_openSubmenu: function(){
+		if (this.disabled){ return; }
+
+		// first close any other open submenu
+		this.parent.closeSubpopup();
+
+		var submenu = dojo.widget.getWidgetById(this.submenuId);
+		if (submenu){
+			this.parent._openSubmenu(submenu, this);
+		}
+	},
+
+	_closedSubmenu: function(){
+		this.onUnhover();
+	},
+
+	setDisabled: function(/*Boolean*/ value){
+		// summary: enable or disable this menu item
+		this.disabled = value;
+
+		if (this.disabled){
+			dojo.html.addClass(this.domNode, 'dojoMenuItem2Disabled');
+		}else{
+			dojo.html.removeClass(this.domNode, 'dojoMenuItem2Disabled');
+		}
+	},
+
+	enable: function(){
+		// summary: enable this menu item so user can click it
+		this.setDisabled(false);
+	},
+
+	disable: function(){
+		// summary: disable this menu item so user can't click it
+		this.setDisabled(true);
+	},
+
+	menuOpen: function(message) {
+		// summary: callback when menu is opened
+		// TODO: I don't see anyone calling this menu item
+	}
+
+});
+
+// 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.disableSelection(this.domNode);
+	}
+});
+
+// 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;
+		}
+
+		return rval;
+	},
+
+	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;
+
+		submenu.open(x, y, this, from_item.domNode);
+
+		this.currentSubmenuTrigger = from_item;
+		this.currentSubmenuTrigger.is_open = true;
+	}
+});
+
+// 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>',
+
+	highlightClass: 'dojoMenuBarItem2Hover',
+
+	setDisabled: function(value){
+		this.disabled = value;
+		if (this.disabled){
+			dojo.html.addClass(this.domNode, 'dojoMenuBarItem2Disabled');
+		}else{
+			dojo.html.removeClass(this.domNode, 'dojoMenuBarItem2Disabled');
+		}
+	}
+});
+
+
+// ************************** 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/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/MonthlyCalendar.js Fri Mar  2 21:48:54 2007
@@ -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()};
+	}
+}

Added: struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js?view=auto&rev=514083
==============================================================================
--- struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js (added)
+++ struts/struts2/trunk/plugins/dojo/src/main/resources/org/apache/struts2/static/dojo/src/widget/PageContainer.js Fri Mar  2 21:48:54 2007
@@ -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
+});