You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by jk...@apache.org on 2006/09/23 01:22:51 UTC

svn commit: r449122 [30/40] - in /tapestry/tapestry4/trunk/tapestry-framework/src: java/org/apache/tapestry/ java/org/apache/tapestry/dojo/ java/org/apache/tapestry/dojo/form/ java/org/apache/tapestry/dojo/html/ java/org/apache/tapestry/form/ java/org/...

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SortableTable.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SortableTable.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SortableTable.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SortableTable.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,615 @@
+/*
+	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.SortableTable");
+
+dojo.deprecated("SortableTable will be removed in favor of FilteringTable.", "0.5");
+
+dojo.require("dojo.lang.common");
+dojo.require("dojo.date.format");
+dojo.require("dojo.html.*");
+dojo.require("dojo.html.selection");
+dojo.require("dojo.html.util");
+dojo.require("dojo.html.style");
+dojo.require("dojo.event.*");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.HtmlWidget");
+
+dojo.widget.defineWidget(
+	"dojo.widget.SortableTable",
+	dojo.widget.HtmlWidget,
+	function(){
+		this.data=[];
+		this.selected=[];		//	always an array to handle multiple selections.
+		this.columns=[];
+	},
+	{
+		//	custom properties
+		enableMultipleSelect: false,
+		maximumNumberOfSelections: 0,	//	0 for unlimited, is the default.
+		enableAlternateRows: false,
+		minRows: 0,	//	0 means ignore.
+		defaultDateFormat: "%D",
+		sortIndex: 0,		//	index of the column sorted on, first is the default.
+		sortDirection: 0,	//	0==asc, 1==desc
+		valueField: "Id",	//	if a JSON structure is parsed and there is a field of this name,
+							//	a value attribute will be added to the row (tr value="{Id}")
+
+		headClass: "",
+		tbodyClass: "",
+		headerClass: "",
+		headerSortUpClass: "selected",
+		headerSortDownClass: "selected",
+		rowClass: "",
+		rowAlternateClass: "alt",
+		rowSelectedClass: "selected",
+		columnSelected: "sorted-column",
+
+		isContainer: false,
+		templatePath:null,
+		templateCssPath:null,
+
+		getTypeFromString:function(/* string */ s){
+			//	summary
+			//	Find the constructor that matches param s by searching through the entire object tree.
+			var parts=s.split("."),i=0,obj=dj_global; 
+			do{obj=obj[parts[i++]];}while(i<parts.length&&obj); 
+			return(obj!=dj_global)?obj:null;	//	function
+		},
+		compare:function(/* object */ o1, /* object */ o2){
+			//	summary
+			//	Compare two objects using a shallow property compare
+			for(var p in o1){
+				if(!(p in o2)) return false;	//	boolean
+				if(o1[p].valueOf()!=o2[p].valueOf()) return false;	//	boolean
+			}
+			return true;	// boolean
+		},
+		isSelected:function(/* object */ o){
+			//	summary
+			//	checked to see if the passed object is in the current selection.
+			for(var i=0;i<this.selected.length;i++){
+				if(this.compare(this.selected[i],o)){
+					return true; // boolean
+				}
+			}
+			return false;	// boolean
+		},
+		removeFromSelected:function(/* object */ o){
+			//	summary
+			//	remove the passed object from the current selection.
+			var idx=-1;
+			for(var i=0;i<this.selected.length;i++){
+				if(this.compare(this.selected[i],o)){
+					idx=i;
+					break;
+				}
+			}
+			if(idx>=0){
+				this.selected.splice(idx,1);
+			}
+		},
+		getSelection:function(){
+			//	summary
+			//	return the array of currently selected objects (JSON format)
+			return this.selected;	//	array
+		},
+		getValue:function(){
+			//	summary
+			//	return a comma-delimited list of selected valueFields.
+			var a=[];
+			for(var i=0;i<this.selected.length;i++){
+				if (this.selected[i][this.valueField]){
+					a.push(this.selected[i][this.valueField]);
+				}
+			}
+			return a.join();	//	string
+		},
+		reset:function(){
+			//	summary
+			//	completely resets the internal representations.
+			this.columns=[];
+			this.data=[];
+			this.resetSelections(this.domNode.getElementsByTagName("tbody")[0]);
+		},
+		resetSelections:function(/* HTMLTableBodyElement */ body){
+			this.selected=[];
+			var idx=0;
+			var rows=body.getElementsByTagName("tr");
+			for(var i=0; i<rows.length; i++){
+				if(rows[i].parentNode==body){
+					rows[i].removeAttribute("selected");
+					if(this.enableAlternateRows&&idx%2==1){
+						rows[i].className=this.rowAlternateClass;
+					}else{
+						rows[i].className="";
+					}
+					idx++;
+				}
+			}
+		},
+
+		getObjectFromRow:function(/* HTMLTableRowElement */ row){
+			//	summary
+			//	creates a JSON object based on the passed row
+			var cells=row.getElementsByTagName("td");
+			var o={};
+			for(var i=0; i<this.columns.length;i++){
+				if(this.columns[i].sortType=="__markup__"){
+					//	FIXME: should we parse this instead?  Because if the user may not get back the markup they put in...
+					o[this.columns[i].getField()]=cells[i].innerHTML;
+				}else{
+					var text=dojo.html.renderedTextContent(cells[i]);
+					var val=text;
+					if (this.columns[i].getType() != String){
+						var val=new (this.columns[i].getType())(text);
+					}
+					o[this.columns[i].getField()]=val;
+				}
+			}
+			if(dojo.html.hasAttribute(row,"value")){
+				o[this.valueField]=dojo.html.getAttribute(row,"value");
+			}
+			return o;	//	object
+		},
+		setSelectionByRow:function(/* HTMLTableElementRow */ row){
+			//	summary
+			//	create the selection object based on the passed row, makes sure it's unique.
+			//	note that you need to call render manually (because of multi-select operations)
+			var o=this.getObjectFromRow(row);
+			var b=false;
+			for(var i=0;i<this.selected.length;i++){
+				if(this.compare(this.selected[i], o)){
+					b=true;
+					break;
+				}
+			}
+			if(!b){
+				this.selected.push(o);
+			}
+		},
+
+		parseColumns:function(/* HTMLTableHeadElement */ node){
+			//	summary
+			//	parses the passed element to create column objects
+			this.reset();
+			var row=node.getElementsByTagName("tr")[0];
+			var cells=row.getElementsByTagName("td");
+			if (cells.length==0) cells=row.getElementsByTagName("th");
+			for(var i=0; i<cells.length; i++){
+				var o={
+					field:null,
+					format:null,
+					noSort:false,
+					sortType:"String",
+					dataType:String,
+					sortFunction:null,
+					label:null,
+					align:"left",
+					valign:"middle",
+					getField:function(){ return this.field||this.label; },
+					getType:function(){ return this.dataType; }
+				};
+				//	presentation attributes
+				if(dojo.html.hasAttribute(cells[i], "align")){
+					o.align=dojo.html.getAttribute(cells[i],"align");
+				}
+				if(dojo.html.hasAttribute(cells[i], "valign")){
+					o.valign=dojo.html.getAttribute(cells[i],"valign");
+				}
+
+				//	sorting features.
+				if(dojo.html.hasAttribute(cells[i], "nosort")){
+					o.noSort=dojo.html.getAttribute(cells[i],"nosort")=="true";
+				}
+				if(dojo.html.hasAttribute(cells[i], "sortusing")){
+					var trans=dojo.html.getAttribute(cells[i],"sortusing");
+					var f=this.getTypeFromString(trans);
+					if (f!=null && f!=window && typeof(f)=="function") 
+						o.sortFunction=f;
+				}
+
+				if(dojo.html.hasAttribute(cells[i], "field")){
+					o.field=dojo.html.getAttribute(cells[i],"field");
+				}
+				if(dojo.html.hasAttribute(cells[i], "format")){
+					o.format=dojo.html.getAttribute(cells[i],"format");
+				}
+				if(dojo.html.hasAttribute(cells[i], "dataType")){
+					var sortType=dojo.html.getAttribute(cells[i],"dataType");
+					if(sortType.toLowerCase()=="html"||sortType.toLowerCase()=="markup"){
+						o.sortType="__markup__";	//	always convert to "__markup__"
+						o.noSort=true;
+					}else{
+						var type=this.getTypeFromString(sortType);
+						if(type){
+							o.sortType=sortType;
+							o.dataType=type;
+						}
+					}
+				}
+				o.label=dojo.html.renderedTextContent(cells[i]);
+				this.columns.push(o);
+
+				//	check to see if there's a default sort, and set the properties necessary
+				if(dojo.html.hasAttribute(cells[i], "sort")){
+					this.sortIndex=i;
+					var dir=dojo.html.getAttribute(cells[i], "sort");
+					if(!isNaN(parseInt(dir))){
+						dir=parseInt(dir);
+						this.sortDirection=(dir!=0)?1:0;
+					}else{
+						this.sortDirection=(dir.toLowerCase()=="desc")?1:0;
+					}
+				}
+			}
+		},
+
+		parseData:function(/* array */ data){
+			//	summary
+			//	Parse the passed JSON data structure, and cast based on columns.
+			this.data=[];
+			this.selected=[];
+			for(var i=0; i<data.length; i++){
+				var o={};	//	new data object.
+				for(var j=0; j<this.columns.length; j++){
+					var field=this.columns[j].getField();
+					if(this.columns[j].sortType=="__markup__"){
+						o[field]=String(data[i][field]);
+					}else{
+						var type=this.columns[j].getType();
+						var val=data[i][field];
+						var t=this.columns[j].sortType.toLowerCase();
+						if(type == String) {
+							o[field]=val;
+						} else {
+							if(val!=null){
+								o[field]=new type(val);
+							}else{
+								o[field]=new type();	//	let it use the default.
+							}
+						}
+					}
+				}
+				//	check for the valueField if not already parsed.
+				if(data[i][this.valueField]&&!o[this.valueField]){
+					o[this.valueField]=data[i][this.valueField];
+				}
+				this.data.push(o);
+			}
+		}, 
+
+		parseDataFromTable:function(/* HTMLTableBodyElement */ tbody){
+			//	summary
+			//	parses the data in the tbody of a table to create a set of objects.
+			//	Will add objects to this.selected if an attribute 'selected="true"' is present on the row.
+			this.data=[];
+			this.selected=[];
+			var rows=tbody.getElementsByTagName("tr");
+			for(var i=0; i<rows.length; i++){
+				if(dojo.html.getAttribute(rows[i],"ignoreIfParsed")=="true"){
+					continue;
+				}
+				var o={};	//	new data object.
+				var cells=rows[i].getElementsByTagName("td");
+				for(var j=0; j<this.columns.length; j++){
+					var field=this.columns[j].getField();
+					if(this.columns[j].sortType=="__markup__"){
+						//	FIXME: parse this?
+						o[field]=cells[j].innerHTML;
+					}else{
+						var type=this.columns[j].getType();
+						var val=dojo.html.renderedTextContent(cells[j]); //	should be the same index as the column.
+						if(type == String){
+							o[field]=val;
+						} else {
+							if (val!=null){
+								o[field]=new type(val);
+							} else {
+								o[field]=new type();	//	let it use the default.
+							}
+						}
+					}
+				}
+				if(dojo.html.hasAttribute(rows[i],"value")&&!o[this.valueField]){
+					o[this.valueField]=dojo.html.getAttribute(rows[i],"value");
+				}
+				//	FIXME: add code to preserve row attributes in __metadata__ field?
+				this.data.push(o);
+				
+				//	add it to the selections if selected="true" is present.
+				if(dojo.html.getAttribute(rows[i],"selected")=="true"){
+					this.selected.push(o);
+				}
+			}
+		},
+		
+		showSelections:function(){
+			var body=this.domNode.getElementsByTagName("tbody")[0];
+			var rows=body.getElementsByTagName("tr");
+			var idx=0;
+			for(var i=0; i<rows.length; i++){
+				if(rows[i].parentNode==body){
+					if(dojo.html.getAttribute(rows[i],"selected")=="true"){
+						rows[i].className=this.rowSelectedClass;
+					} else {
+						if(this.enableAlternateRows&&idx%2==1){
+							rows[i].className=this.rowAlternateClass;
+						}else{
+							rows[i].className="";
+						}
+					}
+					idx++;
+				}
+			}
+		},
+		render:function(bDontPreserve){
+			//	summary
+			//	renders the table to the browser
+			var data=[];
+			var body=this.domNode.getElementsByTagName("tbody")[0];
+
+			if(!bDontPreserve){
+				//	rebuild data and selection
+				this.parseDataFromTable(body);
+			}
+
+			//	clone this.data for sorting purposes.
+			for(var i=0; i<this.data.length; i++){
+				data.push(this.data[i]);
+			}
+			
+			var col=this.columns[this.sortIndex];
+			if(!col.noSort){
+				var field=col.getField();
+				if(col.sortFunction){
+					var sort=col.sortFunction;
+				}else{
+					var sort=function(a,b){
+						if (a[field]>b[field]) return 1;
+						if (a[field]<b[field]) return -1;
+						return 0;
+					}
+				}
+				data.sort(sort);
+				if(this.sortDirection!=0) data.reverse();
+			}
+
+			//	build the table and pop it in.
+			while(body.childNodes.length>0) body.removeChild(body.childNodes[0]);
+			for(var i=0; i<data.length;i++){
+				var row=document.createElement("tr");
+				dojo.html.disableSelection(row);
+				if (data[i][this.valueField]){
+					row.setAttribute("value",data[i][this.valueField]);
+				}
+				if(this.isSelected(data[i])){
+					row.className=this.rowSelectedClass;
+					row.setAttribute("selected","true");
+				} else {
+					if(this.enableAlternateRows&&i%2==1){
+						row.className=this.rowAlternateClass;
+					}
+				}
+				for(var j=0;j<this.columns.length;j++){
+					var cell=document.createElement("td");
+					cell.setAttribute("align", this.columns[j].align);
+					cell.setAttribute("valign", this.columns[j].valign);
+					dojo.html.disableSelection(cell);
+					if(this.sortIndex==j){
+						cell.className=this.columnSelected;
+					}
+					if(this.columns[j].sortType=="__markup__"){
+						cell.innerHTML=data[i][this.columns[j].getField()];
+						for(var k=0; k<cell.childNodes.length; k++){
+							var node=cell.childNodes[k];
+							if(node&&node.nodeType==dojo.html.ELEMENT_NODE){
+								dojo.html.disableSelection(node);
+							}
+						}
+					}else{
+						if(this.columns[j].getType()==Date){
+							var format=this.defaultDateFormat;
+							if(this.columns[j].format) format=this.columns[j].format;
+							cell.appendChild(document.createTextNode(dojo.date.strftime(data[i][this.columns[j].getField()], format)));
+						}else{
+							cell.appendChild(document.createTextNode(data[i][this.columns[j].getField()]));
+						}
+					}
+					row.appendChild(cell);
+				}
+				body.appendChild(row);
+				dojo.event.connect(row, "onclick", this, "onUISelect");
+			}
+			
+			//	if minRows exist.
+			var minRows=parseInt(this.minRows);
+			if (!isNaN(minRows) && minRows>0 && data.length<minRows){
+				var mod=0;
+				if(data.length%2==0) mod=1;
+				var nRows=minRows-data.length;
+				for(var i=0; i<nRows; i++){
+					var row=document.createElement("tr");
+					row.setAttribute("ignoreIfParsed","true");
+					if(this.enableAlternateRows&&i%2==mod){
+						row.className=this.rowAlternateClass;
+					}
+					for(var j=0;j<this.columns.length;j++){
+						var cell=document.createElement("td");
+						cell.appendChild(document.createTextNode("\u00A0"));
+						row.appendChild(cell);
+					}
+					body.appendChild(row);
+				}
+			}
+		},
+
+		//	the following the user can override.
+		onSelect:function(/* DomEvent */ e){ 
+			//	summary
+			//	empty function for the user to attach code to, fired by onUISelect
+		},
+		onUISelect:function(/* DomEvent */ e){
+			//	summary
+			//	fired when a user selects a row
+			var row=dojo.html.getParentByType(e.target,"tr");
+			var body=dojo.html.getParentByType(row,"tbody");
+			if(this.enableMultipleSelect){
+				if(e.metaKey||e.ctrlKey){
+					if(this.isSelected(this.getObjectFromRow(row))){
+						this.removeFromSelected(this.getObjectFromRow(row));
+						row.removeAttribute("selected");
+					}else{
+						//	push onto the selection stack.
+						this.setSelectionByRow(row);
+						row.setAttribute("selected","true");
+					}
+				}else if(e.shiftKey){
+					//	the tricky one.  We need to figure out the *last* selected row above, 
+					//	and select all the rows in between.
+					var startRow;
+					var rows=body.getElementsByTagName("tr");
+					//	if there's a selection above, we go with that first. 
+					for(var i=0;i<rows.length;i++){
+						if(rows[i].parentNode==body){
+							if(rows[i]==row) break;
+							if(dojo.html.getAttribute(rows[i],"selected")=="true"){
+								startRow=rows[i];
+							}
+						}
+					}
+					//	if there isn't a selection above, we continue with a selection below.
+					if(!startRow){
+						startRow=row;
+						for(;i<rows.length;i++){
+							if(dojo.html.getAttribute(rows[i],"selected")=="true"){
+								row=rows[i];
+								break;
+							}
+						}
+					}
+					this.resetSelections(body);
+					if(startRow==row){
+						//	this is the only selection
+						row.setAttribute("selected","true");
+						this.setSelectionByRow(row);
+					}else{
+						var doSelect=false;
+						for(var i=0; i<rows.length; i++){
+							if(rows[i].parentNode==body){
+								rows[i].removeAttribute("selected");
+								if(rows[i]==startRow){
+									doSelect=true;
+								}
+								if(doSelect){
+									this.setSelectionByRow(rows[i]);
+									rows[i].setAttribute("selected","true");
+								}
+								if(rows[i]==row){
+									doSelect=false;
+								}
+							}
+						}
+					}
+				}else{
+					//	reset the selection
+					this.resetSelections(body);
+					row.setAttribute("selected","true");
+					this.setSelectionByRow(row);
+				}
+			}else{
+				//	reset the data selection and go.
+				this.resetSelections(body);
+				row.setAttribute("selected","true");
+				this.setSelectionByRow(row);
+			}
+			this.showSelections();
+			this.onSelect(e);
+			e.stopPropagation();
+			e.preventDefault();
+		},
+		onHeaderClick:function(/* DomEvent */ e){
+			//	summary
+			//	Main handler function for each header column click.
+			var oldIndex=this.sortIndex;
+			var oldDirection=this.sortDirection;
+			var source=e.target;
+			var row=dojo.html.getParentByType(source,"tr");
+			var cellTag="td";
+			if(row.getElementsByTagName(cellTag).length==0) cellTag="th";
+
+			var headers=row.getElementsByTagName(cellTag);
+			var header=dojo.html.getParentByType(source,cellTag);
+			
+			for(var i=0; i<headers.length; i++){
+				if(headers[i]==header){
+					if(i!=oldIndex){
+						//	new col.
+						this.sortIndex=i;
+						this.sortDirection=0;
+						headers[i].className=this.headerSortDownClass
+					}else{
+						this.sortDirection=(oldDirection==0)?1:0;
+						if(this.sortDirection==0){
+							headers[i].className=this.headerSortDownClass;
+						}else{
+							headers[i].className=this.headerSortUpClass;
+						}
+					}
+				}else{
+					//	reset the header class.
+					headers[i].className=this.headerClass;
+				}
+			}
+			this.render();
+		},
+
+		postCreate:function(){ 
+			// 	summary
+			//	overridden from HtmlWidget, initializes and renders the widget.
+			var thead=this.domNode.getElementsByTagName("thead")[0];
+			if(this.headClass.length>0){
+				thead.className=this.headClass;
+			}
+
+			//	disable selections
+			dojo.html.disableSelection(this.domNode);
+
+			//	parse the columns.
+			this.parseColumns(thead);
+
+			//	attach header handlers.
+			var header="td";
+			if(thead.getElementsByTagName(header).length==0) header="th";
+			var headers=thead.getElementsByTagName(header);
+			for(var i=0; i<headers.length; i++){
+				if(!this.columns[i].noSort){
+					dojo.event.connect(headers[i], "onclick", this, "onHeaderClick");
+				}
+				if(this.sortIndex==i){
+					if(this.sortDirection==0){
+						headers[i].className=this.headerSortDownClass;
+					}else{
+						headers[i].className=this.headerSortUpClass;
+					}
+				}
+			}
+
+			//	parse the tbody element and re-render it.
+			var tbody=this.domNode.getElementsByTagName("tbody")[0];
+			if (this.tbodyClass.length>0) {
+				tbody.className=this.tbodyClass;
+			}
+
+			this.parseDataFromTable(tbody);
+			this.render(true);
+		}
+	}
+);

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SortableTable.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/Spinner.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/Spinner.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/Spinner.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/Spinner.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,597 @@
+/*
+	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.Spinner");
+
+dojo.require("dojo.io.*");
+dojo.require("dojo.lfx.*");
+dojo.require("dojo.html.*");
+dojo.require("dojo.html.layout");
+dojo.require("dojo.string");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.validate");
+
+// Mixin for validation widgets with a spinner
+// This class basically (conceptually) extends dojo.widget.validate.ValidationTextbox.
+// It modifies the template to have up/down arrows, and provides related handling code.
+dojo.declare(
+	"dojo.widget.Spinner",
+	null, 
+	{
+		// attach points
+		upArrowNode: null,
+		downArrowNode: null,
+
+		_inputWidget: null,
+		_typamaticTimer: null,
+		_typamaticFunction: null,
+		defaultTimeout: 500,
+		timeoutChangeRate: 0.90,
+		_currentTimeout: this.defaultTimeout,
+		_eventCount: 0,
+
+		templatePath: dojo.uri.dojoUri("src/widget/templates/Spinner.html"),
+		templateCssPath: dojo.uri.dojoUri("src/widget/templates/Spinner.css"),
+		incrementSrc: dojo.uri.dojoUri("src/widget/templates/images/spinnerIncrement.gif"),
+		decrementSrc: dojo.uri.dojoUri("src/widget/templates/images/spinnerDecrement.gif"),
+
+		// does the keyboard related stuff
+		_handleKeyEvents: function(evt){
+			var k = dojo.event.browser.keys;
+			var keyCode = evt.keyCode;
+
+			switch(keyCode){
+ 			case k.KEY_DOWN_ARROW:
+					dojo.event.browser.stopEvent(evt);
+					this._downArrowPressed(evt);
+					return;
+				case k.KEY_UP_ARROW:
+					dojo.event.browser.stopEvent(evt);
+					this._upArrowPressed(evt);
+					return;
+			}
+			this._eventCount++;
+		},
+
+		onSpinnerKeyDown: function(evt){
+			// IE needs to stop keyDown others need to stop keyPress
+			if(!document.createEvent){ // only IE
+				this._handleKeyEvents(evt);
+			}
+		},
+
+		onSpinnerKeyPress: function(evt){
+			if(document.createEvent){ // never IE
+				this._handleKeyEvents(evt);
+			}
+		},
+		
+		onSpinnerKeyUp: function(evt){
+			this._arrowReleased(evt);
+			this.onkeyup(evt);
+		},
+
+		// reset button size; this function is called when the input area has changed size
+		resize: function(){
+			var inputSize = dojo.html.getBorderBox(this.textbox);
+			this.buttonSize = { width: inputSize.height / 2, height: inputSize.height / 2 };
+			if(this.upArrowNode){
+				dojo.html.setMarginBox(this.upArrowNode, this.buttonSize);
+				dojo.html.setMarginBox(this.downArrowNode, this.buttonSize);
+			}
+		},
+
+		_pressButton: function(node){
+			with(node.style){
+				borderWidth = "1px 0px 0px 1px";
+				borderStyle = "inset";
+			}
+		},
+
+		_releaseButton: function(node){
+			with(node.style){
+				borderWidth = "0px 1px 1px 0px";
+				borderStyle = "outset";
+			}
+		},
+
+		_arrowPressed: function(evt, direction){
+			var nodePressed = (direction == -1) ? this.downArrowNode : this.upArrowNode;
+			var nodeReleased = (direction == +1) ? this.downArrowNode : this.upArrowNode;
+			if(typeof evt != "number"){
+				if(this._typamaticTimer != null){
+					if(this._typamaticNode == nodePressed){
+						return;
+					}
+					dojo.lang.clearTimeout(this._typamaticTimer);
+				}
+				this._releaseButton(nodeReleased);
+				this._eventCount++;
+				this._typamaticTimer = null;
+				this._currentTimeout = this.defaultTimeout;
+
+			}else if (evt != this._eventCount){
+				this._releaseButton(nodePressed);
+				return;
+			}
+			this._pressButton(nodePressed);
+			this.setCursorX(this.adjustValue(direction,this.getCursorX()));
+			this._typamaticNode = nodePressed;
+			this._typamaticTimer = dojo.lang.setTimeout(this, "_arrowPressed", this._currentTimeout, this._eventCount, direction);
+			this._currentTimeout = Math.round(this._currentTimeout * this.timeoutChangeRate);
+		},
+
+		_downArrowPressed: function(evt){
+			return this._arrowPressed(evt,-1);
+		},
+
+		// IE sends these events when rapid clicking, mimic an extra single click
+		_downArrowDoubleClicked: function(evt){
+			var rc = this._downArrowPressed(evt);
+			dojo.lang.setTimeout(this, "_arrowReleased", 50, null);
+			return rc;
+		},
+
+		_upArrowPressed: function(evt){
+			return this._arrowPressed(evt,+1);
+		},
+
+		// IE sends these events when rapid clicking, mimic an extra single click
+		_upArrowDoubleClicked: function(evt){
+			var rc = this._upArrowPressed(evt);
+			dojo.lang.setTimeout(this, "_arrowReleased", 50, null);
+			return rc;
+		},
+
+		_arrowReleased: function(evt){
+			this.textbox.focus();
+			if(evt != null && typeof evt == "object" && evt.keyCode && evt.keyCode != null){
+				var keyCode = evt.keyCode;
+				var k = dojo.event.browser.keys;
+
+				switch(keyCode){
+					case k.KEY_DOWN_ARROW:
+					case k.KEY_UP_ARROW:
+						dojo.event.browser.stopEvent(evt);
+						break;
+				}
+			}
+			this._releaseButton(this.upArrowNode);
+			this._releaseButton(this.downArrowNode);
+			this._eventCount++;
+			if(this._typamaticTimer != null){
+				dojo.lang.clearTimeout(this._typamaticTimer);
+			}
+			this._typamaticTimer = null;
+			this._currentTimeout = this.defaultTimeout;
+		},
+
+		_mouseWheeled: function(evt) {
+			var scrollAmount = 0;
+			if(typeof evt.wheelDelta == 'number'){ // IE
+				scrollAmount = evt.wheelDelta;
+			}else if (typeof evt.detail == 'number'){ // Mozilla+Firefox
+				scrollAmount = -evt.detail;
+			}
+			if(scrollAmount > 0){
+				this._upArrowPressed(evt);
+				this._arrowReleased(evt);
+			}else if (scrollAmount < 0){
+				this._downArrowPressed(evt);
+				this._arrowReleased(evt);
+			}
+		},
+
+		_discardEvent: function(evt) {
+			dojo.event.browser.stopEvent(evt);
+		},
+
+		getCursorX: function(){
+			var x = -1;
+			try{
+				this.textbox.focus();
+				if (typeof this.textbox.selectionEnd == "number"){
+					x = this.textbox.selectionEnd;
+				}else if (document.selection && document.selection.createRange) {
+					var range = document.selection.createRange().duplicate();
+					if(range.parentElement() == this.textbox){
+						range.moveStart('textedit', -1);
+						x = range.text.length;
+					}
+				}
+			}catch(e){ /* squelch! */ }
+			return x;
+		},
+
+		setCursorX: function(x){
+			try{
+				this.textbox.focus();
+				if(!x){ x = 0 }
+				if(typeof this.textbox.selectionEnd == "number"){
+				this.textbox.selectionEnd = x;
+				}else if(this.textbox.createTextRange){
+				var range = this.textbox.createTextRange();
+				range.collapse(true);
+				range.moveEnd('character', x);
+				range.moveStart('character', x);
+				range.select();
+				}
+			}catch(e){ /* squelch! */ }
+		},
+
+		postMixInProperties: function(args, frag){
+			this.inherited("postMixInProperties");
+
+			// set image size before instantiating template;
+			// changing it aftwards doesn't work on FF
+			var inputNode = this.getFragNodeRef(frag);
+			var inputSize = dojo.html.getBorderBox(inputNode);
+			this.buttonSize = { width: inputSize.height / 2 - 1, height: inputSize.height / 2 - 1};
+		},
+
+		postCreate: function(){
+			this.inherited("postCreate");
+
+			// extra listeners
+			if(this.textbox.addEventListener){
+				// dojo.event.connect() doesn't seem to work with DOMMouseScroll
+				this.textbox.addEventListener('DOMMouseScroll', dojo.lang.hitch(this, "_mouseWheeled"), false); // Mozilla + Firefox + Netscape
+			}else{
+				dojo.event.connect(this.textbox, "onmousewheel", this, "_mouseWheeled"); // IE + Safari
+			}
+			//dojo.event.connect(window, "onchange", this, "resize");
+		}
+	}
+);
+
+/*
+  ****** SpinnerIntegerTextbox ******
+
+  A subclass of IntegerTextbox.
+*/
+dojo.widget.defineWidget(
+	"dojo.widget.SpinnerIntegerTextbox",
+	[dojo.widget.validate.IntegerTextbox, dojo.widget.Spinner],
+{
+	// new subclass properties
+	delta: "1",
+
+	adjustValue: function(direction, x){
+			var val = this.getValue().replace(/[^\-+\d]/g, "");
+			if(val.length == 0){ return; }
+
+			var num = Math.min(Math.max((parseInt(val)+(parseInt(this.delta) * direction)), (this.flags.min?this.flags.min:-Infinity)), (this.flags.max?this.flags.max:+Infinity));
+			val = num.toString();
+
+			if(num >= 0){
+				val = ((this.flags.signed == true)?'+':' ')+val; // make sure first char is a nondigit
+			}
+
+			if(this.flags.separator.length > 0){
+				for (var i=val.length-3; i > 1; i-=3){
+					val = val.substr(0,i)+this.flags.separator+val.substr(i);
+				}
+			}
+
+			if(val.substr(0,1) == ' '){ val = val.substr(1); } // remove space
+
+			this.setValue(val);
+
+			return val.length;
+	}
+});
+
+/*
+  ****** SpinnerRealNumberTextbox ******
+
+  A subclass of RealNumberTextbox.
+  @attr places    The exact number of decimal places.  If omitted, it's unlimited and optional.
+  @attr exponent  Can be true or false.  If omitted the exponential part is optional.
+  @attr eSigned   Is the exponent signed?  Can be true or false, if omitted the sign is optional.
+*/
+dojo.widget.defineWidget(
+	"dojo.widget.SpinnerRealNumberTextbox",
+	[dojo.widget.validate.RealNumberTextbox, dojo.widget.Spinner],
+{
+	// new subclass properties
+	delta: "1e1",
+
+	adjustValue: function(direction, x){
+			var val = this.getValue().replace(/[^\-+\.eE\d]/g, "");
+			if(!val.length){ return; }
+
+			var num = parseFloat(val);
+			if(isNaN(num)){ return; }
+			var delta = this.delta.split(/[eE]/);
+			if(!delta.length){
+				delta = [1, 1];
+			}else{
+				delta[0] = parseFloat(delta[0].replace(/[^\-+\.\d]/g, ""));
+				if(isNaN(delta[0])){ delta[0] = 1; }
+				if(delta.length > 1){
+					delta[1] = parseInt(delta[1]);
+				}
+				if(isNaN(delta[1])){ delta[1] = 1; }
+			}
+			val = this.getValue().split(/[eE]/);
+			if(!val.length){ return; }
+			var numBase = parseFloat(val[0].replace(/[^\-+\.\d]/g, ""));
+			if(val.length == 1){
+				var numExp = 0;
+			}else{
+				var numExp = parseInt(val[1].replace(/[^\-+\d]/g, ""));
+			}
+			if(x <= val[0].length){
+				x = 0;
+				numBase += delta[0] * direction;
+			}else{
+				x = Number.MAX_VALUE;
+				numExp += delta[1] * direction;
+				if(this.flags.eSigned == false && numExp < 0){
+					numExp = 0;
+				}
+			}
+			num = Math.min(Math.max((numBase * Math.pow(10,numExp)), (this.flags.min?this.flags.min:-Infinity)), (this.flags.max?this.flags.max:+Infinity));
+			if((this.flags.exponent == true || (this.flags.exponent != false && x != 0)) && num.toExponential){
+				if (isNaN(this.flags.places) || this.flags.places == Infinity){
+					val = num.toExponential();
+				}else{
+					val = num.toExponential(this.flags.places);
+				}
+			}else if(num.toFixed && num.toPrecision){
+				if(isNaN(this.flags.places) || this.flags.places == Infinity){
+					val = num.toPrecision((1/3).toString().length-1);
+				}else{
+					val = num.toFixed(this.flags.places);
+				}
+			}else{
+				val = num.toString();
+			}
+
+			if(num >= 0){
+				if(this.flags.signed == true){
+					val = '+' + val;
+				}
+			}
+			val = val.split(/[eE]/);
+			if(this.flags.separator.length > 0){
+				if(num >= 0 && val[0].substr(0,1) != '+'){
+					val[0] = ' ' + val[0]; // make sure first char is nondigit for easy algorithm
+				}
+				var i = val[0].lastIndexOf('.');
+				if(i >= 0){
+					i -= 3;
+				}else{
+					i = val[0].length-3;
+				}
+				for (; i > 1; i-=3){
+					val[0] = val[0].substr(0,i)+this.flags.separator+val[0].substr(i);
+				}
+				if(val[0].substr(0,1) == ' '){ val[0] = val[0].substr(1); } // remove space
+			}
+			if(val.length > 1){
+				if((this.flags.eSigned == true)&&(val[1].substr(0,1) != '+')){
+					val[1] = '+' + val[1];
+				}else if((!this.flags.eSigned)&&(val[1].substr(0,1) == '+')){
+					val[1] = val[1].substr(1);
+				}else if((!this.flags.eSigned)&&(val[1].substr(0,1) == '-')&&(num.toFixed && num.toPrecision)){
+					if(isNaN(this.flags.places)){
+						val[0] = num.toPrecision((1/3).toString().length-1);
+					}else{
+						val[0] = num.toFixed(this.flags.places).toString();
+					}
+					val[1] = "0";
+				}
+				val[0] += 'e' + val[1];
+			}
+			this.setValue(val[0]);
+			if(x > val[0].length){ x = val[0].length; }
+			return x;
+	}
+});
+
+/*
+  ****** SpinnerTimeTextbox ******
+
+  A subclass of TimeTextbox.
+*/
+
+dojo.widget.defineWidget(
+	"dojo.widget.SpinnerTimeTextbox",
+	[dojo.widget.validate.TimeTextbox, dojo.widget.Spinner],
+{
+	adjustValue: function(direction, x){
+		var val = this.getValue();
+		var format = (this.flags.format && this.flags.format.search(/[Hhmst]/) >= 0) ? this.flags.format : "hh:mm:ss t";
+		if(direction == 0 || !val.length || !this.isValid()){ return; }
+		if (!this.flags.amSymbol) {
+			this.flags.amSymbol = "AM";
+		}
+		if (!this.flags.pmSymbol) {
+			this.flags.pmSymbol = "PM";
+		}
+		var re = dojo.regexp.time(this.flags);
+		var qualifiers = format.replace(/H/g,"h").replace(/[^hmst]/g,"").replace(/([hmst])\1/g,"$1");
+		var hourPos = qualifiers.indexOf('h') + 1;
+		var minPos = qualifiers.indexOf('m') + 1;
+		var secPos = qualifiers.indexOf('s') + 1;
+		var ampmPos = qualifiers.indexOf('t') + 1;
+		// tweak format to match the incoming data exactly to help find where the cursor is
+		var cursorFormat = format;
+		var ampm = "";
+		if (ampmPos > 0){
+			ampm = val.replace(new RegExp(re),"$"+ampmPos);
+			cursorFormat = cursorFormat.replace(/t+/, ampm.replace(/./g,"t"));
+		}
+		var hour = 0;
+		var deltaHour = 1;
+		if (hourPos > 0){
+			hour = val.replace(new RegExp(re),"$"+hourPos);
+			if (dojo.lang.isString(this.delta)) {
+				deltaHour = this.delta.replace(new RegExp(re),"$"+hourPos);
+			}
+			if (isNaN(deltaHour)) {
+				deltaHour = 1;
+			} else {
+				deltaHour = parseInt(deltaHour);
+			}
+			if (hour.length == 2) {
+				cursorFormat = cursorFormat.replace(/([Hh])+/, "$1$1");
+			} else {
+				cursorFormat = cursorFormat.replace(/([Hh])+/, "$1");
+			}
+			if (isNaN(hour)) {
+				hour = 0;
+			} else {
+				hour = parseInt(hour.replace(/^0(\d)/,"$1"));
+			}
+		}
+		var min = 0;
+		var deltaMin = 1;
+		if (minPos > 0){
+			min = val.replace(new RegExp(re),"$"+minPos);
+			if (dojo.lang.isString(this.delta)) {
+				deltaMin = this.delta.replace(new RegExp(re),"$"+minPos);
+			}
+			if (isNaN(deltaMin)) {
+				deltaMin = 1;
+			} else {
+				deltaMin = parseInt(deltaMin);
+			}
+			cursorFormat = cursorFormat.replace(/m+/, min.replace(/./g,"m"));
+			if (isNaN(min)) {
+				min = 0;
+			} else {
+				min = parseInt(min.replace(/^0(\d)/,"$1"));
+			}
+		}
+		var sec = 0;
+		var deltaSec = 1;
+		if (secPos > 0){
+			sec = val.replace(new RegExp(re),"$"+secPos);
+			if (dojo.lang.isString(this.delta)) {
+				deltaSec = this.delta.replace(new RegExp(re),"$"+secPos);
+			}
+			if (isNaN(deltaSec)) {
+				deltaSec = 1;
+			} else {
+				deltaSec = parseInt(deltaSec);
+			}
+			cursorFormat = cursorFormat.replace(/s+/, sec.replace(/./g,"s"));
+			if (isNaN(sec)) {
+				sec = 0;
+			} else {
+				sec = parseInt(sec.replace(/^0(\d)/,"$1"));
+			}
+		}
+		if (isNaN(x) || x >= cursorFormat.length){
+			x = cursorFormat.length-1;
+		}
+		var cursorToken = cursorFormat.charAt(x);
+
+		switch(cursorToken) {
+			case 't':
+				if (ampm == this.flags.amSymbol) {
+					ampm = this.flags.pmSymbol;
+				}
+				else if (ampm == this.flags.pmSymbol) {
+					ampm = this.flags.amSymbol;
+				}
+				break;
+			default:
+				if (hour >= 1 && hour < 12 && ampm == this.flags.pmSymbol) {
+					hour += 12;
+				}
+				if (hour == 12 && ampm == this.flags.amSymbol) {
+					hour = 0;
+				}
+				switch(cursorToken) {
+					case 's':
+						sec += deltaSec * direction;
+						while (sec < 0) {
+							min--;
+							sec += 60;
+						}
+						while (sec >= 60) {
+							min++;
+							sec -= 60;
+						}
+					case 'm':
+						if (cursorToken == 'm') {
+							min += deltaMin * direction;
+						}
+						while (min < 0) {
+							hour--;
+							min += 60;
+						}
+						while (min >= 60) {
+							hour++;
+							min -= 60;
+						}
+					case 'h':
+					case 'H':
+						if (cursorToken == 'h' || cursorToken == 'H') {
+							hour += deltaHour * direction;
+						}
+						while (hour < 0) {
+							hour += 24;
+						}
+						while (hour >= 24) {
+							hour -= 24;
+						}
+						break;
+					default: // should never get here
+						return;
+				}
+				if (hour >= 12) {
+					ampm = this.flags.pmSymbol;
+					if (format.indexOf('h') >= 0 && hour >= 13) {
+						hour -= 12;
+					}
+				} else {
+					ampm = this.flags.amSymbol;
+					if (format.indexOf('h') >= 0 && hour == 0) {
+						hour = 12;
+					}
+				}
+		}
+
+		cursorFormat = format;
+		if (hour >= 0 && hour < 10 && format.search(/[hH]{2}/) >= 0) {
+			hour = "0" + hour.toString();
+		}
+		if (hour >= 10 && cursorFormat.search(/[hH]{2}/) < 0 ) {
+			cursorFormat = cursorFormat.replace(/(h|H)/, "$1$1");
+		}
+		if (min >= 0 && min < 10 && cursorFormat.search(/mm/) >= 0) {
+			min = "0" + min.toString();
+		}
+		if (min >= 10 && cursorFormat.search(/mm/) < 0 ) {
+			cursorFormat = cursorFormat.replace(/m/, "$1$1");
+		}
+		if (sec >= 0 && sec < 10 && cursorFormat.search(/ss/) >= 0) {
+			sec = "0" + sec.toString();
+		}
+		if (sec >= 10 && cursorFormat.search(/ss/) < 0 ) {
+			cursorFormat = cursorFormat.replace(/s/, "$1$1");
+		}
+		x = cursorFormat.indexOf(cursorToken);
+		if (x == -1) {
+			x = format.length;
+		}
+		format = format.replace(/[hH]+/, hour);
+		format = format.replace(/m+/, min);
+		format = format.replace(/s+/, sec);
+		format = format.replace(/t/, ampm);
+		this.setValue(format);
+		if(x > format.length){ x = format.length; }
+		return x;
+	}
+});

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/Spinner.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SplitContainer.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SplitContainer.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SplitContainer.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SplitContainer.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,528 @@
+/*
+	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.SplitContainer");
+dojo.provide("dojo.widget.SplitContainerPanel");
+
+//
+// TODO
+// make it prettier
+// active dragging upwards doesn't always shift other bars (direction calculation is wrong in this case)
+//
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.ContentPane");
+dojo.require("dojo.widget.HtmlWidget");
+dojo.require("dojo.html.style");
+dojo.require("dojo.html.layout");
+dojo.require("dojo.html.selection");
+dojo.require("dojo.io.cookie");
+
+dojo.widget.defineWidget(
+	"dojo.widget.SplitContainer",
+	dojo.widget.HtmlWidget,
+	function(){
+		this.sizers = [];
+	},
+{
+	isContainer: true,
+
+	// variables
+	virtualSizer: null,
+	isHorizontal: null,
+	paneBefore: null,
+	paneAfter: null,
+	isSizing: false,
+	dragOffset: 0,
+	startPoint: 0,
+	lastPoint: 0,
+	sizingSplitter: null,
+	screenToClientOffset: 0,
+	isDraggingLeft: 0,
+	templateCssPath: dojo.uri.dojoUri("src/widget/templates/SplitContainer.css"),
+	originPos: 0,
+
+	// parameters (user settable)
+	activeSizing: false,
+	sizerWidth: 15,
+	orientation: 'horizontal',
+	persist: true,		// save splitter positions in a cookie
+
+	debugName: '',
+
+	fillInTemplate: function(){
+
+		dojo.html.insertCssFile(this.templateCssPath, null, true);
+		dojo.html.addClass(this.domNode, "dojoSplitContainer");
+		// overflow has to be explicitly hidden for splitContainers using gekko (trac #1435)
+		// to keep other combined css classes from inadvertantly making the overflow visible
+		if (dojo.render.html.moz) {
+		        this.domNode.style.overflow = '-moz-scrollbars-none'; // hidden doesn't work
+		}
+		
+		var content = dojo.html.getContentBox(this.domNode);
+		this.paneWidth = content.width;
+		this.paneHeight = content.height;
+
+		this.isHorizontal = (this.orientation == 'horizontal');
+
+		//dojo.debug("fillInTemplate for "+this.debugName);
+	},
+
+	onResized: function(e){
+		var content = dojo.html.getContentBox(this.domNode);
+		this.paneWidth = content.width;
+		this.paneHeight = content.height;
+		this.layoutPanels();
+	},
+
+	postCreate: function(args, fragment, parentComp){
+
+		// dojo.debug("post create for "+this.debugName);
+
+		// attach the children and create the draggers
+		for(var i=0; i<this.children.length; i++){
+            with(this.children[i].domNode.style){
+                position = "absolute";
+            }
+            dojo.html.addClass(this.children[i].domNode,
+                "dojoSplitPane");
+
+            if(i == this.children.length-1){
+                break;
+            }
+
+            this._addSizer();
+		}
+
+		// create the fake dragger
+		if (typeof this.sizerWidth == "object") { 
+			try {
+				this.sizerWidth = parseInt(this.sizerWidth.toString()); 
+			} catch(e) { this.sizerWidth = 15; }
+		}
+		this.virtualSizer = document.createElement('div');
+		this.virtualSizer.style.position = 'absolute';
+		this.virtualSizer.style.display = 'none';
+		//this.virtualSizer.style.backgroundColor = 'lime';
+		this.virtualSizer.style.zIndex = 10;
+		this.virtualSizer.className = this.isHorizontal ? 'dojoSplitContainerVirtualSizerH' : 'dojoSplitContainerVirtualSizerV';
+		this.domNode.appendChild(this.virtualSizer);
+
+		dojo.html.disableSelection(this.virtualSizer);
+
+		if(this.persist){
+			this.restoreState();
+		}
+
+		// size the panels once the browser has caught up
+		this.resizeSoon();
+	},
+
+    _injectChild: function(child) {
+        with(child.domNode.style){
+            position = "absolute";
+        }
+        dojo.html.addClass(child.domNode,
+            "dojoSplitPane");
+    },
+
+    _addSizer: function() {
+        var i = this.sizers.length;
+
+        this.sizers[i] = document.createElement('div');
+        this.sizers[i].style.position = 'absolute';
+        this.sizers[i].className = this.isHorizontal ? 'dojoSplitContainerSizerH' : 'dojoSplitContainerSizerV';
+
+        var self = this;
+        var handler = (function(){ var sizer_i = i; return function(e){ self.beginSizing(e, sizer_i); } })();
+        dojo.event.connect(this.sizers[i], "onmousedown", handler);
+
+        this.domNode.appendChild(this.sizers[i]);
+        dojo.html.disableSelection(this.sizers[i]);
+    },
+
+    removeChild: function(widget){
+        // Remove sizer, but only if widget is really our child and
+        // we have at least one sizer to throw away
+        if (this.sizers.length > 0) {
+            for(var x=0; x<this.children.length; x++){
+                if(this.children[x] === widget){
+                    var i = this.sizers.length - 1;
+                    this.domNode.removeChild(this.sizers[i]);
+                    this.sizers.length = i;
+                    break;
+                }
+            }
+        }
+
+        // Remove widget and repaint
+        dojo.widget.SplitContainer.superclass.removeChild.call(this, widget, arguments);
+        this.onResized();
+    },
+
+    addChild: function(widget, overrideContainerNode, pos, ref, insertIndex){
+        dojo.widget.SplitContainer.superclass.addChild.call(this, widget, overrideContainerNode, pos, ref, insertIndex);
+        this._injectChild(widget);
+
+        if (this.children.length > 1) {
+            this._addSizer();
+        }
+
+        this.layoutPanels();
+    },
+
+    layoutPanels: function(){
+        if (this.children.length == 0){ return; }
+
+		//
+		// calculate space
+		//
+
+		var space = this.isHorizontal ? this.paneWidth : this.paneHeight;
+
+		if (this.children.length > 1){
+
+			space -= this.sizerWidth * (this.children.length - 1);
+		}
+
+
+		//
+		// calculate total of SizeShare values
+		//
+
+		var out_of = 0;
+
+		for(var i=0; i<this.children.length; i++){
+
+			out_of += this.children[i].sizeShare;
+		}
+
+
+		//
+		// work out actual pixels per sizeshare unit
+		//
+
+		var pix_per_unit = space / out_of;
+
+
+		//
+		// set the SizeActual member of each pane
+		//
+
+		var total_size = 0;
+
+		for(var i=0; i<this.children.length-1; i++){
+
+			var size = Math.round(pix_per_unit * this.children[i].sizeShare);
+			this.children[i].sizeActual = size;
+			total_size += size;
+		}
+		this.children[this.children.length-1].sizeActual = space - total_size;
+
+		//
+		// make sure the sizes are ok
+		//
+
+		this.checkSizes();
+
+
+		//
+		// now loop, positioning each pane and letting children resize themselves
+		//
+
+		var pos = 0;
+		var size = this.children[0].sizeActual;
+		this.movePanel(this.children[0].domNode, pos, size);
+		this.children[0].position = pos;
+		this.children[0].checkSize();
+		pos += size;
+
+		for(var i=1; i<this.children.length; i++){
+
+			// first we position the sizing handle before this pane
+			this.movePanel(this.sizers[i-1], pos, this.sizerWidth);
+			this.sizers[i-1].position = pos;
+			pos += this.sizerWidth;
+
+			size = this.children[i].sizeActual;
+			this.movePanel(this.children[i].domNode, pos, size);
+			this.children[i].position = pos;
+			this.children[i].checkSize();
+			pos += size;
+		}
+	},
+
+	movePanel: function(panel, pos, size){
+		if (this.isHorizontal){
+			panel.style.left = pos + 'px';
+			panel.style.top = 0;
+
+			dojo.html.setMarginBox(panel, { width: size, height: this.paneHeight });
+		}else{
+			panel.style.left = 0;
+			panel.style.top = pos + 'px';
+
+			dojo.html.setMarginBox(panel, { width: this.paneWidth, height: size });
+		}
+	},
+
+	growPane: function(growth, pane){
+
+		if (growth > 0){
+			if (pane.sizeActual > pane.sizeMin){
+				if ((pane.sizeActual - pane.sizeMin) > growth){
+
+					// stick all the growth in this pane
+					pane.sizeActual = pane.sizeActual - growth;
+					growth = 0;
+				}else{
+					// put as much growth in here as we can
+					growth -= pane.sizeActual - pane.sizeMin;
+					pane.sizeActual = pane.sizeMin;
+				}
+			}
+		}
+		return growth;
+	},
+
+	checkSizes: function(){
+
+		var total_min_size = 0;
+		var total_size = 0;
+
+		for(var i=0; i<this.children.length; i++){
+
+			total_size += this.children[i].sizeActual;
+			total_min_size += this.children[i].sizeMin;
+		}
+
+		// only make adjustments if we have enough space for all the minimums
+
+		if (total_min_size <= total_size){
+
+			var growth = 0;
+
+			for(var i=0; i<this.children.length; i++){
+
+				if (this.children[i].sizeActual < this.children[i].sizeMin){
+
+					growth += this.children[i].sizeMin - this.children[i].sizeActual;
+					this.children[i].sizeActual = this.children[i].sizeMin;
+				}
+			}
+
+			if (growth > 0){
+				if (this.isDraggingLeft){
+					for(var i=this.children.length-1; i>=0; i--){
+						growth = this.growPane(growth, this.children[i]);
+					}
+				}else{
+					for(var i=0; i<this.children.length; i++){
+						growth = this.growPane(growth, this.children[i]);
+					}
+				}
+			}
+		}else{
+
+			for(var i=0; i<this.children.length; i++){
+				this.children[i].sizeActual = Math.round(total_size * (this.children[i].sizeMin / total_min_size));
+			}
+		}
+	},
+
+	beginSizing: function(e, i){
+		this.paneBefore = this.children[i];
+		this.paneAfter = this.children[i+1];
+
+		this.isSizing = true;
+		this.sizingSplitter = this.sizers[i];
+		this.originPos = dojo.html.getAbsolutePosition(this.children[0].domNode, true, dojo.html.boxSizing.MARGIN_BOX);
+		if (this.isHorizontal){
+			var client = (e.layerX ? e.layerX : e.offsetX);
+			var screen = e.pageX;
+			this.originPos = this.originPos.x;
+		}else{
+			var client = (e.layerY ? e.layerY : e.offsetY);
+			var screen = e.pageY;
+			this.originPos = this.originPos.y;
+		}
+		this.startPoint = this.lastPoint = screen;
+		this.screenToClientOffset = screen - client;
+		this.dragOffset = this.lastPoint - this.paneBefore.sizeActual - this.originPos - this.paneBefore.position;
+
+		if (!this.activeSizing){
+			this.showSizingLine();
+		}
+
+		//
+		// attach mouse events
+		//
+
+		dojo.event.connect(document.documentElement, "onmousemove", this, "changeSizing");
+		dojo.event.connect(document.documentElement, "onmouseup", this, "endSizing");
+		dojo.event.browser.stopEvent(e);
+	},
+
+	changeSizing: function(e){
+		this.lastPoint = this.isHorizontal ? e.pageX : e.pageY;
+		if (this.activeSizing){
+			this.movePoint();
+			this.updateSize();
+		}else{
+			this.movePoint();
+			this.moveSizingLine();
+		}
+		dojo.event.browser.stopEvent(e);
+	},
+
+	endSizing: function(e){
+
+		if (!this.activeSizing){
+			this.hideSizingLine();
+		}
+
+		this.updateSize();
+
+		this.isSizing = false;
+
+		dojo.event.disconnect(document.documentElement, "onmousemove", this, "changeSizing");
+		dojo.event.disconnect(document.documentElement, "onmouseup", this, "endSizing");
+		
+		if(this.persist){
+			this.saveState(this);
+		}
+	},
+
+	movePoint: function(){
+
+		// make sure lastPoint is a legal point to drag to
+		var p = this.lastPoint - this.screenToClientOffset;
+
+		var a = p - this.dragOffset;
+		a = this.legaliseSplitPoint(a);
+		p = a + this.dragOffset;
+
+		this.lastPoint = p + this.screenToClientOffset;
+	},
+
+	legaliseSplitPoint: function(a){
+
+		a += this.sizingSplitter.position;
+
+		this.isDraggingLeft = (a > 0) ? 1 : 0;
+
+		if (!this.activeSizing){
+
+			if (a < this.paneBefore.position + this.paneBefore.sizeMin){
+
+				a = this.paneBefore.position + this.paneBefore.sizeMin;
+			}
+
+			if (a > this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin))){
+
+				a = this.paneAfter.position + (this.paneAfter.sizeActual - (this.sizerWidth + this.paneAfter.sizeMin));
+			}
+		}
+
+		a -= this.sizingSplitter.position;
+
+		this.checkSizes();
+
+		return a;
+	},
+
+	updateSize: function(){
+		var pos = this.lastPoint - this.dragOffset - this.originPos;
+
+		var start_region = this.paneBefore.position;
+		var end_region   = this.paneAfter.position + this.paneAfter.sizeActual;
+
+		this.paneBefore.sizeActual = pos - start_region;
+		this.paneAfter.position    = pos + this.sizerWidth;
+		this.paneAfter.sizeActual  = end_region - this.paneAfter.position;
+
+		for(var i=0; i<this.children.length; i++){
+
+			this.children[i].sizeShare = this.children[i].sizeActual;
+		}
+
+		this.layoutPanels();
+	},
+
+	showSizingLine: function(){
+
+		this.moveSizingLine();
+
+		if (this.isHorizontal){
+			dojo.html.setMarginBox(this.virtualSizer, { width: this.sizerWidth, height: this.paneHeight });
+		}else{
+			dojo.html.setMarginBox(this.virtualSizer, { width: this.paneWidth, height: this.sizerWidth });
+		}
+
+		this.virtualSizer.style.display = 'block';
+	},
+
+	hideSizingLine: function(){
+		this.virtualSizer.style.display = 'none';
+	},
+
+	moveSizingLine: function(){
+		var pos = this.lastPoint - this.startPoint + this.sizingSplitter.position;
+		if (this.isHorizontal){
+			this.virtualSizer.style.left = pos + 'px';
+		}else{
+			var pos = (this.lastPoint - this.startPoint) + this.sizingSplitter.position;
+			this.virtualSizer.style.top = pos + 'px';
+		}
+
+	},
+	
+	_getCookieName: function(i) {
+		return this.widgetId + "_" + i;
+	},
+
+	restoreState: function () {
+		for(var i=0; i<this.children.length; i++) {
+			var cookieName = this._getCookieName(i);
+			var cookieValue = dojo.io.cookie.getCookie(cookieName);
+			if (cookieValue != null) {
+				var pos = parseInt(cookieValue);
+				if (typeof pos == "number") {
+					this.children[i].sizeShare=pos;
+				}
+			}
+		}
+	},
+
+	saveState: function (){
+		for(var i=0; i<this.children.length; i++) {
+			var cookieName = this._getCookieName(i);
+			dojo.io.cookie.setCookie(cookieName, this.children[i].sizeShare, null, null, null, null);
+		}
+	}
+});
+
+// These arguments can be specified for the children of a SplitContainer.
+// Since any widget can be specified as a SplitContainer child, mix them
+// into the base widget class.  (This is a hack, but it's effective.)
+dojo.lang.extend(dojo.widget.Widget, {
+	sizeMin: 10,
+	sizeShare: 10
+});
+
+// Deprecated class for split pane children.
+// Actually any widget can be the child of a split pane
+dojo.widget.defineWidget(
+	"dojo.widget.SplitContainerPanel",
+	dojo.widget.ContentPane,
+	{}
+);
+

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SplitContainer.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgButton.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgButton.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgButton.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgButton.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,141 @@
+/*
+	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
+*/
+
+// FIXME: not yet functional
+
+dojo.provide("dojo.widget.SvgButton");
+
+dojo.require("dojo.widget.Button");
+
+dojo.widget.SvgButton = function(){
+	// FIXME: this is incomplete and doesn't work yet
+	// if DOMButton turns into a mixin, we should subclass Button instead and
+	// just mix in the DOMButton properties.
+
+	dojo.widget.DomButton.call(this);
+	dojo.widget.SvgWidget.call(this);
+
+	// FIXME: freaking implement this already!
+	this.onFoo = function(){ alert("bar"); }
+
+	this.label = "huzzah!";
+
+	this.setLabel = function(x, y, textSize, label, shape){
+		//var labelNode = this.domNode.ownerDocument.createTextNode(this.label);
+		//var textNode = this.domNode.ownerDocument.createElement("text");
+		var coords = dojo.widget.SvgButton.prototype.coordinates(x, y, textSize, label, shape);
+		var textString = "";
+		switch(shape) {
+			case "ellipse":
+				textString = "<text x='"+ coords[6] + "' y='"+ coords[7] + "'>"+ label + "</text>";
+				//textNode.setAttribute("x", coords[6]);
+				//textNode.setAttribute("y", coords[7]);
+				break;
+			case "rectangle":
+				//FIXME: implement
+				textString = "";
+				//textNode.setAttribute("x", coords[6]);
+				//textNode.setAttribute("y", coords[7]);
+				break;
+			case "circle":
+				//FIXME: implement
+				textString = "";
+				//textNode.setAttribute("x", coords[6]);
+				//textNode.setAttribute("y", coords[7]);
+				break;
+		}
+		//textNode.appendChild(labelNode);
+		//this.domNode.appendChild(textNode);
+		return textString;
+		//alert(textNode.getComputedTextLength());
+	}
+
+	this.fillInTemplate = function(x, y, textSize, label, shape){
+		// the idea is to set the text to the appropriate place given its length
+		// and the template shape
+		
+		// FIXME: For now, assuming text sizes are integers in SVG units
+		this.textSize = textSize || 12;
+		this.label = label;
+		// FIXEME: for now, I'm going to fake this... need to come up with a real way to 
+		// determine the actual width of the text, such as computedStyle
+		var textWidth = this.label.length*this.textSize ;
+		//this.setLabel();
+	}
+}
+
+dojo.inherits(dojo.widget.SvgButton, dojo.widget.DomButton);
+
+// FIXME
+dojo.widget.SvgButton.prototype.shapeString = function(x, y, textSize, label, shape) {
+	switch(shape) {
+		case "ellipse":
+			var coords = dojo.widget.SvgButton.prototype.coordinates(x, y, textSize, label, shape)
+			return "<ellipse cx='"+ coords[4]+"' cy='"+ coords[5]+"' rx='"+ coords[2]+"' ry='"+ coords[3]+"'/>";
+			break;
+		case "rect":
+			//FIXME: implement
+			return "";
+			//return "<rect x='110' y='45' width='70' height='30'/>";
+			break;
+		case "circle":
+			//FIXME: implement
+			return "";
+			//return "<circle cx='210' cy='60' r='23'/>";
+			break;
+	}
+}
+
+dojo.widget.SvgButton.prototype.coordinates = function(x, y, textSize, label, shape) {
+	switch(shape) {
+		case "ellipse":
+			var buttonWidth = label.length*textSize;
+			var buttonHeight = textSize*2.5
+			var rx = buttonWidth/2;
+			var ry = buttonHeight/2;
+			var cx = rx + x;
+			var cy = ry + y;
+			var textX = cx - rx*textSize/25;
+			var textY = cy*1.1;
+			return [buttonWidth, buttonHeight, rx, ry, cx, cy, textX, textY];
+			break;
+		case "rectangle":
+			//FIXME: implement
+			return "";
+			break;
+		case "circle":
+			//FIXME: implement
+			return "";
+			break;
+	}
+}
+
+dojo.widget.SvgButton.prototype.labelString = function(x, y, textSize, label, shape){
+	var textString = "";
+	var coords = dojo.widget.SvgButton.prototype.coordinates(x, y, textSize, label, shape);
+	switch(shape) {
+		case "ellipse":
+			textString = "<text x='"+ coords[6] + "' y='"+ coords[7] + "'>"+ label + "</text>";
+			break;
+		case "rectangle":
+			//FIXME: implement
+			textString = "";
+			break;
+		case "circle":
+			//FIXME: implement
+			textString = "";
+			break;
+	}
+	return textString;
+}
+
+dojo.widget.SvgButton.prototype.templateString = function(x, y, textSize, label, shape) {
+	return "<g class='dojoButton' dojoAttachEvent='onClick; onMouseMove: onFoo;' dojoAttachPoint='labelNode'>"+ dojo.widgets.SVGButton.prototype.shapeString(x, y, textSize, label, shape) + dojo.widget.SVGButton.prototype.labelString(x, y, textSize, label, shape) + "</g>";
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgButton.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgWidget.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgWidget.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgWidget.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgWidget.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,89 @@
+/*
+	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.require("dojo.widget.DomWidget");
+dojo.provide("dojo.widget.SvgWidget");
+dojo.provide("dojo.widget.SVGWidget"); // back compat
+
+dojo.require("dojo.dom");
+
+// SVGWidget is a mixin ONLY
+dojo.widget.declare(
+	"dojo.widget.SvgWidget",
+	dojo.widget.DomWidget,
+{
+	getContainerHeight: function(){
+		// NOTE: container height must be returned as the INNER height
+		dojo.unimplemented("dojo.widget.SvgWidget.getContainerHeight");
+	},
+
+	getContainerWidth: function(){
+		// return this.parent.domNode.offsetWidth;
+		dojo.unimplemented("dojo.widget.SvgWidget.getContainerWidth");
+	},
+
+	setNativeHeight: function(height){
+		// var ch = this.getContainerHeight();
+		dojo.unimplemented("dojo.widget.SVGWidget.setNativeHeight");
+	},
+
+	createNodesFromText: function(txt, wrap){
+		return dojo.dom.createNodesFromText(txt, wrap);
+	}
+});
+
+dojo.widget.SVGWidget = dojo.widget.SvgWidget;
+
+try{
+(function(){
+	var tf = function(){
+		// FIXME: fill this in!!!
+		var rw = new function(){
+			dojo.widget.SvgWidget.call(this);
+			this.buildRendering = function(){ return; }
+			this.destroyRendering = function(){ return; }
+			this.postInitialize = function(){ return; }
+			this.cleanUp = function(){ return; }
+			this.widgetType = "SVGRootWidget";
+			this.domNode = document.documentElement;
+		}
+		var wm = dojo.widget.manager;
+		wm.root = rw;
+		wm.add(rw);
+
+		// extend the widgetManager with a getWidgetFromNode method
+		wm.getWidgetFromNode = function(node){
+			var filter = function(x){
+				if(x.domNode == node){
+					return true;
+				}
+			}
+			var widgets = [];
+			while((node)&&(widgets.length < 1)){
+				widgets = this.getWidgetsByFilter(filter);
+				node = node.parentNode;
+			}
+			if(widgets.length > 0){
+				return widgets[0];
+			}else{
+				return null;
+			}
+		}
+
+		wm.getWidgetFromEvent = function(domEvt){
+			return this.getWidgetFromNode(domEvt.target);
+		}
+
+		wm.getWidgetFromPrimitive = wm.getWidgetFromNode;
+	}
+	// make sure we get called when the time is right
+	dojo.event.connect(dojo.hostenv, "loaded", tf);
+})();
+}catch(e){ alert(e); }

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SvgWidget.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SwtWidget.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SwtWidget.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SwtWidget.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SwtWidget.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,64 @@
+/*
+	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.SwtWidget");
+
+dojo.require("dojo.event.*");
+dojo.require("dojo.widget.Widget");
+dojo.require("dojo.uri.*");
+dojo.require("dojo.lang.func");
+dojo.require("dojo.lang.extras");
+
+try{
+	importPackage(Packages.org.eclipse.swt.widgets);
+
+	dojo.declare("dojo.widget.SwtWidget", dojo.widget.Widget,
+		function() {
+			if((arguments.length>0)&&(typeof arguments[0] == "object")){
+				this.create(arguments[0]);
+			}
+		},
+	{
+		display: null,
+		shell: null,
+
+		show: function(){ },
+		hide: function(){ },
+
+		addChild: function(){ },
+		registerChild: function(){ },
+		addWidgetAsDirectChild: function(){ },
+		removeChild: function(){ },
+		cleanUp: function(){ },
+		destroyRendering: function(){ },
+		postInitialize: function(){ },
+	});
+
+	// initialize SWT runtime
+
+	dojo.widget.SwtWidget.prototype.display = new Display();
+	dojo.widget.SwtWidget.prototype.shell = new Shell(dojo.widget.SwtWidget.prototype.display);
+
+	dojo.widget.manager.startShell = function(){
+		var sh = dojo.widget.SwtWidget.prototype.shell;
+		var d = dojo.widget.SwtWidget.prototype.display;
+		sh.open();
+		while(!sh.isDisposed()){
+			dojo.widget.manager.doNext();
+			if(!d.readAndDispatch()){
+				d.sleep();
+			}
+		}
+		d.dispose();
+	};
+}catch(e){
+	// seems we didn't have the SWT classes in the environment. Log it.
+	dojo.debug("dojo.widget.SwtWidget not loaded. SWT classes not available");
+}

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/SwtWidget.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TabContainer.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TabContainer.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TabContainer.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TabContainer.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,313 @@
+/*
+	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.TabContainer");
+
+dojo.require("dojo.lang.func");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.PageContainer");
+dojo.require("dojo.event.*");
+dojo.require("dojo.html.selection");
+dojo.require("dojo.widget.html.layout");
+
+// A TabContainer is a container that has multiple panes, but shows only
+// one pane at a time.  There are a set of tabs corresponding to each pane,
+// where each tab has the title (aka label) of the pane, and optionally a close button.
+//
+// Publishes topics <widgetId>-addPane, <widgetId>-removePane, and <widgetId>-selectPane
+dojo.widget.defineWidget("dojo.widget.TabContainer", dojo.widget.PageContainer, {
+
+	// String
+	//   Defines where tab labels go relative to tab content.
+	//   "top", "bottom", "left-h", "right-h"
+	labelPosition: "top",
+	
+	// String
+	//   If closebutton=="tab", then every tab gets a close button.
+	//   But this is deprecated.  Should just say tabCloseButton=true on each
+	//   pane you want to be closable.
+	closeButton: "none",
+
+	templateString: null,	// override setting in PageContainer
+	templatePath: dojo.uri.dojoUri("src/widget/templates/TabContainer.html"),
+	templateCssPath: dojo.uri.dojoUri("src/widget/templates/TabContainer.css"),
+
+	// String
+	//	initially selected tab (widgetId)
+	selectedTab: "",
+
+	fillInTemplate: function() {
+		// create the tab list that will have a tab (a.k.a. tab button) for each tab panel
+		this.tablist = dojo.widget.createWidget("TabList",
+			{
+				id: this.widgetId + "_tablist",
+				labelPosition: this.labelPosition,
+				doLayout: this.doLayout,
+				tabContainer: this.widgetId
+			}, this.tablistNode);
+		dojo.widget.TabContainer.superclass.fillInTemplate.apply(this, arguments);
+	},
+
+	_setupPage: function(tab){
+		if(this.closeButton=="tab" || this.closeButton=="pane"){
+			tab.tabCloseButton=true;
+		}
+		dojo.html.addClass(tab.domNode, "dojoTabPane");
+		dojo.widget.TabContainer.superclass._setupPage.apply(this, arguments);
+	},
+
+	onResized: function(){
+		// Summary: Configure the content pane to take up all the space except for where the tabs are
+		if(!this.doLayout){ return; }
+
+		// position the labels and the container node
+		var labelAlign=this.labelPosition.replace(/-h/,"");
+		var children = [
+			{domNode: this.tablist.domNode, layoutAlign: labelAlign},
+			{domNode: this.containerNode, layoutAlign: "client"}
+		];
+		dojo.widget.html.layout(this.domNode, children);
+		
+		dojo.widget.TabContainer.superclass.onResized.apply(this, arguments);
+	},
+
+	selectTab: function(tab, _noRefresh, callingWidget){
+		this.selectPage(tab, _noRefresh, callingWidget);
+	},
+
+	// Keystroke handling for keystrokes on the tab panel itself (that were bubbled up to me)
+	// Ctrl-up: focus is returned from the pane to the tab button
+	// Alt-del: close tab
+	onKey: function(e){ 
+		if(e.keyCode == e.KEY_UP_ARROW && e.ctrlKey){
+			// set focus to current tab
+			var button = this.correspondingTabButton || this.selectedTabWidget.tabButton;
+			button.focus();
+			dojo.event.browser.stopEvent(e);
+		}else if(e.keyCode == e.KEY_DELETE && e.altKey){
+			if (this.closeButton == "tab" || this.closeButton == "pane" || this.selectedTabWidget.tabCloseButton){
+				this._runOnCloseTab(this.selectedTabWidget);
+				dojo.event.browser.stopEvent(e);
+			}
+		}
+	},
+
+	destroy: function(){
+		this.tabList.destroy();
+		dojo.widget.TabContainer.superclass.destroy.apply(this, arguments);
+	}
+});
+
+
+// These arguments can be specified for the children of a TabContainer.
+// Since any widget can be specified as a TabContainer 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?
+	tabCloseButton: false
+});
+
+// Tab (the thing you click to select a pane)
+// Contains the title (aka label) of the pane, and optionally a close-button to destroy the pane
+
+dojo.widget.defineWidget("dojo.widget.TabButton", dojo.widget.HtmlWidget,
+{
+	templateString: "<div class='dojoTabPaneTab' dojoAttachEvent='onClick'>"
+						+"<div dojoAttachPoint='innerDiv'>"
+							+"<span dojoAttachPoint='titleNode' tabIndex='-1' waiRole='tab'>${this.label}</span>"
+							+"<span dojoAttachPoint='closeButtonNode' class='dojoTabPaneTabClose dojoTabPaneTabCloseImage' style='${this.closeButtonStyle}'"
+							+"    dojoAttachEvent='onMouseOver:onCloseButtonMouseOver; onMouseOut:onCloseButtonMouseOut; onClick:onCloseButtonClick'></span>"
+						+"</div>"
+					+"</div>",
+
+	// parameters
+	label: "",			// text string for the label
+	tabContainer: "",	// the tab container id
+	pane: null,		// child of the tab container corresponding to this label
+	closable: false,
+
+	postMixInProperties: function(){
+		this.tabContainer = dojo.widget.byId(this.tabContainer);
+		this.closeButtonStyle = this.closable ? "" : "display: none";
+	},
+
+	fillInTemplate: function(){
+		dojo.html.disableSelection(this.titleNode);
+	},
+
+	postCreate: function(){
+		dojo.event.connect(this.pane, "show", this, "onPaneSelect");
+		dojo.event.connect(this.pane, "hide", this, "onPaneDeselect");
+	},
+
+	// Clicking a tab button will select the corresponding pane
+	onClick: function(){
+		this.focus();
+		this.tabContainer.selectTab(this.pane, false, this);
+	},
+
+	// The close button changes color a bit when you mouse over	
+	onCloseButtonMouseOver: function(){
+		dojo.html.addClass(this.closeButtonNode, "dojoTabPaneTabCloseHover");
+	},
+
+	// Revert close button to normal color on mouse out
+	onCloseButtonMouseOut: function(){
+		dojo.html.removeClass(this.closeButtonNode, "dojoTabPaneTabCloseHover");
+	},
+
+	// Handle clicking the close button for this tab
+	onCloseButtonClick: function(evt){
+		this.tabContainer.closePage(this.pane);
+		dojo.event.browser.stopEvent(evt);
+	},
+	
+	// This is run whenever the pane corresponding to this button is selected
+	onPaneSelect: function(){
+		dojo.html.addClass(this.domNode, "current");
+		this.titleNode.setAttribute("tabIndex","0");
+	},
+	
+	// This function is run whenever the pane corresponding to this button is deselected (and another pane is shown)
+	onPaneDeselect: function(){
+		dojo.html.removeClass(this.domNode, "current");
+		this.titleNode.setAttribute("tabIndex","-1");
+	},
+
+	// This function is called when the target pane is destroyed or detached from the TabContainer
+	destroy: function(){
+		dojo.event.disconnect(this.pane, "show", this, "onPaneSelected");
+		dojo.event.disconnect(this.pane, "hide", this, "onPaneDeselected");
+		this.inherited("destroy");
+	},
+	
+	// This will focus on the this tab label (for accessibility you need to do this when the tab is selected)
+	focus: function(){
+		this.titleNode.focus();
+		this.parent._currentTab = this;
+	}
+});
+
+
+// Tab for display in high-contrast mode (where background images don't show up)
+dojo.widget.defineWidget(
+	"dojo.widget.a11y.TabButton",
+	dojo.widget.TabButton,
+	{
+		imgPath: dojo.uri.dojoUri("src/widget/templates/images/tab_close.gif"),
+		
+		templateString: "<div class='dojoTabPaneTab' dojoAttachEvent='onClick;onKey'>"
+							+"<div dojoAttachPoint='innerDiv'>"
+								+"<span dojoAttachPoint='titleNode' tabIndex='-1' waiRole='tab'>${this.label}</span>"
+								+"<img class='dojoTabPaneTabClose' src='${this.imgPath}' alt='[x]' style='${this.closeButtonStyle}'"
+								+"    dojoAttachEvent='onClick:onCloseButtonClick'>"
+							+"</div>"
+						+"</div>"
+	}
+);
+
+
+
+// TabList - set of tabs (the things with labels and a close button, that you click to show a tab panel)
+// When intialized, the TabList monitors the TabContainer, and whenever a pane is
+// added or deleted updates itself accordingly.
+dojo.widget.defineWidget(
+    "dojo.widget.TabList",
+    dojo.widget.HtmlWidget,
+	{
+		templateString: "<div wairole='tablist' class='dojoTabLabels-${this.labelPosition}' dojoAttachEvent='onKey'></div>",
+		isContainer: true,
+		_currentTab: null,
+
+		// parameters
+		labelPosition: "top",
+		doLayout: true,
+		tabContainer: "",			// the id of the tab container that I point to
+
+		fillInTemplate: function() {
+			dojo.widget.wai.setAttr(this.domNode, "waiRole", "role", "tablist");
+			if(!this.doLayout){ dojo.html.addClass(this.domNode, "dojoTabNoLayout"); }
+		},
+
+		postCreate: function(){
+			this.pane2tab = {};		// mapping from panes to tabs
+
+			// If children have already been added to the tab container then create buttons for them
+			var container = dojo.widget.byId(this.tabContainer);
+			if(container){
+				dojo.lang.forEach(container.children, this.addTab, this);
+			}
+
+			dojo.event.topic.subscribe(this.tabContainer+"-addPane", this, "addTab");
+			dojo.event.topic.subscribe(this.tabContainer+"-removePane", this, "removeTab");
+		},
+
+		destroy: function(){
+			dojo.event.topic.unsubscribe(this.tabContainer+"-addPane", this, "addTab");
+			dojo.event.topic.unsubscribe(this.tabContainer+"-removePane", this, "removeTab");
+			this.inherited("destroy");		
+		},
+
+		addTab: function(/* dojo.widget.TabPane */ pane){
+			// summary
+			//   Called whenever a pane is added to the TabContainer
+			//   Create the tab (the thing with the title (aka label) and the close button) corresponding to the pane
+			var tab = dojo.widget.createWidget("TabButton", 
+				{
+					tabContainer: this.tabContainer,
+					pane: pane,
+					label: pane.label,
+					closable: pane.tabCloseButton
+				});
+			this.addChild(tab);
+			this.domNode.appendChild(tab.domNode);
+			this.pane2tab[pane]=tab;
+			pane.tabButton = tab;	// this value might be overwritten if two tabs point to same TabContainer
+		},
+
+		removeTab: function(/* dojo.widget.TabPane */ pane){
+			// summary
+			//   Called whenever a pane is removed from the TabContainer
+			//   Remove the tab (the thing with the title (aka label) and the close button) corresponding to the pane
+			this.pane2tab[pane].destroy();
+			this.pane2tab[pane] = null;
+		},
+
+		onKey: function(evt){
+			// summary:
+			//   Handle keystrokes on the tablist, for advancing to next/previous tab
+
+			if( (evt.keyCode == evt.KEY_RIGHT_ARROW)||
+				(evt.keyCode == evt.KEY_LEFT_ARROW) ){
+				var current = 0;
+				var next = null;	// the next tab to focus on
+				
+				// find currently focused tab in children array
+				for(var i=0; i < this.children.length; i++){
+					if(this.children[i] == this._currentTab){
+						current = i; 
+						break;
+					}
+				}
+				
+				// pick next tab 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();
+			}
+		}
+	}
+);

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TabContainer.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TaskBar.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TaskBar.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TaskBar.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TaskBar.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,79 @@
+/*
+	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.TaskBar");
+dojo.provide("dojo.widget.TaskBarItem");
+
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.FloatingPane");
+dojo.require("dojo.widget.HtmlWidget");
+dojo.require("dojo.event.*");
+dojo.require("dojo.html.selection");
+
+// Icon associated w/a floating pane
+dojo.widget.defineWidget(
+	"dojo.widget.TaskBarItem",
+	dojo.widget.HtmlWidget,
+{
+	// constructor arguments
+	iconSrc: '',
+	caption: 'Untitled',
+	window: null,
+	templatePath: dojo.uri.dojoUri("src/widget/templates/TaskBarItemTemplate.html"),
+	templateCssPath: dojo.uri.dojoUri("src/widget/templates/TaskBar.css"),
+
+	fillInTemplate: function() {
+		if ( this.iconSrc != '' ) {
+			var img = document.createElement("img");
+			img.src = this.iconSrc;
+			this.domNode.appendChild(img);
+		}
+		this.domNode.appendChild(document.createTextNode(this.caption));
+		dojo.html.disableSelection(this.domNode);
+	},
+
+	postCreate: function() {
+		this.window=dojo.widget.getWidgetById(this.windowId);
+		this.window.explodeSrc = this.domNode;
+		dojo.event.connect(this.window, "destroy", this, "destroy")
+	},
+
+	onClick: function() {
+		this.window.toggleDisplay();
+	}
+});
+
+// Collection of widgets in a bar, like Windows task bar
+dojo.widget.defineWidget(
+	"dojo.widget.TaskBar",
+	dojo.widget.FloatingPane,
+	function(){
+		this._addChildStack = [];
+	},
+{
+	resizable: false,
+	titleBarDisplay: "none",
+
+	addChild: function(child) {
+		if(!this.containerNode){ 
+			this._addChildStack.push(child);
+		}else if(this._addChildStack.length > 0){
+			var oarr = this._addChildStack;
+			this._addChildStack = [];
+			dojo.lang.forEach(oarr, this.addChild, this);
+		}
+		var tbi = dojo.widget.createWidget("TaskBarItem",
+			{	windowId: child.widgetId, 
+				caption: child.title, 
+				iconSrc: child.iconSrc
+			});
+		dojo.widget.TaskBar.superclass.addChild.call(this,tbi);
+	}
+});

Propchange: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TaskBar.js
------------------------------------------------------------------------------
    svn:eol-style = native

Added: tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TimePicker.js
URL: http://svn.apache.org/viewvc/tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TimePicker.js?view=auto&rev=449122
==============================================================================
--- tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TimePicker.js (added)
+++ tapestry/tapestry4/trunk/tapestry-framework/src/js/dojo/src/widget/TimePicker.js Fri Sep 22 16:22:30 2006
@@ -0,0 +1,341 @@
+/*
+	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.TimePicker");
+dojo.require("dojo.widget.*");
+dojo.require("dojo.widget.HtmlWidget");
+dojo.require("dojo.event.*");
+dojo.require("dojo.date.format");
+dojo.require("dojo.dom");
+dojo.require("dojo.html.style");
+
+dojo.requireLocalization("dojo.i18n.calendar", "gregorian");
+dojo.requireLocalization("dojo.widget", "TimePicker");
+
+dojo.widget.defineWidget(
+	"dojo.widget.TimePicker",
+	dojo.widget.HtmlWidget,
+	function(){
+		// selected time, JS Date object
+		this.time = "";
+		// set following flag to true if a default time should be set
+		this.useDefaultTime = false;
+		// set the following to true to set default minutes to current time, false to // use zero
+		this.useDefaultMinutes = false;
+		// rfc 3339 date
+		this.storedTime = "";
+		// time currently selected in the UI, stored in hours, minutes, seconds in the format that will be actually displayed
+		this.currentTime = {};
+		this.classNames = {
+			selectedTime: "selectedItem"
+		};
+		this.any = "any"; //FIXME: never used?
+		// dom node indecies for selected hour, minute, amPm, and "any time option"
+		this.selectedTime = {
+			hour: "",
+			minute: "",
+			amPm: "",
+			anyTime: false
+		};
+
+		// minutes are ordered as follows: ["12", "6", "1", "7", "2", "8", "3", "9", "4", "10", "5", "11"]
+		this.hourIndexMap = ["", 2, 4, 6, 8, 10, 1, 3, 5, 7, 9, 11, 0];
+		// minutes are ordered as follows: ["00", "30", "05", "35", "10", "40", "15", "45", "20", "50", "25", "55"]
+		this.minuteIndexMap = [0, 2, 4, 6, 8, 10, 1, 3, 5, 7, 9, 11];
+	},
+{
+	isContainer: false,
+	templatePath: dojo.uri.dojoUri("src/widget/templates/TimePicker.html"),
+	templateCssPath: dojo.uri.dojoUri("src/widget/templates/TimePicker.css"),
+
+	fillInTemplate: function(args, frag){
+		// Copy style info from input node to output node
+		var source = this.getFragNodeRef(frag);
+		dojo.html.copyStyle(this.domNode, source);
+
+		this.initData();
+		this.initUI();
+	},
+
+	postMixInProperties: function(localProperties, frag) {
+		dojo.widget.TimePicker.superclass.postMixInProperties.apply(this, arguments);
+		this.calendar = dojo.i18n.getLocalization("dojo.i18n.calendar", "gregorian", this.lang); // "am","pm"
+		this.widgetStrings = dojo.i18n.getLocalization("dojo.widget", "TimePicker", this.lang); // "any"
+	},
+
+	initData: function() {
+		// FIXME: doesn't currently validate the time before trying to set it
+		// Determine the date/time from stored info, or by default don't 
+		//  have a set time
+		// FIXME: should normalize against whitespace on storedTime... for now 
+		// just a lame hack
+		if(this.storedTime.indexOf("T")!=-1 && this.storedTime.split("T")[1] && this.storedTime!=" " && this.storedTime.split("T")[1]!="any") {
+			this.time = dojo.widget.TimePicker.util.fromRfcDateTime(this.storedTime, this.useDefaultMinutes, this.selectedTime.anyTime);
+		} else if (this.useDefaultTime) {
+			this.time = dojo.widget.TimePicker.util.fromRfcDateTime("", this.useDefaultMinutes, this.selectedTime.anyTime);
+		} else {
+			this.selectedTime.anyTime = true;
+			this.time = dojo.widget.TimePicker.util.fromRfcDateTime("", 0, 1);
+		}
+	},
+
+	initUI: function() {
+		// set UI to match the currently selected time
+		if(!this.selectedTime.anyTime && this.time) {
+			var amPmHour = dojo.widget.TimePicker.util.toAmPmHour(this.time.getHours());
+			var hour = amPmHour[0];
+			var isAm = amPmHour[1];
+			var minute = this.time.getMinutes();
+			var minuteIndex = parseInt(minute/5);
+			this.onSetSelectedHour(this.hourIndexMap[hour]);
+			this.onSetSelectedMinute(this.minuteIndexMap[minuteIndex]);
+			this.onSetSelectedAmPm(isAm);
+		} else {
+			this.onSetSelectedAnyTime();
+		}
+	},
+	
+	setTime: function(date) {
+		if(date) {
+			this.selectedTime.anyTime = false;
+			this.setDateTime(dojo.date.toRfc3339(date));
+		} else {
+			this.selectedTime.anyTime = true;
+		}
+		this.initData();
+		this.initUI();
+	},
+
+	setDateTime: function(rfcDate) {
+		this.storedTime = rfcDate;
+	},
+	
+	onClearSelectedHour: function(evt) {
+		this.clearSelectedHour();
+	},
+
+	onClearSelectedMinute: function(evt) {
+		this.clearSelectedMinute();
+	},
+
+	onClearSelectedAmPm: function(evt) {
+		this.clearSelectedAmPm();
+	},
+
+	onClearSelectedAnyTime: function(evt) {
+		this.clearSelectedAnyTime();
+		if(this.selectedTime.anyTime) {
+			this.selectedTime.anyTime = false;
+			this.time = dojo.widget.TimePicker.util.fromRfcDateTime("", this.useDefaultMinutes);
+			this.initUI();
+		}
+	},
+
+	clearSelectedHour: function() {
+		var hourNodes = this.hourContainerNode.getElementsByTagName("td");
+		for (var i=0; i<hourNodes.length; i++) {
+			dojo.html.setClass(hourNodes.item(i), "");
+		}
+	},
+
+	clearSelectedMinute: function() {
+		var minuteNodes = this.minuteContainerNode.getElementsByTagName("td");
+		for (var i=0; i<minuteNodes.length; i++) {
+			dojo.html.setClass(minuteNodes.item(i), "");
+		}
+	},
+
+	clearSelectedAmPm: function() {
+		var amPmNodes = this.amPmContainerNode.getElementsByTagName("td");
+		for (var i=0; i<amPmNodes.length; i++) {
+			dojo.html.setClass(amPmNodes.item(i), "");
+		}
+	},
+
+	clearSelectedAnyTime: function() {
+		dojo.html.setClass(this.anyTimeContainerNode, "anyTimeContainer");
+	},
+
+	onSetSelectedHour: function(evt) {
+		this.onClearSelectedAnyTime();
+		this.onClearSelectedHour();
+		this.setSelectedHour(evt);
+		this.onSetTime();
+	},
+
+	setSelectedHour: function(evt) {
+		if(evt && evt.target) {
+			if(evt.target.nodeType == dojo.dom.ELEMENT_NODE) {
+				var eventTarget = evt.target;
+			} else {
+				var eventTarget = evt.target.parentNode;
+			}
+			dojo.event.browser.stopEvent(evt);
+			dojo.html.setClass(eventTarget, this.classNames.selectedTime);
+			this.selectedTime["hour"] = eventTarget.innerHTML;
+		} else if (!isNaN(evt)) {
+			var hourNodes = this.hourContainerNode.getElementsByTagName("td");
+			if(hourNodes.item(evt)) {
+				dojo.html.setClass(hourNodes.item(evt), this.classNames.selectedTime);
+				this.selectedTime["hour"] = hourNodes.item(evt).innerHTML;
+			}
+		}
+		this.selectedTime.anyTime = false;
+	},
+
+	onSetSelectedMinute: function(evt) {
+		this.onClearSelectedAnyTime();
+		this.onClearSelectedMinute();
+		this.setSelectedMinute(evt);
+		this.selectedTime.anyTime = false;
+		this.onSetTime();
+	},
+
+	setSelectedMinute: function(evt) {
+		if(evt && evt.target) {
+			if(evt.target.nodeType == dojo.dom.ELEMENT_NODE) {
+				var eventTarget = evt.target;
+			} else {
+				var eventTarget = evt.target.parentNode;
+			}
+			dojo.event.browser.stopEvent(evt);
+			dojo.html.setClass(eventTarget, this.classNames.selectedTime);
+			this.selectedTime["minute"] = eventTarget.innerHTML;
+		} else if (!isNaN(evt)) {
+			var minuteNodes = this.minuteContainerNode.getElementsByTagName("td");
+			if(minuteNodes.item(evt)) {
+				dojo.html.setClass(minuteNodes.item(evt), this.classNames.selectedTime);
+				this.selectedTime["minute"] = minuteNodes.item(evt).innerHTML;
+			}
+		}
+	},
+
+	onSetSelectedAmPm: function(evt) {
+		this.onClearSelectedAnyTime();
+		this.onClearSelectedAmPm();
+		this.setSelectedAmPm(evt);
+		this.selectedTime.anyTime = false;
+		this.onSetTime();
+	},
+
+	setSelectedAmPm: function(evt) {
+		var eventTarget = evt.target;
+		if(evt && eventTarget) {
+			if(eventTarget.nodeType != dojo.dom.ELEMENT_NODE) {
+				eventTarget = eventTarget.parentNode;
+			}
+			dojo.event.browser.stopEvent(evt);
+			this.selectedTime.amPm = eventTarget.id;
+			dojo.html.setClass(eventTarget, this.classNames.selectedTime);
+		} else {
+			evt = evt ? 0 : 1;
+			var amPmNodes = this.amPmContainerNode.getElementsByTagName("td");
+			if(amPmNodes.item(evt)) {
+				this.selectedTime.amPm = amPmNodes.item(evt).id;
+				dojo.html.setClass(amPmNodes.item(evt), this.classNames.selectedTime);
+			}
+		}
+	},
+
+	onSetSelectedAnyTime: function(evt) {
+		this.onClearSelectedHour();
+		this.onClearSelectedMinute();
+		this.onClearSelectedAmPm();
+		this.setSelectedAnyTime();
+		this.onSetTime();
+	},
+
+	setSelectedAnyTime: function(evt) {
+		this.selectedTime.anyTime = true;
+		dojo.html.setClass(this.anyTimeContainerNode, this.classNames.selectedTime + " " + "anyTimeContainer");
+	},
+
+	onClick: function(evt) {
+		dojo.event.browser.stopEvent(evt);
+	},
+
+	onSetTime: function() {
+		if(this.selectedTime.anyTime) {
+			this.time = new Date();
+			var tempDateTime = dojo.widget.TimePicker.util.toRfcDateTime(this.time);
+			this.setDateTime(tempDateTime.split("T")[0]);
+		} else {
+			var hour = 12;
+			var minute = 0;
+			var isAm = false;
+			if(this.selectedTime["hour"]) {
+				hour = parseInt(this.selectedTime["hour"], 10);
+			}
+			if(this.selectedTime["minute"]) {
+				minute = parseInt(this.selectedTime["minute"], 10);
+			}
+			if(this.selectedTime["amPm"]) {
+				isAm = (this.selectedTime["amPm"].toLowerCase() == "am");
+			}
+			this.time = new Date();
+			this.time.setHours(dojo.widget.TimePicker.util.fromAmPmHour(hour, isAm));
+			this.time.setMinutes(minute);
+			this.setDateTime(dojo.widget.TimePicker.util.toRfcDateTime(this.time));
+		}
+	}
+});
+
+dojo.widget.TimePicker.util = new function() {
+	// utility functions
+	this.toRfcDateTime = function(jsDate) {
+		if(!jsDate) {
+			jsDate = new Date();
+		}
+		jsDate.setSeconds(0);
+		return dojo.date.strftime(jsDate, "%Y-%m-%dT%H:%M:00%z"); //FIXME: use dojo.date.toRfc3339 instead
+	}
+
+	this.fromRfcDateTime = function(rfcDate, useDefaultMinutes, isAnyTime) {
+		var tempDate = new Date();
+		if(!rfcDate || rfcDate.indexOf("T")==-1) {
+			if(useDefaultMinutes) {
+				tempDate.setMinutes(Math.floor(tempDate.getMinutes()/5)*5);
+			} else {
+				tempDate.setMinutes(0);
+			}
+		} else {
+			var tempTime = rfcDate.split("T")[1].split(":");
+			// fullYear, month, date
+			var tempDate = new Date();
+			tempDate.setHours(tempTime[0]);
+			tempDate.setMinutes(tempTime[1]);
+		}
+		return tempDate;
+	}
+
+	this.toAmPmHour = function(hour) {
+		var amPmHour = hour;
+		var isAm = true;
+		if (amPmHour == 0) {
+			amPmHour = 12;
+		} else if (amPmHour>12) {
+			amPmHour = amPmHour - 12;
+			isAm = false;
+		} else if (amPmHour == 12) {
+			isAm = false;
+		}
+		return [amPmHour, isAm];
+	}
+
+	this.fromAmPmHour = function(amPmHour, isAm) {
+		var hour = parseInt(amPmHour, 10);
+		if(isAm && hour == 12) {
+			hour = 0;
+		} else if (!isAm && hour<12) {
+			hour = hour + 12;
+		}
+		return hour;
+	}
+}