[Pkg-javascript-commits] [dojo] 05/10: Imported Upstream version 1.10.3+dfsg

David Prévot taffit at moszumanska.debian.org
Tue Dec 9 02:14:33 UTC 2014


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository dojo.

commit 3b404493d8673c1e814310324a89d7c07d1582ad
Merge: 6ac4b73 6a8b1f2
Author: David Prévot <taffit at debian.org>
Date:   Mon Dec 8 21:17:32 2014 -0400

    Imported Upstream version 1.10.3+dfsg

 dijit/ConfirmDialog.js                             |   2 +-
 dijit/ConfirmTooltipDialog.js                      |   2 +-
 dijit/ProgressBar.js                               |   5 +-
 dijit/_ConfirmDialogMixin.js                       |   2 +-
 dijit/_WidgetBase.js                               |   2 +-
 dijit/_editor/plugins/FontChoice.js                |   2 +-
 dijit/form/ComboBoxMixin.js                        |   6 +
 dijit/form/NumberTextBox.js                        |  23 +++-
 dijit/form/_DateTimeTextBox.js                     |  30 ++++-
 dijit/package.json                                 |   4 +-
 .../tests/_BidiSupport/form/test_TimeTextBox.html  |  11 +-
 dijit/tests/form/_autoComplete.html                |  33 +++++
 dijit/tests/form/robot/ValidationTextBox.html      |  33 +++++
 dijit/tests/form/robot/_autoComplete_a11y.html     |  21 +++
 dijit/tests/form/test_DateTextBox.html             | 142 +++++++++++----------
 dijit/tests/form/test_validate.html                |  23 +++-
 dijit/themes/tundra/Menu.css                       |   6 +
 dojo/_base/kernel.js                               |   4 +-
 dojo/package.json                                  |   2 +-
 dojox/calendar/CONTRIBUTING.md                     |   0
 dojox/calendar/LICENSE                             |   0
 dojox/calendar/nls/bg/buttons.js                   |   0
 dojox/calendar/nls/he/buttons.js                   |   0
 dojox/calendar/nls/hr/buttons.js                   |   0
 dojox/calendar/nls/uk/buttons.js                   |   0
 dojox/calendar/tests/hcalendar.html                |   0
 dojox/dgauges/CONTRIBUTING.md                      |   0
 dojox/dojox.profile.js                             |   2 -
 dojox/embed/Flash.js                               |  47 +++----
 .../enhanced/plugins/filter/FilterDefDialog.js     |   7 +-
 dojox/layout/ContentPane.js                        |   2 +-
 dojox/layout/ResizeHandle.js                       |  15 ++-
 dojox/mobile/ComboBox.js                           |   2 +-
 dojox/package.json                                 |   6 +-
 dojox/socket.js                                    | 115 +++++++++--------
 dojox/socket/Reconnect.js                          | 109 ++++++++--------
 dojox/storage/Storage.swf                          | Bin 0 -> 3325 bytes
 util/build/transforms/optimizer/uglify.js          |  15 ++-
 util/build/transforms/optimizer/uglify_worker.js   |  29 ++++-
 util/build/version.js                              |   4 +-
 util/doh/_nodeRunner.js                            |   2 +-
 util/doh/_rhinoRunner.js                           |   2 +-
 util/doh/mobileRunner.html                         |   2 +-
 util/doh/package.json                              |   2 +-
 util/doh/runner.html                               |   2 +-
 util/package.json                                  |   2 +-
 46 files changed, 463 insertions(+), 255 deletions(-)

diff --cc dijit/ConfirmDialog.js
index 84a9299,0000000..468634b
mode 100644,000000..100644
--- a/dijit/ConfirmDialog.js
+++ b/dijit/ConfirmDialog.js
@@@ -1,11 -1,0 +1,11 @@@
 +define([
- 	"../dojo/_base/declare",
++	"dojo/_base/declare",
 +	"./Dialog",
 +	"./_ConfirmDialogMixin"
 +], function(declare, Dialog, _ConfirmDialogMixin) {
 +
 +	return declare("dijit/ConfirmDialog", [Dialog, _ConfirmDialogMixin], {
 +		// summary:
 +		//		A Dialog with OK/Cancel buttons.
 +	});
 +});
diff --cc dijit/ConfirmTooltipDialog.js
index abf61b0,0000000..a217235
mode 100644,000000..100644
--- a/dijit/ConfirmTooltipDialog.js
+++ b/dijit/ConfirmTooltipDialog.js
@@@ -1,11 -1,0 +1,11 @@@
 +define([
- 	"../dojo/_base/declare",
++	"dojo/_base/declare",
 +	"./TooltipDialog",
 +	"./_ConfirmDialogMixin"
 +], function(declare, TooltipDialog, _ConfirmDialogMixin) {
 +	
 +	return declare("dijit/ConfirmTooltipDialog", [TooltipDialog, _ConfirmDialogMixin], {
 +		// summary:
 +		//		A TooltipDialog with OK/Cancel buttons.
 +	});
 +});
diff --cc dijit/ProgressBar.js
index 93ca28c,0000000..c0e649f
mode 100644,000000..100644
--- a/dijit/ProgressBar.js
+++ b/dijit/ProgressBar.js
@@@ -1,169 -1,0 +1,170 @@@
 +define([
 +	"require", // require.toUrl
 +	"dojo/_base/declare", // declare
 +	"dojo/dom-class", // domClass.toggle
 +	"dojo/_base/lang", // lang.mixin
 +	"dojo/number", // number.format
 +	"./_Widget",
 +	"./_TemplatedMixin",
 +	"dojo/text!./templates/ProgressBar.html"
 +], function(require, declare, domClass, lang, number, _Widget, _TemplatedMixin, template){
 +
 +	// module:
 +	//		dijit/ProgressBar
 +
 +	return declare("dijit.ProgressBar", [_Widget, _TemplatedMixin], {
 +		// summary:
 +		//		A progress indication widget, showing the amount completed
 +		//		(often the percentage completed) of a task.
 +
 +		// progress: [const] String (Percentage or Number)
 +		//		Number or percentage indicating amount of task completed.
 +		//		Deprecated.   Use "value" instead.
 +		progress: "0",
 +
 +		// value: String (Percentage or Number)
 +		//		Number or percentage indicating amount of task completed.
 +		//		With "%": percentage value, 0% <= progress <= 100%, or
 +		//		without "%": absolute value, 0 <= progress <= maximum.
 +		//		Infinity means that the progress bar is indeterminate.
 +		value: "",
 +
 +		// maximum: [const] Float
 +		//		Max sample number
 +		maximum: 100,
 +
 +		// places: [const] Number
 +		//		Number of places to show in values; 0 by default
 +		places: 0,
 +
 +		// indeterminate: [const] Boolean
 +		//		If false: show progress value (number or percentage).
 +		//		If true: show that a process is underway but that the amount completed is unknown.
 +		//		Deprecated.   Use "value" instead.
 +		indeterminate: false,
 +
 +		// label: String?
 +		//		HTML label on progress bar.   Defaults to percentage for determinate progress bar and
 +		//		blank for indeterminate progress bar.
 +		label: "",
 +
 +		// name: String
 +		//		this is the field name (for a form) if set. This needs to be set if you want to use
 +		//		this widget in a dijit/form/Form widget (such as dijit/Dialog)
 +		name: '',
 +
 +		templateString: template,
 +
 +		// _indeterminateHighContrastImagePath: [private] URL
 +		//		URL to image to use for indeterminate progress bar when display is in high contrast mode
 +		_indeterminateHighContrastImagePath: require.toUrl("./themes/a11y/indeterminate_progress.gif"),
 +
 +		postMixInProperties: function(){
 +			this.inherited(arguments);
 +
 +			// Back-compat for when constructor specifies indeterminate or progress, rather than value.   Remove for 2.0.
 +			if(!(this.params && "value" in this.params)){
 +				this.value = this.indeterminate ? Infinity : this.progress;
 +			}
 +		},
 +
 +		buildRendering: function(){
 +			this.inherited(arguments);
 +			this.indeterminateHighContrastImage.setAttribute("src",
 +				this._indeterminateHighContrastImagePath.toString());
 +			this.update();
 +		},
 +
 +		_setDirAttr: function(val){
 +			// Normally _CssStateMixin takes care of this, but we aren't extending it
- 			domClass.toggle(this.domNode, "dijitProgressBarRtl", val == "rtl");
- 			domClass.toggle(this.domNode, "dijitProgressBarIndeterminateRtl", this.indeterminate && val == "rtl");
++			var rtl = val.toLowerCase() == "rtl";
++			domClass.toggle(this.domNode, "dijitProgressBarRtl", rtl);
++			domClass.toggle(this.domNode, "dijitProgressBarIndeterminateRtl", this.indeterminate && rtl);
 +			this.inherited(arguments);
 +		},
 +
 +		update: function(/*Object?*/attributes){
 +			// summary:
 +			//		Internal method to change attributes of ProgressBar, similar to set(hash).  Users should call
 +			//		set("value", ...) rather than calling this method directly.
 +			// attributes:
 +			//		May provide progress and/or maximum properties on this parameter;
 +			//		see attribute specs for details.
 +			// example:
 +			//	|	myProgressBar.update({'indeterminate': true});
 +			//	|	myProgressBar.update({'progress': 80});
 +			//	|	myProgressBar.update({'indeterminate': true, label:"Loading ..." })
 +			// tags:
 +			//		private
 +
 +			// TODO: deprecate this method and use set() instead
 +
 +			lang.mixin(this, attributes || {});
 +			var tip = this.internalProgress, ap = this.domNode;
 +			var percent = 1;
 +			if(this.indeterminate){
 +				ap.removeAttribute("aria-valuenow");
 +			}else{
 +				if(String(this.progress).indexOf("%") != -1){
 +					percent = Math.min(parseFloat(this.progress) / 100, 1);
 +					this.progress = percent * this.maximum;
 +				}else{
 +					this.progress = Math.min(this.progress, this.maximum);
 +					percent = this.maximum ? this.progress / this.maximum : 0;
 +				}
 +				ap.setAttribute("aria-valuenow", this.progress);
 +			}
 +
 +			// Even indeterminate ProgressBars should have these attributes
 +			ap.setAttribute("aria-labelledby", this.labelNode.id);
 +			ap.setAttribute("aria-valuemin", 0);
 +			ap.setAttribute("aria-valuemax", this.maximum);
 +
 +			this.labelNode.innerHTML = this.report(percent);
 +
 +			domClass.toggle(this.domNode, "dijitProgressBarIndeterminate", this.indeterminate);
 +			domClass.toggle(this.domNode, "dijitProgressBarIndeterminateRtl", this.indeterminate && !this.isLeftToRight());
 +
 +			tip.style.width = (percent * 100) + "%";
 +			this.onChange();
 +		},
 +
 +		_setValueAttr: function(v){
 +			this._set("value", v);
 +			if(v == Infinity){
 +				this.update({indeterminate: true});
 +			}else{
 +				this.update({indeterminate: false, progress: v});
 +			}
 +		},
 +
 +		_setLabelAttr: function(label){
 +			this._set("label", label);
 +			this.update();
 +		},
 +
 +		_setIndeterminateAttr: function(indeterminate){
 +			// Deprecated, use set("value", ...) instead
 +			this._set("indeterminate", indeterminate);
 +			this.update();
 +		},
 +
 +		report: function(/*float*/percent){
 +			// summary:
 +			//		Generates HTML message to show inside progress bar (normally indicating amount of task completed).
 +			//		May be overridden.
 +			// tags:
 +			//		extension
 +
 +			return this.label ? this.label :
 +				(this.indeterminate ? " " : number.format(percent, { type: "percent", places: this.places, locale: this.lang }));
 +		},
 +
 +		onChange: function(){
 +			// summary:
 +			//		Callback fired when progress updates.
 +			// tags:
 +			//		extension
 +		}
 +	});
 +});
diff --cc dijit/_ConfirmDialogMixin.js
index 6ae67c9,0000000..ecd9146
mode 100644,000000..100644
--- a/dijit/_ConfirmDialogMixin.js
+++ b/dijit/_ConfirmDialogMixin.js
@@@ -1,26 -1,0 +1,26 @@@
 +define([
- 	"../dojo/_base/declare",
++	"dojo/_base/declare",
 +	"./_WidgetsInTemplateMixin",
 +	"dojo/i18n!./nls/common",
 +	"dojo/text!./templates/actionBar.html",
 +	"./form/Button"		// used by template
 +], function(declare, _WidgetsInTemplateMixin, strings, actionBarMarkup) {
 +
 +	return declare("dijit/_ConfirmDialogMixin", _WidgetsInTemplateMixin, {
 +		// summary:
 +		//		Mixin for Dialog/TooltipDialog with OK/Cancel buttons.
 +
 +		// HTML snippet for action bar, overrides _DialogMixin.actionBarTemplate
 +		actionBarTemplate: actionBarMarkup,
 +
 +		// buttonOk: String
 +		//		Label of OK button
 +		buttonOk: strings.buttonOk,
 +		_setButtonOkAttr: { node: "okButton", attribute: "label" },
 +
 +		// buttonCancel: String
 +		//		Label of cancel button
 +		buttonCancel: strings.buttonCancel,
 +		_setButtonCancelAttr: { node: "cancelButton", attribute: "label" }
 +	});
 +});
diff --cc dijit/_WidgetBase.js
index 9a52b07,0000000..b84e203
mode 100644,000000..100644
--- a/dijit/_WidgetBase.js
+++ b/dijit/_WidgetBase.js
@@@ -1,1200 -1,0 +1,1200 @@@
 +define([
 +	"require", // require.toUrl
 +	"dojo/_base/array", // array.forEach array.map
 +	"dojo/aspect",
 +	"dojo/_base/config", // config.blankGif
 +	"dojo/_base/connect", // connect.connect
 +	"dojo/_base/declare", // declare
 +	"dojo/dom", // dom.byId
 +	"dojo/dom-attr", // domAttr.set domAttr.remove
 +	"dojo/dom-class", // domClass.add domClass.replace
 +	"dojo/dom-construct", // domConstruct.destroy domConstruct.place
 +	"dojo/dom-geometry", // isBodyLtr
 +	"dojo/dom-style", // domStyle.set, domStyle.get
 +	"dojo/has",
 +	"dojo/_base/kernel",
 +	"dojo/_base/lang", // mixin(), isArray(), etc.
 +	"dojo/on",
 +	"dojo/ready",
 +	"dojo/Stateful", // Stateful
 +	"dojo/topic",
 +	"dojo/_base/window", // win.body()
 +	"./Destroyable",
 +	"dojo/has!dojo-bidi?./_BidiMixin",
 +	"./registry"    // registry.getUniqueId(), registry.findWidgets()
 +], function(require, array, aspect, config, connect, declare,
 +			dom, domAttr, domClass, domConstruct, domGeometry, domStyle, has, kernel,
 +			lang, on, ready, Stateful, topic, win, Destroyable, _BidiMixin, registry){
 +
 +	// module:
 +	//		dijit/_WidgetBase
 +
 +	// Flag to make dijit load modules the app didn't explicitly request, for backwards compatibility
 +	has.add("dijit-legacy-requires", !kernel.isAsync);
 +
 +	// Flag to enable support for textdir attribute
 +	has.add("dojo-bidi", false);
 +
 +
 +	// For back-compat, remove in 2.0.
 +	if(has("dijit-legacy-requires")){
 +		ready(0, function(){
 +			var requires = ["dijit/_base/manager"];
 +			require(requires);	// use indirection so modules not rolled into a build
 +		});
 +	}
 +
 +	// Nested hash listing attributes for each tag, all strings in lowercase.
 +	// ex: {"div": {"style": true, "tabindex" true}, "form": { ...
 +	var tagAttrs = {};
 +
 +	function getAttrs(obj){
 +		var ret = {};
 +		for(var attr in obj){
 +			ret[attr.toLowerCase()] = true;
 +		}
 +		return ret;
 +	}
 +
 +	function nonEmptyAttrToDom(attr){
 +		// summary:
 +		//		Returns a setter function that copies the attribute to this.domNode,
 +		//		or removes the attribute from this.domNode, depending on whether the
 +		//		value is defined or not.
 +		return function(val){
 +			domAttr[val ? "set" : "remove"](this.domNode, attr, val);
 +			this._set(attr, val);
 +		};
 +	}
 +
 +	function isEqual(a, b){
 +		//	summary:
 +		//		Function that determines whether two values are identical,
 +		//		taking into account that NaN is not normally equal to itself
 +		//		in JS.
 +
 +		return a === b || (/* a is NaN */ a !== a && /* b is NaN */ b !== b);
 +	}
 +
 +	var _WidgetBase = declare("dijit._WidgetBase", [Stateful, Destroyable], {
 +		// summary:
 +		//		Future base class for all Dijit widgets.
 +		// description:
 +		//		Future base class for all Dijit widgets.
 +		//		_Widget extends this class adding support for various features needed by desktop.
 +		//
 +		//		Provides stubs for widget lifecycle methods for subclasses to extend, like postMixInProperties(), buildRendering(),
 +		//		postCreate(), startup(), and destroy(), and also public API methods like set(), get(), and watch().
 +		//
 +		//		Widgets can provide custom setters/getters for widget attributes, which are called automatically by set(name, value).
 +		//		For an attribute XXX, define methods _setXXXAttr() and/or _getXXXAttr().
 +		//
 +		//		_setXXXAttr can also be a string/hash/array mapping from a widget attribute XXX to the widget's DOMNodes:
 +		//
 +		//		- DOM node attribute
 +		// |		_setFocusAttr: {node: "focusNode", type: "attribute"}
 +		// |		_setFocusAttr: "focusNode"	(shorthand)
 +		// |		_setFocusAttr: ""		(shorthand, maps to this.domNode)
 +		//		Maps this.focus to this.focusNode.focus, or (last example) this.domNode.focus
 +		//
 +		//		- DOM node innerHTML
 +		//	|		_setTitleAttr: { node: "titleNode", type: "innerHTML" }
 +		//		Maps this.title to this.titleNode.innerHTML
 +		//
 +		//		- DOM node innerText
 +		//	|		_setTitleAttr: { node: "titleNode", type: "innerText" }
 +		//		Maps this.title to this.titleNode.innerText
 +		//
 +		//		- DOM node CSS class
 +		// |		_setMyClassAttr: { node: "domNode", type: "class" }
 +		//		Maps this.myClass to this.domNode.className
 +		//
 +		//		If the value of _setXXXAttr is an array, then each element in the array matches one of the
 +		//		formats of the above list.
 +		//
 +		//		If the custom setter is null, no action is performed other than saving the new value
 +		//		in the widget (in this).
 +		//
 +		//		If no custom setter is defined for an attribute, then it will be copied
 +		//		to this.focusNode (if the widget defines a focusNode), or this.domNode otherwise.
 +		//		That's only done though for attributes that match DOMNode attributes (title,
 +		//		alt, aria-labelledby, etc.)
 +
 +		// id: [const] String
 +		//		A unique, opaque ID string that can be assigned by users or by the
 +		//		system. If the developer passes an ID which is known not to be
 +		//		unique, the specified ID is ignored and the system-generated ID is
 +		//		used instead.
 +		id: "",
 +		_setIdAttr: "domNode", // to copy to this.domNode even for auto-generated id's
 +
 +		// lang: [const] String
 +		//		Rarely used.  Overrides the default Dojo locale used to render this widget,
 +		//		as defined by the [HTML LANG](http://www.w3.org/TR/html401/struct/dirlang.html#adef-lang) attribute.
 +		//		Value must be among the list of locales specified during by the Dojo bootstrap,
 +		//		formatted according to [RFC 3066](http://www.ietf.org/rfc/rfc3066.txt) (like en-us).
 +		lang: "",
 +		// set on domNode even when there's a focus node.	but don't set lang="", since that's invalid.
 +		_setLangAttr: nonEmptyAttrToDom("lang"),
 +
 +		// dir: [const] String
 +		//		Bi-directional support, as defined by the [HTML DIR](http://www.w3.org/TR/html401/struct/dirlang.html#adef-dir)
 +		//		attribute. Either left-to-right "ltr" or right-to-left "rtl".  If undefined, widgets renders in page's
 +		//		default direction.
 +		dir: "",
 +		// set on domNode even when there's a focus node.	but don't set dir="", since that's invalid.
 +		_setDirAttr: nonEmptyAttrToDom("dir"), // to set on domNode even when there's a focus node
 +
 +		// class: String
 +		//		HTML class attribute
 +		"class": "",
 +		_setClassAttr: { node: "domNode", type: "class" },
 +
 +		// Override automatic assigning type --> focusNode, it causes exception on IE6-8.
 +		// Instead, type must be specified as ${type} in the template, as part of the original DOM.
 +		_setTypeAttr: null,
 +
 +		// style: String||Object
 +		//		HTML style attributes as cssText string or name/value hash
 +		style: "",
 +
 +		// title: String
 +		//		HTML title attribute.
 +		//
 +		//		For form widgets this specifies a tooltip to display when hovering over
 +		//		the widget (just like the native HTML title attribute).
 +		//
 +		//		For TitlePane or for when this widget is a child of a TabContainer, AccordionContainer,
 +		//		etc., it's used to specify the tab label, accordion pane title, etc.  In this case it's
 +		//		interpreted as HTML.
 +		title: "",
 +
 +		// tooltip: String
 +		//		When this widget's title attribute is used to for a tab label, accordion pane title, etc.,
 +		//		this specifies the tooltip to appear when the mouse is hovered over that text.
 +		tooltip: "",
 +
 +		// baseClass: [protected] String
 +		//		Root CSS class of the widget (ex: dijitTextBox), used to construct CSS classes to indicate
 +		//		widget state.
 +		baseClass: "",
 +
 +		// srcNodeRef: [readonly] DomNode
 +		//		pointer to original DOM node
 +		srcNodeRef: null,
 +
 +		// domNode: [readonly] DomNode
 +		//		This is our visible representation of the widget! Other DOM
 +		//		Nodes may by assigned to other properties, usually through the
 +		//		template system's data-dojo-attach-point syntax, but the domNode
 +		//		property is the canonical "top level" node in widget UI.
 +		domNode: null,
 +
 +		// containerNode: [readonly] DomNode
 +		//		Designates where children of the source DOM node will be placed.
 +		//		"Children" in this case refers to both DOM nodes and widgets.
 +		//		For example, for myWidget:
 +		//
 +		//		|	<div data-dojo-type=myWidget>
 +		//		|		<b> here's a plain DOM node
 +		//		|		<span data-dojo-type=subWidget>and a widget</span>
 +		//		|		<i> and another plain DOM node </i>
 +		//		|	</div>
 +		//
 +		//		containerNode would point to:
 +		//
 +		//		|		<b> here's a plain DOM node
 +		//		|		<span data-dojo-type=subWidget>and a widget</span>
 +		//		|		<i> and another plain DOM node </i>
 +		//
 +		//		In templated widgets, "containerNode" is set via a
 +		//		data-dojo-attach-point assignment.
 +		//
 +		//		containerNode must be defined for any widget that accepts innerHTML
 +		//		(like ContentPane or BorderContainer or even Button), and conversely
 +		//		is null for widgets that don't, like TextBox.
 +		containerNode: null,
 +
 +		// ownerDocument: [const] Document?
 +		//		The document this widget belongs to.  If not specified to constructor, will default to
 +		//		srcNodeRef.ownerDocument, or if no sourceRef specified, then to the document global
 +		ownerDocument: null,
 +		_setOwnerDocumentAttr: function(val){
 +			// this setter is merely to avoid automatically trying to set this.domNode.ownerDocument
 +			this._set("ownerDocument", val);
 +		},
 +
 +		/*=====
 +		// _started: [readonly] Boolean
 +		//		startup() has completed.
 +		_started: false,
 +		=====*/
 +
 +		// attributeMap: [protected] Object
 +		//		Deprecated.	Instead of attributeMap, widget should have a _setXXXAttr attribute
 +		//		for each XXX attribute to be mapped to the DOM.
 +		//
 +		//		attributeMap sets up a "binding" between attributes (aka properties)
 +		//		of the widget and the widget's DOM.
 +		//		Changes to widget attributes listed in attributeMap will be
 +		//		reflected into the DOM.
 +		//
 +		//		For example, calling set('title', 'hello')
 +		//		on a TitlePane will automatically cause the TitlePane's DOM to update
 +		//		with the new title.
 +		//
 +		//		attributeMap is a hash where the key is an attribute of the widget,
 +		//		and the value reflects a binding to a:
 +		//
 +		//		- DOM node attribute
 +		// |		focus: {node: "focusNode", type: "attribute"}
 +		//		Maps this.focus to this.focusNode.focus
 +		//
 +		//		- DOM node innerHTML
 +		//	|		title: { node: "titleNode", type: "innerHTML" }
 +		//		Maps this.title to this.titleNode.innerHTML
 +		//
 +		//		- DOM node innerText
 +		//	|		title: { node: "titleNode", type: "innerText" }
 +		//		Maps this.title to this.titleNode.innerText
 +		//
 +		//		- DOM node CSS class
 +		// |		myClass: { node: "domNode", type: "class" }
 +		//		Maps this.myClass to this.domNode.className
 +		//
 +		//		If the value is an array, then each element in the array matches one of the
 +		//		formats of the above list.
 +		//
 +		//		There are also some shorthands for backwards compatibility:
 +		//
 +		//		- string --> { node: string, type: "attribute" }, for example:
 +		//
 +		//	|	"focusNode" ---> { node: "focusNode", type: "attribute" }
 +		//
 +		//		- "" --> { node: "domNode", type: "attribute" }
 +		attributeMap: {},
 +
 +		// _blankGif: [protected] String
 +		//		Path to a blank 1x1 image.
 +		//		Used by `<img>` nodes in templates that really get their image via CSS background-image.
 +		_blankGif: config.blankGif || require.toUrl("dojo/resources/blank.gif"),
 +
 +		//////////// INITIALIZATION METHODS ///////////////////////////////////////
 +
 +		/*=====
 +		constructor: function(params, srcNodeRef){
 +			// summary:
 +			//		Create the widget.
 +			// params: Object|null
 +			//		Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
 +			//		and functions, typically callbacks like onClick.
 +			//		The hash can contain any of the widget's properties, excluding read-only properties.
 +			// srcNodeRef: DOMNode|String?
 +			//		If a srcNodeRef (DOM node) is specified:
 +			//
 +			//		- use srcNodeRef.innerHTML as my contents
 +			//		- if this is a behavioral widget then apply behavior to that srcNodeRef
 +			//		- otherwise, replace srcNodeRef with my generated DOM tree
 +		},
 +		=====*/
 +
 +		_introspect: function(){
 +			// summary:
 +			//		Collect metadata about this widget (only once per class, not once per instance):
 +			//
 +			//			- list of attributes with custom setters, storing in this.constructor._setterAttrs
 +			//			- generate this.constructor._onMap, mapping names like "mousedown" to functions like onMouseDown
 +
 +			var ctor = this.constructor;
 +			if(!ctor._setterAttrs){
 +				var proto = ctor.prototype,
 +					attrs = ctor._setterAttrs = [], // attributes with custom setters
 +					onMap = (ctor._onMap = {});
 +
 +				// Items in this.attributeMap are like custom setters.  For back-compat, remove for 2.0.
 +				for(var name in proto.attributeMap){
 +					attrs.push(name);
 +				}
 +
 +				// Loop over widget properties, collecting properties with custom setters and filling in ctor._onMap.
 +				for(name in proto){
 +					if(/^on/.test(name)){
 +						onMap[name.substring(2).toLowerCase()] = name;
 +					}
 +
 +					if(/^_set[A-Z](.*)Attr$/.test(name)){
 +						name = name.charAt(4).toLowerCase() + name.substr(5, name.length - 9);
 +						if(!proto.attributeMap || !(name in proto.attributeMap)){
 +							attrs.push(name);
 +						}
 +					}
 +				}
 +
 +				// Note: this isn't picking up info on properties like aria-label and role, that don't have custom setters
 +				// but that set() maps to attributes on this.domNode or this.focusNode
 +			}
 +		},
 +
 +		postscript: function(/*Object?*/params, /*DomNode|String*/srcNodeRef){
 +			// summary:
 +			//		Kicks off widget instantiation.  See create() for details.
 +			// tags:
 +			//		private
 +
 +			// Note that we skip calling this.inherited(), i.e. dojo/Stateful::postscript(), because 1.x widgets don't
 +			// expect their custom setters to get called until after buildRendering().  Consider changing for 2.0.
 +
 +			this.create(params, srcNodeRef);
 +		},
 +
 +		create: function(params, srcNodeRef){
 +			// summary:
 +			//		Kick off the life-cycle of a widget
 +			// description:
 +			//		Create calls a number of widget methods (postMixInProperties, buildRendering, postCreate,
 +			//		etc.), some of which of you'll want to override. See http://dojotoolkit.org/reference-guide/dijit/_WidgetBase.html
 +			//		for a discussion of the widget creation lifecycle.
 +			//
 +			//		Of course, adventurous developers could override create entirely, but this should
 +			//		only be done as a last resort.
 +			// params: Object|null
 +			//		Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
 +			//		and functions, typically callbacks like onClick.
 +			//		The hash can contain any of the widget's properties, excluding read-only properties.
 +			// srcNodeRef: DOMNode|String?
 +			//		If a srcNodeRef (DOM node) is specified:
 +			//
 +			//		- use srcNodeRef.innerHTML as my contents
 +			//		- if this is a behavioral widget then apply behavior to that srcNodeRef
 +			//		- otherwise, replace srcNodeRef with my generated DOM tree
 +			// tags:
 +			//		private
 +
 +			// First time widget is instantiated, scan prototype to figure out info about custom setters etc.
 +			this._introspect();
 +
 +			// store pointer to original DOM tree
 +			this.srcNodeRef = dom.byId(srcNodeRef);
 +
 +			// No longer used, remove for 2.0.
 +			this._connects = [];
 +			this._supportingWidgets = [];
 +
 +			// this is here for back-compat, remove in 2.0 (but check NodeList-instantiate.html test)
 +			if(this.srcNodeRef && (typeof this.srcNodeRef.id == "string")){
 +				this.id = this.srcNodeRef.id;
 +			}
 +
 +			// mix in our passed parameters
 +			if(params){
 +				this.params = params;
 +				lang.mixin(this, params);
 +			}
 +			this.postMixInProperties();
 +
 +			// Generate an id for the widget if one wasn't specified, or it was specified as id: undefined.
 +			// Do this before buildRendering() because it might expect the id to be there.
 +			if(!this.id){
 +				this.id = registry.getUniqueId(this.declaredClass.replace(/\./g, "_"));
 +				if(this.params){
 +					// if params contains {id: undefined}, prevent _applyAttributes() from processing it
 +					delete this.params.id;
 +				}
 +			}
 +
 +			// The document and <body> node this widget is associated with
 +			this.ownerDocument = this.ownerDocument || (this.srcNodeRef ? this.srcNodeRef.ownerDocument : document);
 +			this.ownerDocumentBody = win.body(this.ownerDocument);
 +
 +			registry.add(this);
 +
 +			this.buildRendering();
 +
 +			var deleteSrcNodeRef;
 +
 +			if(this.domNode){
 +				// Copy attributes listed in attributeMap into the [newly created] DOM for the widget.
 +				// Also calls custom setters for all attributes with custom setters.
 +				this._applyAttributes();
 +
 +				// If srcNodeRef was specified, then swap out original srcNode for this widget's DOM tree.
 +				// For 2.0, move this after postCreate().  postCreate() shouldn't depend on the
 +				// widget being attached to the DOM since it isn't when a widget is created programmatically like
 +				// new MyWidget({}).	See #11635.
 +				var source = this.srcNodeRef;
 +				if(source && source.parentNode && this.domNode !== source){
 +					source.parentNode.replaceChild(this.domNode, source);
 +					deleteSrcNodeRef = true;
 +				}
 +
 +				// Note: for 2.0 may want to rename widgetId to dojo._scopeName + "_widgetId",
 +				// assuming that dojo._scopeName even exists in 2.0
 +				this.domNode.setAttribute("widgetId", this.id);
 +			}
 +			this.postCreate();
 +
 +			// If srcNodeRef has been processed and removed from the DOM (e.g. TemplatedWidget) then delete it to allow GC.
 +			// I think for back-compatibility it isn't deleting srcNodeRef until after postCreate() has run.
 +			if(deleteSrcNodeRef){
 +				delete this.srcNodeRef;
 +			}
 +
 +			this._created = true;
 +		},
 +
 +		_applyAttributes: function(){
 +			// summary:
 +			//		Step during widget creation to copy  widget attributes to the
 +			//		DOM according to attributeMap and _setXXXAttr objects, and also to call
 +			//		custom _setXXXAttr() methods.
 +			//
 +			//		Skips over blank/false attribute values, unless they were explicitly specified
 +			//		as parameters to the widget, since those are the default anyway,
 +			//		and setting tabIndex="" is different than not setting tabIndex at all.
 +			//
 +			//		For backwards-compatibility reasons attributeMap overrides _setXXXAttr when
 +			//		_setXXXAttr is a hash/string/array, but _setXXXAttr as a functions override attributeMap.
 +			// tags:
 +			//		private
 +
 +			// Call this.set() for each property that was either specified as parameter to constructor,
 +			// or is in the list found above.	For correlated properties like value and displayedValue, the one
 +			// specified as a parameter should take precedence.
 +			// Particularly important for new DateTextBox({displayedValue: ...}) since DateTextBox's default value is
 +			// NaN and thus is not ignored like a default value of "".
 +
 +			// Step 1: Save the current values of the widget properties that were specified as parameters to the constructor.
 +			// Generally this.foo == this.params.foo, except if postMixInProperties() changed the value of this.foo.
 +			var params = {};
 +			for(var key in this.params || {}){
 +				params[key] = this._get(key);
 +			}
 +
 +			// Step 2: Call set() for each property with a non-falsy value that wasn't passed as a parameter to the constructor
 +			array.forEach(this.constructor._setterAttrs, function(key){
 +				if(!(key in params)){
 +					var val = this._get(key);
 +					if(val){
 +						this.set(key, val);
 +					}
 +				}
 +			}, this);
 +
 +			// Step 3: Call set() for each property that was specified as parameter to constructor.
 +			// Use params hash created above to ignore side effects from step #2 above.
 +			for(key in params){
 +				this.set(key, params[key]);
 +			}
 +		},
 +
 +		postMixInProperties: function(){
 +			// summary:
 +			//		Called after the parameters to the widget have been read-in,
 +			//		but before the widget template is instantiated. Especially
 +			//		useful to set properties that are referenced in the widget
 +			//		template.
 +			// tags:
 +			//		protected
 +		},
 +
 +		buildRendering: function(){
 +			// summary:
 +			//		Construct the UI for this widget, setting this.domNode.
 +			//		Most widgets will mixin `dijit._TemplatedMixin`, which implements this method.
 +			// tags:
 +			//		protected
 +
 +			if(!this.domNode){
 +				// Create root node if it wasn't created by _TemplatedMixin
 +				this.domNode = this.srcNodeRef || this.ownerDocument.createElement("div");
 +			}
 +
 +			// baseClass is a single class name or occasionally a space-separated list of names.
 +			// Add those classes to the DOMNode.  If RTL mode then also add with Rtl suffix.
 +			// TODO: make baseClass custom setter
 +			if(this.baseClass){
 +				var classes = this.baseClass.split(" ");
 +				if(!this.isLeftToRight()){
 +					classes = classes.concat(array.map(classes, function(name){
 +						return name + "Rtl";
 +					}));
 +				}
 +				domClass.add(this.domNode, classes);
 +			}
 +		},
 +
 +		postCreate: function(){
 +			// summary:
 +			//		Processing after the DOM fragment is created
 +			// description:
 +			//		Called after the DOM fragment has been created, but not necessarily
 +			//		added to the document.  Do not include any operations which rely on
 +			//		node dimensions or placement.
 +			// tags:
 +			//		protected
 +		},
 +
 +		startup: function(){
 +			// summary:
 +			//		Processing after the DOM fragment is added to the document
 +			// description:
 +			//		Called after a widget and its children have been created and added to the page,
 +			//		and all related widgets have finished their create() cycle, up through postCreate().
 +			//
 +			//		Note that startup() may be called while the widget is still hidden, for example if the widget is
 +			//		inside a hidden dijit/Dialog or an unselected tab of a dijit/layout/TabContainer.
 +			//		For widgets that need to do layout, it's best to put that layout code inside resize(), and then
 +			//		extend dijit/layout/_LayoutWidget so that resize() is called when the widget is visible.
 +			if(this._started){
 +				return;
 +			}
 +			this._started = true;
 +			array.forEach(this.getChildren(), function(obj){
 +				if(!obj._started && !obj._destroyed && lang.isFunction(obj.startup)){
 +					obj.startup();
 +					obj._started = true;
 +				}
 +			});
 +		},
 +
 +		//////////// DESTROY FUNCTIONS ////////////////////////////////
 +
 +		destroyRecursive: function(/*Boolean?*/ preserveDom){
 +			// summary:
 +			//		Destroy this widget and its descendants
 +			// description:
 +			//		This is the generic "destructor" function that all widget users
 +			//		should call to cleanly discard with a widget. Once a widget is
 +			//		destroyed, it is removed from the manager object.
 +			// preserveDom:
 +			//		If true, this method will leave the original DOM structure
 +			//		alone of descendant Widgets. Note: This will NOT work with
 +			//		dijit._TemplatedMixin widgets.
 +
 +			this._beingDestroyed = true;
 +			this.destroyDescendants(preserveDom);
 +			this.destroy(preserveDom);
 +		},
 +
 +		destroy: function(/*Boolean*/ preserveDom){
 +			// summary:
 +			//		Destroy this widget, but not its descendants.  Descendants means widgets inside of
 +			//		this.containerNode.   Will also destroy any resources (including widgets) registered via this.own().
 +			//
 +			//		This method will also destroy internal widgets such as those created from a template,
 +			//		assuming those widgets exist inside of this.domNode but outside of this.containerNode.
 +			//
 +			//		For 2.0 it's planned that this method will also destroy descendant widgets, so apps should not
 +			//		depend on the current ability to destroy a widget without destroying its descendants.   Generally
 +			//		they should use destroyRecursive() for widgets with children.
 +			// preserveDom: Boolean
 +			//		If true, this method will leave the original DOM structure alone.
 +			//		Note: This will not yet work with _TemplatedMixin widgets
 +
 +			this._beingDestroyed = true;
 +			this.uninitialize();
 +
 +			function destroy(w){
 +				if(w.destroyRecursive){
 +					w.destroyRecursive(preserveDom);
 +				}else if(w.destroy){
 +					w.destroy(preserveDom);
 +				}
 +			}
 +
 +			// Back-compat, remove for 2.0
 +			array.forEach(this._connects, lang.hitch(this, "disconnect"));
 +			array.forEach(this._supportingWidgets, destroy);
 +
 +			// Destroy supporting widgets, but not child widgets under this.containerNode (for 2.0, destroy child widgets
 +			// here too).   if() statement is to guard against exception if destroy() called multiple times (see #15815).
 +			if(this.domNode){
 +				array.forEach(registry.findWidgets(this.domNode, this.containerNode), destroy);
 +			}
 +
 +			this.destroyRendering(preserveDom);
 +			registry.remove(this.id);
 +			this._destroyed = true;
 +		},
 +
 +		destroyRendering: function(/*Boolean?*/ preserveDom){
 +			// summary:
 +			//		Destroys the DOM nodes associated with this widget.
 +			// preserveDom:
 +			//		If true, this method will leave the original DOM structure alone
 +			//		during tear-down. Note: this will not work with _Templated
 +			//		widgets yet.
 +			// tags:
 +			//		protected
 +
 +			if(this.bgIframe){
 +				this.bgIframe.destroy(preserveDom);
 +				delete this.bgIframe;
 +			}
 +
 +			if(this.domNode){
 +				if(preserveDom){
 +					domAttr.remove(this.domNode, "widgetId");
 +				}else{
 +					domConstruct.destroy(this.domNode);
 +				}
 +				delete this.domNode;
 +			}
 +
 +			if(this.srcNodeRef){
 +				if(!preserveDom){
 +					domConstruct.destroy(this.srcNodeRef);
 +				}
 +				delete this.srcNodeRef;
 +			}
 +		},
 +
 +		destroyDescendants: function(/*Boolean?*/ preserveDom){
 +			// summary:
 +			//		Recursively destroy the children of this widget and their
 +			//		descendants.
 +			// preserveDom:
 +			//		If true, the preserveDom attribute is passed to all descendant
 +			//		widget's .destroy() method. Not for use with _Templated
 +			//		widgets.
 +
 +			// get all direct descendants and destroy them recursively
 +			array.forEach(this.getChildren(), function(widget){
 +				if(widget.destroyRecursive){
 +					widget.destroyRecursive(preserveDom);
 +				}
 +			});
 +		},
 +
 +		uninitialize: function(){
 +			// summary:
 +			//		Deprecated. Override destroy() instead to implement custom widget tear-down
 +			//		behavior.
 +			// tags:
 +			//		protected
 +			return false;
 +		},
 +
 +		////////////////// GET/SET, CUSTOM SETTERS, ETC. ///////////////////
 +
 +		_setStyleAttr: function(/*String||Object*/ value){
 +			// summary:
 +			//		Sets the style attribute of the widget according to value,
 +			//		which is either a hash like {height: "5px", width: "3px"}
 +			//		or a plain string
 +			// description:
 +			//		Determines which node to set the style on based on style setting
 +			//		in attributeMap.
 +			// tags:
 +			//		protected
 +
 +			var mapNode = this.domNode;
 +
 +			// Note: technically we should revert any style setting made in a previous call
 +			// to his method, but that's difficult to keep track of.
 +
 +			if(lang.isObject(value)){
 +				domStyle.set(mapNode, value);
 +			}else{
 +				if(mapNode.style.cssText){
 +					mapNode.style.cssText += "; " + value;
 +				}else{
 +					mapNode.style.cssText = value;
 +				}
 +			}
 +
 +			this._set("style", value);
 +		},
 +
 +		_attrToDom: function(/*String*/ attr, /*String*/ value, /*Object?*/ commands){
 +			// summary:
 +			//		Reflect a widget attribute (title, tabIndex, duration etc.) to
 +			//		the widget DOM, as specified by commands parameter.
 +			//		If commands isn't specified then it's looked up from attributeMap.
 +			//		Note some attributes like "type"
 +			//		cannot be processed this way as they are not mutable.
 +			// attr:
 +			//		Name of member variable (ex: "focusNode" maps to this.focusNode) pointing
 +			//		to DOMNode inside the widget, or alternately pointing to a subwidget
 +			// tags:
 +			//		private
 +
 +			commands = arguments.length >= 3 ? commands : this.attributeMap[attr];
 +
 +			array.forEach(lang.isArray(commands) ? commands : [commands], function(command){
 +
 +				// Get target node and what we are doing to that node
 +				var mapNode = this[command.node || command || "domNode"];	// DOM node
 +				var type = command.type || "attribute";	// class, innerHTML, innerText, or attribute
 +
 +				switch(type){
 +					case "attribute":
 +						if(lang.isFunction(value)){ // functions execute in the context of the widget
 +							value = lang.hitch(this, value);
 +						}
 +
 +						// Get the name of the DOM node attribute; usually it's the same
 +						// as the name of the attribute in the widget (attr), but can be overridden.
 +						// Also maps handler names to lowercase, like onSubmit --> onsubmit
 +						var attrName = command.attribute ? command.attribute :
 +							(/^on[A-Z][a-zA-Z]*$/.test(attr) ? attr.toLowerCase() : attr);
 +
 +						if(mapNode.tagName){
 +							// Normal case, mapping to a DOMNode.  Note that modern browsers will have a mapNode.set()
 +							// method, but for consistency we still call domAttr
 +							domAttr.set(mapNode, attrName, value);
 +						}else{
 +							// mapping to a sub-widget
 +							mapNode.set(attrName, value);
 +						}
 +						break;
 +					case "innerText":
 +						mapNode.innerHTML = "";
 +						mapNode.appendChild(this.ownerDocument.createTextNode(value));
 +						break;
 +					case "innerHTML":
 +						mapNode.innerHTML = value;
 +						break;
 +					case "class":
 +						domClass.replace(mapNode, value, this[attr]);
 +						break;
 +				}
 +			}, this);
 +		},
 +
 +		get: function(name){
 +			// summary:
 +			//		Get a property from a widget.
 +			// name:
 +			//		The property to get.
 +			// description:
 +			//		Get a named property from a widget. The property may
 +			//		potentially be retrieved via a getter method. If no getter is defined, this
 +			//		just retrieves the object's property.
 +			//
 +			//		For example, if the widget has properties `foo` and `bar`
 +			//		and a method named `_getFooAttr()`, calling:
 +			//		`myWidget.get("foo")` would be equivalent to calling
 +			//		`widget._getFooAttr()` and `myWidget.get("bar")`
 +			//		would be equivalent to the expression
 +			//		`widget.bar2`
 +			var names = this._getAttrNames(name);
 +			return this[names.g] ? this[names.g]() : this._get(name);
 +		},
 +
 +		set: function(name, value){
 +			// summary:
 +			//		Set a property on a widget
 +			// name:
 +			//		The property to set.
 +			// value:
 +			//		The value to set in the property.
 +			// description:
 +			//		Sets named properties on a widget which may potentially be handled by a
 +			//		setter in the widget.
 +			//
 +			//		For example, if the widget has properties `foo` and `bar`
 +			//		and a method named `_setFooAttr()`, calling
 +			//		`myWidget.set("foo", "Howdy!")` would be equivalent to calling
 +			//		`widget._setFooAttr("Howdy!")` and `myWidget.set("bar", 3)`
 +			//		would be equivalent to the statement `widget.bar = 3;`
 +			//
 +			//		set() may also be called with a hash of name/value pairs, ex:
 +			//
 +			//	|	myWidget.set({
 +			//	|		foo: "Howdy",
 +			//	|		bar: 3
 +			//	|	});
 +			//
 +			//	This is equivalent to calling `set(foo, "Howdy")` and `set(bar, 3)`
 +
 +			if(typeof name === "object"){
 +				for(var x in name){
 +					this.set(x, name[x]);
 +				}
 +				return this;
 +			}
 +			var names = this._getAttrNames(name),
 +				setter = this[names.s];
 +			if(lang.isFunction(setter)){
 +				// use the explicit setter
 +				var result = setter.apply(this, Array.prototype.slice.call(arguments, 1));
 +			}else{
 +				// Mapping from widget attribute to DOMNode/subwidget attribute/value/etc.
 +				// Map according to:
 +				//		1. attributeMap setting, if one exists (TODO: attributeMap deprecated, remove in 2.0)
 +				//		2. _setFooAttr: {...} type attribute in the widget (if one exists)
 +				//		3. apply to focusNode or domNode if standard attribute name, excluding funcs like onClick.
 +				// Checks if an attribute is a "standard attribute" by whether the DOMNode JS object has a similar
 +				// attribute name (ex: accept-charset attribute matches jsObject.acceptCharset).
 +				// Note also that Tree.focusNode() is a function not a DOMNode, so test for that.
 +				var defaultNode = this.focusNode && !lang.isFunction(this.focusNode) ? "focusNode" : "domNode",
 +					tag = this[defaultNode] && this[defaultNode].tagName,
 +					attrsForTag = tag && (tagAttrs[tag] || (tagAttrs[tag] = getAttrs(this[defaultNode]))),
 +					map = name in this.attributeMap ? this.attributeMap[name] :
 +						names.s in this ? this[names.s] :
 +							((attrsForTag && names.l in attrsForTag && typeof value != "function") ||
 +								/^aria-|^data-|^role$/.test(name)) ? defaultNode : null;
 +				if(map != null){
 +					this._attrToDom(name, value, map);
 +				}
 +				this._set(name, value);
 +			}
 +			return result || this;
 +		},
 +
 +		_attrPairNames: {}, // shared between all widgets
 +		_getAttrNames: function(name){
 +			// summary:
 +			//		Helper function for get() and set().
 +			//		Caches attribute name values so we don't do the string ops every time.
 +			// tags:
 +			//		private
 +
 +			var apn = this._attrPairNames;
 +			if(apn[name]){
 +				return apn[name];
 +			}
 +			var uc = name.replace(/^[a-z]|-[a-zA-Z]/g, function(c){
 +				return c.charAt(c.length - 1).toUpperCase();
 +			});
 +			return (apn[name] = {
 +				n: name + "Node",
 +				s: "_set" + uc + "Attr", // converts dashes to camel case, ex: accept-charset --> _setAcceptCharsetAttr
 +				g: "_get" + uc + "Attr",
 +				l: uc.toLowerCase()        // lowercase name w/out dashes, ex: acceptcharset
 +			});
 +		},
 +
 +		_set: function(/*String*/ name, /*anything*/ value){
 +			// summary:
 +			//		Helper function to set new value for specified property, and call handlers
 +			//		registered with watch() if the value has changed.
 +			var oldValue = this[name];
 +			this[name] = value;
 +			if(this._created && !isEqual(oldValue, value)){
 +				if(this._watchCallbacks){
 +					this._watchCallbacks(name, oldValue, value);
 +				}
 +				this.emit("attrmodified-" + name, {
 +					detail: {
 +						prevValue: oldValue,
 +						newValue: value
 +					}
 +				});
 +			}
 +		},
 +
 +		_get: function(/*String*/ name){
 +			// summary:
 +			//		Helper function to get value for specified property stored by this._set(),
 +			//		i.e. for properties with custom setters.  Used mainly by custom getters.
 +			//
 +			//		For example, CheckBox._getValueAttr() calls this._get("value").
 +
 +			// future: return name in this.props ? this.props[name] : this[name];
 +			return this[name];
 +		},
 +
 +		emit: function(/*String*/ type, /*Object?*/ eventObj, /*Array?*/ callbackArgs){
 +			// summary:
 +			//		Used by widgets to signal that a synthetic event occurred, ex:
 +			//	|	myWidget.emit("attrmodified-selectedChildWidget", {}).
 +			//
 +			//		Emits an event on this.domNode named type.toLowerCase(), based on eventObj.
 +			//		Also calls onType() method, if present, and returns value from that method.
 +			//		By default passes eventObj to callback, but will pass callbackArgs instead, if specified.
 +			//		Modifies eventObj by adding missing parameters (bubbles, cancelable, widget).
 +			// tags:
 +			//		protected
 +
 +			// Specify fallback values for bubbles, cancelable in case they are not set in eventObj.
 +			// Also set pointer to widget, although since we can't add a pointer to the widget for native events
 +			// (see #14729), maybe we shouldn't do it here?
 +			eventObj = eventObj || {};
 +			if(eventObj.bubbles === undefined){
 +				eventObj.bubbles = true;
 +			}
 +			if(eventObj.cancelable === undefined){
 +				eventObj.cancelable = true;
 +			}
 +			if(!eventObj.detail){
 +				eventObj.detail = {};
 +			}
 +			eventObj.detail.widget = this;
 +
 +			var ret, callback = this["on" + type];
 +			if(callback){
 +				ret = callback.apply(this, callbackArgs ? callbackArgs : [eventObj]);
 +			}
 +
 +			// Emit event, but avoid spurious emit()'s as parent sets properties on child during startup/destroy
 +			if(this._started && !this._beingDestroyed){
 +				on.emit(this.domNode, type.toLowerCase(), eventObj);
 +			}
 +
 +			return ret;
 +		},
 +
 +		on: function(/*String|Function*/ type, /*Function*/ func){
 +			// summary:
 +			//		Call specified function when event occurs, ex: myWidget.on("click", function(){ ... }).
 +			// type:
 +			//		Name of event (ex: "click") or extension event like touch.press.
 +			// description:
 +			//		Call specified function when event `type` occurs, ex: `myWidget.on("click", function(){ ... })`.
 +			//		Note that the function is not run in any particular scope, so if (for example) you want it to run in the
 +			//		widget's scope you must do `myWidget.on("click", lang.hitch(myWidget, func))`.
 +
 +			// For backwards compatibility, if there's an onType() method in the widget then connect to that.
 +			// Remove in 2.0.
 +			var widgetMethod = this._onMap(type);
 +			if(widgetMethod){
 +				return aspect.after(this, widgetMethod, func, true);
 +			}
 +
 +			// Otherwise, just listen for the event on this.domNode.
 +			return this.own(on(this.domNode, type, func))[0];
 +		},
 +
 +		_onMap: function(/*String|Function*/ type){
 +			// summary:
 +			//		Maps on() type parameter (ex: "mousemove") to method name (ex: "onMouseMove").
 +			//		If type is a synthetic event like touch.press then returns undefined.
 +			var ctor = this.constructor, map = ctor._onMap;
 +			if(!map){
 +				map = (ctor._onMap = {});
 +				for(var attr in ctor.prototype){
 +					if(/^on/.test(attr)){
 +						map[attr.replace(/^on/, "").toLowerCase()] = attr;
 +					}
 +				}
 +			}
 +			return map[typeof type == "string" && type.toLowerCase()];	// String
 +		},
 +
 +		toString: function(){
 +			// summary:
 +			//		Returns a string that represents the widget.
 +			// description:
 +			//		When a widget is cast to a string, this method will be used to generate the
 +			//		output. Currently, it does not implement any sort of reversible
 +			//		serialization.
 +			return '[Widget ' + this.declaredClass + ', ' + (this.id || 'NO ID') + ']'; // String
 +		},
 +
 +		getChildren: function(){
 +			// summary:
 +			//		Returns all direct children of this widget, i.e. all widgets underneath this.containerNode whose parent
 +			//		is this widget.   Note that it does not return all descendants, but rather just direct children.
 +			//		Analogous to [Node.childNodes](https://developer.mozilla.org/en-US/docs/DOM/Node.childNodes),
 +			//		except containing widgets rather than DOMNodes.
 +			//
 +			//		The result intentionally excludes internally created widgets (a.k.a. supporting widgets)
 +			//		outside of this.containerNode.
 +			//
 +			//		Note that the array returned is a simple array.  Application code should not assume
 +			//		existence of methods like forEach().
 +
 +			return this.containerNode ? registry.findWidgets(this.containerNode) : []; // dijit/_WidgetBase[]
 +		},
 +
 +		getParent: function(){
 +			// summary:
 +			//		Returns the parent widget of this widget.
 +
 +			return registry.getEnclosingWidget(this.domNode.parentNode);
 +		},
 +
 +		connect: function(/*Object|null*/ obj, /*String|Function*/ event, /*String|Function*/ method){
 +			// summary:
 +			//		Deprecated, will be removed in 2.0, use this.own(on(...)) or this.own(aspect.after(...)) instead.
 +			//
 +			//		Connects specified obj/event to specified method of this object
 +			//		and registers for disconnect() on widget destroy.
 +			//
 +			//		Provide widget-specific analog to dojo.connect, except with the
 +			//		implicit use of this widget as the target object.
 +			//		Events connected with `this.connect` are disconnected upon
 +			//		destruction.
 +			// returns:
 +			//		A handle that can be passed to `disconnect` in order to disconnect before
 +			//		the widget is destroyed.
 +			// example:
 +			//	|	var btn = new Button();
 +			//	|	// when foo.bar() is called, call the listener we're going to
 +			//	|	// provide in the scope of btn
 +			//	|	btn.connect(foo, "bar", function(){
 +			//	|		console.debug(this.toString());
 +			//	|	});
 +			// tags:
 +			//		protected
 +
 +			return this.own(connect.connect(obj, event, this, method))[0];	// handle
 +		},
 +
 +		disconnect: function(handle){
 +			// summary:
 +			//		Deprecated, will be removed in 2.0, use handle.remove() instead.
 +			//
 +			//		Disconnects handle created by `connect`.
 +			// tags:
 +			//		protected
 +
 +			handle.remove();
 +		},
 +
 +		subscribe: function(t, method){
 +			// summary:
 +			//		Deprecated, will be removed in 2.0, use this.own(topic.subscribe()) instead.
 +			//
 +			//		Subscribes to the specified topic and calls the specified method
 +			//		of this object and registers for unsubscribe() on widget destroy.
 +			//
 +			//		Provide widget-specific analog to dojo.subscribe, except with the
 +			//		implicit use of this widget as the target object.
 +			// t: String
 +			//		The topic
 +			// method: Function
 +			//		The callback
 +			// example:
 +			//	|	var btn = new Button();
 +			//	|	// when /my/topic is published, this button changes its label to
 +			//	|	// be the parameter of the topic.
 +			//	|	btn.subscribe("/my/topic", function(v){
 +			//	|		this.set("label", v);
 +			//	|	});
 +			// tags:
 +			//		protected
 +			return this.own(topic.subscribe(t, lang.hitch(this, method)))[0];	// handle
 +		},
 +
 +		unsubscribe: function(/*Object*/ handle){
 +			// summary:
 +			//		Deprecated, will be removed in 2.0, use handle.remove() instead.
 +			//
 +			//		Unsubscribes handle created by this.subscribe.
 +			//		Also removes handle from this widget's list of subscriptions
 +			// tags:
 +			//		protected
 +
 +			handle.remove();
 +		},
 +
 +		isLeftToRight: function(){
 +			// summary:
 +			//		Return this widget's explicit or implicit orientation (true for LTR, false for RTL)
 +			// tags:
 +			//		protected
- 			return this.dir ? (this.dir == "ltr") : domGeometry.isBodyLtr(this.ownerDocument); //Boolean
++			return this.dir ? (this.dir.toLowerCase() == "ltr") : domGeometry.isBodyLtr(this.ownerDocument); //Boolean
 +		},
 +
 +		isFocusable: function(){
 +			// summary:
 +			//		Return true if this widget can currently be focused
 +			//		and false if not
 +			return this.focus && (domStyle.get(this.domNode, "display") != "none");
 +		},
 +
 +		placeAt: function(/*String|DomNode|DocumentFragment|dijit/_WidgetBase*/ reference, /*String|Int?*/ position){
 +			// summary:
 +			//		Place this widget somewhere in the DOM based
 +			//		on standard domConstruct.place() conventions.
 +			// description:
 +			//		A convenience function provided in all _Widgets, providing a simple
 +			//		shorthand mechanism to put an existing (or newly created) Widget
 +			//		somewhere in the dom, and allow chaining.
 +			// reference:
 +			//		Widget, DOMNode, DocumentFragment, or id of widget or DOMNode
 +			// position:
 +			//		If reference is a widget (or id of widget), and that widget has an ".addChild" method,
 +			//		it will be called passing this widget instance into that method, supplying the optional
 +			//		position index passed.  In this case position (if specified) should be an integer.
 +			//
 +			//		If reference is a DOMNode (or id matching a DOMNode but not a widget),
 +			//		the position argument can be a numeric index or a string
 +			//		"first", "last", "before", or "after", same as dojo/dom-construct::place().
 +			// returns: dijit/_WidgetBase
 +			//		Provides a useful return of the newly created dijit._Widget instance so you
 +			//		can "chain" this function by instantiating, placing, then saving the return value
 +			//		to a variable.
 +			// example:
 +			//	|	// create a Button with no srcNodeRef, and place it in the body:
 +			//	|	var button = new Button({ label:"click" }).placeAt(win.body());
 +			//	|	// now, 'button' is still the widget reference to the newly created button
 +			//	|	button.on("click", function(e){ console.log('click'); }));
 +			// example:
 +			//	|	// create a button out of a node with id="src" and append it to id="wrapper":
 +			//	|	var button = new Button({},"src").placeAt("wrapper");
 +			// example:
 +			//	|	// place a new button as the first element of some div
 +			//	|	var button = new Button({ label:"click" }).placeAt("wrapper","first");
 +			// example:
 +			//	|	// create a contentpane and add it to a TabContainer
 +			//	|	var tc = dijit.byId("myTabs");
 +			//	|	new ContentPane({ href:"foo.html", title:"Wow!" }).placeAt(tc)
 +
 +			var refWidget = !reference.tagName && registry.byId(reference);
 +			if(refWidget && refWidget.addChild && (!position || typeof position === "number")){
 +				// Adding this to refWidget and can use refWidget.addChild() to handle everything.
 +				refWidget.addChild(this, position);
 +			}else{
 +				// "reference" is a plain DOMNode, or we can't use refWidget.addChild().   Use domConstruct.place() and
 +				// target refWidget.containerNode for nested placement (position==number, "first", "last", "only"), and
 +				// refWidget.domNode otherwise ("after"/"before"/"replace").  (But not supported officially, see #14946.)
 +				var ref = refWidget && ("domNode" in refWidget) ?
 +					(refWidget.containerNode && !/after|before|replace/.test(position || "") ?
 +						refWidget.containerNode : refWidget.domNode) : dom.byId(reference, this.ownerDocument);
 +				domConstruct.place(this.domNode, ref, position);
 +
 +				// Start this iff it has a parent widget that's already started.
 +				// TODO: for 2.0 maybe it should also start the widget when this.getParent() returns null??
 +				if(!this._started && (this.getParent() || {})._started){
 +					this.startup();
 +				}
 +			}
 +			return this;
 +		},
 +
 +		defer: function(fcn, delay){
 +			// summary:
 +			//		Wrapper to setTimeout to avoid deferred functions executing
 +			//		after the originating widget has been destroyed.
 +			//		Returns an object handle with a remove method (that returns null) (replaces clearTimeout).
 +			// fcn: Function
 +			//		Function reference.
 +			// delay: Number?
 +			//		Delay, defaults to 0.
 +			// tags:
 +			//		protected
 +
 +			var timer = setTimeout(lang.hitch(this,
 +				function(){
 +					if(!timer){
 +						return;
 +					}
 +					timer = null;
 +					if(!this._destroyed){
 +						lang.hitch(this, fcn)();
 +					}
 +				}),
 +				delay || 0
 +			);
 +			return {
 +				remove: function(){
 +					if(timer){
 +						clearTimeout(timer);
 +						timer = null;
 +					}
 +					return null; // so this works well: handle = handle.remove();
 +				}
 +			};
 +		}
 +	});
 +
 +	if(has("dojo-bidi")){
 +		_WidgetBase.extend(_BidiMixin);
 +	}
 +
 +	return _WidgetBase;
 +});
diff --cc dijit/_editor/plugins/FontChoice.js
index 76a3372,0000000..e617889
mode 100644,000000..100644
--- a/dijit/_editor/plugins/FontChoice.js
+++ b/dijit/_editor/plugins/FontChoice.js
@@@ -1,606 -1,0 +1,606 @@@
 +define([
 +	"require",
 +	"dojo/_base/array", // array.indexOf array.map
 +	"dojo/_base/declare", // declare
 +	"dojo/dom-construct", // domConstruct.place
 +	"dojo/i18n", // i18n.getLocalization
 +	"dojo/_base/lang", // lang.delegate lang.hitch lang.isString
 +	"dojo/store/Memory", // MemoryStore
 +	"../../registry", // registry.getUniqueId
 +	"../../_Widget",
 +	"../../_TemplatedMixin",
 +	"../../_WidgetsInTemplateMixin",
 +	"../../form/FilteringSelect",
 +	"../_Plugin",
 +	"../range",
 +	"dojo/i18n!../nls/FontChoice"
 +], function(require, array, declare, domConstruct, i18n, lang, MemoryStore,
 +	registry, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, FilteringSelect, _Plugin, rangeapi){
 +
 +	// module:
 +	//		dijit/_editor/plugins/FontChoice
 +
 +	var _FontDropDown = declare("dijit._editor.plugins._FontDropDown",
 +		[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
 +			// summary:
 +			//		Base class for widgets that contains a label (like "Font:")
 +			//		and a FilteringSelect drop down to pick a value.
 +			//		Used as Toolbar entry.
 +
 +			// label: [public] String
 +			//		The label to apply to this particular FontDropDown.
 +			label: "",
 +
 +			// plainText: [public] boolean
 +			//		Flag to indicate that the returned label should be plain text
 +			//		instead of an example.
 +			plainText: false,
 +
 +			// templateString: [public] String
 +			//		The template used to construct the labeled dropdown.
 +			templateString: "<span style='white-space: nowrap' class='dijit dijitReset dijitInline'>" +
 +				"<label class='dijitLeft dijitInline' for='${selectId}'>${label}</label>" +
 +				"<input data-dojo-type='../../form/FilteringSelect' required='false' " +
 +				"data-dojo-props='labelType:\"html\", labelAttr:\"label\", searchAttr:\"name\"' " +
 +				"class='${comboClass}' " +
 +				"tabIndex='-1' id='${selectId}' data-dojo-attach-point='select' value=''/>" +
 +				"</span>",
 +
 +			// contextRequire: [public] Function
 +			//		The context require that is used to resolve modules in template.
 +			contextRequire: require,
 +
 +			postMixInProperties: function(){
 +				// summary:
 +				//		Over-ride to set specific properties.
 +				this.inherited(arguments);
 +
 +				this.strings = i18n.getLocalization("dijit._editor", "FontChoice");
 +
 +				// Set some substitution variables used in the template
 +				this.label = this.strings[this.command];
 +
 +				// _WidgetBase sets the id after postMixInProperties(), but we need it now.
 +				// Alternative is to have a buildRendering() method and move this.selectId setting there,
 +				// or alternately get rid of selectId variable and just access ${id} in template?
 +				this.id = registry.getUniqueId(this.declaredClass.replace(/\./g, "_"));
 +
 +				this.selectId = this.id + "_select";	// used in template
 +
 +				this.inherited(arguments);
 +			},
 +
 +			postCreate: function(){
 +				// summary:
 +				//		Over-ride for the default postCreate action
 +				//		This establishes the filtering selects and the like.
 +
 +				// Initialize the list of items in the drop down by creating data store with items like:
 +				// {value: 1, name: "xx-small", label: "<font size=1>xx-small</font-size>" }
 +				this.select.set("store", new MemoryStore({
 +					idProperty: "value",
 +					data: array.map(this.values, function(value){
 +						var name = this.strings[value] || value;
 +						return {
 +							label: this.getLabel(value, name),
 +							name: name,
 +							value: value
 +						};
 +					}, this)
 +				}));
 +
 +				this.select.set("value", "", false);
 +				this.disabled = this.select.get("disabled");
 +			},
 +
 +			_setValueAttr: function(value, priorityChange){
 +				// summary:
 +				//		Over-ride for the default action of setting the
 +				//		widget value, maps the input to known values
 +				// value: Object|String
 +				//		The value to set in the select.
 +				// priorityChange:
 +				//		Optional parameter used to tell the select whether or not to fire
 +				//		onChange event.
 +
 +				// if the value is not a permitted value, just set empty string to prevent showing the warning icon
 +				priorityChange = priorityChange !== false;
 +				this.select.set('value', array.indexOf(this.values, value) < 0 ? "" : value, priorityChange);
 +				if(!priorityChange){
 +					// Clear the last state in case of updateState calls.  Ref: #10466
 +					this.select._lastValueReported = null;
 +				}
 +			},
 +
 +			_getValueAttr: function(){
 +				// summary:
 +				//		Allow retrieving the value from the composite select on
 +				//		call to button.get("value");
 +				return this.select.get('value');
 +			},
 +
 +			focus: function(){
 +				// summary:
 +				//		Over-ride for focus control of this widget.  Delegates focus down to the
 +				//		filtering select.
 +				this.select.focus();
 +			},
 +
 +			_setDisabledAttr: function(value){
 +				// summary:
 +				//		Over-ride for the button's 'disabled' attribute so that it can be
 +				//		disabled programmatically.
 +
 +				// Save off ths disabled state so the get retrieves it correctly
 +				//without needing to have a function proxy it.
 +				this._set("disabled", value);
 +				this.select.set("disabled", value);
 +			}
 +		});
 +
 +
 +	var _FontNameDropDown = declare("dijit._editor.plugins._FontNameDropDown", _FontDropDown, {
 +		// summary:
 +		//		Dropdown to select a font; goes in editor toolbar.
 +
 +		// generic: [const] Boolean
 +		//		Use generic (web standard) font names
 +		generic: false,
 +
 +		// command: [public] String
 +		//		The editor 'command' implemented by this plugin.
 +		command: "fontName",
 +
 +		comboClass: "dijitFontNameCombo",
 +
 +		postMixInProperties: function(){
 +			// summary:
 +			//		Over-ride for the default posr mixin control
 +			if(!this.values){
 +				this.values = this.generic ?
 +					["serif", "sans-serif", "monospace", "cursive", "fantasy"] : // CSS font-family generics
 +					["Arial", "Times New Roman", "Comic Sans MS", "Courier New"];
 +			}
 +			this.inherited(arguments);
 +		},
 +
 +		getLabel: function(value, name){
 +			// summary:
 +			//		Function used to generate the labels of the format dropdown
 +			//		will return a formatted, or plain label based on the value
 +			//		of the plainText option.
 +			// value: String
 +			//		The 'insert value' associated with a name
 +			// name: String
 +			//		The text name of the value
 +			if(this.plainText){
 +				return name;
 +			}else{
 +				return "<div style='font-family: " + value + "'>" + name + "</div>";
 +			}
 +		},
 +
 +		_setValueAttr: function(value, priorityChange){
 +			// summary:
 +			//		Over-ride for the default action of setting the
 +			//		widget value, maps the input to known values
 +
 +			priorityChange = priorityChange !== false;
 +			if(this.generic){
 +				var map = {
 +					"Arial": "sans-serif",
 +					"Helvetica": "sans-serif",
 +					"Myriad": "sans-serif",
 +					"Times": "serif",
 +					"Times New Roman": "serif",
 +					"Comic Sans MS": "cursive",
 +					"Apple Chancery": "cursive",
 +					"Courier": "monospace",
 +					"Courier New": "monospace",
 +					"Papyrus": "fantasy",
 +					"Estrangelo Edessa": "cursive", // Windows 7
 +					"Gabriola": "fantasy" // Windows 7
 +				};
 +				value = map[value] || value;
 +			}
 +			this.inherited(arguments, [value, priorityChange]);
 +		}
 +	});
 +
 +	var _FontSizeDropDown = declare("dijit._editor.plugins._FontSizeDropDown", _FontDropDown, {
 +		// summary:
 +		//		Dropdown to select a font size; goes in editor toolbar.
 +
 +		// command: [public] String
 +		//		The editor 'command' implemented by this plugin.
 +		command: "fontSize",
 +
 +		comboClass: "dijitFontSizeCombo",
 +
 +		// values: [public] Number[]
 +		//		The HTML font size values supported by this plugin
 +		values: [1, 2, 3, 4, 5, 6, 7], // sizes according to the old HTML FONT SIZE
 +
 +		getLabel: function(value, name){
 +			// summary:
 +			//		Function used to generate the labels of the format dropdown
 +			//		will return a formatted, or plain label based on the value
 +			//		of the plainText option.
 +			//		We're stuck using the deprecated FONT tag to correspond
 +			//		with the size measurements used by the editor
 +			// value: String
 +			//		The 'insert value' associated with a name
 +			// name: String
 +			//		The text name of the value
 +			if(this.plainText){
 +				return name;
 +			}else{
 +				return "<font size=" + value + "'>" + name + "</font>";
 +			}
 +		},
 +
 +		_setValueAttr: function(value, priorityChange){
 +			// summary:
 +			//		Over-ride for the default action of setting the
 +			//		widget value, maps the input to known values
 +			priorityChange = priorityChange !== false;
 +			if(value.indexOf && value.indexOf("px") != -1){
 +				var pixels = parseInt(value, 10);
 +				value = {10: 1, 13: 2, 16: 3, 18: 4, 24: 5, 32: 6, 48: 7}[pixels] || value;
 +			}
 +
 +			this.inherited(arguments, [value, priorityChange]);
 +		}
 +	});
 +
 +
 +	var _FormatBlockDropDown = declare("dijit._editor.plugins._FormatBlockDropDown", _FontDropDown, {
 +		// summary:
 +		//		Dropdown to select a format (like paragraph or heading); goes in editor toolbar.
 +
 +		// command: [public] String
 +		//		The editor 'command' implemented by this plugin.
 +		command: "formatBlock",
 +
 +		comboClass: "dijitFormatBlockCombo",
 +
 +		// values: [public] Array
 +		//		The HTML format tags supported by this plugin
 +		values: ["noFormat", "p", "h1", "h2", "h3", "pre"],
 +
 +		postCreate: function(){
 +			// Init and set the default value to no formatting.  Update state will adjust it
 +			// as needed.
 +			this.inherited(arguments);
 +			this.set("value", "noFormat", false);
 +		},
 +
 +		getLabel: function(value, name){
 +			// summary:
 +			//		Function used to generate the labels of the format dropdown
 +			//		will return a formatted, or plain label based on the value
 +			//		of the plainText option.
 +			// value: String
 +			//		The 'insert value' associated with a name
 +			// name: String
 +			//		The text name of the value
 +			if(this.plainText || value == "noFormat"){
 +				return name;
 +			}else{
 +				return "<" + value + ">" + name + "</" + value + ">";
 +			}
 +		},
 +
 +		_execCommand: function(editor, command, choice){
 +			// summary:
 +			//		Over-ride for default exec-command label.
 +			//		Allows us to treat 'none' as special.
 +			if(choice === "noFormat"){
 +				var start;
 +				var end;
 +				var sel = rangeapi.getSelection(editor.window);
 +				if(sel && sel.rangeCount > 0){
 +					var range = sel.getRangeAt(0);
 +					var node, tag;
 +					if(range){
 +						start = range.startContainer;
 +						end = range.endContainer;
 +
 +						// find containing nodes of start/end.
 +						while(start && start !== editor.editNode &&
 +							start !== editor.document.body &&
 +							start.nodeType !== 1){
 +							start = start.parentNode;
 +						}
 +
 +						while(end && end !== editor.editNode &&
 +							end !== editor.document.body &&
 +							end.nodeType !== 1){
 +							end = end.parentNode;
 +						}
 +
 +						var processChildren = lang.hitch(this, function(node, ary){
 +							if(node.childNodes && node.childNodes.length){
 +								var i;
 +								for(i = 0; i < node.childNodes.length; i++){
 +									var c = node.childNodes[i];
 +									if(c.nodeType == 1){
 +										if(editor.selection.inSelection(c)){
 +											var tag = c.tagName ? c.tagName.toLowerCase() : "";
 +											if(array.indexOf(this.values, tag) !== -1){
 +												ary.push(c);
 +											}
 +											processChildren(c, ary);
 +										}
 +									}
 +								}
 +							}
 +						});
 +
 +						var unformatNodes = lang.hitch(this, function(nodes){
 +							// summary:
 +							//		Internal function to clear format nodes.
 +							// nodes:
 +							//		The array of nodes to strip formatting from.
 +							if(nodes && nodes.length){
 +								editor.beginEditing();
 +								while(nodes.length){
 +									this._removeFormat(editor, nodes.pop());
 +								}
 +								editor.endEditing();
 +							}
 +						});
 +
 +						var clearNodes = [];
 +						if(start == end){
 +							//Contained within the same block, may be collapsed, but who cares, see if we
 +							// have a block element to remove.
 +							var block;
 +							node = start;
 +							while(node && node !== editor.editNode && node !== editor.document.body){
 +								if(node.nodeType == 1){
 +									tag = node.tagName ? node.tagName.toLowerCase() : "";
 +									if(array.indexOf(this.values, tag) !== -1){
 +										block = node;
 +										break;
 +									}
 +								}
 +								node = node.parentNode;
 +							}
 +
 +							//Also look for all child nodes in the selection that may need to be
 +							//cleared of formatting
 +							processChildren(start, clearNodes);
 +							if(block){
 +								clearNodes = [block].concat(clearNodes);
 +							}
 +							unformatNodes(clearNodes);
 +						}else{
 +							// Probably a multi select, so we have to process it.  Whee.
 +							node = start;
 +							while(editor.selection.inSelection(node)){
 +								if(node.nodeType == 1){
 +									tag = node.tagName ? node.tagName.toLowerCase() : "";
 +									if(array.indexOf(this.values, tag) !== -1){
 +										clearNodes.push(node);
 +									}
 +									processChildren(node, clearNodes);
 +								}
 +								node = node.nextSibling;
 +							}
 +							unformatNodes(clearNodes);
 +						}
 +						editor.onDisplayChanged();
 +					}
 +				}
 +			}else{
 +				editor.execCommand(command, choice);
 +			}
 +		},
 +
 +		_removeFormat: function(editor, node){
 +			// summary:
 +			//		function to remove the block format node.
 +			// node:
 +			//		The block format node to remove (and leave the contents behind)
 +			if(editor.customUndo){
 +				// So of course IE doesn't work right with paste-overs.
 +				// We have to do this manually, which is okay since IE already uses
 +				// customUndo and we turned it on for WebKit.  WebKit pasted funny,
 +				// so couldn't use the execCommand approach
 +				while(node.firstChild){
 +					domConstruct.place(node.firstChild, node, "before");
 +				}
 +				node.parentNode.removeChild(node);
 +			}else{
 +				// Everyone else works fine this way, a paste-over and is native
 +				// undo friendly.
 +				editor.selection.selectElementChildren(node);
 +				var html = editor.selection.getSelectedHtml();
 +				editor.selection.selectElement(node);
 +				editor.execCommand("inserthtml", html || "");
 +			}
 +		}
 +	});
 +
 +	// TODO: for 2.0, split into FontChoice plugin into three separate classes,
 +	// one for each command (and change registry below)
 +	var FontChoice = declare("dijit._editor.plugins.FontChoice", _Plugin, {
 +		// summary:
 +		//		This plugin provides three drop downs for setting style in the editor
 +		//		(font, font size, and format block), as controlled by command.
 +		//
 +		// description:
 +		//		The commands provided by this plugin are:
 +		//
 +		//		- fontName: Provides a drop down to select from a list of font names
 +		//		- fontSize: Provides a drop down to select from a list of font sizes
 +		//		- formatBlock: Provides a drop down to select from a list of block styles
 +		//		  which can easily be added to an editor by including one or more of the above commands
 +		//		  in the `plugins` attribute as follows:
 +		//
 +		//	|	plugins="['fontName','fontSize',...]"
 +		//
 +		//		It is possible to override the default dropdown list by providing an Array for the `custom` property when
 +		//		instantiating this plugin, e.g.
 +		//
 +		//	|	plugins="[{name:'dijit._editor.plugins.FontChoice', command:'fontName', values:['Verdana','Myriad','Garamond']},...]"
 +		//
 +		//		Alternatively, for `fontName` only, `generic:true` may be specified to provide a dropdown with
 +		//		[CSS generic font families](http://www.w3.org/TR/REC-CSS2/fonts.html#generic-font-families).
 +		//
 +		//		Note that the editor is often unable to properly handle font styling information defined outside
 +		//		the context of the current editor instance, such as pre-populated HTML.
 +
 +		// useDefaultCommand: [protected] Boolean
 +		//		Override _Plugin.useDefaultCommand...
 +		//		processing is handled by this plugin, not by dijit/Editor.
 +		useDefaultCommand: false,
 +
 +		_initButton: function(){
 +			// summary:
 +			//		Overrides _Plugin._initButton(), to initialize the FilteringSelect+label in toolbar,
 +			//		rather than a simple button.
 +			// tags:
 +			//		protected
 +
 +			// Create the widget to go into the toolbar (the so-called "button")
 +			var clazz = {
 +					fontName: _FontNameDropDown,
 +					fontSize: _FontSizeDropDown,
 +					formatBlock: _FormatBlockDropDown
 +				}[this.command],
 +				params = this.params;
 +
 +			// For back-compat reasons support setting custom values via "custom" parameter
 +			// rather than "values" parameter.   Remove in 2.0.
 +			if(this.params.custom){
 +				params.values = this.params.custom;
 +			}
 +
 +			var editor = this.editor;
 +			this.button = new clazz(lang.delegate({dir: editor.dir, lang: editor.lang}, params));
 +
 +			// Reflect changes to the drop down in the editor
 +			this.own(this.button.select.on("change", lang.hitch(this, function(choice){
 +				// User invoked change, since all internal updates set priorityChange to false and will
 +				// not trigger an onChange event.
 +
 +				if(this.editor.focused){
 +					// put focus back in the iframe, unless focus has somehow been shifted out of the editor completely
 +					this.editor.focus();
 +				}
 +
 +				if(this.command == "fontName" && choice.indexOf(" ") != -1){
 +					choice = "'" + choice + "'";
 +				}
 +
 +				// Invoke, the editor already normalizes commands called through its
 +				// execCommand.
 +				if(this.button._execCommand){
 +					this.button._execCommand(this.editor, this.command, choice);
 +				}else{
 +					this.editor.execCommand(this.command, choice);
 +				}
 +			})));
 +		},
 +
 +		updateState: function(){
 +			// summary:
 +			//		Overrides _Plugin.updateState().  This controls updating the menu
 +			//		options to the right values on state changes in the document (that trigger a
 +			//		test of the actions.)
 +			//		It set value of drop down in toolbar to reflect font/font size/format block
 +			//		of text at current caret position.
 +			// tags:
 +			//		protected
 +			var _e = this.editor;
 +			var _c = this.command;
 +			if(!_e || !_e.isLoaded || !_c.length){
 +				return;
 +			}
 +
 +			if(this.button){
 +				var disabled = this.get("disabled");
 +				this.button.set("disabled", disabled);
 +				if(disabled){
 +					return;
 +				}
 +				var value;
 +				try{
 +					value = _e.queryCommandValue(_c) || "";
 +				}catch(e){
 +					//Firefox may throw error above if the editor is just loaded, ignore it
 +					value = "";
 +				}
 +
 +				// strip off single quotes, if any
- 				var quoted = lang.isString(value) && value.match(/'([^']*)'/);
++				var quoted = lang.isString(value) && (value.match(/'([^']*)'/) || value.match(/"([^"]*)"/));
 +				if(quoted){
 +					value = quoted[1];
 +				}
 +
 +				if(_c === "formatBlock"){
 +					if(!value || value == "p"){
 +						// Some browsers (WebKit) doesn't actually get the tag info right.
 +						// and IE returns paragraph when in a DIV!, so incorrect a lot,
 +						// so we have double-check it.
 +						value = null;
 +						var elem;
 +						// Try to find the current element where the caret is.
 +						var sel = rangeapi.getSelection(this.editor.window);
 +						if(sel && sel.rangeCount > 0){
 +							var range = sel.getRangeAt(0);
 +							if(range){
 +								elem = range.endContainer;
 +							}
 +						}
 +
 +						// Okay, now see if we can find one of the formatting types we're in.
 +						while(elem && elem !== _e.editNode && elem !== _e.document){
 +							var tg = elem.tagName ? elem.tagName.toLowerCase() : "";
 +							if(tg && array.indexOf(this.button.values, tg) > -1){
 +								value = tg;
 +								break;
 +							}
 +							elem = elem.parentNode;
 +						}
 +						if(!value){
 +							// Still no value, so lets select 'none'.
 +							value = "noFormat";
 +						}
 +					}else{
 +						// Check that the block format is one allowed, if not,
 +						// null it so that it gets set to empty.
 +						if(array.indexOf(this.button.values, value) < 0){
 +							value = "noFormat";
 +						}
 +					}
 +				}
 +				if(value !== this.button.get("value")){
 +					// Set the value, but denote it is not a priority change, so no
 +					// onchange fires.
 +					this.button.set('value', value, false);
 +				}
 +			}
 +		}
 +	});
 +
 +	// Register these plugins
 +	array.forEach(["fontName", "fontSize", "formatBlock"], function(name){
 +		_Plugin.registry[name] = function(args){
 +			return new FontChoice({
 +				command: name,
 +				plainText: args.plainText
 +			});
 +		};
 +	});
 +
 +	// Make all classes available through AMD, and return main class
 +	FontChoice._FontDropDown = _FontDropDown;
 +	FontChoice._FontNameDropDown = _FontNameDropDown;
 +	FontChoice._FontSizeDropDown = _FontSizeDropDown;
 +	FontChoice._FormatBlockDropDown = _FormatBlockDropDown;
 +	return FontChoice;
 +
 +});
diff --cc dijit/form/ComboBoxMixin.js
index c841d69,0000000..5d2e630
mode 100644,000000..100644
--- a/dijit/form/ComboBoxMixin.js
+++ b/dijit/form/ComboBoxMixin.js
@@@ -1,141 -1,0 +1,147 @@@
 +define([
 +	"dojo/_base/declare", // declare
 +	"dojo/Deferred",
 +	"dojo/_base/kernel", // kernel.deprecated
 +	"dojo/_base/lang", // lang.mixin
 +	"dojo/store/util/QueryResults",
 +	"./_AutoCompleterMixin",
 +	"./_ComboBoxMenu",
 +	"../_HasDropDown",
 +	"dojo/text!./templates/DropDownBox.html"
 +], function(declare, Deferred, kernel, lang, QueryResults, _AutoCompleterMixin, _ComboBoxMenu, _HasDropDown, template){
 +
 +
 +	// module:
 +	//		dijit/form/ComboBoxMixin
 +
 +	return declare("dijit.form.ComboBoxMixin", [_HasDropDown, _AutoCompleterMixin], {
 +		// summary:
 +		//		Provides main functionality of ComboBox widget
 +
 +		// dropDownClass: [protected extension] Function String
 +		//		Dropdown widget class used to select a date/time.
 +		//		Subclasses should specify this.
 +		dropDownClass: _ComboBoxMenu,
 +
 +		// hasDownArrow: Boolean
 +		//		Set this textbox to have a down arrow button, to display the drop down list.
 +		//		Defaults to true.
 +		hasDownArrow: true,
 +
 +		templateString: template,
 +
 +		baseClass: "dijitTextBox dijitComboBox",
 +
 +		/*=====
 +		// store: [const] dojo/store/api/Store|dojo/data/api/Read
 +		//		Reference to data provider object used by this ComboBox.
 +		//
 +		//		Should be dojo/store/api/Store, but dojo/data/api/Read supported
 +		//		for backwards compatibility.
 +		store: null,
 +		=====*/
 +
 +		// Set classes like dijitDownArrowButtonHover depending on
 +		// mouse action over button node
 +		cssStateNodes: {
 +			"_buttonNode": "dijitDownArrowButton"
 +		},
 +
 +		_setHasDownArrowAttr: function(/*Boolean*/ val){
 +			this._set("hasDownArrow", val);
 +			this._buttonNode.style.display = val ? "" : "none";
 +		},
 +
 +		_showResultList: function(){
 +			// hide the tooltip
 +			this.displayMessage("");
 +			this.inherited(arguments);
 +		},
 +
 +		_setStoreAttr: function(store){
 +			// For backwards-compatibility, accept dojo.data store in addition to dojo/store/api/Store.  Remove in 2.0.
 +			if(!store.get){
 +				lang.mixin(store, {
 +					_oldAPI: true,
 +					get: function(id){
 +						// summary:
 +						//		Retrieves an object by it's identity. This will trigger a fetchItemByIdentity.
 +						//		Like dojo/store/DataStore.get() except returns native item.
 +						var deferred = new Deferred();
 +						this.fetchItemByIdentity({
 +							identity: id,
 +							onItem: function(object){
 +								deferred.resolve(object);
 +							},
 +							onError: function(error){
 +								deferred.reject(error);
 +							}
 +						});
 +						return deferred.promise;
 +					},
 +					query: function(query, options){
 +						// summary:
 +						//		Queries the store for objects.   Like dojo/store/DataStore.query()
 +						//		except returned Deferred contains array of native items.
 +						var deferred = new Deferred(function(){ fetchHandle.abort && fetchHandle.abort(); });
 +						deferred.total = new Deferred();
 +						var fetchHandle = this.fetch(lang.mixin({
 +							query: query,
 +							onBegin: function(count){
 +								deferred.total.resolve(count);
 +							},
 +							onComplete: function(results){
 +								deferred.resolve(results);
 +							},
 +							onError: function(error){
 +								deferred.reject(error);
 +							}
 +						}, options));
 +						return QueryResults(deferred);
 +					}
 +				});
 +			}
 +			this._set("store", store);
 +		},
 +
 +		postMixInProperties: function(){
 +			// Since _setValueAttr() depends on this.store, _setStoreAttr() needs to execute first.
 +			// Unfortunately, without special code, it ends up executing second.
 +			var store = this.params.store || this.store;
 +			if(store){
 +				this._setStoreAttr(store);
 +			}
 +
 +			this.inherited(arguments);
 +
 +			// User may try to access this.store.getValue() etc.  in a custom labelFunc() function.
 +			// It's not available with the new data store for handling inline <option> tags, so add it.
 +			if(!this.params.store && this.store && !this.store._oldAPI){
 +				var clazz = this.declaredClass;
 +				lang.mixin(this.store, {
 +					getValue: function(item, attr){
 +						kernel.deprecated(clazz + ".store.getValue(item, attr) is deprecated for builtin store.  Use item.attr directly", "", "2.0");
 +						return item[attr];
 +					},
 +					getLabel: function(item){
 +						kernel.deprecated(clazz + ".store.getLabel(item) is deprecated for builtin store.  Use item.label directly", "", "2.0");
 +						return item.name;
 +					},
 +					fetch: function(args){
 +						kernel.deprecated(clazz + ".store.fetch() is deprecated for builtin store.", "Use store.query()", "2.0");
 +						var shim = ["dojo/data/ObjectStore"];	// indirection so it doesn't get rolled into a build
 +						require(shim, lang.hitch(this, function(ObjectStore){
 +							new ObjectStore({objectStore: this}).fetch(args);
 +						}));
 +					}
 +				});
 +			}
++		},
++
++		buildRendering: function(){
++			this.inherited(arguments);
++
++			this.focusNode.setAttribute("aria-autocomplete", this.autoComplete ? "both" : "list");
 +		}
 +	});
 +});
diff --cc dijit/form/NumberTextBox.js
index 620dfe1,0000000..02eee3e
mode 100644,000000..100644
--- a/dijit/form/NumberTextBox.js
+++ b/dijit/form/NumberTextBox.js
@@@ -1,394 -1,0 +1,407 @@@
 +define([
 +	"dojo/_base/declare", // declare
 +	"dojo/_base/lang", // lang.hitch lang.mixin
 +	"dojo/i18n", // i18n.normalizeLocale, i18n.getLocalization
 +	"dojo/string", // string.rep
 +	"dojo/number", // number._realNumberRegexp number.format number.parse number.regexp
 +	"./RangeBoundTextBox"
 +], function(declare, lang, i18n, string, number, RangeBoundTextBox){
 +
 +	// module:
 +	//		dijit/form/NumberTextBox
 +
 +	// A private helper function to determine decimal information
 +	// Returns an object with "sep" and "places" properties
 +	var getDecimalInfo = function(constraints){
 +		var constraints = constraints || {},
 +			bundle = i18n.getLocalization("dojo.cldr", "number", i18n.normalizeLocale(constraints.locale)),
- 			pattern = constraints.pattern ? constraints.pattern : bundle[(constraints.type || "decimal")+"Format"],
- 			placesSpecified = typeof constraints.places == "number",
- 			// The "places" property trumps the pattern property if both are specified in number.format, we follow the same
- 			// logic here
- 			places = placesSpecified ? constraints.places : (pattern.indexOf(".") != -1 ? pattern.split(".")[1].replace(/[^#0]/g, "").length : 0);
++			pattern = constraints.pattern ? constraints.pattern : bundle[(constraints.type || "decimal")+"Format"];
++
++		// The number of places in the constraint can be specified in several ways,
++		// the resolution order is:
++		//
++		// 1. If constraints.places is a number, use that
++		// 2. If constraints.places is a string, which specifies a range, use the range max (e.g. 0,4)
++		// 3. If a pattern is specified, use the implicit number of places in the pattern.
++		// 4. If neither constraints.pattern or constraints.places is specified, use the locale default pattern
++		var places;
++		if(typeof constraints.places == "number"){
++			places = constraints.places;
++		}else if(typeof constraints.places === "string" && constraints.places.length > 0){
++			places = constraints.places.replace(/.*,/, "");
++		}else{
++			places = (pattern.indexOf(".") != -1 ? pattern.split(".")[1].replace(/[^#0]/g, "").length : 0);
++		}
++
 +		return { sep: bundle.decimal, places: places };
 +	};
 +
 +	var NumberTextBoxMixin = declare("dijit.form.NumberTextBoxMixin", null, {
 +		// summary:
 +		//		A mixin for all number textboxes
 +		// tags:
 +		//		protected
 +
 +		// Override ValidationTextBox.pattern.... we use a reg-ex generating function rather
 +		// than a straight regexp to deal with locale (plus formatting options too?)
 +		pattern: function(constraints){
 +			// if focused, accept either currency data or NumberTextBox format
 +			return '(' + (this.focused && this.editOptions ? this._regExpGenerator(lang.delegate(constraints, this.editOptions)) + '|' : '')
 +				+ this._regExpGenerator(constraints) + ')';
 +		},
 +
 +		/*=====
 +		// constraints: NumberTextBox.__Constraints
 +		//		Despite the name, this parameter specifies both constraints on the input
 +		//		(including minimum/maximum allowed values) as well as
 +		//		formatting options like places (the number of digits to display after
 +		//		the decimal point).
 +		constraints: {},
 +		======*/
 +
 +		// value: Number
 +		//		The value of this NumberTextBox as a Javascript Number (i.e., not a String).
 +		//		If the displayed value is blank, the value is NaN, and if the user types in
 +		//		an gibberish value (like "hello world"), the value is undefined
 +		//		(i.e. get('value') returns undefined).
 +		//
 +		//		Symmetrically, set('value', NaN) will clear the displayed value,
 +		//		whereas set('value', undefined) will have no effect.
 +		value: NaN,
 +
 +		// editOptions: [protected] Object
 +		//		Properties to mix into constraints when the value is being edited.
 +		//		This is here because we edit the number in the format "12345", which is
 +		//		different than the display value (ex: "12,345")
 +		editOptions: { pattern: '#.######' },
 +
 +		/*=====
 +		_formatter: function(value, options){
 +			// summary:
 +			//		_formatter() is called by format().  It's the base routine for formatting a number,
 +			//		as a string, for example converting 12345 into "12,345".
 +			// value: Number
 +			//		The number to be converted into a string.
 +			// options: number.__FormatOptions?
 +			//		Formatting options
 +			// tags:
 +			//		protected extension
 +
 +			return "12345";		// String
 +		},
 +		 =====*/
 +		_formatter: number.format,
 +
 +		/*=====
 +		_regExpGenerator: function(constraints){
 +			// summary:
 +			//		Generate a localized regular expression as a string, according to constraints.
 +			// constraints: number.__ParseOptions
 +			//		Formatting options
 +			// tags:
 +			//		protected
 +
 +			return "(\d*).(\d*)";	// string
 +		},
 +		=====*/
 +		_regExpGenerator: number.regexp,
 +
 +		// _decimalInfo: Object
 +		// summary:
 +		//		An object containing decimal related properties relevant to this TextBox.
 +		// tags:
 +		//		private
 +		_decimalInfo: getDecimalInfo(),
 +
 +		postMixInProperties: function(){
 +			this.inherited(arguments);
 +			this._set("type", "text"); // in case type="number" was specified which messes up parse/format
 +		},
 +
 +		_setConstraintsAttr: function(/*Object*/ constraints){
 +			var places = typeof constraints.places == "number"? constraints.places : 0;
 +			if(places){ places++; } // decimal rounding errors take away another digit of precision
 +			if(typeof constraints.max != "number"){
 +				constraints.max = 9 * Math.pow(10, 15-places);
 +			}
 +			if(typeof constraints.min != "number"){
 +				constraints.min = -9 * Math.pow(10, 15-places);
 +			}
 +			this.inherited(arguments, [ constraints ]);
 +			if(this.focusNode && this.focusNode.value && !isNaN(this.value)){
 +				this.set('value', this.value);
 +			}
 +			// Capture decimal information based on the constraint locale and pattern.
 +			this._decimalInfo = getDecimalInfo(constraints);
 +		},
 +
 +		_onFocus: function(){
 +			if(this.disabled || this.readOnly){ return; }
 +			var val = this.get('value');
 +			if(typeof val == "number" && !isNaN(val)){
 +				var formattedValue = this.format(val, this.constraints);
 +				if(formattedValue !== undefined){
 +					this.textbox.value = formattedValue;
 +				}
 +			}
 +			this.inherited(arguments);
 +		},
 +
 +		format: function(/*Number*/ value, /*number.__FormatOptions*/ constraints){
 +			// summary:
 +			//		Formats the value as a Number, according to constraints.
 +			// tags:
 +			//		protected
 +
 +			var formattedValue = String(value);
 +			if(typeof value != "number"){ return formattedValue; }
 +			if(isNaN(value)){ return ""; }
 +			// check for exponential notation that dojo/number.format() chokes on
 +			if(!("rangeCheck" in this && this.rangeCheck(value, constraints)) && constraints.exponent !== false && /\de[-+]?\d/i.test(formattedValue)){
 +				return formattedValue;
 +			}
 +			if(this.editOptions && this.focused){
 +				constraints = lang.mixin({}, constraints, this.editOptions);
 +			}
 +			return this._formatter(value, constraints);
 +		},
 +
 +		/*=====
 +		_parser: function(value, constraints){
 +			// summary:
 +			//		Parses the string value as a Number, according to constraints.
 +			// value: String
 +			//		String representing a number
 +			// constraints: number.__ParseOptions
 +			//		Formatting options
 +			// tags:
 +			//		protected
 +
 +			return 123.45;		// Number
 +		},
 +		=====*/
 +		_parser: number.parse,
 +
 +		parse: function(/*String*/ value, /*number.__FormatOptions*/ constraints){
 +			// summary:
 +			//		Replaceable function to convert a formatted string to a number value
 +			// tags:
 +			//		protected extension
 +
 +			var v = this._parser(value, lang.mixin({}, constraints, (this.editOptions && this.focused) ? this.editOptions : {}));
 +			if(this.editOptions && this.focused && isNaN(v)){
 +				v = this._parser(value, constraints); // parse w/o editOptions: not technically needed but is nice for the user
 +			}
 +			return v;
 +		},
 +
 +		_getDisplayedValueAttr: function(){
 +			var v = this.inherited(arguments);
 +			return isNaN(v) ? this.textbox.value : v;
 +		},
 +
 +		filter: function(/*Number*/ value){
 +			// summary:
 +			//		This is called with both the display value (string), and the actual value (a number).
 +			//		When called with the actual value it does corrections so that '' etc. are represented as NaN.
 +			//		Otherwise it dispatches to the superclass's filter() method.
 +			//
 +			//		See `dijit/form/TextBox.filter()` for more details.
 +			if(value == null  /* or undefined */ || typeof value == "string" && value ==''){
 +				return NaN;
 +			}else if(typeof value == "number" && !isNaN(value) && value != 0){
 +				value = number.round(value, this._decimalInfo.places);
 +			}
 +			return this.inherited(arguments, [value]);
 +		},
 +
 +		serialize: function(/*Number*/ value, /*Object?*/ options){
 +			// summary:
 +			//		Convert value (a Number) into a canonical string (ie, how the number literal is written in javascript/java/C/etc.)
 +			// tags:
 +			//		protected
 +			return (typeof value != "number" || isNaN(value)) ? '' : this.inherited(arguments);
 +		},
 +
 +		_setBlurValue: function(){
 +			var val = lang.hitch(lang.delegate(this, { focused: true }), "get")('value'); // parse with editOptions
 +			this._setValueAttr(val, true);
 +		},
 +
 +		_setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
 +			// summary:
 +			//		Hook so set('value', ...) works.
 +			if(value !== undefined && formattedValue === undefined){
 +				formattedValue = String(value);
 +				if(typeof value == "number"){
 +					if(isNaN(value)){ formattedValue = '' }
 +					// check for exponential notation that number.format chokes on
 +					else if(("rangeCheck" in this && this.rangeCheck(value, this.constraints)) || this.constraints.exponent === false || !/\de[-+]?\d/i.test(formattedValue)){
 +						formattedValue = undefined; // lets format compute a real string value
 +					}
 +				}else if(!value){ // 0 processed in if branch above, ''|null|undefined flows through here
 +					formattedValue = '';
 +					value = NaN;
 +				}else{ // non-numeric values
 +					value = undefined;
 +				}
 +			}
 +			this.inherited(arguments, [value, priorityChange, formattedValue]);
 +		},
 +
 +		_getValueAttr: function(){
 +			// summary:
 +			//		Hook so get('value') works.
 +			//		Returns Number, NaN for '', or undefined for unparseable text
 +			var v = this.inherited(arguments); // returns Number for all values accepted by parse() or NaN for all other displayed values
 +
 +			// If the displayed value of the textbox is gibberish (ex: "hello world"), this.inherited() above
 +			// returns NaN; this if() branch converts the return value to undefined.
 +			// Returning undefined prevents user text from being overwritten when doing _setValueAttr(_getValueAttr()).
 +			// A blank displayed value is still returned as NaN.
 +			if(isNaN(v) && this.textbox.value !== ''){
 +				if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value) && (new RegExp("^"+number._realNumberRegexp(lang.delegate(this.constraints))+"$").test(this.textbox.value))){	// check for exponential notation that parse() rejected (erroneously?)
 +					var n = Number(this.textbox.value);
 +					return isNaN(n) ? undefined : n; // return exponential Number or undefined for random text (may not be possible to do with the above RegExp check)
 +				}else{
 +					return undefined; // gibberish
 +				}
 +			}else{
 +				return v; // Number or NaN for ''
 +			}
 +		},
 +
 +		isValid: function(/*Boolean*/ isFocused){
 +			// Overrides dijit/form/RangeBoundTextBox.isValid() to check that the editing-mode value is valid since
 +			// it may not be formatted according to the regExp validation rules
 +			if(!this.focused || this._isEmpty(this.textbox.value)){
 +				return this.inherited(arguments);
 +			}else{
 +				var v = this.get('value');
 +				if(!isNaN(v) && this.rangeCheck(v, this.constraints)){
 +					if(this.constraints.exponent !== false && /\de[-+]?\d/i.test(this.textbox.value)){ // exponential, parse doesn't like it
 +						return true; // valid exponential number in range
 +					}else{
 +						return this.inherited(arguments);
 +					}
 +				}else{
 +					return false;
 +				}
 +			}
 +		},
 +
 +		_isValidSubset: function(){
 +			// Overrides dijit/form/ValidationTextBox._isValidSubset()
 +			//
 +			// The inherited method only checks that the computed regex pattern is valid, which doesn't
 +			// take into account that numbers are a special case. Specifically:
 +			//
 +			//  (1) An arbitrary amount of leading or trailing zero's can be ignored.
 +			//  (2) Since numeric input always occurs in the order of most significant to least significant
 +			//      digits, the maximum and minimum possible values for partially inputted numbers can easily
 +			//      be determined by using the number of remaining digit spaces available.
 +			//
 +			// For example, if an input has a maxLength of 5, and a min value of greater than 100, then the subset
 +			// is invalid if there are 3 leading 0s. It remains valid for the first two.
 +			//
 +			// Another example is if the min value is 1.1. Once a value of 1.0 is entered, no additional trailing digits
 +			// could possibly satisify the min requirement.
 +			//
 +			// See ticket #17923
 +			var hasMinConstraint = (typeof this.constraints.min == "number"),
 +				hasMaxConstraint = (typeof this.constraints.max == "number"),
 +				curVal = this.get('value');
 +
 +			// If there is no parsable number, or there are no min or max bounds, then we can safely
 +			// skip all remaining checks
 +			if(isNaN(curVal) || (!hasMinConstraint && !hasMaxConstraint)){
 +				return this.inherited(arguments);
 +			}
 +
 +			// This block picks apart the values in the text box to be used later to compute the min and max possible
 +			// values based on the current value and the remaining available digits.
 +			//
 +			// Warning: The use of a "num|0" expression, can be confusing. See the link below
 +			// for an explanation.
 +			//
 +			// http://stackoverflow.com/questions/12125421/why-does-a-shift-by-0-truncate-the-decimal
 +			var integerDigits = curVal|0,
 +				valNegative = curVal < 0,
 +				// Check if the current number has a decimal based on its locale
 +				hasDecimal = this.textbox.value.indexOf(this._decimalInfo.sep) != -1,
 +				// Determine the max digits based on the textbox length. If no length is
 +				// specified, chose a huge number to account for crazy formatting.
 +				maxDigits = this.maxLength || 20,
 +				// Determine the remaining digits, based on the max digits
 +				remainingDigitsCount = maxDigits - this.textbox.value.length,
 +				// avoid approximation issues by capturing the decimal portion of the value as the user-entered string
 +				fractionalDigitStr = hasDecimal ? this.textbox.value.split(this._decimalInfo.sep)[1].replace(/[^0-9]/g, "") : "";
 +
 +			// Create a normalized value string in the form of #.###
 +			var normalizedValueStr = hasDecimal ? integerDigits+"."+fractionalDigitStr : integerDigits+"";
 +
 +			// The min and max values for the field can be determined using the following
 +			// logic:
 +			//
 +			//  If the number is positive:
 +			//      min value = the current value
 +			//      max value = the current value with 9s appended for all remaining possible digits
 +			//  else
 +			//      min value = the current value with 9s appended for all remaining possible digits
 +			//      max value = the current value
 +			//
 +			var ninePaddingStr = string.rep("9", remainingDigitsCount),
 +			    minPossibleValue = curVal,
 +			    maxPossibleValue = curVal;
 +			if (valNegative){
 +				minPossibleValue = Number(normalizedValueStr+ninePaddingStr);
 +			} else{
 +				maxPossibleValue = Number(normalizedValueStr+ninePaddingStr);
 +			}
 +
 +			return !((hasMinConstraint && maxPossibleValue < this.constraints.min)
 +					|| (hasMaxConstraint && minPossibleValue > this.constraints.max));
 +		}
 +	});
 +
 +	var NumberTextBox = declare("dijit.form.NumberTextBox", [RangeBoundTextBox, NumberTextBoxMixin], {
 +		// summary:
 +		//		A TextBox for entering numbers, with formatting and range checking
 +		// description:
 +		//		NumberTextBox is a textbox for entering and displaying numbers, supporting
 +		//		the following main features:
 +		//
 +		//		1. Enforce minimum/maximum allowed values (as well as enforcing that the user types
 +		//			a number rather than a random string)
 +		//		2. NLS support (altering roles of comma and dot as "thousands-separator" and "decimal-point"
 +		//			depending on locale).
 +		//		3. Separate modes for editing the value and displaying it, specifically that
 +		//			the thousands separator character (typically comma) disappears when editing
 +		//			but reappears after the field is blurred.
 +		//		4. Formatting and constraints regarding the number of places (digits after the decimal point)
 +		//			allowed on input, and number of places displayed when blurred (see `constraints` parameter).
 +
 +		baseClass: "dijitTextBox dijitNumberTextBox"
 +	});
 +
 +	NumberTextBox.Mixin = NumberTextBoxMixin;	// for monkey patching
 +
 +	/*=====
 +	 NumberTextBox.__Constraints = declare([RangeBoundTextBox.__Constraints, number.__FormatOptions, number.__ParseOptions], {
 +		 // summary:
 +		 //		Specifies both the rules on valid/invalid values (minimum, maximum,
 +		 //		number of required decimal places), and also formatting options for
 +		 //		displaying the value when the field is not focused.
 +		 // example:
 +		 //		Minimum/maximum:
 +		 //		To specify a field between 0 and 120:
 +		 //	|		{min:0,max:120}
 +		 //		To specify a field that must be an integer:
 +		 //	|		{fractional:false}
 +		 //		To specify a field where 0 to 3 decimal places are allowed on input:
 +		 //	|		{places:'0,3'}
 +	 });
 +	 =====*/
 +
 +	return NumberTextBox;
 +});
diff --cc dijit/form/_DateTimeTextBox.js
index fa91fe3,0000000..0cd4acf
mode 100644,000000..100644
--- a/dijit/form/_DateTimeTextBox.js
+++ b/dijit/form/_DateTimeTextBox.js
@@@ -1,275 -1,0 +1,293 @@@
 +define([
 +	"dojo/date", // date date.compare
 +	"dojo/date/locale", // locale.regexp
 +	"dojo/date/stamp", // stamp.fromISOString stamp.toISOString
 +	"dojo/_base/declare", // declare
 +	"dojo/_base/lang", // lang.getObject
 +	"./RangeBoundTextBox",
 +	"../_HasDropDown",
 +	"dojo/text!./templates/DropDownBox.html"
 +], function(date, locale, stamp, declare, lang, RangeBoundTextBox, _HasDropDown, template){
 +
 +	// module:
 +	//		dijit/form/_DateTimeTextBox
 +
 +	new Date("X"); // workaround for #11279, new Date("") == NaN
 +
 +	var _DateTimeTextBox = declare("dijit.form._DateTimeTextBox", [RangeBoundTextBox, _HasDropDown], {
 +		// summary:
 +		//		Base class for validating, serializable, range-bound date or time text box.
 +
 +		templateString: template,
 +
 +		// hasDownArrow: [const] Boolean
 +		//		Set this textbox to display a down arrow button, to open the drop down list.
 +		hasDownArrow: true,
 +
 +		// Set classes like dijitDownArrowButtonHover depending on mouse action over button node
 +		cssStateNodes: {
 +			"_buttonNode": "dijitDownArrowButton"
 +		},
 +
 +		/*=====
 +		// constraints: _DateTimeTextBox.__Constraints
 +		//		Despite the name, this parameter specifies both constraints on the input
 +		//		(including starting/ending dates/times allowed) as well as
 +		//		formatting options like whether the date is displayed in long (ex: December 25, 2005)
 +		//		or short (ex: 12/25/2005) format.  See `dijit/form/_DateTimeTextBox.__Constraints` for details.
 +		constraints: {},
 +		======*/
 +
 +		// The constraints without the min/max properties. Used by the compare() method
 +		_unboundedConstraints: {},
 +
 +		// Override ValidationTextBox.pattern.... we use a reg-ex generating function rather
 +		// than a straight regexp to deal with locale  (plus formatting options too?)
 +		pattern: locale.regexp,
 +
 +		// datePackage: String
 +		//		JavaScript namespace to find calendar routines.	 If unspecified, uses Gregorian calendar routines
 +		//		at dojo/date and dojo/date/locale.
 +		datePackage: "",
 +		//		TODO: for 2.0, replace datePackage with dateModule and dateLocalModule attributes specifying MIDs,
 +		//		or alternately just get rid of this completely and tell user to use module ID remapping
 +		//		via require
 +
 +		postMixInProperties: function(){
 +			this.inherited(arguments);
 +			this._set("type", "text"); // in case type="date"|"time" was specified which messes up parse/format
 +		},
 +
 +		// Override _FormWidget.compare() to work for dates/times
 +		compare: function(/*Date*/ val1, /*Date*/ val2){
 +			var isInvalid1 = this._isInvalidDate(val1);
 +			var isInvalid2 = this._isInvalidDate(val2);
 +			if (isInvalid1 || isInvalid2){
 +				return (isInvalid1 && isInvalid2) ? 0 : (!isInvalid1 ? 1 : -1);
 +			}
 +			// Format and parse the values before comparing them to make sure that only the parts of the
 +			// date that will make the "round trip" get compared.
 +			var fval1 = this.format(val1, this._unboundedConstraints),
 +				fval2 = this.format(val2, this._unboundedConstraints),
 +				pval1 = this.parse(fval1, this._unboundedConstraints),
 +				pval2 = this.parse(fval2, this._unboundedConstraints);
 +
 +			return fval1 == fval2 ? 0 : date.compare(pval1, pval2, this._selector);
 +		},
 +
 +		// flag to _HasDropDown to make drop down Calendar width == <input> width
 +		autoWidth: true,
 +
 +		format: function(/*Date*/ value, /*locale.__FormatOptions*/ constraints){
 +			// summary:
 +			//		Formats the value as a Date, according to specified locale (second argument)
 +			// tags:
 +			//		protected
 +			if(!value){ return ''; }
 +			return this.dateLocaleModule.format(value, constraints);
 +		},
 +
 +		"parse": function(/*String*/ value, /*locale.__FormatOptions*/ constraints){
 +			// summary:
 +			//		Parses as string as a Date, according to constraints
 +			// tags:
 +			//		protected
 +
 +			return this.dateLocaleModule.parse(value, constraints) || (this._isEmpty(value) ? null : undefined);	 // Date
 +		},
 +
 +		// Overrides ValidationTextBox.serialize() to serialize a date in canonical ISO format.
 +		serialize: function(/*anything*/ val, /*Object?*/ options){
 +			if(val.toGregorian){
 +				val = val.toGregorian();
 +			}
 +			return stamp.toISOString(val, options);
 +		},
 +
 +		// dropDownDefaultValue: Date
 +		//		The default value to focus in the popupClass widget when the textbox value is empty.
 +		dropDownDefaultValue : new Date(),
 +
 +		// value: Date
 +		//		The value of this widget as a JavaScript Date object.  Use get("value") / set("value", val) to manipulate.
 +		//		When passed to the parser in markup, must be specified according to `dojo/date/stamp.fromISOString()`
 +		value: new Date(""),	// value.toString()="NaN"
 +
 +		_blankValue: null,	// used by filter() when the textbox is blank
 +
 +		// popupClass: [protected extension] String
 +		//		Name of the popup widget class used to select a date/time.
 +		//		Subclasses should specify this.
 +		popupClass: "", // default is no popup = text only
 +
 +
 +		// _selector: [protected extension] String
 +		//		Specifies constraints.selector passed to dojo.date functions, should be either
 +		//		"date" or "time".
 +		//		Subclass must specify this.
 +		_selector: "",
 +
 +		constructor: function(params /*===== , srcNodeRef =====*/){
 +			// summary:
 +			//		Create the widget.
 +			// params: Object|null
 +			//		Hash of initialization parameters for widget, including scalar values (like title, duration etc.)
 +			//		and functions, typically callbacks like onClick.
 +			//		The hash can contain any of the widget's properties, excluding read-only properties.
 +			// srcNodeRef: DOMNode|String?
 +			//		If a srcNodeRef (DOM node) is specified, replace srcNodeRef with my generated DOM tree
 +
 +			params = params || {};
 +			this.dateModule = params.datePackage ? lang.getObject(params.datePackage, false) : date;
 +			this.dateClassObj = this.dateModule.Date || Date;
++			if(!(this.dateClassObj instanceof Date)){
++				this.value = new this.dateClassObj(this.value);
++			}
 +			this.dateLocaleModule = params.datePackage ? lang.getObject(params.datePackage+".locale", false) : locale;
 +			this._set('pattern', this.dateLocaleModule.regexp);
 +			this._invalidDate = this.constructor.prototype.value.toString();
 +		},
 +
 +		buildRendering: function(){
 +			this.inherited(arguments);
 +
 +			if(!this.hasDownArrow){
 +				this._buttonNode.style.display = "none";
 +			}
 +
 +			// If hasDownArrow is false, we basically just want to treat the whole widget as the
 +			// button.
 +			if(!this.hasDownArrow){
 +				this._buttonNode = this.domNode;
 +				this.baseClass += " dijitComboBoxOpenOnClick";
 +			}
 +		},
 +
 +		_setConstraintsAttr: function(/*Object*/ constraints){
 +			constraints.selector = this._selector;
 +			constraints.fullYear = true; // see #5465 - always format with 4-digit years
 +			var fromISO = stamp.fromISOString;
- 			if(typeof constraints.min == "string"){ constraints.min = fromISO(constraints.min); }
- 			if(typeof constraints.max == "string"){ constraints.max = fromISO(constraints.max); }
++			if(typeof constraints.min == "string"){
++				constraints.min = fromISO(constraints.min);
++				if(!(this.dateClassObj instanceof Date)){
++					constraints.min = new this.dateClassObj(constraints.min);
++				}
++			}
++			if(typeof constraints.max == "string"){
++				constraints.max = fromISO(constraints.max);
++				if(!(this.dateClassObj instanceof Date)){
++					constraints.max = new this.dateClassObj(constraints.max);
++				}
++			}
 +			this.inherited(arguments);
 +			this._unboundedConstraints = lang.mixin({}, this.constraints, {min: null, max: null});
 +		},
 +
 +		_isInvalidDate: function(/*Date*/ value){
 +			// summary:
 +			//		Runs various tests on the value, checking for invalid conditions
 +			// tags:
 +			//		private
 +			return !value || isNaN(value) || typeof value != "object" || value.toString() == this._invalidDate;
 +		},
 +
 +		_setValueAttr: function(/*Date|String*/ value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){
 +			// summary:
 +			//		Sets the date on this textbox. Note: value can be a JavaScript Date literal or a string to be parsed.
 +			if(value !== undefined){
 +				if(typeof value == "string"){
 +					value = stamp.fromISOString(value);
 +				}
 +				if(this._isInvalidDate(value)){
 +					value = null;
 +				}
 +				if(value instanceof Date && !(this.dateClassObj instanceof Date)){
 +					value = new this.dateClassObj(value);
 +				}
 +			}
- 			this.inherited(arguments);
++			this.inherited(arguments, [value, priorityChange, formattedValue]);
 +			if(this.value instanceof Date){
 +				this.filterString = "";
 +			}
 +			if(this.dropDown){
 +				this.dropDown.set('value', value, false);
 +			}
 +		},
 +
 +		_set: function(attr, value){
 +			// Avoid spurious watch() notifications when value is changed to new Date object w/the same value
- 			var oldValue = this._get("value");
- 			if(attr == "value" && oldValue instanceof Date && this.compare(value, oldValue) == 0){
- 				return;
++			if(attr == "value"){
++				if(value instanceof Date && !(this.dateClassObj instanceof Date)){
++					value = new this.dateClassObj(value);
++				}
++				var oldValue = this._get("value");
++				if(oldValue instanceof this.dateClassObj && this.compare(value, oldValue) == 0){
++					return;
++				}
 +			}
 +			this.inherited(arguments);
 +		},
 +
 +		_setDropDownDefaultValueAttr: function(/*Date*/ val){
 +			if(this._isInvalidDate(val)){
 +				// convert null setting into today's date, since there needs to be *some* default at all times.
 +				 val = new this.dateClassObj();
 +			}
 +			this._set("dropDownDefaultValue", val);
 +		},
 +
 +		openDropDown: function(/*Function*/ callback){
 +			// rebuild drop down every time, so that constraints get copied (#6002)
 +			if(this.dropDown){
 +				this.dropDown.destroy();
 +			}
 +			var PopupProto = lang.isString(this.popupClass) ? lang.getObject(this.popupClass, false) : this.popupClass,
 +				textBox = this,
 +				value = this.get("value");
 +			this.dropDown = new PopupProto({
 +				onChange: function(value){
 +					// this will cause InlineEditBox and other handlers to do stuff so make sure it's last
 +					textBox.set('value', value, true);
 +				},
 +				id: this.id + "_popup",
 +				dir: textBox.dir,
 +				lang: textBox.lang,
 +				value: value,
 +				textDir: textBox.textDir,
 +				currentFocus: !this._isInvalidDate(value) ? value : this.dropDownDefaultValue,
 +				constraints: textBox.constraints,
 +				filterString: textBox.filterString, // for TimeTextBox, to filter times shown
 +				datePackage: textBox.datePackage,
 +				isDisabledDate: function(/*Date*/ date){
 +					// summary:
 +					//		disables dates outside of the min/max of the _DateTimeTextBox
 +					return !textBox.rangeCheck(date, textBox.constraints);
 +				}
 +			});
 +
 +			this.inherited(arguments);
 +		},
 +
 +		_getDisplayedValueAttr: function(){
 +			return this.textbox.value;
 +		},
 +
 +		_setDisplayedValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){
 +			this._setValueAttr(this.parse(value, this.constraints), priorityChange, value);
 +		}
 +	});
 +
 +
 +	/*=====
 +	 _DateTimeTextBox.__Constraints = declare([RangeBoundTextBox.__Constraints, locale.__FormatOptions], {
 +		 // summary:
 +		 //		Specifies both the rules on valid/invalid values (first/last date/time allowed),
 +		 //		and also formatting options for how the date/time is displayed.
 +		 // example:
 +		 //		To restrict to dates within 2004, displayed in a long format like "December 25, 2005":
 +		 //	|		{min:'2004-01-01',max:'2004-12-31', formatLength:'long'}
 +	 });
 +	 =====*/
 +
 +	return _DateTimeTextBox;
 +});
diff --cc dijit/package.json
index 52bfcec,0000000..1eee170
mode 100644,000000..100644
--- a/dijit/package.json
+++ b/dijit/package.json
@@@ -1,26 -1,0 +1,26 @@@
 +{
 +	"name": "dijit",
- 	"version":"1.10.2",
++	"version":"1.10.3",
 +	"directories": {
 +		"lib": "."
 +	},
 +	"main":"main",
 +	"dependencies": {
- 		"dojo":"1.10.2"
++		"dojo":"1.10.3"
 +	},
 +	"description": "Dijit provides a complete collection of user interface controls based on Dojo, giving you the power to create web applications that are highly optimized for usability, performance, internationalization, accessibility, but above all deliver an incredible user experience.",
 +	"licenses": [
 +		 {
 +				 "type": "AFLv2.1",
 +				 "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L43"
 +		 },
 +		 {
 +				 "type": "BSD",
 +				 "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L13"
 +		 }
 +	],
 +	"bugs": "http://bugs.dojotoolkit.org/",
 +	"keywords": ["JavaScript", "Dojo", "Widget"],
 +	"homepage": "http://dojotoolkit.org/",
 +	"dojoBuild": "dijit.profile.js"
 +}
diff --cc dijit/tests/_BidiSupport/form/test_TimeTextBox.html
index 4444e6a,0000000..34abdbb
mode 100644,000000..100644
--- a/dijit/tests/_BidiSupport/form/test_TimeTextBox.html
+++ b/dijit/tests/_BidiSupport/form/test_TimeTextBox.html
@@@ -1,309 -1,0 +1,310 @@@
 +<!DOCTYPE html>
 +<html>
 +<head>
 +	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
 +	<title>Bidi TimeTextBox</title>
 +
 +	<script type="text/javascript" src="../../boilerplate.js"
 +			data-dojo-config="parseOnLoad: true, has: { 'dojo-bidi': true }"></script>
 +
 +	<script type="text/javascript">
 +		require([
 +			"doh/runner",
 +			"dojo/_base/array",
 +			"dojo/dom-class",
 +			"dojo/parser",
 +			"dijit/registry",
 +			"dijit/form/TimeTextBox",
 +			"dijit/form/Button",
 +			"dojo/domReady!"
 +		], function(doh, array, domClass, parser, registry, TimeTextBox){
 +
 +			doh.register("parse", function(){
 +				return parser.parse();
 +			});
 +
 +			function testWidget(element, guiDir, textDir, isfirstChildLatin){
 +				doh.is(registry.byId(element).get("textDir"), textDir, "widget textDir");
 +				doh.is(registry.byId(element).dir, guiDir, "GUI dir");
++				doh.is(guiDir.toLowerCase() == "ltr", registry.byId(element).isLeftToRight(), "isLeftToRight()");
 +				if(textDir == "auto"){
 +					textDir = isfirstChildLatin? "ltr" : "rtl";
 +				}
 +				doh.is(registry.byId(element).focusNode.dir, textDir, "focusNode textDir");
- 				doh[guiDir == "rtl" ? "t" : "f"](domClass.contains(registry.byId(element).domNode, "dijitTimeTextBoxRtl"), "dijitTimeTextBoxRtl");
++				doh[guiDir.toLowerCase() == "rtl" ? "t" : "f"](domClass.contains(registry.byId(element).domNode, "dijitTimeTextBoxRtl"), "dijitTimeTextBoxRtl");
 +			}
 +
 +			function testDropDownList(widgetId){
 +				var widget = registry.byId(widgetId);
 +				widget.openDropDown();
 +				var widgetTextDir = widget.get("textDir");
 +				var itemTextDir = widgetTextDir;
 +				var list = widget.dropDown.getChildren();
 +				var correct = true;
 +				array.forEach(list, function(child){
 +					if(widgetTextDir == "auto"){
 +						itemTextDir = widget._checkContextual(child.label.replace(/<[^>]*>/gm," "));
 +					}
 +					correct &= itemTextDir == child.textDirNode.dir;
 +				});
 +				widget.closeDropDown();
 +				doh.t(correct, "child textDir");
 +			}
 +
 +			doh.register("setup", function(){
 +				//change textDir dynamically
 +				var btnLtr = registry.byId("btn1");
 +				btnLtr.on("click", function(){
 +					registry.byId("q1").set("textDir", "ltr");
 +
 +				});
 +				var btnRtl = registry.byId("btn2");
 +				btnRtl.on("click", function(){
 +					registry.byId("q1").set("textDir", "rtl");
 +
 +				});
 +				var btnAuto = registry.byId("btn3");
 +				btnAuto.on("click", function(){
 +					registry.byId("q1").set("textDir", "auto");
 +
 +				});
 +
 +				var programmatic1 = new TimeTextBox({});
 +				programmatic1.placeAt('testProgramatic1');
 +				programmatic1.set("textDir",'ltr');
 +
 +				var programmatic2 = new TimeTextBox({});
 +				programmatic2.placeAt('testProgramatic2');
 +				programmatic2.set("textDir",'rtl');
 +
 +				var programmatic3 = new TimeTextBox({});
 +				programmatic3.placeAt('testProgramatic3');
 +				programmatic3.set("textDir",'auto');
 +			});
 +
 +			doh.register("LTR GUI, LTR textDir", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
 +						testWidget("q1", "ltr", "ltr", true);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q1");
 +					}
 +				}
 +			]);
 +
 +			doh.register("LTR GUI, RTL textDir", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
 +						testWidget("q2", "ltr", "rtl", true);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q2");
 +					}
 +				}
 +			]);
 +
 +			doh.register("LTR GUI, auto textDir with LTR text", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
 +						testWidget("q3", "ltr", "auto", true);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q3");
 +					}
 +				}
 +			]);
 +
 +			doh.register("LTR GUI, auto textDir with RTL text", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
- 						testWidget("q4", "ltr", "auto", false);
++						testWidget("q4", "LTR", "auto", false);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q4");
 +					}
 +				}
 +			]);
 +
 +			doh.register("RTL GUI, LTR textDir", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
- 						testWidget("q5", "rtl", "ltr", true);
++						testWidget("q5", "RTL", "ltr", true);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q5");
 +					}
 +				}
 +			]);
 +
 +			doh.register("RTL GUI, RTL textDir", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
 +						testWidget("q6", "rtl", "rtl", true);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q6");
 +					}
 +				}
 +			]);
 +
 +			doh.register("RTL GUI, auto textDir with LTR text", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
 +						testWidget("q7", "rtl", "auto", true);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q7");
 +					}
 +				}
 +			]);
 +
 +			doh.register("RTL GUI, auto textDir with RTL text", [
 +				{
 +					name:"textbox",
 +					runTest:function(){
 +						testWidget("q8", "rtl", "auto", false);
 +					}
 +				},
 +				{
 +					name:"dropdown",
 +					runTest:function(){
 +						testDropDownList("q8");
 +					}
 +				}
 +			]);
 +
 +			doh.run();
 +		});
 +	</script>
 +
 +</head>
 +<body>
 +
 +	<h1 class="testTitle">TimeTextBox</h1>
 +	<br>
 +	<table border="1">
 +		<tr>
 +			<td colspan="2" rowspan="2"></td>
 +			<th colspan="4" align="right"><h2><div align=center><font size="5">Base text direction</font></div></h2></th>
 +		</tr>
 +		<tr>
 +			<td>  <font size=4 color=red> LTR </font></td>
 +			<td>  <font size=4 color=red> RTL </font></td>
 +			<td>  <font size=4 color=red> Auto/LTR </font></td>
 +			<td>  <font size=4 color=red> Auto/RTL </font></td>
 +		</tr>
 +		<tr>
 +
 +			<th rowspan="2" ><h2><div align=center><font size="5">GUI direction</font></div></h2></th>
 +			<td >  <font size=4 color=blue>LTR</font></td>
 +			<td>
 +				<input dir="ltr" id="q1" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T10:30:00", textDir:"ltr",
 +					constraints:{formatLength:"short"},
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +			<td>
 +				<input dir="ltr" id="q2" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T11:30:00", textDir:"rtl",
 +					constraints:{formatLength:"short"},
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +			<td>
 +				<input dir="ltr" id="q3" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T12:30:00", textDir:"auto",
 +					constraints:{formatLength:"short"},
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +			<td>
- 				<input dir="ltr" id="q4" data-dojo-type="dijit/form/TimeTextBox"
++				<input dir="LTR" id="q4" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T12:30:00", textDir:"auto",
 +					constraints:{formatLength:"short"},
 +					lang:"ar",
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +		</tr>
 +		<tr>
 +			<td >  <font size=4 color=blue>RTL</font></td>
 +			<td>
- 				<input dir="rtl" id="q5" data-dojo-type="dijit/form/TimeTextBox"
++				<input dir="RTL" id="q5" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T10:30:00", textDir:"ltr",
 +					constraints:{formatLength:"short"},
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +			<td>
 +				<input dir="rtl" id="q6" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T11:30:00", textDir:"rtl",
 +					constraints:{formatLength:"short"},
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +			<td>
 +				<input dir="rtl" id="q7" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T12:30:00", textDir:"auto",
 +					constraints:{formatLength:"short"},
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +			<td>
 +				<input dir="rtl" id="q8" data-dojo-type="dijit/form/TimeTextBox"
 +					data-dojo-props='type:"text", name:"time1a", value:"T12:30:00", textDir:"auto",
 +					constraints:{formatLength:"short"},
 +					lang:"ar",
 +					required:true,
 +					invalidMessage:"" '/>
 +			</td>
 +		</tr>
 +	</table>
 +
 +	<br>
 +
 +	<h4 class="testSubtitle">Buttons to change BTD dynamically (for LTR-LTR cell)</h4><br>
 +		<button data-dojo-type="dijit/form/Button" id="btn1" data-dojo-props='label:"Set textDir to ltr"'></button>
 +		<button data-dojo-type="dijit/form/Button" id="btn2" data-dojo-props='label:"Set textDir to rtl"'></button>
 +		<button data-dojo-type="dijit/form/Button" id="btn3" data-dojo-props='label:"Set textDir to auto"'></button>
 +	<br>
 +	<br>
 +	<br>
 +	<hr>
 +	<h4 class="testSubtitle">Widgets created programmatically:</h4><br>
 +
 +	<span id="testProgramatic1">LTR: </span>
 +	<span id="testProgramatic2">  RTL: </span>
 +	<span id="testProgramatic3">  AUTO: </span>
 +
 +</body>
 +</html>
diff --cc dijit/tests/form/_autoComplete.html
index 1eee35e,0000000..690cb38
mode 100644,000000..100644
--- a/dijit/tests/form/_autoComplete.html
+++ b/dijit/tests/form/_autoComplete.html
@@@ -1,896 -1,0 +1,929 @@@
 +<!DOCTYPE html>
 +<html lang="en">
 +<head>
 +	<meta http-equiv="Content-type" content="text/html; charset=utf-8">
 +	<title>dijit.form.ComboBox Unit Test</title>
 +
 +	<style>
 +		@import "../../themes/claro/document.css";
 +		@import "../../../util/doh/robot/robot.css";
 +		@import "../css/dijitTests.css";
 +	</style>
 +
 +	<!-- required: the default dijit theme: -->
 +	<link id="themeStyles" rel="stylesheet" href="../../../dijit/themes/claro/claro.css"/>
 +
 +	<!-- required: dojo.js -->
 +	<script type="text/javascript" src="../../../dojo/dojo.js"
 +		data-dojo-config="isDebug: true, parseOnLoad: false"></script>
 +
 +	<!-- only needed for alternate theme testing: -->
 +	<script type="text/javascript" src="../_testCommon.js"></script>
 +
 +	<script type="text/javascript">
 +		var isUnitTest = false;
 +		var testWidget = "dijit.form.ComboBox";
 +		var qstr = window.location.search.substr(1);
 +		if(qstr.length){
 +		        var qparts = qstr.split("&");
 +		        for(var x=0; x<qparts.length; x++){
 +		                var tp = qparts[x].split("=");
 +		                if(tp[0] == "testWidget"){
 +		                        testWidget = tp[1];
 +					document.title = testWidget + " Unit Test";
 +		                }else if(tp[0] == "mode"){
 +					isUnitTest = tp[1] == "test";
 +				}
 +		        }
 +		}
 +		var isComboBox = testWidget == "dijit.form.ComboBox";
 +		dojo.require("dojo.store.Memory");
 +		dojo.require("dojo._base.Deferred");
 +		dojo.require("dojo.data.ItemFileReadStore");
 +		dojo.require("dijit.tests._data.SlowStore");
 +		dojo.require("dijit.dijit"); // optimize: load dijit layer
 +		dojo.require(testWidget);
 +		dojo.require("dojo.parser");	// scan page for widgets and instantiate them
 +		dojo.require("doh.runner");
 +
 +		function setVal2(val){
 +			dojo.byId('value2').value=val;
 +			var w = dijit.byId('datatest');
 +			var item = w.item;
 +			console.debug("Value changed to ["+val+"] in second box (#1652): datastore item label = " + (item?w.store.getLabel(item):'no item'));
 +		}
 +		function setVal3(val){
 +			dojo.byId('value3').value=val;
 +		}
 +		function setVal4(val){
 +			dojo.byId('value4').value=val;
 +		}
 +		var combo;
 +		function myLabelFunc(item, store){
 +			var label=store.getValue(item, 'name');
 +			// DEMO: uncomment to chop off a character
 +			//label=label.substr(0, label.length-1);
 +			// DEMO: uncomment to set to lower case
 +			label = label.toLowerCase();
 +			return label;
 +		}
 +
 +		// Test Custom Store
 +		dojo.declare("StateSelect", isComboBox ? dijit.form.ComboBox : dijit.form.FilteringSelect , {
 +			placeHolder: "Select a State",
 +			searchAttr: "name",
 +			label: "State:",
 +			name: "state",
 +			style: "width: 160px",
 +			store: new dojo.data.ItemFileReadStore({ url: dojo.moduleUrl("dijit.tests._data", "states.json") }),
 +			value: ''
 +		});
 +
 +		function init(){
 +			var combo;
 +
 +			var testClass = dojo.getObject(testWidget);
 +			// substitute testWidget for each data-dojo-type=$testWidget
 +			dojo.query('[data-dojo-type="$testWidget"]').forEach(function(node){
 +				node.setAttribute('data-dojo-type', testWidget);
 +			});
 +			dojo.parser.parse(dojo.body());
 +			store = new dojo.data.ItemFileReadStore({url: dojo.moduleUrl("dijit.tests._data", "states.json")});
 +			combo = new testClass({
 +				name:"prog",
 +				title: "title attribute used for label",
 +				autoComplete:false,
 +				store: store,
 +				searchAttr:"name"
 +			}, dojo.byId("progCombo"));
 +			store2 = new dojo.data.ItemFileReadStore({url: dojo.moduleUrl("dijit.tests._data", "countries.json")});
 +			combo = new testClass({
 +				name:"prog2",
 +				autoComplete:false,
 +				store:store2,
 +				query:{type:'country'},
 +				searchAttr:"name"
 +			}, dojo.byId("progCombo2"));
 +
 +			new testClass({
 +				name:"prog3",
 +				autoComplete:false,
 +				store:store2,
 +				query:{type:'country'},
 +				searchAttr:"name",
 +				fetchProperties: {sort:[{attribute: 'name', descending: true}]}
 +			}, dojo.byId("progCombo3"));
 +
 +			// change Memory store to have an asynchronous total
 +			var oldMemoryQuery = dojo.store.Memory.prototype.query;
 +			dojo.store.Memory.extend({
 +				query: function(){
 +					var results = oldMemoryQuery.apply(this, arguments);
 +					var total = results.total;
 +					results.total = new dojo.Deferred();
 +					setTimeout(function(){
 +						results.total.resolve(total);
 +					}, 100);
 +					return results;
 +				}
 +			});
 +
 +			if(!isUnitTest){ return; }
 +
 +			doh.register("label", [
 +				{
 +					name: "user-specified id",
 +					runTest: function(){
 +						combo = dijit.byId("setvaluetest");
 +						var labelId = combo.domNode.getAttribute('aria-labelledby');
 +						doh.is("setvaluetest_mylabel", labelId, "labelledby");
 +						doh.is(labelId, dojo.byId(labelId).id, "label id");
 +					}
 +				},
 +				{
 +					name: "generated id",
 +					runTest: function(){
 +						combo = dijit.byId("datatest");
 +						var labelId = combo.domNode.getAttribute('aria-labelledby');
 +						doh.is("datatest_label", labelId, "labelledby");
 +						doh.is(labelId, dojo.byId(labelId).id, "label id");
 +					}
 +				},
 +				{
 +					name: "aria roles and attributes",
 +					timeout: 5000,
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						combo = dijit.byId("setvaluetest");
 +						// ComboBox and FilteringSelect have the same roles and attributes
 +						doh.is("combobox", combo._popupStateNode.getAttribute("role"), "combo _popupStateNode role");
 +						doh.t(combo._popupStateNode.getAttribute("aria-haspopup"), "aria-haspopup on combo");
 +						doh.is("setvaluetest_mylabel", combo._popupStateNode.getAttribute("aria-labelledby"), "aria-labelledby");
 +						doh.f(combo._popupStateNode.getAttribute("aria-expanded"), "initially missing aria-expanded");
 +						doh.f(combo._popupStateNode.getAttribute("aria-owns"), "initally missing aria-owns");					
 +							
 +						var handler = dojo.connect(combo, "openDropDown", function(){
 +							dojo.disconnect(handler);
 +							setTimeout(d.getTestCallback(function(){
 +								doh.t(combo._popupStateNode.getAttribute("aria-expanded"), "now aria-expanded should be true");
 +								doh.is("setvaluetest_popup", combo._popupStateNode.getAttribute("aria-owns"), "should aria-own the popup");
 +							
 +								doh.is("listbox", combo.dropDown.domNode.getAttribute("role"), "dropDown.domNode should have a role");
 +								doh.is("setvaluetest", combo.dropDown.domNode.getAttribute("aria-labelledby"), "aria-labelledby should point back to button");
 +								doh.is("region", dojo.byId("widget_setvaluetest_dropdown").getAttribute("role"), "popup wrapper should have role=region since it gets appended to the end of the body");
 +								doh.is("setvaluetest_popup", dojo.byId("widget_setvaluetest_dropdown").getAttribute("aria-label"), "popup wrapper should have aria-label since role=region");
 +							}), 500);
 +						});
 +
 +						combo.loadDropDown();
 +						return d;
 +					},
 +					tearDown:function(){
 +						combo.closeDropDown();
 +					}
 +				}
 +			]);
 +
 +			doh.register("placeHolder", [
 +				{
 +					timeout: 2000,
 +					name: "focus enpty",
 +					combo: "placeholdertest",
 +					setUp: function(){
 +						dojo.byId('native').focus(); // blur combo so onfocus fires
 +						combo = dijit.byId(this.combo);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						doh.is("", combo.get("value"), "value");
 +						doh.is("", combo.get('displayedValue'), "displayedValue");
 +						doh.is("Select a New England State", combo._phspan.innerHTML, "_phspan.innerHTML");
 +						doh.isNot("none", combo._phspan.style.display, "_phspan.style.display 1");
 +
 +						var handler = dojo.connect(combo.textbox, "onfocus", function(){
 +							dojo.disconnect(handler);
 +							setTimeout(d.getTestCallback(function(){
 +								doh.isNot("none", combo._phspan.style.display, "_phspan.style.display 2");
 +							}), 0);
 +						});
 +						combo.focus();
 +						return d;
 +					}
 +				},
 +				{
 +					timeout: 5000,
 +					name: "blur empty",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var handler = dojo.connect(dojo.byId('native'), "onfocus", function(){
 +							dojo.disconnect(handler);
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("", combo.get("value"), "value");
 +								doh.is("", combo.get('displayedValue'), "displayedValue");
 +								doh.isNot("none", combo._phspan.style.display, "_phspan.style.display");
 +							}), 0);
 +						});
 +						dojo.byId('native').focus(); // blur combo
 +						return d;
 +					}
 +				},
 +				{
 +					timeout: 5000,
 +					name: "focus non enpty",
 +					combo: "placeholdertest",
 +					setUp: function(){
 +						dojo.byId('native').focus(); // blur combo so onfocus fires
 +						combo = dijit.byId(this.combo);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var value = isComboBox? 'Connecticut' : 'ct';
 +						combo.set("value", value);
 +						doh.is(value, combo.get("value"), "value");
 +						doh.is("none", combo._phspan.style.display, "_phspan.style.display 1");
 +
 +						var handler = dojo.connect(combo.textbox, "onfocus", function(){
 +							dojo.disconnect(handler);
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("none", combo._phspan.style.display, "_phspan.style.display 2");
 +							}), 0);
 +						});
 +						combo.focus();
 +						return d;
 +					}
 +				},
 +				{
 +					timeout: 5000,
 +					name: "blur non empty",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var value = isComboBox? 'Connecticut' : 'ct';
 +						var handler = dojo.connect(dojo.byId('native'), "onfocus", function(){
 +							dojo.disconnect(handler);
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is(value, combo.get("value"), "value");
 +								doh.is("none", combo._phspan.style.display, "_phspan.style.display");
 +								doh.is('Connecticut', combo.textbox.value, "textbox.value");
 +							}), 0);
 +						});
 +						dojo.byId('native').focus(); // blur combo
 +						return d;
 +					}
 +				},
 +				{
 +					timeout: 5000,
 +					name: "re-empty",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var handler = dojo.connect(combo, "onChange", function(){
 +							dojo.disconnect(handler);
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("", combo.get("value"), "value");
 +								doh.is("", combo.get("displayedValue"), "displayedValue");
 +								doh.isNot("none", combo._phspan.style.display, "_phspan.style.display");
 +							}), 0);
 +						});
 +						combo.set("value", '');
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			if(!isComboBox) {
 +
 +				var fsStore;
 +				
 +				doh.register("FilteringSelect data", [
 +					function setupMemoryStore() {
 +						fsStore = new dojo.store.Memory({
 +							data: [{
 +								id: 1,
 +								name: "one",
 +								value: "one"
 +							},	{
 +								id: 2,
 +								name: "two",
 +								value: "two"
 +							}, {
 +								id: 3,
 +								name: "three",
 +								value: "three"
 +							}]
 +						});
 +					},{
 +						timeout: 5000,
 +						name: "set store after construction",
 +						runTest: function() {
 +							var fs = new dijit.form.FilteringSelect({});
 +							fs.set('store', fsStore);
 +							doh.is(fs.get('store'), fsStore);
 +							fs.destroy();
 +						}
 +					},{
 +						timeout: 5000,
 +						name: "set store and place after construction",
 +						runTest: function() {
 +							var fs = new dijit.form.FilteringSelect({});
 +							var ele = document.getElementById("filteringSelect");
 +							fs.set('store', fsStore);
 +							fs.placeAt(ele);
 +							doh.is(fs.domNode, ele.firstChild);
 +							fs.destroy();
 +						}
 +					}, {
 +						timeout: 5000,
 +						name: "set store and value after construction",
 +						runTest: function() {
 +							var fs = new dijit.form.FilteringSelect({});
 +							var ele = document.getElementById("filteringSelect");
 +							fs.set('store', fsStore);
 +							fs.placeAt(ele);
 +							fs.set('value', 2);
 +							doh.is(fs.get("displayedValue"), fsStore.get(2).name);
 +							fs.destroy();
 +						}
 +					}
 +				]);
 +			}
 +
 +			doh.register("asynchronous data store", [
 +				{
 +					timeout:5000,
 +					name:"total",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("slow");
 +						var handler = dojo.connect(combo, "onSearch", function(results, query, options){
 +							dojo.disconnect(handler);
 +							d.getTestCallback(function(){
 +								doh.is(30, options.start, "start");
 +								doh.is(4, results.length, "count");
 +								doh.is(61, results.total, "total");
 +							})();
 +						});
 +						combo._set('fetchProperties', { start:30, count:4 });
 +						combo._startSearch("");
 +						return d;
 +					},
 +					tearDown:function(){
 +						dijit.byId("slow").closeDropDown();
 +					}
 +				}
 +			]);
 +
 +			doh.run();
 +		}
 +		dojo.ready(init);
 +
 +		function toggleDisabled(button, widget){
 +			widget = dijit.byId(widget);
 +			button = dojo.byId(button);
 +			widget.set('disabled',!widget.disabled);
 +			button.innerHTML= widget.disabled ? "Enable" : "Disable";
 +		}
 +
 +		function getValue(){
 +			var f = document.getElementById("form1");
 +			var s = "";
 +			for(var i = 0; i < f.elements.length; i++){
 +				var elem = f.elements[i];
 +				if(elem.nodeName.toLowerCase() == "button" || elem.type=="submit" || elem.type=="button")  { continue; }
 +				s += elem.name + ": " + elem.value + "\n";
 +			}
 +			return s;
 +		}
 +
 +		// formSubmitted flag is for benefit of DOH test harneess
 +		formSubmitted = false;
 +		function onFormSubmit(){
 +			formSubmitted = true;
 +			console.log(getValue());
 +			return false;
 +		}
 +	</script>
 +</head>
 +
 +<body class="claro" role="main">
 +
 +	<h1 class="testTitle" id="title"></h1>
 +	<script>dojo.byId('title').appendChild(document.createTextNode(testWidget+" Unit Test"))</script>
 +	<form id="form1" action="#" method="GET" onsubmit="return onFormSubmit();">
 +		
 +		<!--
 +			Need a submit button like this (rather than onsubmit handler on <form>) to get
 +			IE to submit when the ENTER key is pressed.
 +			
 +			And (at least on IE8) when ComboBox_a11y.html is run via runTests.html,
 +			it only seems to work if the button is at the top of the form, not at the bottom.
 +		-->
 +		<button type="reset">reset</button>
 +		<button type="submit">fake submit</button><br/>
 +		<hr>
 +
 +		<p>Option tags, autoComplete=false, selectOnClick=true, default value of California, pageSize=30, custom labelFunc method</p>
 +		<label id="setvaluetest_mylabel" for="setvaluetest">US State test 1 (200% Courier font):</label>
 +		<script type="text/javascript">
 +			function setValueTestOnChange(newValue){
 +				if(this.lastlabelFuncMsg){
 +					this.lastlabelFuncMsg = '';
 +				}
 +				dojo.byId('oc1').value = newValue;
 +				var itemLabel;
 +				if(this.item === null){
 +					itemLabel = "null";
 +					if(testWidget != "dijit.form.ComboBox" && this.isValid() && this.textbox.value != ''){
 +						console.error(this.id + ' has a null item, onChange value = ' + newValue + ', widget value = ' + this.value + ', displayed Value = ' + this.textbox.value);
 +						this.itemError = true;
 +					}
 +				}else if(typeof this.item === "undefined"){
 +					itemLabel = "undefined";
 +					console.error(this.id + ' has an undefined item, onChange value = ' + newValue + ', widget value = ' + this.value + ', displayed Value = ' + this.textbox.value);
 +					this.itemError = true;
 +				}else{
 +					itemLabel = this.item[this.searchAttr].toString();
 +				}
 +				dojo.byId('i1').value = itemLabel;
 +			}
 +			function setValueTestLabelFunc(item, store){
 +				var label = item[this.searchAttr];
 +				var value = item.value;
 +				if(!this.labelFuncCount){ this.labelFuncCount = 0; }
 +				if(!this.labelFuncCounts){ this.labelFuncCounts = []; }
 +				if(!this.labelFuncCounts[value]){ this.labelFuncCounts[value] = 0; }
 +				this.labelFuncCount++;
 +				this.labelFuncCounts[value]++;
 +				this.lastlabelFuncMsg = 'my labelfunc ' + label + ' (' + value + '), count = ' + this.labelFuncCounts[value];
 +				return label + ' (' + value + ')';
 +			}
 +		</script>
 +		<select id="setvaluetest" data-dojo-type="$testWidget"
 +				data-dojo-props='name:"state1",
 +				style:{width:"50%", fontSize:"200%", fontFamily:"Courier"},
 +				autoComplete:false,
 +				selectOnClick:true,
 +				onBlur:function(){ dojo.byId("b1").value=dijit.byId("setvaluetest").value },
 +				onClick:function(){ console.log("onclick"); },
 +				pageSize:30,
 +				labelFunc:setValueTestLabelFunc,
 +				onChange:setValueTestOnChange
 +		'>
 +			<option value="AL">Alabama</option>
 +			<option value="AK">Alaska</option>
 +			<option value="AS">American Samoa</option>
 +			<option value="AZ">Arizona</option>
 +			<option value="AR">Arkansas</option>
 +			<option value="AE">Armed Forces Europe</option>
 +			<option value="AP">Armed Forces Pacific</option>
 +			<option value="AA">Armed Forces the Americas</option>
 +			<option value="CA" selected>California</option>
 +			<option value="CO">Colorado</option>
 +			<option value="CT">Connecticut</option>
 +			<option value="DE">Delaware</option>
 +			<option value="DC">District of Columbia</option>
 +			<option value="FM">Federated States of Micronesia</option>
 +			<option value="FL">Florida</option>
 +			<option value="GA">Georgia</option>
 +			<option value="GU">Guam</option>
 +			<option value="HI">Hawaii</option>
 +			<option value="ID">Idaho</option>
 +			<option value="IL">Illinois</option>
 +			<option value="IN">Indiana</option>
 +			<option value="IA">Iowa</option>
 +			<option value="KS">Kansas</option>
 +			<option value="KY">Kentucky</option>
 +			<option value="LA">Louisiana</option>
 +			<option value="ME">Maine</option>
 +			<option value="MH">Marshall Islands</option>
 +			<option value="MD">Maryland</option>
 +			<option value="MA">Massachusetts</option>
 +			<option value="MI">Michigan</option>
 +			<option value="MN">Minnesota</option>
 +			<option value="MS">Mississippi</option>
 +			<option value="MO">Missouri</option>
 +			<option value="MT">Montana</option>
 +			<option value="NE">Nebraska</option>
 +			<option value="NV">Nevada</option>
 +			<option value="NH">New Hampshire</option>
 +			<option value="NJ">New Jersey</option>
 +			<option value="NM">New Mexico</option>
 +			<option value="NY">New York</option>
 +			<option value="NC">North Carolina</option>
 +			<option value="ND">North Dakota</option>
 +			<option value="MP">Northern Mariana Islands</option>
 +			<option value="OH">Ohio</option>
 +			<option value="OK">Oklahoma</option>
 +			<option value="OR">Oregon</option>
 +			<option value="PA">Pennsylvania</option>
 +			<option value="PR">Puerto Rico</option>
 +			<option value="RI">Rhode Island</option>
 +			<option value="SC">South Carolina</option>
 +			<option value="SD">South Dakota</option>
 +			<option value="TN">Tennessee</option>
 +			<option value="TX">Texas</option>
 +			<option value="UT">Utah</option>
 +			<option value="VT">Vermont</option>
 +			<option value="VI">Virgin Islands, U.S.</option>
 +			<option value="VA">Virginia</option>
 +			<option value="WA">Washington</option>
 +			<option value="WV">West Virginia</option>
 +			<option value="WI">Wisconsin</option>
 +			<option value="WY">Wyoming</option>
 +		</select>
 +		<br><label for="oc1">onChange:</label><input id="oc1" disabled value="not fired yet!" autocomplete="off"/>
 +		<br><label for="v1">value:</label><input id="v1" disabled value="not fired yet!" autocomplete="off"/>
 +		<br><label for="b1">blur:</label><input id="b1" disabled value="not fired yet!" autocomplete="off"/>
 +		<br><label for="i1">this.item:</label><input id="i1" disabled value="no onChange yet!" autocomplete="off"/>
 +		<input type="button" id="sv1_1" value="Set displayed value to Kentucky" onClick="dijit.byId('setvaluetest').set('displayedValue', 'Kentucky')"/>
 +	        <input type="button" id="sv1_2" value="Set displayed value to Canada" onClick="dijit.byId('setvaluetest').set('displayedValue', 'Canada')"/>
 +	        <input type="button" id="sv1_3" value="Set value to null" onClick="dijit.byId('setvaluetest').set('value', null)"/>
 +	        <input type="button" id="sv1_4" value="Get value" onClick="dojo.byId('v1').value=dijit.byId('setvaluetest').get('value')"/>
 +
 +		<hr>
 +
 +		<div data-dojo-id="stateStore" data-dojo-type="dojo/data/ItemFileReadStore" data-dojo-props='url:"../_data/states.json"'></div>
 +		<div data-dojo-id="slowStateStore" data-dojo-type="dijit/tests/_data/SlowStore" data-dojo-props='url:"../_data/states.json"'></div>
 +
 +		<div data-dojo-id="dijitStore" data-dojo-type="dojo/data/ItemFileReadStore" data-dojo-props='url:"../_data/dijits.json"'></div>
 +
 +		<p>Data store, autoComplete=true:</p>
 +		<label for="datatest">US State test 2 (8pt font):</label>
 +		<input id="datatest" data-dojo-type="$testWidget"
 +				data-dojo-props='value: (testWidget == "dijit.form.ComboBox") ? "California" : "CA",
 +				store:stateStore,
 +				searchAttr:"name",
 +				style:{width:"200px", fontSize:"8pt"},
 +				name:"state2",
 +				onChange:setVal2
 +				'/>
 +		<br><label for="value2">onChange:</label><input id="value2" disabled value="not fired yet!" autocomplete="off"/>
 +		<hr>
 +		<p>Artificially slowed-down data store, autoComplete=true:</p>
 +		<label for="slow">US State test slow:</label>
 +		<input id="slow" data-dojo-type="$testWidget"
 +				data-dojo-props='value: (testWidget == "dijit.form.ComboBox") ? "California" : "CA",
 +				store:slowStateStore,
 +				searchAttr:"name",
 +				name:"stateSlow",
 +				onChange:function(val){ dojo.byId("ocSlow").value = val; }
 +				'/>
 +		<br><label for="ocSlow">onChange:</label><input id="ocSlow" disabled value="not fired yet!" autocomplete="off"/>
 +		<button id="slowDestroy" type="button" onclick="dijit.byId('slow').destroy();return false">Destroy widget to test in-flight query cancel</button>
 +		<hr>
 +
 +		<label for="datatestDijit">Dijit List test #1 (150% font):</label>
 +		<input id="datatestDijit" data-dojo-type="$testWidget"
 +				data-dojo-props='value:"dijit.Editor",
 +				store:dijitStore,
 +				searchAttr:"className",
 +				style:{width:"200px",fontSize:"150%"},
 +				name:"dijitList1"
 +				'/>
 +		<span>Hey look, this one is kind of useful.</span>
 +		<hr>
 +
 +		<p>Initially disabled, url, autoComplete=false:</p>
 +		<label for="combo3">US State test 3:</label>
 +	 	<input id="combo3" data-dojo-type="$testWidget"
 +	 			data-dojo-props='value:(testWidget == "dijit.form.ComboBox") ? "California" : "CA",
 +				store:stateStore,
 +				searchAttr:"name",
 +				style:{width:"300px"},
 +				name:"state3",
 +				autoComplete:false,
 +				onChange:setVal3,
 +				disabled:true
 +		'/>
 +		<br><label for="value3">onChange:</label><input id="value3" disabled value="not fired yet!" autocomplete="off"/>
 +		<div>
 +			<button id="combo3_disable" type="button" onclick='toggleDisabled("combo3_disable", "combo3"); return false;' tabIndex="-1">Enable</button>
 +		</div>
 +		<hr>
 +		<p>Data store, autoComplete=false required=true and highlightMatch="none"</p>
 +		<label for="combobox4">US State test 4:</label>
 +		<input id="combobox4" data-dojo-type="$testWidget"
 +				data-dojo-props='value:"",
 +				store:stateStore,
 +				searchAttr:"name",
 +				style:{width:"300px"},
 +				name:"state4",
 +				onChange:setVal4,
 +				autoComplete:false,
 +				required:true,
 +				highlightMatch:"none"
 +		'/>
 +		<br><label for="value4">onChange:</label><input id="value4" disabled value="not fired yet!" autocomplete="off"/>
 +		<hr>
 +		<p>test that title used as label is preserved on input</p>
 +		<select id="preservetitletest" data-dojo-type="$testWidget"
 +				data-dojo-props='name:"titletest",
 +				style:{width:"50%", fontFamily:"Courier"},
 +				autoComplete:false,
 +				selectOnClick:true,
 +				pageSize:5,
 +				title:"New England States"
 +		'>
 +			<option value="ct">Connecticut</option>
 +			<option value="me">Maine</option>
 +			<option value="ma">Massachusetts</option>
 +			<option value="nh">New Hampshire</option>
 +			<option value="vt">Vermont</option>
 +		</select>
 +		<hr>
 +		<p>No arrow, data store which searches and highlights matches anywhere in the string</p>
 +		<label for="arrowless">Arrowless:</label>
 +	 	<input id="arrowless" data-dojo-type="$testWidget"
 +				data-dojo-props='value: (testWidget == "dijit.form.ComboBox") ? "California" : "CA",
 +				store:stateStore,
 +				searchAttr:"name",
 +				queryExpr:"*${0}*",
 +				name:"state5",
 +				autoComplete:false,
 +				hasDownArrow:false,
 +				highlightMatch:"all"
 +		'/>
 +		<hr>
 +		<p>Created programmatically</p>
 +		<label for="progCombo">progCombo:</label>
 +		<input id="progCombo"/>
 +		<hr>
 +		<p>Created programmatically with an initial query.  (Limits list to items with type = country.)</p>
 +		<label for="progCombo2">progCombo2</label>
 +		<input id="progCombo2"/>
 +		<hr>
 +		<p>Created programmatically with an ItemFileReadStore and a descending sort.  (Limits list to items with type = country.)</p>
 +		<label for="progCombo3">progCombo3:</label>
 +		<input id="progCombo3"/>
 +		<hr>
 +		<p>With option tags, autoComplete=true, pageSize=30, and a descending sort.</p>
 +		<label for="descending">descending:</label>
 +		<select id="descending" data-dojo-type="$testWidget"
 +				data-dojo-props='name:"descending",
 +				style:{width:"50%",fontSize:"200%",fontFamily:"Courier"},
 +				autoComplete:true,
 +				pageSize:30,
 +				fetchProperties:{sort:[{attribute: "name", descending: true}]}
 +		'>
 +			<option value="AL">Alabama</option>
 +			<option value="AK">Alaska</option>
 +			<option value="AS">American Samoa</option>
 +			<option value="AZ">Arizona</option>
 +			<option value="AR">Arkansas</option>
 +			<option value="AE">Armed Forces Europe</option>
 +			<option value="AP">Armed Forces Pacific</option>
 +			<option value="AA">Armed Forces the Americas</option>
 +			<option value="CA" selected>California</option>
 +			<option value="CO">Colorado</option>
 +			<option value="CT">Connecticut</option>
 +			<option value="DE">Delaware</option>
 +			<option value="DC">District of Columbia</option>
 +			<option value="FM">Federated States of Micronesia</option>
 +			<option value="FL">Florida</option>
 +			<option value="GA">Georgia</option>
 +			<option value="GU">Guam</option>
 +			<option value="HI">Hawaii</option>
 +			<option value="ID">Idaho</option>
 +			<option value="IL">Illinois</option>
 +			<option value="IN">Indiana</option>
 +			<option value="IA">Iowa</option>
 +			<option value="KS">Kansas</option>
 +			<option value="KY">Kentucky</option>
 +			<option value="LA">Louisiana</option>
 +			<option value="ME">Maine</option>
 +			<option value="MH">Marshall Islands</option>
 +			<option value="MD">Maryland</option>
 +			<option value="MA">Massachusetts</option>
 +			<option value="MI">Michigan</option>
 +			<option value="MN">Minnesota</option>
 +			<option value="MS">Mississippi</option>
 +			<option value="MO">Missouri</option>
 +			<option value="MT">Montana</option>
 +			<option value="NE">Nebraska</option>
 +			<option value="NV">Nevada</option>
 +			<option value="NH">New Hampshire</option>
 +			<option value="NJ">New Jersey</option>
 +			<option value="NM">New Mexico</option>
 +			<option value="NY">New York</option>
 +			<option value="NC">North Carolina</option>
 +			<option value="ND">North Dakota</option>
 +			<option value="MP">Northern Mariana Islands</option>
 +			<option value="OH">Ohio</option>
 +			<option value="OK">Oklahoma</option>
 +			<option value="OR">Oregon</option>
 +			<option value="PA">Pennsylvania</option>
 +			<option value="PR">Puerto Rico</option>
 +			<option value="RI">Rhode Island</option>
 +			<option value="SC">South Carolina</option>
 +			<option value="SD">South Dakota</option>
 +			<option value="TN">Tennessee</option>
 +			<option value="TX">Texas</option>
 +			<option value="UT">Utah</option>
 +			<option value="VT">Vermont</option>
 +			<option value="VI">Virgin Islands, U.S.</option>
 +			<option value="VA">Virginia</option>
 +			<option value="WA">Washington</option>
 +			<option value="WV">West Virginia</option>
 +			<option value="WI">Wisconsin</option>
 +			<option value="WY">Wyoming</option>
 +		</select>
 +		<hr>
 +		<p>Special characters</p>
 +		<p>The drop down list should be:</p>
 +		<ul>
 +		   <li>sticks & stones</li>
 +		   <li>rags --> riches to</li>
 +		   <li>more\less</li>
 +		   <li>3 * 5</li>
 +		</ul>
 +		<label for="specialchars">Special chars:</label>
 +		<select id="specialchars" data-dojo-type="$testWidget"
 +			data-dojo-props='name:"specialchars"
 +			'>
 +			<option value="sticks" selected>sticks & stones</option>
 +			<option value="rags">rags --> riches to</option>
 +			<option value="more">more\less</option>
 +			<option value="times">3 * 5</option>
 +		</select>
 +		<hr>
 +		<p>Japanese</p>
 +		<p>Try typing &#x6771;&#x533A; (East), &#x897F;&#x533A; (West), &#x5317;&#x533A; (North), &#x5357;&#x533A; (South) and a few choices will pop up.<br>
 +		Using the Microsoft IME for Japanese (Hiragana), &#x6771; can be inputed by typing higashi followed by SPACE.
 +		</p>
 +		<label for="japanese">Japanese list:</label>
 +		<select id="japanese" data-dojo-type="$testWidget" data-dojo-props='name:"japanese", style:{width:"300px"}, autoComplete:true, required:true, value:""'>
 +			<option value="nanboku">&#x5357;&#x5317; (Nanboku)</option>
 +			<option value="touzai">&#x6771;&#x897F; (Touzai)</option>
 +			<option value="toukyou">&#x6771;&#x4EAC; (Tokyo)</option>
 +			<option value="higashiku">&#x6771;&#x533A; (East)</option>
 +			<option value="nishiku">&#x897F;&#x533A; (West)</option>
 +			<option value="minamiku">&#x5357;&#x533A; (South)</option>
 +			<option value="kitaku">&#x5317;&#x533A; (North)</option>
 +		</select>
 +		<hr>
 +		<p>Custom labelFunc (labels in lowercase), autoComplete=true, prompt message when field is blank:</p>
 +		<label for="labelFunc">custom label function</label>
 +		<input id="labelFunc" data-dojo-type="$testWidget"
 +				data-dojo-props='value: (testWidget == "dijit.form.ComboBox") ? "Oregon" : "OR",
 +				labelFunc:myLabelFunc,
 +				store:stateStore,
 +				name:"labelFunc",
 +				autoComplete:true,
 +				labelAttr:"label",
 +				labelType:"html",
 +				promptMessage:"Please enter a state",
 +				invalidMessage:"Invalid state name."
 +		'/>
 +		<input type="button" onclick="dijit.byId('labelFunc').set('readOnly',false);" value="Remove readOnly"/>
 +		<input type="button" onclick="dijit.byId('labelFunc').set('readOnly',true);" value="Set readOnly"/>
 +		<input type="button" onclick="dijit.byId('labelFunc').set('disabled',false);" value="Remove disabled"/>
 +		<input type="button" onclick="dijit.byId('labelFunc').set('disabled',true);" value="Set disabled"/>
 +		<hr>
 +
 +		<p>Rich text label</p>
 +		<label for="richtexttest">Rich text labels in drop down:</label>
 +		<select id="richtexttest" data-dojo-type="$testWidget"
 +				data-dojo-props='name:"richtexttest",
 +				autoComplete:false,
 +				selectOnClick:true,
 +				value:"h1",
 +				labelType:"html",
 +				labelFunc:function(item){ var txt = item.value; return "<"+txt+">"+txt+"</"+txt+">"; }
 +		'>
 +			<option value="h1">h1</option>
 +			<option value="h2">h2</option>
 +			<option value="p">p</option>
 +			<option value="pre">pre</option>
 +		</select>
 +		<hr>
 +
 +		<input type="button" value="Create one in a window" onclick="var win=window.open(window.location);"/>
 +
 +	</form>
 +	
 +	<hr>
 +	<p><label for="placeholdertest">test placeholder</label></p>
 +	<select id="placeholdertest" data-dojo-type="$testWidget"
 +			data-dojo-props='name:"placetest",
 +			style:{width:"50%",fontFamily:"Courier"},
 +			autoComplete:false,
 +			selectOnClick:true,
 +			pageSize:5,
 +			placeHolder:"Select a New England State",
 +			value:""
 +	'>
 +		<option value="ct">Connecticut</option>
 +		<option value="me">Maine</option>
 +		<option value="ma">Massachusetts</option>
 +		<option value="nh">New Hampshire</option>
 +		<option value="vt">Vermont</option>
 +	</select>
 +	<p id="nativeLabel">
 +	This is some text below the boxes. It shouldn't get pushed out of the way when search results get returned.
 +	A native select tag to test IE bleed through problem:
 +	</p>
 +
 +	<select id="native" aria-labelledby="nativeLabel">
 +	  <option>test for</option>
 +	  <option>IE bleed through</option>
 +	  <option>problem</option>
 +	</select>
 +	
 +	<label for="combo_01">Destroy test:</label><div id="destroyDiv"
 +		><select id="combo_01" data-dojo-type="$testWidget" 
 +				data-dojo-props='name:"state", 
 +				disabled:true,
 +				style:{width:"300px"},
 +				autoComplete:false'>
 +			<option value="AL">Alabama</option>
 +			<option value="AK">Alaska</option>
 +			<option value="AS">American Samoa</option>
 +			<option value="AZ">Arizona</option>
 +			<option value="AR">Arkansas</option>
 +			<option value="AE">Armed Forces Europe</option>
 +			<option value="AP">Armed Forces Pacific</option>
 +		</select
 +	></div>
 +
 +	<br>
 +	<fieldset style="position:relative;border:1px solid black;display:inline;">
 +		<legend>Highlight test</legend>
 +		<div style="border:0;margin:1.5em;">
 +			<span style='white-space:nowrap;'>
 +			<label for="ignoreCase">ignoreCase:</label><select id="ignoreCase" onchange="dijit.byId('highlight').set('ignoreCase', this.value=='true')">
 +				<option value="true" selected>true</option>
 +				<option value="false">false</option>
 +			</select>
 +			<label for="highlightMatch">highlightMatch:</label>
 +			<select id="highlightMatch" onchange="dijit.byId('highlight').set('highlightMatch', this.value)">
 +				<option value="first" selected>first</option>
 +				<option value="all">all</option>
 +				<option value="none">none</option>
 +			</select>
 +			<label for="queryExpr">queryExpr:</label>
 +			<select id="queryExpr" onchange="dijit.byId('highlight').set('queryExpr', this.value)">
 +				<option value="${0}*" selected>${0}*</option>
 +				<option value="*${0}*">*${0}*</option>
 +				<option value="*${0}">*${0}</option>
 +			</select>
 +			</span>
 +			<br>
 +			<span style='white-space:nowrap;'>
 +			<label for="highlight">Highlight test:</label>
 +			<select id="highlight" data-dojo-type="$testWidget"
 +					data-dojo-props='ignoreCase:true,
 +					highlightMatch:"first",
 +					autoComplete:false,
 +					required:false,
 +					labelType:"text",
 +					onBlur:function(){ this.set("value",null) },
 +					value:""'>
 +				<option value="AA">AA</option>
 +				<option value="Aa">Aa</option>
 +				<option value="aA">aA</option>
 +				<option value="aa">aa</option>
 +			</select>
 +			</span>
 +		</div>
 +	</fieldset>
 +
 +	<div id="debugbox"></div>
 +
 +	<p><label for="subclass">User-defined subclass of ComboBox/FilteringSelect:</label></p>
 +	<input id="subclass" data-dojo-type="StateSelect"/>
 +
 +	<div id="filteringSelect"></div>
 +
++	<hr>
++	<p>autoComplete=true</p>
++	<select id="autocompleteon" data-dojo-type="$testWidget"
++			data-dojo-props='name:"autocompleteon",
++			style:{width:"50%", fontFamily:"Courier"},
++			autoComplete:true,
++			selectOnClick:true,
++			pageSize:5,
++			title:"New England States"
++	'>
++		<option value="ct">Connecticut</option>
++		<option value="me">Maine</option>
++		<option value="ma">Massachusetts</option>
++		<option value="nh">New Hampshire</option>
++		<option value="vt">Vermont</option>
++	</select>
++
++	<hr>
++	<p>autoComplete=false</p>
++	<select id="autocompleteoff" data-dojo-type="$testWidget"
++			data-dojo-props='name:"autocompleteon",
++			style:{width:"50%", fontFamily:"Courier"},
++			autoComplete:false,
++			selectOnClick:true,
++			pageSize:5,
++			title:"New England States"
++	'>
++		<option value="ct">Connecticut</option>
++		<option value="me">Maine</option>
++		<option value="ma">Massachusetts</option>
++		<option value="nh">New Hampshire</option>
++		<option value="vt">Vermont</option>
++	</select>
 +</body>
 +</html>
diff --cc dijit/tests/form/robot/ValidationTextBox.html
index 2ec0eb5,0000000..c217191
mode 100644,000000..100644
--- a/dijit/tests/form/robot/ValidationTextBox.html
+++ b/dijit/tests/form/robot/ValidationTextBox.html
@@@ -1,1530 -1,0 +1,1563 @@@
 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
 +		"http://www.w3.org/TR/html4/strict.dtd">
 +<html>
 +	<head>
 +		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 +		<title>doh.robot Validation Test</title>
 +
 +		<style>
 +			@import "../../../../util/doh/robot/robot.css";
 +		</style>
 +
 +		<!-- required: dojo.js -->
 +		<script type="text/javascript" src="../../../../dojo/dojo.js"
 +			data-dojo-config="isDebug: true"></script>
 +
 +		<script type="text/javascript">
 +			dojo.require("dijit.robotx");
 +			dojo.require("dojo.on");
 +			dojo.require("dijit.tests.helpers");	// functions to help test
 +
 +			dojo.ready(function(){
 +				doh.robot.initRobot('../test_validate.html');
 +
 +				doh.register("intermediatechanges", {
 +					name: "valid",
 +					textbox: "q01",
 +					timeout: 9000,
 +					setUp: function(){
 +						this.textbox = dijit.byId(this.textbox);
 +						this.textbox.set('value', '');
 +						this.textbox.focusNode.focus();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var onChange = dojo.byId('oc1');
 +						var keyPressOnTextBox;
 +						this.textbox.on("keypress", function(){
 +							keypressOnTextbox = true;
 +						});
 +
 +						var keypressOnParent;
 +						dojo.on(this.textbox.domNode.parentNode, "keypress", function(){
 +							keypressOnParent = true;
 +						});
 +
 +						doh.robot.typeKeys('Testing', 1000, 1400);
 +
 +						doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +							// test that value changed while typing since intermediateChanges = true
 +							doh.is('Testing', this.textbox.focusNode.value, "focusNode value");
 +							doh.is('Testing', this.textbox.get('value'), "attr value");
 +							doh.is('Testing', onChange.value, "onChange.value");
 +
 +							// Test that keypress event visible on TextBox but doesn't bubble to TextBox's parent.
 +							// This is necessary when a TextBox is inside a Toolbar, which interprets keystrokes
 +							// as keyboard search (to the Toolbar's children)
 +							doh.t(keypressOnTextbox, "keypressOnTextbox");
 +							doh.f(keypressOnParent, "keypressOnParent");
 +						})), 1000);
 +						return d;
 +					}
 +				});
 +
 +				doh.register("allcaps", {
 +					name: "valid",
 +					textbox: "q02",
 +					timeout: 9000,
 +					setUp: function(){
 +						this.textbox = dijit.byId(this.textbox);
 +						this.textbox.set('value', '');
 +						this.textbox.focusNode.focus();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						doh.robot.typeKeys('Testing', 1000, 1400);
 +						doh.robot.sequence(function(){
 +							dojo.byId("q01").focus();
 +						}, 500);
 +						doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +							doh.is('TESTING', this.textbox.focusNode.value, "focusNode.value");
 +							doh.is('TESTING', this.textbox.get('value'), "get('value')");
 +						})), 1000);
 +						return d;
 +					}
 +				});
 +
 +				doh.register("aria-invalid", [
 +					{
 +						name: "initial state",
 +						textbox: "fav",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +						},
 +						runTest: function(){
 +							// Even though it's required, it shouldn't initially be marked invalid
 +							doh.is('false', this.textbox.focusNode.getAttribute("aria-invalid"), "aria-invalid");
 +						}
 +					},
 +					{
 +						name: "after blur",
 +						textbox: "fav",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.keyPress(dojo.keys.TAB, 100, {});
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('true', this.textbox.focusNode.getAttribute("aria-invalid"), "aria-invalid");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "type invalid value",
 +						textbox: "fav",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('abc', 1000, 600);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('true', this.textbox.focusNode.getAttribute("aria-invalid"), "aria-invalid");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "type valid value",
 +						textbox: "fav",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('abc', 1000, 600);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('true', this.textbox.focusNode.getAttribute("aria-invalid"), "aria-invalid");
 +							})), 1000);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("maxlength", [
 +					{
 +						name: "3chars",
 +						textbox: "fav",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('100', 1000, 600);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								// test that value changed while typing since intermediateChanges = true
 +								doh.is('100', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(100, this.textbox.get('value'), "get('value')");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "4chars",
 +						textbox: "fav",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('1001', 1000, 800);
 +							doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +								// test that value changed while typing since intermediateChanges = true
 +								doh.is('100', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(100, this.textbox.get('value'), "get('value')");
 +								doh.robot.typeKeys('1', 500, 200);
 +								doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +									doh.is('100', this.textbox.focusNode.value, "focusNode.value");
 +									doh.is(100, this.textbox.get('value'), "get('value')");
 +								})), 500);
 +							})), 1000);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("errorStyle", [
 +					{
 +						name: "beforeFocus",
 +						textbox: "q04",
 +						runTest: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							doh.is('Incomplete', this.textbox.get('state'));
 +							doh.is(false, this.textbox.isValid(), "isValid()");
 +						}
 +					},
 +
 +					{
 +						name: "focus",
 +						textbox: "q04",
 +						timeout: 9000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.focusNode.focus();
 +							doh.robot.sequence(dojo.hitch(this, function(){
 +								dojo.byId("q01").focus();
 +							}), 1000);	// time for promptMessage to appear on q04 (IE6 takes a while due to iframe)
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('Error', this.textbox.get('state'));
 +								doh.is(false, this.textbox.isValid(), "isValid()");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "valid",
 +						textbox: "q04",
 +						timeout: 9000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.focusNode.focus();
 +							doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +								doh.is('Error', this.textbox.get('state'));
 +								doh.is(false, this.textbox.isValid(), "isValid()");
 +								doh.robot.typeKeys('a', 500, 200);
 +								doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +									doh.is('a', this.textbox.get('value'), "get('value')");
 +									doh.is('', this.textbox.get('state'), "state 1");
 +									doh.is(true, this.textbox.isValid(), "isValid() 1");
 +									dojo.byId("q01").focus();
 +									doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +										doh.is('', this.textbox.get('state'), "state 2");
 +										doh.is(true, this.textbox.isValid(), "isValid() 2");
 +									})), 1000);
 +								})), 500);
 +							})), 1000);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("commaformat", [
 +					{
 +						name: "beforeFocus",
 +						textbox: "q05",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', 3000);
 +						},
 +						runTest: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							doh.is('3,000', this.textbox.focusNode.value, "focusNode.value");
 +							doh.is('3000', this.textbox.get('value'), "get('value')");
 +							doh.is(true, this.textbox.isValid(), "isValid()");
 +						}
 +					},
 +
 +					{
 +						name: "focus",
 +						timeout: 9000,
 +						textbox: "q05",
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.focusNode.focus();
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								// comma should disappear on click, value shouldn't change
 +								doh.is('3,000', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('3000', this.textbox.get('value'), "get('value')");
 +								doh.is(true, this.textbox.isValid(), "isValid()");
 +							})), 500);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "type_valid_nocomma",
 +						timeout: 9000,
 +						textbox: "q05",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var onChange = dojo.byId('oc5');
 +							doh.robot.typeKeys('3000', 1000, 800);
 +							doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +								doh.is('3000', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('3000', this.textbox.get('value'), "get('value')");
 +								doh.is(true, this.textbox.isValid(), "isValid()");
 +								doh.is('NaN', onChange.value);
 +								dojo.byId("q01").focus();
 +								doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +									doh.is('3,000', this.textbox.focusNode.value, "focusNode.value");
 +									doh.is('3000', this.textbox.get('value'), "get('value')");
 +									doh.is(true, this.textbox.isValid(), "isValid()");
 +									doh.is('3000', onChange.value);
 +								})), 1000);
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "type_valid_comma",
 +						timeout: 9000,
 +						textbox: "q05",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var onChange = dojo.byId('oc5');
 +							doh.robot.typeKeys('3,000', 1000, 1000);
 +							doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +								doh.is('3,000', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('3000', this.textbox.get('value'), "get('value')");
 +								doh.is(true, this.textbox.isValid(), "isValid()");
 +								doh.is('NaN', onChange.value);
 +								dojo.byId("q01").focus();
 +								doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +									doh.is('3,000', this.textbox.focusNode.value, "focusNode.value");
 +									doh.is('3000', this.textbox.get('value'), "get('value')");
 +									doh.is(true, this.textbox.isValid(), "isValid()");
 +									doh.is('3000', onChange.value);
 +								})), 1000);
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "type_invalid_comma",
 +						timeout: 9000,
 +						textbox: "q05",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var onChange = dojo.byId('oc5');
 +							doh.robot.typeKeys('300,0', 1000, 1000);
 +							doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +								doh.is('300,0', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(undefined, this.textbox.get('value'), "get('value')");
 +								doh.is(false, this.textbox.isValid(), "isValid()");
 +								doh.is('NaN', onChange.value);
 +								dojo.byId("q01").focus();
 +								doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +									doh.is('300,0', this.textbox.focusNode.value, "focusNode.value");
 +									doh.is(undefined, this.textbox.get('value'), "get('value')");
 +									doh.is(false, this.textbox.isValid(), "isValid()");
 +									doh.is('undefined', onChange.value);
 +								})), 1000);
 +							})), 1000);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("currencyFormat", [
 +					{
 +						name: "beforeFocus",
 +						textbox: "q08",
 +						runTest: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set("value", 54775.53);
 +							doh.is('$54,775.53', this.textbox.focusNode.value, "focusNode.value");
 +							doh.is('54775.53', this.textbox.get('value'), "get('value')");
 +							doh.is(true, this.textbox.isValid(), "isValid()");
 +						}
 +					},
 +
 +					{
 +						name: "focus",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								// comma should disappear on click, value shouldn't change
 +								doh.is('54775.53', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('54775.53', this.textbox.get('value'), "get('value')");
 +								doh.is(true, this.textbox.isValid(), "isValid()");
 +							})), 500);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "type_valid_number",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var onChange = dojo.byId('oc8');
 +							doh.robot.typeKeys('10000.01', 1000, 1600);
 +							doh.robot.sequence(d.getTestErrback(dojo.hitch(this, function(){
 +								doh.is('10000.01', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('10000.01', this.textbox.get('value'), "get('value')");
 +								doh.is(true, this.textbox.isValid(), "isValid()");
 +								doh.is('NaN', onChange.value);
 +								dojo.byId("q01").focus();
 +								doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +									doh.is('$10,000.01', this.textbox.focusNode.value, "focusNode.value");
 +									doh.is('10000.01', this.textbox.get('value'), "get('value')");
 +									doh.is(true, this.textbox.isValid(), "isValid()");
 +									doh.is('10000.01', onChange.value);
 +								})), 1000);
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "type_valid_dollarsign",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var textbox = this.textbox;
 +							var onChange = dojo.byId('oc8');
 +							doh.robot.typeKeys('$20000.01', 1000, 1800);
 +							doh.robot.sequence(d.getTestErrback(function(){
 +								doh.is('$20000.01', textbox.focusNode.value, "focusNode.value");
 +								doh.is(20000.01, textbox.get('value'), "get('value')");
 +								doh.is(true, textbox.isValid(), "isValid()");
 +								doh.is('NaN', onChange.value);
 +								var handler = textbox.connect(textbox, 'onChange',
 +									function(){
 +										textbox.disconnect(handler);
 +										setTimeout(d.getTestCallback(function(){
 +											doh.is('$20,000.01', textbox.focusNode.value, "blurred focusNode.value");
 +											doh.is(20000.01, textbox.get('value'), "blurred get('value')");
 +											doh.is(true, textbox.isValid(), "blurred isValid()");
 +											doh.is('20000.01', onChange.value);
 +										}), 1);
 +									});
 +								dojo.byId("q01").focus();
 +							}), 500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "missing required decimal",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('123', 1000, 600);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('123', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(undefined, this.textbox.get('value'), "get('value')");
 +								doh.f(this.textbox.isValid(), "!isValid()");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "too few decimal digits",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('123.0', 1000, 1000);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('123.0', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(undefined, this.textbox.get('value'), "get('value')");
 +								doh.f(this.textbox.isValid(), "!isValid()");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "too many decimal digits",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('123.000', 1000, 1400);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('123.000', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(undefined, this.textbox.get('value'), "get('value')");
 +								doh.f(this.textbox.isValid(), "!isValid()");
 +							})), 1500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "negative decimal",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('-123.00', 1000, 1400);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('-123.00', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(-123, this.textbox.get('value'), "get('value')");
 +								doh.t(this.textbox.isValid(), "isValid()");
 +							})), 1500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "negative currency",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('($123.00)', 1000, 1600);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('($123.00)', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(-123, this.textbox.get('value'), "get('value')");
 +								doh.t(this.textbox.isValid(), "isValid()");
 +							})), 2000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "convert negative decimal to negative currency",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('-123.45', 1000, 1400);
 +
 +							var textbox = this.textbox;
 +							var handler = textbox.connect(textbox, '_onBlur',
 +								function(){
 +									textbox.disconnect(handler);
 +									setTimeout(d.getTestCallback(function(){
 +										doh.is('($123.45)', textbox.focusNode.value, "focusNode.value");
 +										doh.is(-123.45, textbox.get('value'), "get('value')");
 +										doh.t(textbox.isValid(), "isValid()");
 +									}), 150);
 +								});
 +							doh.robot.keyPress(dojo.keys.TAB, 100, {});
 +							return d;
 +						}
 +					},
 +					{
 +						name: "convert negative negative currency to negative decimal",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('($123.45)', 1000, 1800);
 +							doh.robot.keyPress(dojo.keys.TAB, 500, {});
 +							doh.robot.keyPress(dojo.keys.TAB, 500, {shift:true});
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('-123.45', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(-123.45, this.textbox.get('value'), "get('value')");
 +								doh.t(this.textbox.isValid(), "isValid()");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "exponent not allowed",
 +						timeout: 9000,
 +						textbox: "q08",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('1.23e0', 1000, 1200);
 +							doh.robot.keyPress(dojo.keys.TAB, 500, {});
 +							doh.robot.keyPress(dojo.keys.TAB, 500, {shift:true});
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('1.23e0', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is(undefined, this.textbox.get('value'), "get('value')");
 +								doh.f(this.textbox.isValid(), "!isValid()");
 +							})), 1000);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("euroformat", {
 +					name: "type_1",
 +					timeout: 9000,
 +					textbox: "q08eur",
 +					setUp: function(){
 +						this.textbox = dijit.byId(this.textbox);
 +						this.textbox.set('value', '');
 +						this.textbox.focusNode.focus();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						doh.robot.typeKeys('1', 1000, 200);
 +						doh.robot.sequence(dojo.hitch(this, function(){
 +							dijit.byId('q01').focusNode.focus();
 +						}), 500);
 +						doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +							doh.is('€1.00', this.textbox.focusNode.value, "focusNode.value");
 +							doh.is('1', this.textbox.get('value'), "get('value')");
 +							doh.is(true, this.textbox.isValid(), "isValid()");
 +						})), 1000);
 +						return d;
 +					}
 +				});
 +
 +				doh.register("regexp", [
 +					{
 +						name: "valid",
 +						timeout: 9000,
 +						textbox: "q22",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('a', 1000, 200);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('a', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('a', this.textbox.get('value'), "get('value')");
 +								doh.is(true, this.textbox.isValid(), "isValid()");
 +							})), 500);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "invalid",
 +						timeout: 9000,
 +						textbox: "q22",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.focusNode.focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							doh.robot.typeKeys('a ', 1000, 400);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('a ', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('a ', this.textbox.get('value'), "get('value')");
 +								doh.is(false, this.textbox.isValid(), "isValid()");
 +							})), 500);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("password", {
 +					name: "type",
 +					timeout: 9000,
 +					textbox: "q23",
 +					setUp: function(){
 +						this.textbox = dijit.byId(this.textbox);
 +						this.textbox.set('value', '');
 +						this.textbox.focusNode.focus();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						doh.robot.typeKeys('abcdef', 1000, 1200);
 +						doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +							doh.is('abcdef', this.textbox.focusNode.value, "focusNode.value");
 +							doh.is('abcdef', this.textbox.get('value'), "get('value')");
 +						})), 1000);
 +						return d;
 +					}
 +				});
 +
 +				doh.register("readonly", [
 +					{
 +						name: "readonly",
 +						timeout: 9000,
 +						textbox: "q24",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							dojo.byId("mname").focus();
 +						},
 +						runTest: function(){
 +							doh.t(this.textbox.isFocusable(), "readOnly is focusable");
 +							var d = new doh.Deferred();
 +
 +							// Tab into element (readonly *can* be focused, although disabled can't)
 +							doh.robot.keyPress(dojo.keys.TAB, 1000);
 +
 +							// typing on a disabled element should have no effect
 +							doh.robot.typeKeys('abc', 1000, 600);
 +
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is("q24", (dojo.global.dijit.focus.curNode||{}).id, "did focus");
 +								doh.is('cannot type here', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('cannot type here', this.textbox.get('value'), "get('value')");
 +							})), 1000);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "write",
 +						timeout: 9000,
 +						textbox: "q24",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('value', '');
 +							this.textbox.set('readOnly', false);
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +
 +							//  Click to focus
 +							doh.robot.mouseMoveAt(this.textbox.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +
 +							doh.robot.typeKeys('abc', 1000, 600);
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is('abc', this.textbox.focusNode.value, "focusNode.value");
 +								doh.is('abc', this.textbox.get('value'), "get('value')");
 +							})), 1000);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("disabled", [
 +					{
 +						name: "click doesn't focus",
 +						timeout: 9000,
 +						setUp: function(){
 +							this.textbox = dijit.byId("q24");
 +							dojo.byId("mname").focus();
 +							this.textbox.set('disabled', true);
 +						},
 +						runTest: function(){
 +							doh.f(this.textbox.isFocusable(), "disabled is not focusable");
 +							var d = new doh.Deferred();
 +
 +							//  Clicking shouldn't have any effect since it's disabled
 +							doh.robot.mouseMoveAt(this.textbox.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.isNot("q24", (dojo.global.dijit.focus.curNode||{}).id, "didn't focus");
 +							})), 500);
 +							return d;
 +						}
 +					},
 +
 +					{
 +						name: "tab jumps over",
 +						timeout: 9000,
 +
 +						setUp: function(){
 +							var textbox = dijit.byId("q24");
 +							dojo.byId("mname").focus();
 +							textbox.set('disabled', true);
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +
 +							doh.robot.keyPress(dojo.keys.TAB, 500);
 +
 +							doh.robot.sequence(d.getTestCallback(dojo.hitch(this, function(){
 +								doh.is("q26", dojo.global.dijit.focus.curNode.id,
 +										"tabbed past input, to the button after it");
 +							})), 500);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("selectOnClick", [
 +					{
 +						name: "1 click does highlight",
 +						timeout: 9000,
 +						setUp: function(){
 +							dijit.byId("q02").focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred(),
 +								textbox = dijit.byId("q01");
 +
 +							textbox.set('value', 'Testing');
 +							doh.robot.mouseMoveAt(textbox.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +							doh.robot.typeKeys("abc", 1000, 600);
 +
 +							doh.robot.sequence(d.getTestCallback(function(){
 +								doh.is("Abc", textbox.get('value'), "was highlighted");
 +							}), 500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "2 clicks doesn't highlight",
 +						timeout: 9000,
 +						setUp: function(){
 +							dijit.byId("q02").focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred(),
 +								textbox = dijit.byId("q01");
 +
 +							textbox.set('value', 'Testing');
 +							doh.robot.mouseMoveAt(textbox.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +							doh.robot.mouseClick({left: true}, 1000);
 +							var oldValue = textbox.get('value');
 +							doh.robot.typeKeys("abc", 500, 600);
 +
 +							doh.robot.sequence(d.getTestCallback(function(){
 +								doh.isNot(oldValue, textbox.get('value'), "didn't change at all");
 +								doh.isNot("Abc", textbox.get('value'), "was highlighted");
 +							}), 500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "TAB focus still highlights a selectOnFocus textbox",
 +						timeout: 9000,
 +						setUp: function(){
 +							dijit.byId("q02").focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred(),
 +								textbox = dijit.byId("q01");
 +
 +							textbox.set('value', 'Testing');
 +							doh.robot.mouseMoveAt(textbox.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +							doh.robot.mouseClick({left: true}, 1000);
 +							doh.robot.keyPress(dojo.keys.TAB, 1000);
 +							doh.robot.keyPress(dojo.keys.TAB, 1000, {shift:true});
 +							doh.robot.typeKeys("abc", 500, 600);
 +
 +							doh.robot.sequence(d.getTestCallback(function(){
 +								doh.is("Abc", textbox.get('value'), "was not highlighted");
 +							}), 500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "click doesn't highlight after TAB focus",
 +						timeout: 9000,
 +						setUp: function(){
 +							dijit.byId("q02").focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred(),
 +								textbox = dijit.byId("q01");
 +
 +							textbox.set('value', 'Testing');
 +							doh.robot.mouseMoveAt(textbox.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +							doh.robot.mouseClick({left: true}, 1000);
 +							doh.robot.keyPress(dojo.keys.TAB, 1000);
 +							doh.robot.keyPress(dojo.keys.TAB, 1000, {shift:true});
 +							doh.robot.mouseClick({left: true}, 1000);
 +							var oldValue = textbox.get('value');
 +							doh.robot.typeKeys("abc", 500, 600);
 +
 +							doh.robot.sequence(d.getTestCallback(function(){
 +								doh.isNot(oldValue, textbox.get('value'), "didn't change at all");
 +								doh.isNot("Abc", textbox.get('value'), "was highlighted");
 +							}), 500);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "mouse selection still works",
 +						timeout: 9000,
 +						setUp: function(){
 +							dijit.byId("q02").focus();
 +						},
 +						runTest: function(){
 +							var d = new doh.Deferred(),
 +								textbox = dijit.byId("q01");
 +
 +							textbox.set('value', 'MMMMMMM');
 +							doh.robot.mouseMoveAt(textbox.focusNode, 500, 1, 3, 6);
 +							doh.robot.mousePress({left: true}, 500);
 +							doh.robot.mouseMoveAt(textbox.focusNode, 500, 500, 10, 6);
 +							doh.robot.mouseRelease({left: true}, 500);
 +							doh.robot.typeKeys("abc", 500, 600);
 +
 +							doh.robot.sequence(d.getTestCallback(function(){
 +								doh.is("AbcMMMMMM", textbox.get('value'), "could not select text");
 +							}), 500);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("set constraints", [
 +					{
 +						name: "number",
 +						timeout: 9000,
 +						runTest: function(){
 +							var textWidget = dijit.byId("q05");
 +							textWidget.set('value', 12345);
 +							doh.is("12,345", textWidget.get('displayedValue'), "default value");
 +							textWidget.set('constraints', {places:2});
 +							doh.is("12,345.00", textWidget.get('displayedValue'), "decimal value");
 +						}
 +					},
 +					{
 +						name: "currency",
 +						timeout: 9000,
 +						runTest: function(){
 +							var textWidget = dijit.byId("q08eurde");
 +							textWidget.set('value', 12345.25);
 +							doh.is("12.345,25\xa0€", textWidget.get('displayedValue'), "EUR value");
 +							textWidget.set('constraints', {currency:'USD', locale:'en-us'});
 +							doh.is("$12,345.25", textWidget.get('displayedValue'), "USD value");
 +						}
 +					}
 +				]);
 +
 +				doh.register("placeholder", [
 +					{
 +						name: "textbox",
 +						runTest: function(){
 +							var textWidget = dijit.byId("q26");
 +							doh.is('', textWidget.get('value'),'initial value is empty');
 +							doh.is('placeholder is here', textWidget._phspan.innerHTML, '_phspan.innerHTML');
 +							textWidget.set('value','abc');
 +							doh.is('abc', textWidget.get('value'));
 +							textWidget.set('placeHolder','new placeholder');
 +							doh.f(textWidget.focusNode.hasAttribute("placeholder"),
 +									"make sure placeholder attribute didn't get set natively on <input> too")
 +							doh.is('abc', textWidget.get('value'));
 +							textWidget.set('value','');
 +							doh.is('new placeholder', textWidget._phspan.innerHTML, '_phspan.innerHTML 1');
 +							doh.is('', textWidget.get('value'));
 +						}
 +					},
 +					{
 +						name: "focus/blur textbox",
 +						timeout: 9000,
 +						runTest: function(){
 +							var d = new doh.Deferred(), textWidget = dijit.byId("q26");
 +							textWidget.set('placeHolder','placeholder is here');
 +							textWidget.set('value','');
 +							
 +							//  Clicking into the input should hide _phspan
 +							doh.robot.mouseMoveAt(textWidget.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +							
 +							doh.robot.sequence(d.getTestErrback(function(){
 +								doh.is("", textWidget.get('value'), "get('value')");
 +								doh.isNot("none", textWidget._phspan.style.display, "_phspan.style.display");
 +								
 +								doh.robot.keyPress(dojo.keys.TAB, 500, {shift:true});
 +								doh.robot.sequence(d.getTestCallback(function(){
 +									doh.is("", textWidget.get('value'), "get('value')");
 +									doh.isNot("none", textWidget._phspan.style.display, "_phspan.style.display 1");
 +								}), 1000);
 +							}), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "type in textbox",
 +						timeout: 9000,
 +						runTest: function(){
 +							var d = new doh.Deferred(), textWidget = dijit.byId("q26");
 +							textWidget.set('placeHolder','placeholder is here');
 +							textWidget.set('value','');
 +							
 +							//  Clicking into the input should hide _phspan
 +							doh.robot.mouseMoveAt(textWidget.focusNode, 500, 1);
 +							doh.robot.mouseClick({left: true}, 500);
 +							
 +							doh.robot.sequence(d.getTestErrback(function(){
 +								doh.is("", textWidget.get('value'), "get('value')");
 +								doh.isNot("none", textWidget._phspan.style.display, "_phspan.style.display 1");
 +								
 +								doh.robot.typeKeys('new', 0, 600);
 +								doh.robot.sequence(d.getTestCallback(function(){
 +									doh.is("new", textWidget.get('value'), "get('value')");
 +									doh.is("none", textWidget._phspan.style.display, "_phspan.style.display 2");
 +								}), 500);
 +							}), 1000);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "reset textbox",
 +						timeout: 9000,
 +						runTest: function(){
 +							var textWidget = dijit.byId("q26"), d = new doh.Deferred();
 +							textWidget.focus();
 +							textWidget.set('placeHolder','placeholder is here');
 +							textWidget.set('value','');
 +							
 +							doh.is("", textWidget.get('value'), "get('value') 1");
 +							doh.isNot("none", textWidget._phspan.style.display, "_phspan.style.display");
 +							
 +							textWidget.set('value','abc');
 +							textWidget.reset();
 +							
 +							doh.is("", textWidget.get('value'), "get('value') 2");
 +							doh.isNot("none", textWidget._phspan.style.display, "_phspan.style.display 1");
 +							
 +							var handler = textWidget.connect(textWidget, '_onBlur',
 +								function(){
 +									textWidget.disconnect(handler);
 +									textWidget.set('value','xyz');
 +									textWidget.reset();
 +									setTimeout(d.getTestCallback(function(){
 +										doh.is("", textWidget.get('value'), "get('value')");
 +										doh.isNot("none", textWidget._phspan.style.display, "_phspan.style.display 2");
 +									}), 150);
 +								});
 +							doh.robot.keyPress(dojo.keys.TAB, 500, {});
 +							
 +							return d;
 +						}
 +					},
 +					{
 +						name: "set textbox value",
 +						runTest: function(){
 +							var textWidget = dijit.byId("q26");
 +							textWidget.set('placeHolder','placeholder is here');
 +							textWidget.set('value','value');
 +							doh.is("none", textWidget._phspan.style.display, "_phspan.style.display");
 +						}
 +					}
 +				]);
 +
 +				function testOn(evt, widget, deferred, callback, delay){
 +					var handler = widget.connect(widget.focusNode, "on"+evt,
 +						function(){
 +							widget.disconnect(handler);
 +							setTimeout(deferred.getTestCallback(callback), delay||250);
 +						});
 +				}
 +
 +				// Supplementary test from validationMessages.html, to make sure tooltip doesn't flash
 +				// on typing multiple invalid characters
 +				doh.register("no flashing on validation", [
 +					{
 +						name: "first focus, empty value",
 +						timeout: 5000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							textbox = dijit.byId("q03");
 +							textbox.focusNode.scrollIntoView();
 +							textbox.set("value", "");
 +							textbox.focus();
 +							setTimeout(d.getTestCallback(function(){
 +								// Tooltip should appear with information message
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.t(masterTT && isVisible(masterTT.domNode), "visible");
 +								doh.is("(optional) Enter an age between 0 and 120", dojo.trim(innerText(masterTT.domNode)));
 +							}), 750);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "first invalid character",
 +						timeout: 2000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							testOn('keyup', textbox, d, function(){
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.t(masterTT && isVisible(masterTT.domNode), "visible");
 +								doh.is("The value entered is not valid.", dojo.trim(innerText(masterTT.domNode)),
 +									"message changed from info message to error message");
 +							});
 +							doh.robot.typeKeys("a", 0, 0);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "second invalid character",
 +						timeout: 2000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var tooltipHidden = false;
 +							testOn('keyup', textbox, d, function(){
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.f(tooltipHidden, "tooltip didn't blink or disappear");
 +								doh.is("The value entered is not valid.", dojo.trim(innerText(masterTT.domNode)),
 +									"same message");
 +							});
 +							dojo.connect(dijit, "hideTooltip", function(){ tooltipHidden = true; });
 +							doh.robot.typeKeys("a", 0, 0);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "tab away",
 +						timeout: 2000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							testOn('blur', textbox, d, function(){
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.t(masterTT && isHidden(masterTT.domNode), "hidden");
 +							});
 +							doh.robot.keyPress(dojo.keys.TAB, 0);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "tab back",
 +						timeout: 2000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							testOn('focus', textbox, d, function(){
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.t(masterTT && isVisible(masterTT.domNode), "visible again");
 +								doh.is("The value entered is not valid.", dojo.trim(innerText(masterTT.domNode)),
 +									"same message");
 +							});
 +							doh.robot.keyPress(dojo.keys.TAB, 0, { shift:true });
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("Ensure value formatting doesn't change entered values", [
 +					{
 +						name: "Ensure leading 0s don't cause a value change on blur",
 +						timeout: 1000,
 +						setUp: function(){
 +							var textWidget = dijit.byId("ticket17955");
 +							textWidget.set('maxLength', 8);
 +							textWidget.set('constraints', {pattern: "000.#"});
 +						},
 +						runTest: function(){
 +							var textWidget = dijit.byId("ticket17955");
 +							textWidget.textbox.focus();
 +							textWidget.textbox.value = "02103.5";
 +							textWidget.textbox.blur();
 +							doh.is(2103.5, textWidget.get("value"), "textbox.get('value')");
 +						}
 +					},
 +					{
 +						name: "Ensure extra digits do not cause a value change on blur",
 +						timeout: 1000,
 +						setUp: function(){
 +							var textWidget = dijit.byId("ticket17955");
 +							textWidget.set('maxLength', 8);
 +							textWidget.set('constraints', {pattern: "000.#"});
 +						},
 +						runTest: function(){
 +							var textWidget = dijit.byId("ticket17955");
 +							textWidget.textbox.focus();
 +							textWidget.textbox.value = "5123123";
 +							textWidget.textbox.blur();
 +							doh.is(5123123, textWidget.get("value"), "textbox.get('value')");
 +						}
 +					},
 +					{
 +						name: "Verify patterns without a decimal work as expected",
 +						timeout: 1000,
 +						setUp: function(){
 +							var textWidget = dijit.byId("ticket17955");
 +							textWidget.set('maxLength', null);
 +							textWidget.set('constraints', {pattern: "#"});
 +						},
 +						runTest: function(){
 +							var textWidget = dijit.byId("ticket17955");
 +							textWidget.textbox.focus();
 +							textWidget.textbox.value = "80000";
 +							textWidget.textbox.blur();
 +							doh.is(80000, textWidget.get("value"), "textbox.get('value')");
 +						}
 +					}
 +				]);
 +
 +				doh.register("Leading 0 parsing for NumberTextBox", [
 +				{
 +					name: "valid with 2 leading zeros",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +							textbox.set('value', '');
 +							textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: 1}));
 +							textbox.set('maxLength', 3);
 +						// When this is run at the end of the tests in IE 9, the incomplete
 +						// state is not properly triggered. Focus and blur to fix.
 +						textbox.focusNode.focus();
 +						textbox.focusNode.blur();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('00', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Incomplete", textbox.get('state'), "textbox.get('state')");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "invalid with 3 leading zeros",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: 1}));
 +						textbox.set('maxLength', 3);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +						textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('000', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Error", textbox.get('state'), "textbox.get('state')");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "invalid with 2 leading zeros",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: 100}));
 +						textbox.set('maxLength', 4);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('00', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Error", textbox.get('state'), "textbox.get('state')");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "valid with 5 leading zeros",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: 10}));
 +						textbox.set('maxLength', 7);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('00000', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Incomplete", textbox.get('state'), "textbox.get('state')");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "invalid with 0.1 leading zeros",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: 10}));
 +						textbox.set('maxLength', 7);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('0.1', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +						  doh.is("Error", textbox.get('state'), "textbox.get('state')");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "invalid with -9.01",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {max: -9.02}));
 +						textbox.set('maxLength', 7);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('-9.01', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +						  doh.is("Error", textbox.get('state'), "textbox.get('state')");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "valid with -9.021",
 +					timeout: 2000,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: -9.03, max: -9.02}));
 +						textbox.set('maxLength', 7);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('-9.021', 500, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +						  doh.is("", textbox.get('state'), "textbox.get('state')");
 +						}),500);
 +						return d;
 +					}
 +				},
 +				{
 +					name: "checking each keypress for -9.01: 1 of 5",
 +					timeout: 500,
 +					setUp: function(){
 +						var textbox = dijit.byId("ticket17923");
 +						textbox.set('value', '');
 +						textbox.set('constraints', dojo.mixin({}, textbox.get('constraints'), {min: -9.03, max: -9.02}));
 +						textbox.set('maxLength', 7);
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.scrollIntoView();
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('-', 50, 100);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +						  doh.is("Incomplete", textbox.get('state'), "textbox.get('state')");
 +						}));
 +						return d;
 +					}
 +				},
 +				{
 +					name: "checking each keypress for -9.01: 2 of 5",
 +					timeout: 500,
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +						textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('9', 50, 100);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Incomplete", textbox.get('state'), "textbox.get('state')");
 +						}));
 +						return d;
 +					}
 +				},
 +				{
 +					name: "checking each keypress for -9.01: 3 of 5",
 +					timeout: 500,
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +						textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('.', 50, 100);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Incomplete", textbox.get('state'), "textbox.get('state')");
 +						}));
 +						return d;
 +					}
 +				},
 +				{
 +					name: "checking each keypress for -9.01: 4 of 5",
 +					timeout: 500,
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +						textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('0', 50, 100);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Incomplete", textbox.get('state'), "textbox.get('state')");
 +						}));
 +						return d;
 +					}
 +				},
 +				{
 +					name: "checking each keypress for -9.01: 5 of 5",
 +					timeout: 500,
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +						textbox = dijit.byId("ticket17923");
 +						textbox.focusNode.focus();
 +						doh.robot.typeKeys('1', 50, 100);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("Error", textbox.get('state'), "textbox.get('state')");
 +						}));
 +						return d;
 +					}
 +				}]);
 +
 +				// This should be the last test since it destroys a ValidationTextBox
 +				doh.register("tooltip hide on destroy", [
 +					{
 +						name: "focus",
 +						timeout: 5000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							textbox = dijit.byId("q03");
 +							textbox.focusNode.scrollIntoView();
 +							textbox.set("value", "");
 +							textbox.focus();
 +							setTimeout(d.getTestCallback(function(){
 +								// Tooltip should appear with information message
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.t(masterTT && isVisible(masterTT.domNode), "visible");
 +							}), 750);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "destroy",
 +						timeout: 2000,
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							textbox.destroy();
 +							setTimeout(d.getTestCallback(function(){
 +								// Tooltip should disappear when ValidationTextBox (or enclosing Dialog) destroyed
 +								masterTT = dojo.global.dijit._masterTT;
 +								doh.t(masterTT && isHidden(masterTT.domNode), "hidden");
 +							}), 300);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("Ensure rounding works with locale's whose decimal separator is not a period", [
 +					{
 +						name: "basic rounding test",
 +						runTest: function(){
 +							// Expects the textbox to have a constraint of up to 1 decimal place, and the lang of 'de-de',
 +							// which uses a comma instead of a period as the decimal separator.
 +							var textbox = dijit.byId("ticket18260");
 +							textbox.set('value', 12.9);
 +							doh.is(12.9, textbox.get('value'), 'textbox.value == 12.9');
 +							textbox.set('value', 12.99);
 +							doh.is(13.0, textbox.get('value'), 'textbox.value == 13.0');
 +						}
 +					}
 +				]);
++
++				doh.register("Ensure using both forms of the places constraint work as expected", [
++					{
++						name: "Fixed numeric places test, 2",
++						runTest: function(){
++							var textbox = dijit.byId("ticket18367");
++							textbox.set('constraints', {places:2});
++							textbox.set('value', 12.991);
++							doh.is(12.99, textbox.get('value'), 'textbox.value == 12.99');
++						}
++					},
++					{
++						name: "Range places test 0,4",
++						runTest: function(){
++							var textbox = dijit.byId("ticket18367");
++							textbox.set('constraints', {places: "0,4"});
++							textbox.set('value', 12);
++							doh.is(12, textbox.get('value'), 'textbox.value == 12');
++							textbox.set('value', 12.9);
++							doh.is(12.9, textbox.get('value'), 'textbox.value == 12.9');
++							textbox.set('value', 12.99);
++							doh.is(12.99, textbox.get('value'), 'textbox.value == 12.99');
++							textbox.set('value', 12.991);
++							doh.is(12.991, textbox.get('value'), 'textbox.value == 12.991');
++							textbox.set('value', 12.9912);
++							doh.is(12.9912, textbox.get('value'), 'textbox.value == 12.9912');
++							textbox.set('value', 12.99123);
++							doh.is(12.9912, textbox.get('value'), 'textbox.value == 12.9912');
++							textbox.set('value', 12.1);
++							doh.is(12.1, textbox.get('value'), 'textbox.value == 12.1');
++						}
++					}
++				]);
 +				doh.run();
 +			});
 +		</script>
 +	</head>
 +</html>
diff --cc dijit/tests/form/robot/_autoComplete_a11y.html
index acfd30f,0000000..3682ddd
mode 100644,000000..100644
--- a/dijit/tests/form/robot/_autoComplete_a11y.html
+++ b/dijit/tests/form/robot/_autoComplete_a11y.html
@@@ -1,1608 -1,0 +1,1629 @@@
 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
 +		"http://www.w3.org/TR/html4/strict.dtd">
 +<html>
 +<head>
 +	<title>doh.robot ComboBox/FilteringSelect General and A11Y Tests</title>
 +
 +	<style>
 +		@import "../../../../util/doh/robot/robot.css";
 +	</style>
 +
 +	<!-- required: dojo.js -->
 +	<script type="text/javascript" src="../../../../dojo/dojo.js"></script>
 +
 +	<script type="text/javascript">
 +		dojo.require("dijit.robotx");
 +		dojo.require("dojo.aspect");
 +		dojo.require("dijit.tests.helpers");	// functions to help test
 +		dojo.require("dijit.form._TextBoxMixin");	// selectInputText
 +
 +		// TODO: provide URL toggle for FilteringSelect
 +		var testWidget = "dijit.form.ComboBox";
 +		var qstr = window.location.search.substr(1);
 +		if(qstr.length){
 +		        var qparts = qstr.split("&");
 +		        for(var x=0; x<qparts.length; x++){
 +		                var tp = qparts[x].split("=");
 +		                if(tp[0] == "testWidget"){
 +		                        testWidget = tp[1];
 +		                }
 +		        }
 +		}
 +		isComboBox = testWidget=="dijit.form.ComboBox";
 +		dojo.ready(function(){
 +
 +			doh.robot.initRobot('../_autoComplete.html?testWidget='+testWidget);
 +
 +			var arrowlessComboBoxes=['arrowless'];
 +
 +			var robot_typeValue = function(combo, text, value, expectedText){
 +				if(value===undefined) value = text;
 +				if(expectedText===undefined) expectedText = text;
 +				var d = new doh.Deferred();
 +
 +				// setup watch() on "value" and "displayedValue"
 +				var dv, v;					
 +				var h1 = combo.watch("displayedValue", function(name, oldVal, newVal){ dv = newVal; });
 +				var h2 = combo.watch("value", function(name, oldVal, newVal){ v = newVal; });
 +
 +				combo.focusNode.focus();
 +				combo.itemError = false;
 +				doh.robot.sequence(function(){ combo.set("value", null); }, 500);
 +				doh.robot.typeKeys(text.replace(/^(.).*$/, "$1"), 500);
 +				doh.robot.typeKeys(text.replace(/^./, ""), 1500);
 +				doh.robot.keyPress(dojo.keys.ENTER, 1500);
 +
 +				doh.robot.sequence(d.getTestCallback(function(){
 +					// general tests
 +					doh.is(value, combo.get("value"), "combo.get(value)");
 +					doh.is(expectedText, combo.focusNode.value, "expectedText");
 +					doh.f(combo._opened, "not opened");
 +					doh.f(combo.itemError, "no itemError");
 +					
 +					// watch() tests
 +					doh.is(expectedText, dv, "watch of displayedValue");
 +					doh.is(value, v, "watch of value");
 +					h1.unwatch();
 +					h2.unwatch();
 +				}), 2000);
 +				return d;
 +			};
 +
 +			var findMenuItem = function(combo, text){
 +				var node = combo.dropDown.domNode.firstChild;
 +				while(innerText(node).indexOf(text) < 0 && node.nextSibling){
 +					node = node.nextSibling;
 +				}
 +				return node;
 +			};
 +
 +			// Select a value from the drop down using the keyboard, using "more choices" button to page as necessary
 +			var robot_a11ySelectValue = function(combo, text, value, expectedText){
 +				if(!value) value = text;
 +				if(!expectedText) expectedText = text;
 +				var d = new doh.Deferred();
 +
 +				// setup watch() on "value", "displayedValue", "item"
 +				var dv, v, i;					
 +				var h1 = combo.watch("displayedValue", function(name, oldVal, newVal){ dv = newVal; });
 +				var h2 = combo.watch("value", function(name, oldVal, newVal){ v = newVal; });
 +				var h3 = combo.watch("item", function(name, oldVal, newVal){ i = newVal; });
 +
 +				combo.focusNode.focus();
 +				combo.itemError = false;
 +				doh.robot.sequence(function(){ combo.set("value", null); }, 1000);
 +
 +				doh.robot.mouseMoveAt(combo.focusNode, 0); // get cursor out of the way so that wiggling doesn't mess up test
 +				doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500);
 +				var repeat = function(){
 +					var node = findMenuItem(combo, text);
 +					var isMoreChoices = node == combo.dropDown.nextButton;
 +					var selected = combo.dropDown.getHighlightedOption() || combo.dropDown.domNode.firstChild;
 +					while(selected != node){
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 300);
 +						selected = selected.nextSibling;
 +					}
 +					doh.robot.keyPress(dojo.keys.ENTER, 500);
 +					if(isMoreChoices){
 +						// can go faster since the data will have loaded by now
 +						doh.robot.sequence(repeat, 1000);
 +					}else{
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is(value, combo.get("value"), "combo.get(value)");
 +							doh.is(expectedText, combo.focusNode.value, "expectedText");
 +							doh.f(combo._opened, "not opened");
 +							doh.f(combo.itemError, "no itemError");
 +							
 +							// watch() tests
 +							doh.is(expectedText, dv, "watch of displayedValue");
 +							doh.is(value, v, "watch of value");
 +							doh.is(value, isComboBox ? i[combo.searchAttr] : combo.store.getIdentity(i), "watch of item");
 +							h1.unwatch();
 +							h2.unwatch();
 +							h3.unwatch();
 +						}), 500);
 +					}
 +
 +				};
 +				// first time, wait for the data to come in
 +				doh.robot.sequence(repeat, 3000);
 +				return d;
 +			};
 +
 +			// Wait for data stores to finish loading before starting tests
 +			doh.register("wait for data store load",
 +				dojo.map(["store", "store2", "stateStore", "dijitStore"], function(name){
 +					return {
 +							name: "wait for " + name,
 +							timeout: 5000,
 +							runTest: function(){
 +								var d = new doh.Deferred();
 +								dojo.global[name].fetch({
 +									onComplete: function(){
 +										d.callback(true);
 +									},
 +									onError: function(e){
 +										d.errback(e);
 +									}
 +								});
 +								return d;
 +							}
 +						};
 +				})
 +			 );
 +
 +			// Verify that all of the form values are correct at init
 +			doh.register("verify values",
 +				{
 +					name:"verifyValues",
 +					runTest:function(){
 +						// Spot check of initial conditions of widgets and DOM nodes
 +
 +						doh.is("California", dijit.byId("setvaluetest").get("displayedValue"), "state1 displayed value");
 +						doh.is(isComboBox ? "California" : "CA", dijit.byId("setvaluetest").get("value"), "state1 value");
 +						doh.is("not fired yet!", dojo.byId("oc1").value, "state1 onChange hasn't fired");
 +
 +						// Test that dojo.query() finds hidden field, see #8660
 +						var elems = dojo.doc.getElementsByName("state2");
 +						doh.is(1, elems.length, "exactly one node with name=state2");
 +						doh.is(isComboBox ? "California" : "CA", elems[0].value, "state2 submit value");
 +						var qelems = dojo.query("input[name=state2]");
 +						doh.is(1, qelems.length, "dojo.query() returns exactly one node with name=state2");
 +						doh.is(isComboBox ? "California" : "CA", qelems[0].value, "state2 dojo.query value");
 +
 +						doh.is(1, dojo.query("input[name=state3]").length,
 +							"Just one input inside of " + testWidget + " w/name specified");
 +						if(!isComboBox){
 +							// Filtering select should have two inputs, but the displayed one is hidden
 +							doh.is(2, dojo.query("input", dijit.byId("combo3").domNode).length-dojo.query("input[readOnly]", dijit.byId("combo3").domNode).length,
 +								"Two inputs inside of filteringSelect");
 +						}
 +
 +						doh.is("sticks & stones", dijit.byId("specialchars").get("displayedValue"), "specialchars get('displayValue')");
 +						doh.is(isComboBox ? "sticks & stones" : "sticks", dijit.byId("specialchars").get("value"), "specialchars value");
 +						doh.is("sticks & stones", dojo.query("input[id=specialchars]")[0].value, "specialchars display value via DOMNode");
 +						doh.is(isComboBox ? "sticks & stones" : "sticks", dojo.query("input[name=specialchars]")[0].value, "specialchars submit value via DOMNode");
 +					}
 +				}
 +			);
 +
 +			var comboIds=['setvaluetest','datatest','combo3','combobox4','arrowless','descending'];
 +			for(var i=0; i<comboIds.length; i++){
 +				doh.register("query input by name",{
 +					name:comboIds[i],
 +					combo:comboIds[i],
 +					runTest:function(){
 +						this.combo = dijit.byId(this.combo);
 +						var queried=dojo.query("input[name="+(this.combo.valueNode||this.combo.focusNode).name+"]");
 +						doh.is(1,queried.length,"Expected 1 combo with name "+(this.combo.valueNode||this.combo.focusNode).name+", found "+queried.length);
 +						doh.is(this.combo.valueNode||this.combo.focusNode,queried[0],"Combo's valueNode did not match the one found by dojo.query.");
 +					}
 +				});
 +			}
 +
 +			doh.register("set('displayedValue', ...)", [
 +				// Set displayedValue to Kentucky.
 +				// This should be a valid assignment.
 +				{
 +					timeout:5000,
 +					name:"valid",
 +					runTest:function(){
 +						var d = new doh.Deferred(),
 +							combo = dijit.byId("setvaluetest");
 +						
 +						var dv, v;	
 +						combo.watch("displayedValue", function(name, oldVal, newVal){ dv = newVal; });
 +						combo.watch("value", function(name, oldVal, newVal){ v = newVal; });
 +
 +						combo.set('displayedValue', 'Kentucky');
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var oc1=dojo.byId('oc1');
 +							doh.is(isComboBox ? "Kentucky" : "KY", combo.get("value"), "get('value')");
 +							doh.is(isComboBox ? "Kentucky" : "KY", oc1.value, "onChange");
 +							if(!isComboBox){
 +								doh.t(combo.isValid(), "isValid()");
 +							}
 +							doh.is(isComboBox ? "Kentucky" : "KY", v, "watch of value");
 +							doh.is("Kentucky", dv, "watch of displayedValue")
 +						}), 900);
 +						return d;
 +					}
 +				},
 +
 +				// Set displayedValue to Canada.
 +				// This should be an invalid assignment for FilteringSelect, but ok for ComboBox.
 +				{
 +					timeout:5000,
 +					name:"invalid",
 +					runTest:function(){
 +						var d = new doh.Deferred(),
 +							combo = dijit.byId("setvaluetest");
 +						combo.set('displayedValue', 'Canada');
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var oc1=dojo.byId('oc1');
 +							doh.is(isComboBox ? "Canada" : "", combo.get("value"), "get('value')");
 +							doh.is(isComboBox ? "Canada" : "", oc1.value, "onChange");
 +							if(!isComboBox){
 +								doh.f(combo.isValid(), "isValid()");
 +							}
 +						}), 900);
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			doh.register("set('value', ...)", [
 +				// Set value to null.
 +				// This should be an invalid assignment for FilteringSelect, but ok for ComboBox.
 +				{
 +					timeout:5000,
 +					name:"nullvalue",
 +					runTest:function(){
 +						var d = new doh.Deferred(),
 +							combo = dijit.byId("setvaluetest");
 +						combo.set("value", null);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var oc1=dojo.byId('oc1');
 +							doh.is("", combo.get("value"), "get('value')");
 +							doh.is("", oc1.value, "onChange");
 +							if(!isComboBox){
 +								doh.f(combo.isValid(), "isValid()");
 +							}
 +						}), 900);
 +						return d;
 +					}
 +				}
 +
 +				// TODO: test some other set("value") calls
 +			]);
 +
 +			doh.register("back compat", [
 +				function fetch_optionTags(){
 +					// Make sure that myComboBox.store.fetch() still works even though
 +					// ComboBox is using the new dojo.store API
 +					var d = new doh.Deferred();
 +					doh.t(dijit.byId("setvaluetest").store, "store exists");
 +					doh.t(dojo.isFunction(dijit.byId("setvaluetest").store.fetch), "fetch exists");
 +					dijit.byId("setvaluetest").store.fetch({
 +						onBegin: d.getTestCallback(function(total){
 +							doh.is(61, total, "# records");
 +						})
 +					});
 +					return d;
 +				},
 +				function fetch_externalStore(){
 +					// Make sure that myComboBox.store.fetch() still works even though
 +					// ComboBox is using the new dojo.store API
 +					var d = new doh.Deferred();
 +					doh.t(dijit.byId("datatest").store, "store exists");
 +					doh.t(dojo.isFunction(dijit.byId("datatest").store.fetch), "fetch exists");
 +					dijit.byId("datatest").store.fetch({
 +						onBegin: d.getTestCallback(function(total){
 +							doh.is(61, total, "# records");
 +						})
 +					});
 +					return d;
 +				},
 +				{
 +					timeout:60000,
 +					name:"fetch() called with string",
 +					runTest:function(){
 +						// Make sure typing into a ComboBox connected to an
 +						// old dojo.data store calls store.fetch() with a String argument rather than a Regex
 +
 +						var d = new doh.Deferred();
 +						combo = dijit.byId("datatest");
 +
 +						doh.t("fetch" in combo.store,
 +								"combo.store has fetch() method (i.e. it's the old dojo.data API)");
 +
 +						combo.focusNode.focus();
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 500);
 +
 +						// Keypress (below) will trigger a call to store.fetch, which will then call this code.
 +						doh.robot.sequence(function(){
 +							var handle = dojo.aspect.after(combo.store, "fetch", d.getTestErrback(function(args){
 +								handle.remove();
 +								doh.isNot(null, args && args.query && args.query.name,
 +										"args.query.name set");
 +								doh.is("string", typeof args.query.name, "typeof searchAttr");
 +							}), true);
 +						}, 500);
 +						doh.robot.keyPress("k", 500);
 +
 +						// Code below merely to let ComboBox finish any pending requests, so "datatest" ComboBox
 +						// doesn't grab focus in the middle of with the "direct input" test below.
 +						doh.robot.keyPress(dojo.keys.ENTER, 500);
 +						doh.robot.sequence(function(){
 +							d.callback(true);
 +						}, 500);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			!isComboBox && doh.register("FilteringSelect back-compat", [
 +				function setDisplayedValue(){
 +					// Make sure that set("displayedValue", ...) on a FilteringSelect connected to an
 +					// old dojo.data store calls store.fetch() with a String argument rather than a Regex
 +					var combo = dijit.byId("datatest");
 +					doh.t("fetch" in combo.store, "combo.store has fetch() method (i.e. it's the old dojo.data API)");
 +					var handle = dojo.aspect.after(combo.store, "fetch", function(args){
 +						handle.remove();
 +						doh.isNot(null, args && args.query && args.query.name, "args.query.name set");
 +						doh.is("string", typeof args.query.name, "typeof searchAttr");
 +					}, true);
 +					combo.set("displayedValue", "Kentucky");
 +				}
 +			]);
 +
 +			doh.register("direct input", [
 +				// Type a valid value and press Enter
 +				{
 +					timeout:60000,
 +					name:"valid value",
 +					combo:"setvaluetest",
 +					runTest:function(){
 +						return robot_typeValue(dijit.byId(this.combo), "California", isComboBox? undefined : "CA", isComboBox? undefined : "California");
 +					}
 +				},
 +
 +				// Type an invalid value and press Enter
 +				{
 +					timeout:60000,
 +					name:"invalid value",
 +					combo:"setvaluetest",
 +					runTest:function(){
 +						return robot_typeValue((this.combo = dijit.byId(this.combo)), "zxcxarax", isComboBox?"zxcxarax":"");
 +					}
 +				},
 +
 +				// Check on the invalid value from the previous test
 +				{
 +					timeout:60000,
 +					name:"invalid value 2",
 +					combo:"setvaluetest",
 +					runTest:function(){
 +						this.combo = dijit.byId(this.combo);
 +						if(isComboBox){
 +							doh.is("zxcxarax", this.combo.get("value"), "Expected value of zxcxarax, got "+this.combo.get("value")+", text is:"+this.combo.focusNode.value);
 +							doh.t(this.combo.isValid(), "Bad value not permitted in ComboBox. Value is: "+this.combo.get("value")+", text is:"+this.combo.focusNode.value);
 +						}else{
 +							doh.is("", this.combo.get("value"), "Expected value of '', got "+this.combo.get("value")+", text is:"+this.combo.focusNode.value);
 +							doh.t(!this.combo.isValid(), "Bad value permitted in FilteringSelect. Value is: "+this.combo.get("value")+", text is:"+this.combo.focusNode.value);
 +						}
 +						doh.t(!this.combo.itemError, "Should not have an itemError");
 +					},
 +					tearDown:function(){
 +						this.combo.set("value", isComboBox?"Alaska":"AK");
 +					}
 +				}
 +			]);
 +
 +			// Test that enter key submits the form, but only when nothing is selected in the drop down
 +			doh.register("enter key", {
 +				timeout:60000,
 +				name:"submit",
 +				runTest:function(){
 +					dojo.global.formSubmitted = false;
 +					var d = new doh.Deferred();
 +					var combo = dijit.byId("setvaluetest");
 +					combo.focus();
 +
 +					// Initial conditions:
 +					doh.robot.sequence(d.getTestErrback(function(){
 +						var popup = dijit.byId('setvaluetest_popup');
 +						doh.t(!popup || isHidden(popup), "popup hidden");
 +					}), 500);
 +
 +					// Down arrow opens the drop down
 +					doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500);
 +					doh.robot.sequence(d.getTestErrback(function(){
 +						var popup = dijit.byId('setvaluetest_popup');
 +						doh.t(popup && isVisible(popup), "popup visible");
 +					}), 500);
 +
 +					// Enter key should select value and close drop down but not submit form
 +					doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // highlight value
 +					doh.robot.keyPress(dojo.keys.ENTER, 500);
 +					doh.robot.sequence(d.getTestErrback(function(){
 +						var popup = dijit.byId('setvaluetest_popup');
 +						doh.t(!popup || isHidden(popup), "popup hidden");
 +						doh.f(dojo.global.formSubmitted, "form not submitted yet")
 +					}), 500);
 +
 +					// Enter key again should submit form
 +					doh.robot.keyPress(dojo.keys.ENTER, 500);
 +					doh.robot.sequence(d.getTestCallback(function(){
 +						var popup = dijit.byId('setvaluetest_popup');
 +						doh.t(!popup || isHidden(popup), "popup hidden");
 +						doh.t(dojo.global.formSubmitted, "form submitted")
 +					}), 500);
 +
 +					return d;
 +				}
 +			});
 +
 +			doh.register("drop down navigation / keyboard", [
 +				// Select a value from the drop down using the keyboard,
 +				// use "more choices" button to page as necessary
 +				{
 +					timeout:60000,
 +					name:"setvaluetest_a11y",
 +					combo:"setvaluetest",
 +					runTest:function(){
 +						return robot_a11ySelectValue(dijit.byId(this.combo), "Texas", isComboBox? "Texas" : "TX", "Texas");
 +					}
 +				}
 +			]);
 +
 +			// Test that drop down choices are filtered to values matching what user has typed
 +			doh.register("filtering of drop down", [
 +				{
 +					timeout:60000,
 +					name:"type C",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("setvaluetest");
 +						combo.itemError = false;
 +
 +						combo.focusNode.focus();
 +
 +						// Filter drop down list to entries starting with "C"
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 1000);
 +						doh.robot.keyPress("C", 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list has appeared, and contains California, Colorado, Connecticut
 +							var list = dojo.byId("setvaluetest_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is(3, entries.length, "three entries in drop down: " + list.innerHTML);
 +							doh.is("California (CA)", innerText(entries[0]), "list #1");
 +							doh.is("Colorado (CO)", innerText(entries[1]), "list #2");
 +							doh.is("Connecticut (CT)", innerText(entries[2]), "list #3");
 +
 +							// Check that search-string highlighting is working
 +							doh.is('<span class=dijitcomboboxhighlightmatch>c</span>onnecticut (ct)', entries[2].innerHTML.toLowerCase().replace(/"/g, ""), //balanced"
 +								"highlighting is working");
 +							doh.f(combo.itemError, testWidget + " item mismatch");
 +						}), 900);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"type 'o' after 'C'",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("setvaluetest");
 +						combo.itemError = false;
 +
 +						// Filter drop down list to entries starting with "Co"
 +						doh.robot.keyPress("o", 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list is still there, and California has disappeared
 +							var list = dojo.byId("setvaluetest_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is(2, entries.length, "three entries in drop down: " + list.innerHTML);
 +							doh.is("Colorado (CO)", innerText(entries[0]), "list #1");
 +							doh.is("Connecticut (CT)", innerText(entries[1]), "list #2");
 +							doh.f(combo.itemError, testWidget + " item mismatch");
 +						}), 900);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"type backspace",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("setvaluetest");
 +						combo.itemError = false;
 +
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// List should again contain California
 +							var list = dojo.byId("setvaluetest_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is(3, entries.length, "three entries in drop down: " + list.innerHTML);
 +							doh.is("California (CA)", innerText(entries[0]), "list #1");
 +							doh.is("Colorado (CO)", innerText(entries[1]), "list #2");
 +							doh.is("Connecticut (CT)", innerText(entries[2]), "list #3");
 +							doh.f(combo.itemError, testWidget + " item mismatch");
 +						}), 900);
 +
 +						return d;
 +					}
 +				}
 +
 +				// TODO: make separate test group for testing highlighting, and then check
 +				//     - matching of anywhere in string (id=arrowless)
 +				//     - highlightmatch=none (combobox4)
 +			]);
 +
 +			// Test auto complete
 +			doh.register("auto-complete", [
 +				{
 +					timeout:60000,
 +					name:"no auto-complete",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("setvaluetest");
 +
 +						combo.focusNode.focus();
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 1000);
 +
 +						// Filter drop down list to entries starting with "C"
 +						doh.robot.keyPress("C", 100);
 +
 +						// Then tab away
 +						doh.robot.keyPress(dojo.keys.TAB, 500);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list has disappeared
 +							var list = dojo.byId("setvaluetest_popup");
 +							doh.t(!list || isHidden(list), "drop down is visible");
 +
 +							// Since autocomplete=false the contents should just be what the user typed
 +							doh.is('C', combo.focusNode.value);
 +							if(!isComboBox){
 +								doh.f(dijit.byId("setvaluetest").isValid(), "FilteringSelect shouldn't be valid");
 +							}
 +							doh.f(dijit.byId("setvaluetest").itemError, testWidget + " item mismatch");
 +						}), 900);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"auto-complete writes suggested letters in input box",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("datatest");	// auto-complete = true
 +
 +						combo.focusNode.focus();
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 1000);
 +
 +						// Filter drop down list to entries starting with "C"
 +						doh.robot.keyPress("C", 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var list = dojo.byId("datatest_popup");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							doh.is('California', combo.focusNode.value, "'alifornia' automatically appended to user input");
 +						}), 900);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"auto-complete changes suggestion based on more typed letters",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("datatest");
 +
 +						// Further filter drop down to entries starting with "Co" (Colorado and Connectictut).
 +						// Note that this depends on (and tests that) "alifornia" is selected (aka highlighted),
 +						// so that the "o" keypress erases it and replaces the input box text with "Co"
 +						doh.robot.keyPress("o", 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is('Colorado', combo.focusNode.value, "suggestion changed from California to Colorado");
 +						}), 900);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"tab-away auto-selects value",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("datatest");
 +
 +						// Then tab away
 +						doh.robot.keyPress(dojo.keys.TAB, 500);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list has disappeared
 +							var list = dojo.byId("datatest_popup");
 +							doh.t(!list || isHidden(list), "drop down is hidden");
 +
 +							doh.is(isComboBox ? "Colorado" : "CO", combo.get("value"));
 +							if(!isComboBox){
 +								doh.t(combo.isValid(), "FilteringSelect should be valid");
 +							}
 +						}), 900);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			// TODO: test ESC key or click on screen background or click on another widget to close drop down
 +
 +			// TODO: click test for arrowless
 +
 +			!isComboBox && doh.register("reverse lookup query options", [
 +				{
 +					timeout: 60000,
 +					name: "delaware",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("arrowless");
 +
 +						combo.set("value", null);
 +						combo.focusNode.focus();
 +
 +						doh.robot.typeKeys("delaware", 500, 100);
 +						doh.robot.keyPress(dojo.keys.TAB, 1000);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Value should have been converted to "Delaware" (in upper case) and marked as valid
 +							doh.is("Delaware", combo.focusNode.value);
 +							doh.is("", combo.state, "marked as valid");
 +						}), 1000);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			// Race condition tests based on slow store:
 +			// Test that drop down choices are filtered to values matching what user has typed
 +			doh.register("race conditions", [
 +				// Test when queries return in different order than issued
 +				{
 +					timeout:60000,
 +					name:"query canceling on new input (#8950)",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("slow");
 +						combo.set('displayedValue', null, false);
 +
 +						combo.focusNode.focus();
 +						// this test is very timing-sensitive
 +						// preload states.json for slow networks
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 1000);
 +						doh.robot.sequence(function(){ dojo.global.slowStateStore.log = []; }, 2000, 100); // clear log of query on * messages
 +
 +						// Filter drop down list to entries starting with "C"
 +						doh.robot.typeKeys("c", 500, 100);
 +						doh.robot.typeKeys("o", 300, 100); // 300ms > searchDelay, so C* query has been sent (but not yet returned results) when the o is typed
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// The query "c*" should be canceled when "co*" is started
 +							var log = dojo.global.slowStateStore.log;
 +							doh.is(4, log.length, "4 events on data store");
 +							doh.is("start c*", log[0].type + " " + log[0].query.name);
 +							doh.is("cancel c*", log[1].type + " " + log[1].query.name);
 +							doh.is("start co*", log[2].type + " " + log[2].query.name);
 +							doh.is("end co*", log[3].type + " " + log[3].query.name);
 +
 +							// Check that drop down list has appeared, and contains only Colorado and Connecticut (not California)
 +							var list = dojo.byId("slow_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is(2, entries.length, "two entries in drop down: " + list.innerHTML);
 +							doh.is("Colorado", innerText(entries[0]), "list #1");
 +							doh.is("Connecticut", innerText(entries[1]), "list #2");
 +						}), 1000, 500);
 +
 +						return d;
 +					}
 +				},
 +
 +				// Test that drop down doesn't show up after ENTER keypress, and also that
 +				// searchDelay is preventing intermediate queries.
 +				{
 +					timeout:60000,
 +					name:"pressing enter before search returns",
 +					setUp: function(){
 +						dojo.global.slowStateStore.log = [];
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("slow");
 +
 +						dojo.global.formSubmitted = false;
 +						dijit.form._TextBoxMixin.selectInputText(combo.focusNode);
 +
 +						// Start to filter drop down list to entries starting with "Co"
 +						doh.robot.typeKeys("C", 500, 0);
 +						doh.robot.typeKeys("o", 300, 0); // 300ms > searchDelay, so C* query has been sent (but not yet returned results) when the o is typed
 +
 +						// But then hit ENTER after we've started the query to the data store, but
 +						// before the data store returns query results... that should cancel the query.
 +						doh.robot.keyPress(dojo.keys.ENTER, 300);	// 300ms > searchDelay
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var log = dojo.global.slowStateStore.log;
 +							var len = log.length;
 +							doh.t(len >= 4, "log length " + len);
 +							doh.is("start C*", log[0].type + " " + log[0].query.name);
 +							doh.is("cancel C*", log[1].type + " " + log[1].query.name);
 +							doh.is("start Co*", log[2].type + " " + log[2].query.name);
 +							doh.is("cancel Co*", log[3].type + " " + log[3].query.name);
 +
 +							// also, form SHOULD have submitted because no popup was visible
 +							doh.t(dojo.global.formSubmitted, "form submitted");
 +
 +							var list = dojo.byId("slow_popup");
 +							doh.t(!list || isHidden(list), "drop down is *not* visible");
 +
 +							doh.is("Co", combo.get('displayedValue'), "auto-complete didn't fire");
 +						}), 1200, 500);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			// disabled tests+standard tests
 +			doh.register("disabled", [
 +
 +				// Test that correct styling is applied
 +				{
 +					timeout:1000,
 +					name:"disabled styling",
 +					combo:"combo3",
 +					runTest:function(){
 +						this.combo = dijit.byId(this.combo);
 +						doh.is(true, this.combo.get('disabled'));
 +						doh.is(true, this.combo.focusNode.disabled);
 +					}
 +				},
 +
 +				// Test that you can't focus a disabled combobox
 +				{
 +					timeout: 5000,
 +					name:"tab over disabled elements",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +
 +						dojo.byId("datatestDijit").focus();
 +
 +						doh.robot.sequence(d.getTestErrback(function(){
 +							// use sequence because focus on IE9+ is asynchronous
 +							doh.is("datatestDijit", dojo.global.dijit.focus.curNode.id, "focused on elem before disabled combo");
 +						}), 100);
 +
 +						// Tab over the disabled ComboBox
 +						doh.robot.keyPress(dojo.keys.TAB, 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("combobox4", dojo.global.dijit.focus.curNode.id, "focused on 'enable' button after disabled combo");
 +						}), 200);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			// now enable it and test that it can be used
 +			doh.register("enabled", [
 +				{
 +					timeout:30000,
 +					name:"combo3_enabledStyle",
 +					setUp: function(){
 +						dijit.byId("combo3").set("disabled", false);
 +					},
 +					runTest:function(){
 +						var combo = dijit.byId("combo3");
 +						doh.is(false, combo.get('disabled'));
 +						doh.is(false, combo.focusNode.disabled);
 +					}
 +				}
 +			]);
 +
 +			doh.register("specialchars", [
 +				{
 +					timeout:10000,
 +					name:"specialchars_type",
 +					combo:"specialchars",
 +					runTest:function(){
 +						return robot_typeValue(dijit.byId(this.combo), "sticks & stones", isComboBox? undefined : "sticks");
 +					}
 +				},
 +				{
 +					timeout:10000,
 +					name:"specialchars_a11y",
 +					combo:"specialchars",
 +					runTest:function(){
 +						return robot_a11ySelectValue(dijit.byId(this.combo), "more\\less", isComboBox? undefined : "more");
 +					}
 +				},
 +				{
 +					timeout:10000,
 +					name:"specialchars_escape",
 +					combo:"specialchars",
 +					runTest:function(){
 +						var combo = dijit.byId(this.combo);
 +						combo.focusNode.focus();
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 1000);
 +						var d = new doh.Deferred();
 +						doh.robot.typeKeys("3 *", 500, 600);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is("3 *", combo.focusNode.value);
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:10000,
 +					name:"specialchars_highlight",
 +					combo:"specialchars",
 +					runTest:function(){
 +						var combo = dijit.byId(this.combo);
 +						combo.queryExpr = '*${0}*';
 +						combo.focusNode.focus();
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 500);
 +						var d = new doh.Deferred();
 +						doh.robot.typeKeys("t", 1, 100);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var item = findMenuItem(combo, 'riches to');
 +							doh.t(item, 'item "rags --> riches to" should be visible');
 +							doh.is('rags --> riches <>t<>o',item.innerHTML.replace(/<[^>]*>/g,'<>'), '"t" in "to" should be highlighted');
 +						}), 1000, 500);
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			doh.register("japanese", [
 +				{
 +					timeout:60000,
 +					name:"japanese_a11y",
 +					combo:"japanese",
 +					runTest:function(){
 +						return robot_a11ySelectValue((this.combo = dijit.byId(this.combo)), "\u6771\u533A (East)", isComboBox? undefined : "higashiku");
 +					},
 +					tearDown:function(){
 +						this.combo.set("value", isComboBox?"\u6771\u533A (East)":"higashiku");
 +					}
 +				},
 +
 +				{
 +					timeout:20000,
 +					name:"japanese_type",
 +					runTest:function(){
 +						var	d = new doh.Deferred(),
 +							s0 = null,
 +							s1 = null,
 +							s2 = null,
 +							s3 = null,
 +							s4 = null,
 +							s5 = null,
 +							combo = dijit.byId("japanese");
 +
 +						combo.focusNode.focus();
 +
 +						doh.robot.keyPress(dojo.keys.END, 1000);
 +
 +						doh.robot.typeKeys("x", 500, 200);
 +						doh.robot.sequence(function(){
 +							s0 = combo.state;
 +						}, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.sequence(function(){
 +							s1 = combo.state;
 +						}, 500);
 +						doh.robot.typeKeys("xx", 500, 400);
 +						doh.robot.sequence(function(){
 +							s2 = combo.state;
 +						}, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.sequence(function(){
 +							s3 = combo.state;
 +						}, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.sequence(function(){
 +							s4 = combo.state;
 +						}, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.sequence(function(){
 +							s5 = combo.state;
 +						}, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.BACKSPACE, 500);
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500);
 +						doh.robot.keyPress(dojo.keys.ENTER, 500);
 +						doh.robot.sequence(dojo.hitch(this, function(){
 +							if(!isComboBox){
 +								doh.is("Error", s0, "s0");
 +								doh.is("Incomplete", s1, "s1");
 +								doh.is("Error", s2, "s2");
 +								doh.is("Error", s3, "s3");
 +								doh.is("Incomplete", s4, "s4");
 +								doh.is("Incomplete", s5, "s5");
 +							}
 +							if(combo.get("value") == (isComboBox?"\u6771\u897F (Touzai)":"touzai") && combo.focusNode.value=="\u6771\u897F (Touzai)"){
 +								d.callback(true);
 +							}else{
 +								d.errback(combo.id+" was supposed to have a value of "+(isComboBox?"\u6771\u897F (Touzai)":"touzai")+". Text is "+combo.focusNode.value+", value is "+combo.get("value"));
 +							}
 +						}), 1000);
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			doh.register("readOnly", [
 +				{
 +					timeout:5000,
 +					name:"cannot type",
 +					combo: "labelFunc",
 +					runTest:function(){
 +						var	d = new doh.Deferred(),
 +							combo = dijit.byId(this.combo),
 +							initVal = combo.get('value');
 +						combo.set('readOnly', true);
 +						combo.focus();
 +						doh.robot.typeKeys("xz", 1000, 500);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.t(combo.isValid(), 'isValid');
 +							doh.is(initVal, combo.get('value'), 'changed from ' + initVal + ' to ' + combo.get('value'));
 +						}), 500, 500);
 +						return d;
 +					},
 +					tearDown:function(){
 +						dijit.byId(this.combo).set("readOnly", false);
 +					}
 +				}
 +			]);
 +
 +			// Labelfunc tests.   See also initial tests on "setvaluetest" ComboBox.
 +			doh.register("labelFunc", [
 +				{
 +					timeout:60000,
 +					name:"labelFunc_keyboardSelect",
 +					combo:"labelFunc",
 +					runTest:function(){
 +						return robot_a11ySelectValue((this.combo = dijit.byId(this.combo)), "texas", isComboBox? "Texas" : "TX", "Texas");
 +					},
 +					tearDown:function(){
 +						this.combo.set("value", isComboBox? "Texas" : "TX");
 +					}
 +				},
 +
 +				{
 +					timeout:60000,
 +					name:"labelFunc_type",
 +					combo:"labelFunc",
 +					runTest:function(){
 +						return robot_typeValue((this.combo = dijit.byId(this.combo)), "Alabama", isComboBox ? "Alabama" : "AL", "Alabama");
 +					}
 +				},
 +
 +				{
 +					timeout:60000,
 +					name: "richtext",
 +					runTest: function(){
 +						var d = new doh.Deferred(),
 +							w = dijit.byId("richtexttest");
 +
 +						// testing that input has right data
 +						doh.is("h1", w.focusNode.value, "initial focus value");
 +						w.set("value", "h2");
 +						doh.is("h2", w.focusNode.value, "set() focus value");
 +
 +						w.focus();
 +
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var list = dojo.byId("richtexttest_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is("<h1>h1</h1>", dojo.trim(entries[0].innerHTML).toLowerCase(), "list #1 is rich text");
 +						}), 500);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			// The specifying a sort order and an initial filter to ComboBox
 +			doh.register("filter and sort params", [
 +				{
 +					timeout:60000,
 +					name:"sort order",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo3");
 +
 +						combo.focusNode.focus();
 +						doh.robot.sequence(function(){ combo.set("value", null); }, 1000);
 +
 +						// Show list, should be in reverse order
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							var list = dojo.byId("progCombo3_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is("United States of America", innerText(entries[0]), "list #1");
 +						}), 900);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"initial filter, descending sort",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo3");
 +
 +						// Filter drop down list to entries starting with "A"
 +						doh.robot.keyPress("A", 100);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list *doesn't* contain Africa (since it's a continent),
 +							// just Argentina and Australia, in descending sort order
 +							var list = dojo.byId("progCombo3_popup");
 +							doh.t(list, "drop down exists");
 +							doh.t(isVisible(list), "drop down is visible");
 +
 +							var entries = dojo.query(".dijitMenuItem", list).filter(isVisible);
 +							doh.is(2, entries.length, "two countries (but no continents) in drop down: " + list.innerHTML);
 +							doh.is("Australia", innerText(entries[0]), "list #1");
 +							doh.is("Argentina", innerText(entries[1]), "list #2");
 +						}), 900);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			// The specifying a sort order and an initial filter to ComboBox
 +			doh.register("blur", [
 +				{
 +					timeout:60000,
 +					name:"tooltip prompt",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("labelFunc");
 +
 +						combo.set("value", null);
 +						combo.focusNode.focus();
 +
 +						// blur
 +						doh.robot.keyPress(dojo.keys.TAB, 1000);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.t(!combo._message, "no tooltip on blur");
 +						}), 1000);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:60000,
 +					name:"empty but required",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("combobox4");
 +
 +						combo.set("value", null);
 +						combo.focusNode.focus();
 +
 +						// blur
 +						doh.robot.keyPress(dojo.keys.TAB, 1000);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.t(combo.state == "Error", "required field has error on blur");
 +						}), 1000);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			doh.register("wrap", [
 +				{
 +					timeout:10000,
 +					name:"wrap without More choices",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("preservetitletest");
 +
 +						combo.set('pageSize', 5); // remove More choices
 +						combo.focusNode.focus();
 +
 +						// Show list, should be in reverse order
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 1000); // wait for focus
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // wait for dropdown to open
 +						for(var i=1; i < 14; i++){
 +							doh.robot.keyPress(dojo.keys.DOWN_ARROW, 250); // down arrow and wrap twice
 +						}
 +						for(i=0; i < 4; i++){
 +							doh.robot.keyPress(dojo.keys.UP_ARROW, 250); // up arrow and wrap at most once
 +						}
 +						doh.robot.keyPress(dojo.keys.ENTER, 500); // select current item
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is(isComboBox ? "Vermont" : "vt", combo.get("value"), "wrap get('value')");
 +						}), 1000);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:10000,
 +					name:"wrap with More choices",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("preservetitletest");
 +
 +						combo.set('pageSize', 4); // add More choices
 +						combo.focusNode.focus();
 +
 +						// Show list, should be in reverse order
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 1000); // wait for focus
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // wait for dropdown to open
 +						for(var i=1; i < 14; i++){
 +							doh.robot.keyPress(dojo.keys.DOWN_ARROW, 250); // down arrow and wrap twice
 +						}
 +						for(i=0; i < 5; i++){
 +							doh.robot.keyPress(dojo.keys.UP_ARROW, 200); // up arrow and wrap at most once
 +						}
 +						doh.robot.keyPress(dojo.keys.ENTER, 500); // select current item
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is(isComboBox ? "Massachusetts" : "ma", combo.get("value"), "wrap get('value')");
 +						}), 1000);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:10000,
 +					name:"wrap and TAB from Prev choices",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("preservetitletest");
 +
 +						combo.set('pageSize', 4); // add More choices
 +						combo.set("value", isComboBox ? "Maine" : "me", true); // fires onChange
 +						combo.set("value", isComboBox ? "Connecticut" : "ct", false); // temporary value
 +						combo.focusNode.focus();
 +
 +						// Show list, should be in reverse order
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 1000); // wait for focus
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // wait for dropdown to open
 +						for(var i=1; i < 5; i++){
 +							doh.robot.keyPress(dojo.keys.DOWN_ARROW, 250); // down arrow to More choices
 +						}
 +						doh.robot.keyPress(dojo.keys.ENTER, 500); // select More choices
 +						doh.robot.keyPress(dojo.keys.UP_ARROW, 1000); // up arrow to Previous choices
 +						doh.robot.keyPress(dojo.keys.TAB, 500); // select current item
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							doh.is(isComboBox ? "Maine" : "me", combo.get("value"), "Previous choice reverts value");
 +						}), 1000);
 +
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			var pageSize, combo;
 +			// Test more choices
 +			doh.register("Select More choices", [
 +				{
 +					timeout:10000,
 +					name:"no auto-complete",
 +					setUp: function(){
 +						combo = dijit.byId("setvaluetest");
 +						pageSize = combo.get('pageSize');
 +						combo.set('pageSize', 1);
 +						combo.set('displayedValue', null, false);
 +						combo.focusNode.focus();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +
 +						// select more choices and press Enter
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 1500); // open dropdown
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // select Alabama
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // select more choices
 +						doh.robot.keyPress(dojo.keys.ENTER, 500);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list has disappeared
 +							var list = dojo.byId("setvaluetest_popup");
 +							doh.t(list && isVisible(list), "drop down is visible");
 +							doh.is(combo.dropDown.getHighlightedOption(), findMenuItem(combo, "Alaska"), "Alaska is selected");
 +
 +							doh.t(combo.focused, "widget is focused");
 +
 +							// Since the user down arrowed thru the choices, the value should be the current selection
 +							doh.is('Alaska', combo.focusNode.value);
 +							if(!isComboBox){
 +								doh.t(combo.isValid(true), "FilteringSelect should be valid");
 +							}
 +						}), 1000, 500);
 +
 +						return d;
 +					},
 +					tearDown: function(){
 +						combo.set('pageSize', pageSize);
 +						combo.closeDropDown();
 +					}
 +				},
 +				{
 +					timeout:10000,
 +					name:"auto-complete",
 +					setUp: function(){
 +						combo = dijit.byId("datatest");
 +						pageSize = combo.get('pageSize');
 +						combo.set('pageSize', 1);
 +						combo.set('displayedValue', null, false);
 +						combo.focusNode.focus();
 +					},
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +
 +						// select more choices and press Enter
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 1500); // open dropdown
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // select Alabama
 +						doh.robot.keyPress(dojo.keys.DOWN_ARROW, 500); // select more choices
 +						doh.robot.keyPress(dojo.keys.ENTER, 500);
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							// Check that drop down list has disappeared
 +							var list = dojo.byId("datatest_popup");
 +							doh.t(list && isVisible(list), "drop down is visible");
 +							doh.is(combo.dropDown.getHighlightedOption(), findMenuItem(combo, "Alaska"), "Alaska is selected");
 +
 +							doh.t(combo.focused, "widget is focused");
 +
 +							// Since the user down arrowed thru the choices, the value should be the current selection
 +							doh.is('Alaska', combo.focusNode.value);
 +							if(!isComboBox){
 +								doh.t(combo.isValid(true), "FilteringSelect should be valid");
 +							}
 +						}), 1000, 500);
 +
 +						return d;
 +					},
 +					tearDown: function(){
 +						combo.set('pageSize', pageSize);
 +						combo.closeDropDown();
 +					}
 +				}
 +			]);
 +
 +			doh.register("onChange", [
 +				{
 +					timeout:6000,
 +					name:"11062",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(){
 +							status = "called";
 +						});
 +						combo.focusNode.focus();
 +						doh.robot.keyPress(dojo.keys.TAB, 500); // blur
 +
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							doh.is("not called", status, "onChange should not have been called");
 +						}), 500);
 +
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.0",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value = isComboBox ? 'Kentucky' : 'KY';
 +						combo._lastValueReported = combo._lastValue = value;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value, true);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("not called", status, "onChange should not have been called");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.1",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value1 = isComboBox ? 'Kentucky' : 'KY';
 +						var value2 = isComboBox ? 'Texas' : 'TX';
 +						combo._lastValueReported = combo._lastValue = value1;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value2, false);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("not called", status, "onChange should not have been called");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.2",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value1 = isComboBox ? 'Kentucky' : 'KY';
 +						var value2 = isComboBox ? 'Texas' : 'TX';
 +						combo._lastValueReported = combo._lastValue = value2;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value1, false);
 +						combo.set('value', value2);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("called with "+value2, status, "onChange should have been called with "+value2);
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.3",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value1 = isComboBox ? 'Kentucky' : 'KY';
 +						var value2 = isComboBox ? 'Texas' : 'TX';
 +						combo._lastValueReported = combo._lastValue = value2;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value1, false);
 +						combo.set('value', value2, false);
 +						combo.set('value', value2, true);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("called with "+value2, status, "onChange should have been called with "+value2);
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.4",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value = isComboBox ? 'Kentucky' : 'KY';
 +						combo._lastValueReported = combo._lastValue = value;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value, false);
 +						combo.set('value', value, true);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("not called", status, "onChange should not have been called");
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.5",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value1 = isComboBox ? 'Kentucky' : 'KY';
 +						var value2 = isComboBox ? 'Texas' : 'TX';
 +						combo._lastValueReported = combo._lastValue = value2;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value1, false);
 +						combo.set('value', value2, true);
 +						combo.set('value', value1, false);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("called with "+value2, status, "onChange should have been called with "+value2);
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.6",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value1 = isComboBox ? 'Kentucky' : 'KY';
 +						var value2 = isComboBox ? 'Texas' : 'TX';
 +						combo._lastValueReported = combo._lastValue = value1;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value2, true);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("called with "+value2, status, "onChange should have been called with true"+value2);
 +						}), 500);
 +						return d;
 +					}
 +				},
 +				{
 +					timeout:2000,
 +					name:"10988.7",
 +					runTest:function(){
 +						var d = new doh.Deferred();
 +						var combo = dijit.byId("progCombo");
 +						var value1 = isComboBox ? 'Kentucky' : 'KY';
 +						var value2 = isComboBox ? 'Texas' : 'TX';
 +						combo._lastValueReported = combo._lastValue = value2;
 +						var status = "not called";
 +						var connectHandle = combo.connect(combo, "onChange", function(v){
 +							status = "called with " + v;
 +						});
 +						combo.set('value', value1, false);
 +						combo.set('value', value1, true);
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							combo.disconnect(connectHandle);
 +							combo._pendingOnChange = false;
 +							doh.is("called with "+value1, status, "onChange should have been called with "+value1);
 +						}), 500);
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			doh.register("destroy", [
 +				{
 +					timeout: 1000,
 +					name: "destroy",
 +					runTest: function(){
 +						var d = new doh.Deferred();
 +						doh.robot.sequence(d.getTestCallback(function(){
 +							dijit.byId("combo_01").destroy();
 +							doh.is(undefined, dojo.byId("combo_01"), 'widget was removed');
 +							doh.f(dojo.byId('destroyDiv').firstChild, 'container node is empty');
 +						}), 0);
 +						return d;
 +					}
 +				}
 +			]);
 +
 +			dojo.forEach(
 +				[
 +					{ attrs: { ignoreCase:true,  highlightMatch:"first", queryExpr:"${0}*"  }, results: { AA: "<>A<>A", Aa: "<>A<>a", aA: "<>a<>A", aa: "<>a<>a" } },
 +					{ attrs: { ignoreCase:false, highlightMatch:"first", queryExpr:"${0}*"  }, results: { aA: "<>a<>A", aa: "<>a<>a" } },
 +					{ attrs: { ignoreCase:true,  highlightMatch:"all",   queryExpr:"${0}*"  }, results: { AA: "<>A<>A", Aa: "<>A<>a", aA: "<>a<>A", aa: "<>a<>a" } },
 +					{ attrs: { ignoreCase:true,  highlightMatch:"all",   queryExpr:"*${0}*" }, results: { AA: "<>A<><>A<>", Aa: "<>A<><>a<>", aA: "<>a<><>A<>", aa: "<>a<><>a<>" } },
 +					{ attrs: { ignoreCase:false, highlightMatch:"all",   queryExpr:"*${0}*" }, results: { Aa: "A<>a<>", aA: "<>a<>A", aa: "<>a<><>a<>" } },
 +					{ attrs: { ignoreCase:false, highlightMatch:"none",  queryExpr:"*${0}*" }, results: { Aa: "Aa", aA: "aA", aa: "aa" } },
 +					{ attrs: { ignoreCase:true,  highlightMatch:"all",   queryExpr:"*${0}"  }, results: { AA: "A<>A<>", Aa: "A<>a<>", aA: "a<>A<>", aa: "a<>a<>" } },
 +					{ attrs: { ignoreCase:false, highlightMatch:"first", queryExpr:"*${0}"  }, results: { Aa: "A<>a<>", aa: "a<>a<>" } }
 +				], function(test){
 +					doh.register("highlight", [
 +						{
 +							timeout: 6000,
 +							name: dojo.toJson(test.attrs),
 +							setUp: function(){
 +								combo = dijit.byId("highlight");
 +								dojo.mixin(combo, test.attrs);
 +								combo.focusNode.focus();
 +							},
 +							runTest: function(){
 +								var d = new doh.Deferred();
 +								doh.robot.keyPress(dojo.keys.BACKSPACE, 1000);
 +								doh.robot.typeKeys("a", 1, 100);
 +								doh.robot.sequence(d.getTestCallback(function(){
 +									dojo.forEach(["AA","Aa","aA","aa"], function(idx){
 +										var item = findMenuItem(combo, idx);
 +										if(idx in test.results){
 +											var expected = test.results[idx];
 +											var actual = item.innerHTML.replace(/\/?span\s*[^>]*@?/ig, "");
 +											doh.is(expected, actual, idx);
 +										}else{
 +											doh.t(item == combo.dropDown.nextButton, "too many menu items: " + idx);
 +										}
 +									});
 +								}), 1000, 500);
 +								return d;
 +							},
 +							tearDown: function(){
 +								combo.closeDropDown();
 +							}
 +						}
 +					]);
 +				}
 +			);
 +
 +			doh.register("custom class", [
 +				// Select a value from the drop down using the keyboard,
 +				// use "more choices" button to page as necessary
 +				{
 +					timeout: 60000,
 +					name:" customstore_a11y",
 +					combo: "subclass",
 +					runTest: function(){
 +						return robot_a11ySelectValue(dijit.byId(this.combo), "Delaware", isComboBox ? "Delaware" : "DE", "Delaware");
 +					}
 +				}
 +			]);
 +
++			doh.register("aria-autocomplete (#9089)", [
++				{
++					timeout: 1000,
++					name:"aria-autocomplete with autoComplete enabled",
++					combo: "autocompleteon",
++					runTest: function(){
++						var combo = dijit.byId(this.combo);
++						doh.is("both", combo.focusNode.getAttribute("aria-autocomplete"), "aria-autocomplete on combo is 'both'");
++					}
++				},
++				{
++					timeout: 1000,
++					name:"aria-autocomplete with autoComplete disabled",
++					combo: "autocompleteoff",
++					runTest: function(){
++						var combo = dijit.byId(this.combo);
++						doh.is("list", combo.focusNode.getAttribute("aria-autocomplete"), "aria-autocomplete on combo is 'list'");
++					}
++				}
++			]);
++
 +			doh.run();
 +		});
 +	</script>
 +</head>
 +</html>
diff --cc dijit/tests/form/test_DateTextBox.html
index 4623192,0000000..afdec74
mode 100644,000000..100644
--- a/dijit/tests/form/test_DateTextBox.html
+++ b/dijit/tests/form/test_DateTextBox.html
@@@ -1,459 -1,0 +1,471 @@@
 +<!DOCTYPE html>
 +<html lang="en">
 +	<head>
 +		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
 +		<title>Test DateTextBox Widget</title>
 +
 +		<style type="text/css">
 +			@import "../../themes/claro/document.css";
 +			@import "../css/dijitTests.css";
 +
 +			.testExample {
 +				background-color:#fbfbfb;
 +				padding:1em;
 +				margin-bottom:1em;
 +				border:1px solid #bfbfbf;
 +			}
 +
 +			.noticeMessage {
 +				color:#093669;
 +				font-size:0.95em;
 +				margin-left:0.5em;
 +			}
 +
 +			.dojoTitlePaneLabel label {
 +				font-weight:bold;
 +			}
 +		</style>
 +
 +		<!-- required: the default dijit theme: -->
 +		<link id="themeStyles" rel="stylesheet" href="../../../dijit/themes/claro/claro.css"/>
 +
 +		<!-- required: dojo.js -->
 +		<script type="text/javascript" src="../../../dojo/dojo.js"
- 			data-dojo-config="isDebug: true, parseOnLoad: true, extraLocale: ['de-de', 'en-us']"></script>
++			data-dojo-config="async: true, isDebug: true, parseOnLoad: true, extraLocale: ['de-de', 'en-us']"></script>
 +
 +		<!-- only needed for alternate theme testing: -->
 +		<script type="text/javascript" src="../_testCommon.js"></script>
 +
 +		<script type="text/javascript">
- 			dojo.require("doh.runner");
- 			dojo.require("dijit.dijit"); // optimize: load dijit layer
- 			dojo.require("dijit.form.DateTextBox");
- 			dojo.require("dijit.form.Form");
- 			dojo.require("dojo.date.locale");
- 			dojo.require("dijit.tests.helpers");    // functions to help test
- 			dojo.require("dojo.parser");	// scan page for widgets and instantiate them
- 
- 			// Add test=true to the URL to run unit tests.
- 			var test = /mode=test/i.test(window.location.href);
- 
 +			function eventHandler(e){
 +				// use this.domNode.getAttribute('widgetId') to show "this" is the widget
 +				// mouseleave/enter map to mouseout/over in all browsers except IE
 +				console.log(this.domNode.getAttribute('widgetId') + ' ' + e.type);
 +			}
- 			dojo.ready(function(){
++
++			require([
++				"doh/runner",
++				"dojo/date",
++				"dojo/date/locale",
++				"dojo/_base/lang",
++				"dojo/on",
++				"dojo/parser",
++				"dijit/registry",
++				"dijit/form/DateTextBox",
++				"dijit/form/Form",
++				"dijit/tests/helpers",
++				"dojo/domReady!"
++			], function(doh, date, locale, lang, on, parser, registry, DateTextBox){
++				// Add test=true to the URL to run unit tests.
++				var test = /mode=test/i.test(window.location.href);
++
++				parser.parse();
++				
 +				// See if we can make a widget in script and attach it to the DOM ourselves.
- 				dojo.connect(dijit.byId('pattern'), "onMouseEnter", eventHandler);
- 				dojo.connect(dijit.byId('pattern'), "onMouseLeave", eventHandler);
- 				dojo.connect(dijit.byId('pattern'), "onKeyDown", eventHandler);
++				on(registry.byId('pattern'), "onMouseEnter", eventHandler);
++				on(registry.byId('pattern'), "onMouseLeave", eventHandler);
++				on(registry.byId('pattern'), "onKeyDown", eventHandler);
 +
 +				var props = {
 +					name: "date4",
 +					value: new Date(2006,10,29),
 +					constraints: {min:new Date(2004,0,1),max:new Date(2006,11,31)},
 +					lang: "de-de",
 +					hasDownArrow: false,
 +					onMouseEnter: eventHandler,
 +					onMouseLeave: eventHandler,
 +					onKeyDown: eventHandler,
 +					promptMessage: "dd.mm.yy",
 +					rangeMessage: "Enter a date in the year range 2004-2006.",
 +					invalidMessage: "Invalid date. Use dd.mm.yy format."
 +				};
- 				var german = new dijit.form.DateTextBox(props, "german");
++				var german = new DateTextBox(props, "german");
 +				german.startup();
- 				var localLong = dijit.byId("localLong");
- 				var american = dijit.byId("american");
++				var localLong = registry.byId("localLong");
++				var american = registry.byId("american");
 +
 +				localLong.parse = function(value,constraints){
 +					return this.dateLocaleModule.parse(value, (constraints.formatLength="long") && constraints) ||
- 						 this.dateLocaleModule.parse(value, (constraints.formatLength="short") && constraints) ||
- 						(this._isEmpty(value) ? null : undefined);	 // Date
++							this.dateLocaleModule.parse(value, (constraints.formatLength="short") && constraints) ||
++							(this._isEmpty(value) ? null : undefined);	 // Date
 +				};
 +
 +				if(test){
 +					doh.register("constraints", [
 +						{
 +							name: "initial state",
 +							timeout: 1000,
 +							runTest: function(t){
 +								var expected = new Date(2005,11,30);
 +								var actual = localLong.get('value');
- 								t.is(0, dojo.date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
++								t.is(0, date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
 +								t.is("", localLong.get('state'));
 +							}
 +						},
 +						{
 +							name: "change value, now invalid",
 +							timeout: 1000,
 +							runTest: function(t){
 +								var expected = new Date(2007,6,15);
 +								localLong.set("value", expected);
 +								var actual = localLong.get('value');
- 								t.is(0, dojo.date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
++								t.is(0, date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
 +								t.is("Error", localLong.get('state'));
 +							}
 +						},
 +						{
 +							name: "change max, still invalid",
 +							timeout: 1000,
 +							runTest: function(t){
- 								localLong.set("constraints", dojo.mixin(localLong.get('constraints'), { max: new Date(2007,5,30)}));
++								localLong.set("constraints", lang.mixin(localLong.get('constraints'), { max: new Date(2007,5,30)}));
 +								var expected = new Date(2007,6,15);
 +								var actual = localLong.get('value');
- 								t.is(0, dojo.date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
++								t.is(0, date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
 +								t.is("Error", localLong.get('state'));
 +							}
 +						},
 +						{
 +							name: "change max, now valid",
 +							timeout: 1000,
 +							runTest: function(t){
- 								localLong.set("constraints", dojo.mixin(localLong.get('constraints'), { max: new Date(2007,11,31)}));
++								localLong.set("constraints", lang.mixin(localLong.get('constraints'), { max: new Date(2007,11,31)}));
 +								var expected = new Date(2007,6,15);
 +								var actual = localLong.get('value');
- 								t.is(0, dojo.date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
++								t.is(0, date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
 +								t.is("", localLong.get('state'));
 +							}
 +						},
 +						{
 +							name: "change max, now invalid",
 +							timeout: 1000,
 +							runTest: function(t){
- 								localLong.set("constraints", dojo.mixin(localLong.get('constraints'), { max: new Date(2006,11,30)}));
++								localLong.set("constraints", lang.mixin(localLong.get('constraints'), { max: new Date(2006,11,30)}));
 +								var expected = new Date(2007,6,15);
 +								var actual = localLong.get('value');
- 								t.is(0, dojo.date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
++								t.is(0, date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
 +								t.is("Error", localLong.get('state'));
 +							}
 +						},
 +						{
 +							name: "change value, now valid",
 +							timeout: 1000,
 +							runTest: function(t){
 +								var expected = new Date(2005,11,30);
 +								localLong.set("value", expected);
 +								var actual = localLong.get('value');
- 								t.is(0, dojo.date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
++								t.is(0, date.compare(expected, actual), 'expected ' + expected.toDateString() + ' but got ' + actual.toDateString());
 +								t.is("", localLong.get('state'));
 +							}
 +						}
 +					]);
 +
 +					doh.register("misc",
- 						function noYear(){
- 							var widget = dijit.byId("noyear");
- 							doh.t(widget.isValid(false), "isValid");
- 							doh.is(2011, widget.get('value').getFullYear(), "programmatic value");
- 							doh.is(2011, widget.value.getFullYear(), "JS value");
- 						},
- 						function noParams(){
- 							// Just making sure we don't get an exception when no parameters are specified.
- 							new dijit.form.DateTextBox();
- 						}
++							function noYear(){
++								var widget = registry.byId("noyear");
++								doh.t(widget.isValid(false), "isValid");
++								doh.is(2011, widget.get('value').getFullYear(), "programmatic value");
++								doh.is(2011, widget.value.getFullYear(), "JS value");
++							},
++							function noParams(){
++								// Just making sure we don't get an exception when no parameters are specified.
++								new DateTextBox();
++							}
 +					);
 +
 +					doh.register("API", [
 +						function initial(){
 +							// initial conditions
- 							doh.is(0, dojo.date.compare(new Date(2005,11,30), american.get('value')), 'wire value of american: ' + american.get('value'));
++							doh.is(0, date.compare(new Date(2005,11,30), american.get('value')), 'wire value of american: ' + american.get('value'));
 +							doh.is('12/30/2005', american.get('displayedValue'), 'displayed value of american');
 +						},
 +
 +						function setValue(){
 +							american.set('value', new Date(2004,9,20));
- 							doh.is(0, dojo.date.compare(new Date(2004,9,20), american.get('value')),
++							doh.is(0, date.compare(new Date(2004,9,20), american.get('value')),
 +									'wire value of american is: ' + american.get('value') +
 +									' but should be: ' + new Date(2004,9,20));
 +							doh.is('10/20/2004', american.get('displayedValue'), 'displayed value of american');
 +							doh.t(american.isValid(), 'marked as valid');
 +						},
 +
 +						function setDisplayedValue(){
 +							american.set('displayedValue', '11/12/2006');
- 							doh.is(0, dojo.date.compare(new Date(2006, 10, 12), american.get('value')), 'wire value of american');
++							doh.is(0, date.compare(new Date(2006, 10, 12), american.get('value')), 'wire value of american');
 +							doh.is('11/12/2006', american.get('displayedValue'), 'displayed value of american');
 +							doh.t(american.isValid(), 'marked as valid');
 +						},
 +
 +						function setInvalidDisplayedValue(){
 +							american.set('displayedValue', 'foo');
 +							doh.t(american.get('value') === undefined, 'value is undefined if displayedValue is garbage');
 +							doh.f(american.isValid(), 'marked as invalid');
 +
 +							// setting the value to get('value') should never change anything, so
 +							// therefore setting the value to undefined shouldn't affect the displayed value
 +							american.set('value', undefined);
 +							doh.is(american.get('displayedValue'), 'foo');
 +						},
 +
 +						function setOutOfRange(){
 +							// This widget is set to be valid between 2004 and 2006 only
 +							american.set('displayedValue', '12/1/2008');
 +							doh.f(american.isValid(), 'marked as invalid since out of range');
 +							doh.is('12/1/2008', american.get('displayedValue'), 'displayed value of american');
 +						},
 +
 +						function noInitialValue(){
- 							var fromDate = dijit.byId('fromDate');
++							var fromDate = registry.byId('fromDate');
 +							doh.is('', fromDate.get('displayedValue'), 'initially blank');
- 							doh.is(dijit.form.DateTextBox.prototype.value, fromDate.value, 'default value');
++							doh.is(new Date('').toString(), fromDate.value.toString(), 'default value');
 +							var d = new doh.Deferred();
 +							var today = new Date();
 +							fromDate.set('value', today, true);
 +							setTimeout(d.getTestCallback(function(){
- 									var toDate = dijit.byId('toDate');
++								var toDate = registry.byId('toDate');
 +								doh.is(today.toDateString(), fromDate.value.toDateString(), 'changed value');
 +								doh.is(today.toDateString(), toDate.constraints.min.toDateString(), 'onChange');
 +							}), 500);
 +							return d;
 +						},
 +
 +						function ariaRolesAndAttributes(){
 +							var d = new doh.Deferred(),
- 								button = dijit.byId("local");
++									button = registry.byId("local");
 +
 +							doh.is("combobox", button._popupStateNode.getAttribute("role"), "button _popupStateNode role");
 +							doh.t(button._popupStateNode.getAttribute("aria-haspopup"), "aria-haspopup on button");
 +							doh.f(button._popupStateNode.getAttribute("aria-expanded"), "initially missing aria-expanded");
 +							doh.f(button._popupStateNode.getAttribute("aria-owns"), "initally missing aria-owns");
 +
 +							button.openDropDown();
 +
 +							setTimeout(d.getTestCallback(function(){
 +								doh.t(button._popupStateNode.getAttribute("aria-expanded"), "now aria-expanded should be true");
 +								doh.is("local_popup", button._popupStateNode.getAttribute("aria-owns"), "should aria-own the Dlg");
 +								// Check roles and attributes on the dialog
- 								var dlg = dijit.byId("local_popup");
++								var dlg = registry.byId("local_popup");
 +								doh.is("grid", dlg.domNode.getAttribute("role"), "Dlg.domNode should have a role");
 +								doh.is("local_popup_mddb local_popup_year", dlg.domNode.getAttribute("aria-labelledby"), "aria-labelledby should not overwrite existing labelledby for the Calendar");
 +
 +								button.closeDropDown();
 +							}), 500);
 +
 +							return d;
 +						}
 +					]);
 +
 +					doh.register("localization", [
 +						function initialGerman(){
- 							doh.is(0, dojo.date.compare(new Date(2006,10,29), german.get('value')), 'wire value of german: ' + german.get('value'));
++							doh.is(0, date.compare(new Date(2006,10,29), german.get('value')), 'wire value of german: ' + german.get('value'));
 +							doh.is('29.11.2006', german.get('displayedValue'), 'displayed value of german');
 +						},
 +
 +						function setValueGerman(){
 +							german.set('value', new Date(2004,9,20));
- 							doh.is(0, dojo.date.compare(new Date(2004,9,20), german.get('value')),
++							doh.is(0, date.compare(new Date(2004,9,20), german.get('value')),
 +									'wire value of german is: ' + german.get('value') +
 +									' but should be: ' + new Date(2004,9,20));
 +							doh.is('20.10.2004', german.get('displayedValue'), 'displayed value of german');
 +							doh.t(german.isValid(), 'marked as valid');
 +						},
 +
 +						function setDisplayedValueGerman(){
 +							german.set('displayedValue', '12.11.2006');
- 							doh.is(0, dojo.date.compare(new Date(2006, 10, 12), german.get('value')), 'wire value of german');
++							doh.is(0, date.compare(new Date(2006, 10, 12), german.get('value')), 'wire value of german');
 +							doh.is('12.11.2006', german.get('displayedValue'), 'displayed value of german');
 +							doh.t(german.isValid(), 'marked as valid');
 +						},
 +						{
 +							name: "labels",
 +							timeout: 6000,
 +							runTest: function(){
 +								german.set('value', new Date(2006, 9, 15));	// 10/15/2006
 +
 +								german.openDropDown();
- 								var calendar = dijit.byId("german_popup");
++								var calendar = registry.byId("german_popup");
 +
 +								// calendar exists and is shown
 +								doh.t(calendar && isVisible(calendar), "calendar is visible");
 +
 +								// Month label
- 								doh.is("Oktober", innerText(dojo.query(".dijitCalendarCurrentMonthLabel", calendar.domNode)[0]));
++								doh.is("Oktober", innerText(query(".dijitCalendarCurrentMonthLabel", calendar.domNode)[0]));
 +
 +								// Day labels
- 								var dayLabels = dojo.query(".dijitCalendarDayLabelTemplate", calendar.domNode);
++								var dayLabels = query(".dijitCalendarDayLabelTemplate", calendar.domNode);
 +								doh.is(7, dayLabels.length, "7 day labels");
 +								doh.is("M", innerText(dayLabels[0]), "day 0");
 +								doh.is("D", innerText(dayLabels[1]), "day 1");
 +								doh.is("M", innerText(dayLabels[2]), "day 2");
 +								doh.is("D", innerText(dayLabels[3]), "day 3");
 +								doh.is("F", innerText(dayLabels[4]), "day 4");
 +								doh.is("S", innerText(dayLabels[5]), "day 5");
 +								doh.is("S", innerText(dayLabels[6]), "day 6");
 +								german.closeDropDown();
 +							}
 +						}
 +					]);
 +
 +					doh.run();
 +				}
- 
 +			});
 +		</script>
 +	</head>
 +
 +	<body class="claro" role="main">
 +
++		<script type="dojo/require">
++			dom: "dojo/dom",
++			query: "dojo/query",
++			registry: "dijit/registry"
++		</script>
++
 +		<h1 class="testTitle">Test DateTextBox Widget</h1>
 +		<!--	to test form submission, you'll need to create an action handler similar to
 +			http://www.utexas.edu/teamweb/cgi-bin/generic.cgi -->
- 		<form id="form1" data-dojo-type='dijit.form.Form' data-dojo-props='action:"", name:"example", method:"", onSubmit:function(){ return this.validate() }'>
++		<form id="form1" data-dojo-type='dijit/form/Form' data-dojo-props='action:"", name:"example", method:"", onSubmit:function(){ return this.validate() }'>
 +			<div class="dojoTitlePaneLabel">
 +				<label for="local"> Date (local format) </label>
 +				<span class="noticeMessage">DateTextBox class, no attributes</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="local" data-dojo-type="dijit/form/DateTextBox" style="width: 100px"
 +					data-dojo-props='name:"noDOMvalue", value:"2008-12-31", type:"text", onMouseEnter:eventHandler,
 +					onMouseLeave:eventHandler,
 +					onKeyDown:eventHandler,
- 					onChange:function(val){ dojo.byId("oc1").value = "" + val; }
++					onChange:function(val){ dom.byId("oc1").value = "" + val; }
 +				'/>
 +				<label for="oc1">onChange:</label><input id="oc1" size="34" disabled value="not fired yet!" autocomplete="off"/>
 +				<span style='white-space:nowrap;'>
- 				<button type="button" onclick="dijit.byId('local').set('value', null);">set('value',null)</button>
- 				<button type="button" onclick="dojo.byId('gv1').value=''+dijit.byId('local').get('value');">get('value')</button>
- 				<button type="button" onclick="dojo.byId('gv1').value=''+dijit.byId('local').value;">.value</button>
++				<button type="button" onclick="registry.byId('local').set('value', null);">set('value',null)</button>
++				<button type="button" onclick="dom.byId('gv1').value=''+registry.byId('local').get('value');">get('value')</button>
++				<button type="button" onclick="dom.byId('gv1').value=''+registry.byId('local').value;">.value</button>
 +				<label for="gv1">l:</label><input id="gv1" size="34" disabled value="not called yet!" autocomplete="off"/>
 +				</span>
 +			</div>
 +			<div class="dojoTitlePaneLabel">
 +				<label for="localLong"> Date (local format - long) </label>
 +				<span class="noticeMessage">DateTextBox class,
 +					Attributes: required="true", trim="true", constraints={min:'2004-01-01',max:'2006-12-31',formatLength:'long'}. Works for leap years</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="localLong" data-dojo-id="localLong" data-dojo-type="dijit/form/DateTextBox"
 +					data-dojo-props='type:"text", name:"date1", value:"2005-12-30",
 +					constraints:{min:"2004-01-01",max:"2006-12-31",formatLength:"long"},
 +					required:true,
 +					trim:true,
- 					onChange:function(val){ dojo.byId("oc2").value = val; },
++					onChange:function(val){ dom.byId("oc2").value = val; },
 +					onMouseEnter:eventHandler,
 +					onMouseLeave:eventHandler,
 +					onKeyDown:eventHandler,
 +					invalidMessage:"Invalid date." '/>
 + 				<label for="oc2">onChange:</label><input id="oc2" size="34" disabled value="not fired yet!" autocomplete="off"/>
- 				<input type="button" value="Destroy" onClick="dijit.byId('localLong').destroy(); return false;"/>
- 				<input type="button" value="set max to 2007-12-31" onClick="dijit.byId('localLong').constraints.max = new Date(2007,11,31); dijit.byId('localLong').validate(false); return false;"/>
++				<input type="button" value="Destroy" onClick="registry.byId('localLong').destroy(); return false;"/>
++				<input type="button" value="set max to 2007-12-31" onClick="registry.byId('localLong').constraints.max = new Date(2007,11,31); registry.byId('localLong').validate(false); return false;"/>
 +			</div>
 +			<div class="dojoTitlePaneLabel">
 +				<label for="american"> Date (American format) </label>
 +				<span class="noticeMessage">DateTextBox class,
 +					Attributes: lang="en-us", required="true", constraints={min:'2004-01-01',max:'2006-12-31'}. Works for leap years.
 +					Prompt message whenever field is blank.
 +				</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="american" data-dojo-type="dijit/form/DateTextBox"
 +					data-dojo-props='type:"text", name:"date2", value:"2005-12-30",
 +					constraints:{min:"2004-01-01",max:"2006-12-31"},
 +					lang:"en-us",
 +					required:true,
 +					onMouseEnter:eventHandler,
 +					onMouseLeave:eventHandler,
 +					onKeyDown:eventHandler,
 +					promptMessage:"mm/dd/yyyy",
 +					invalidMessage:"Invalid date. Use mm/dd/yyyy format."'/>
 +			</div>
 +			<div class="dojoTitlePaneLabel">
 +				<label for="german"> Date (German format)</label>
 +				<span class="noticeMessage">DateTextBox class,
 +					Attributes: lang="de-de", hasDownArrow=false, constraints={min:2004-01-01, max:2006-12-31}. Works for leap years.
 +					Prompt message whenever field is blank.
 +				</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="german"/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="pattern"> Date, overriding pattern</label>
 +				<span class="noticeMessage">Date, overriding pattern with dd-MM-yyyy</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="pattern" data-dojo-type="dijit/form/DateTextBox" data-dojo-props='name:"noDOMvalue", type:"text", constraints:{datePattern:"dd-MM-yyyy", strict:true}'/>
 +			</div>
 +			<div class="dojoTitlePaneLabel">
 +				<strong>Using title attribute for label</strong>
 +				<span class="noticeMessage">DateTextBox class,
 +					Attributes: lang="en-us", required="true", prompt message, invalid message
 +					Prompt message whenever field is blank.
 +				</span>
 +			</div>
 +			<div class="testExample">
 +				<div id="american2" data-dojo-type="dijit/form/DateTextBox"
 +					data-dojo-props='type:"text", name:"date20", value:"2008-12-30",
 +					title:"Date in month, day, year format",
 +					lang:"en-us",
 +					required:true,
 +					promptMessage:"mm/dd/yyyy",
 +					invalidMessage:"Invalid date. Use mm/dd/yyyy format."'>
 +				</div>
 +			</div>
 +
 +			<script>
 +				function displayData(){
 +					var f = document.getElementById("form1");
 +					var s = "";
 +					for(var i = 0; i < f.elements.length; i++){
 +						var elem = f.elements[i];
 +						if(elem.name == "button")  { continue; }
 +						s += elem.name + ": " + elem.value + "\n";
 +					}
 +					alert(s);
 +				}
 +			</script>
 +
 +			<div class="dojoTitlePaneLabel">
 +				 Date pairs, from/to (won't submit unless from/to fields filled in correctly):
 +			</div>
 +			<div class="testExample">
 +				<label for="fromDate">From:</label> <input id="fromDate" data-dojo-type="dijit/form/DateTextBox"
 +					data-dojo-props='type:"text", name:"fromDate", required:true,
- 					onChange:function(){ dijit.byId("toDate").constraints.min = this.get("value"); } '/>
++					onChange:function(){ registry.byId("toDate").constraints.min = this.get("value"); } '/>
 +				<label for="toDate">To:</label> <input id="toDate" data-dojo-type="dijit/form/DateTextBox"
 +					data-dojo-props='type:"text", name:"toDate", required:true,
- 					onChange:function(){ dijit.byId("fromDate").constraints.max = this.get("value"); } '/>
++					onChange:function(){ registry.byId("fromDate").constraints.max = this.get("value"); } '/>
 +
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="noyear">Pick a day in 3Q 2011</label>
 +			</div>
 +			<div class="testExample">
 +				<input data-dojo-type="dijit/form/DateTextBox" id="noyear" data-dojo-props='
 +					name:"noyear",
 +					required:"true",
 +					constraints:{datePattern:"MMMM dd",
 +						min:new Date(2011,9,1),
 +						max:new Date(2011,11,31)
 +					},
 +					parse:function(){
 +						var d = this.inherited("parse", arguments);
 +						if(d instanceof Date)d.setFullYear(2011);
 +						return d;
 +					},
 +					value:new Date(2011,10,25)'
 +				/>
 +			</div>
 +
 +			<button type="button" name="button" onclick="displayData(); return false;">view data</button>
 +			<input type="submit" name="submit" />
 +			<input type="reset" name="reset" />
 +		</form>
 +	</body>
 +</html>
diff --cc dijit/tests/form/test_validate.html
index 94d3e49,0000000..20e775e
mode 100644,000000..100644
--- a/dijit/tests/form/test_validate.html
+++ b/dijit/tests/form/test_validate.html
@@@ -1,961 -1,0 +1,982 @@@
 +<!DOCTYPE html>
 +<html lang="en">
 +	<head>
 +		<meta http-equiv="Content-type" content="text/html; charset=utf-8">
 +		<title>Test TextBox Validation Widgets</title>
 +
 +		<style type="text/css">
 +			@import "../../themes/claro/document.css";
 +			@import "../css/dijitTests.css";
 +
 +			.testExample {
 +				background-color:#fbfbfb;
 +				padding:1em;
 +				margin-bottom:1em;
 +				border:1px solid #bfbfbf;
 +			}
 +
 +			body .small {
 +				width: 3em;
 +			}
 +			body .medium {
 +				width: 10em;
 +			}
 +			body .long {
 +				width: 20em;
 +			}
 +			body .verylong {
 +				width: 90%;
 +			}
 +
 +			.noticeMessage {
 +				color:#093669;
 +				font-size:0.95em;
 +				margin-left:0.5em;
 +			}
 +
 +			.dojoTitlePaneLabel label {
 +				font-weight:bold;
 +			}
 +			#widget_q26 .dijitInputField {
 +				padding:10px !important;
 +			}
 +
 +			.monospace .dijitTextBox {
 +				font-family: monospace;
 +			}
 +
 +			.sans {
 +				font-family: sans-serif;
 +			}
 +		</style>
 +
 +		<!-- required: the default dijit theme: -->
 +		<link id="themeStyles" rel="stylesheet" href="../../themes/claro/claro.css"/>
 +
 +		<!--
 +			required: dojo.js
 +			Note that the locale is hardcoded to english, for benefit of the robot/ValidationTextBox.html test
 +			(when running on a non-US computer).
 +		 -->
 +		<script type="text/javascript" src="../../../dojo/dojo.js"
 +			data-dojo-config="isDebug: true, parseOnLoad: true, locale: 'en-us', extraLocale: ['de-de']"></script>
 +
 +		<!-- only needed for alternate theme testing: -->
 +		<script type="text/javascript" src="../_testCommon.js"></script>
 +
 +		<script type="text/javascript">
 +			dojo.require("doh.runner");
 +
 +			dojo.require("dijit.form.TextBox");
 +			dojo.require("dijit.form.ValidationTextBox");
 +			dojo.require("dijit.form.NumberTextBox");
 +			dojo.require("dijit.form.CurrencyTextBox");
 +
 +			dojo.require("dijit.Dialog");
 +			dojo.require("dijit.form.Button");
 +			dojo.require("dijit.tests.helpers");    // functions to help test
 +
 +			dojo.require("dojo.currency");
 +			dojo.require("dojo.parser");	// scan page for widgets and instantiate them
 +
 +			// Add test=true to the URL to run unit tests.
 +			var test = /mode=test/i.test(window.location.href);
 +
 +			function fixNbsp(val){
 +				return typeof val == "string" ? val.replace(/\xA0/g, " ") : val;
 +			}
 +
 +			dojo.ready(function(){
 +
 +				new dijit.form.TextBox({id: 'monospace1', "aria-label":"monospace1", disabled: true, value: 'M|M:monospace?'}, 'monospace1');
 +				new dijit.form.TextBox({id: 'monospace2', "aria-label":"monospace2",disabled: true, value: 'M|M:monospace?'}).placeAt('widget_monospace1', 'after');
 +
 +				new dijit.form.TextBox({id: 'sans1', "aria-label":"sans1", disabled: true, value: 'sans1'}, 'sans1');
 +				new dijit.form.TextBox({id: 'sans2', "aria-label":"sans2", disabled: true, value: 'sans2'}).placeAt('widget_sans1', 'after');
 +
 +				// See if we can make a widget in script and attach it to the DOM ourselves.
 +				var props = {
 +					name: "elevation",
 +					value: 3000,
 +					constraints: {min:-20000,max:20000,places:0,allowCruft:true},
 +					promptMessage: "Enter a value between -20000 and +20000",
 +					required: true,
 +					style:{ textAlign: document.documentElement.dir == "rtl" ? "left" : "right" },
 +					editOptions:{pattern:'#,##0'},
 +					invalidMessage: "Invalid elevation.",
 +					onChange: function(val){dojo.byId('oc5').value = val; },
 +					"class": "medium"
 +				};
 +				(new dojo.declare("customNumberTextBox", dijit.form.NumberTextBox, props)({}, "q05")).startup();
 +
 +				props = {
 +					name: "ticket1651",
 +					id: "mname",
 +					templateString: null, // #11493
 +					templatePath: dojo.moduleUrl('dijit.form.templates', 'TextBox.html'),
 +					value: null,
 +					"aria-labelledby":"l_ticket1651"
 +				};
 +				(new dijit.form.TextBox(props, "ticket1651")).startup();
 +
 +				if(!test){ return; }
 +
 +				// Test initial conditions
 +				doh.register("initial conditions", {
 +					name: "initial conditions",
 +					runTest: function(){
 +						var form1 = dojo.byId("form1");
 +
 +						doh.is("Testing Testing", form1.firstname.value, "firstname");
 +						doh.is("not fired yet!", dojo.byId("oc1").value, "firstname onchange");
 +
 +						doh.is("TESTING TESTING", form1.lastname.value, "lastname");
 +
 +						doh.is("", form1.age.value, "age");
 +						doh.is("not fired yet!", dojo.byId("oc3").value, "age onchange");
 +
 +						doh.is("", form1.occupation.value, "occupation");
 +
 +						doh.is("3000", form1.elevation.value, "elevation");
 +						doh.is("3,000", dojo.byId("q05").value, "elevation display value");
 +						doh.is("not fired yet!", dojo.byId("oc5").value, "elevation onchange");
 +						doh.t(!!dijit.byId("q05").constraints.allowCruft, "constructor constraints value");
 +
 +						doh.is("54775.53", form1.income1.value, "income1");
 +						doh.is("$54,775.53", dojo.byId("q08").value, "income1 display value");
 +						doh.is("not fired yet!", dojo.byId("oc8").value, "income1 onchange");
 +
 +						doh.is("54775.53", form1.income2.value, "income2");
 +						doh.is("€54,775.53", dojo.byId("q08eur").value, "income2 display value");
 +						doh.f(!!dijit.byId("q08eur").constraints.allowCruft, "constructor constraints value not proliferated");
 +
 +						doh.is("someTestString", form1.phone.value, "phone");
 +						doh.is("", form1.password.value, "password");
 +						doh.is("", form1.ticket1651.value, "ticket1651");
 +						doh.is("cannot type here", form1.readOnly.value, "readonly");
 +						doh.is("cannot type here", form1.disabled.value, "disabled");
 +					}
 +				});
 +
 +				var watchHandle, onChangeHandle;
 +
 +				function setTest(testName, textbox, setDict, watchExpected, unchangedExpected, isValid, onChangeExpected){
 +					// summary:
 +					//		Generate test to call set("value", ...) and check response
 +					// textbox: String
 +					//		id of TextBox widget
 +					// setDict: Dictionary
 +					//		Value to pass to textbox.set(), such as {value: "123"} or {displayedValue: "1,234"}.
 +					// watchExpected: Dictionary
 +					//		Expected changes in state of TextBox, such as
 +					//			{value: 1234, displayedValue: "1,234"}
 +					//		Checks that watch() returns these values.
 +					//		Also checks that textbox is actually displaying displayedValue
 +					// unchangedExpected: Dictionary
 +					//		Attributes of textbox that shouldn't change/be reported by watch(), and their values
 +					//			{value: 1234, displayedValue: "1,234"}
 +					//		Checks that watch() returns these values.
 +					//		Also checks that textbox is actually displaying displayedValue
 +					// isValid: Boolean
 +					//		Checks return of textbox.isValid().   TODO: this should be watchable.
 +					// onChangeExpected:
 +					//		Check that onChange reports this value
 +
 +
 +					return {
 +						name: testName,
 +						timeout: 1000,
 +						runTest: function(){
 +
 +							textbox = dijit.byId(textbox);
 +
 +							// expectedState is a combination of the attributes that we expect to change
 +							// and the attributes that we expect not to change
 +							var expectedState = dojo.delegate(watchExpected, unchangedExpected);
 +
 +							// save all notifications from watch()
 +							var watchActual = {};
 +							watchHandle = textbox.watch(function(attr, oldVal, newVal){
 +									console.log(textbox.id + ": " + attr + ": " + oldVal + " --> " + newVal);
 +									watchActual[attr] = fixNbsp(newVal);
 +								});
 +
 +							// and monitor onChange() calls too
 +							var onChangeActual;
 +							if(onChangeExpected){
 +								onChangeHandle = textbox.on("change", function(newVal){
 +									onChangeActual = fixNbsp(newVal);
 +								});
 +							}
 +
 +							// do the set
 +							textbox.set(setDict);
 +
 +							doh.is(expectedState.displayedValue, fixNbsp(textbox.focusNode.value), "focusNode.value");
 +
 +							doh.is(isValid, textbox.isValid(), "isValid()");
 +
 +							var d = new doh.Deferred();
 +
 +							setTimeout(d.getTestCallback(function(){
 +								// test that onChange() was called correctly
 +								if(onChangeExpected){
 +									doh.is(onChangeExpected, onChangeActual, "onChange()");
 +								}
 +
 +								// check that watch() callbacks were called
 +								for(attr in watchExpected){
 +									doh.t(attr in watchActual, "watch(" + attr + ") fired");
 +									doh.is(watchExpected[attr], watchActual[attr], "watch(" + attr + ")");
 +									doh.is(typeof watchExpected[attr], typeof watchActual[attr], "typeof watch(" + attr + ")")
 +								}
 +
 +								// check that direct get(...) calls are also working, for both the attributes
 +								// that were supposed to change and the attributes that weren't supposed to change
 +								for(attr in expectedState){
 +									doh.is(expectedState[attr], fixNbsp(textbox.get(attr)), "get(" + attr + ")");
 +									doh.is(typeof expectedState[attr], typeof textbox.get(attr), "typeof get(" + attr + ")")
 +								}
 +
 +								// check that watch() didn't report attributes as changed when they didn't change
 +								for(attr in unchangedExpected){
 +									doh.f(attr in watchActual, attr + " shouldn't have been reported as changed by watch " +
 +										watchActual[attr]);
 +								}
 +							}), 50);
 +
 +							return d;
 +						},
 +						tearDown: function(){
 +							watchHandle.unwatch();
 +							watchHandle = null;
 +							if(onChangeHandle){
 +								onChangeHandle.remove();
 +								onChangeHandle = null;
 +							}
 +						}
 +					};
 +				}
 +
 +				doh.register("set('value')", [
 +					// test valid and invalid settings of value
 +					setTest("valid_max", 'q03', {value: 120}, {value: 120, displayedValue: "120"}, {}, true),
 +					setTest("out_of_range_max",'q03', {value: 121}, {value: 121, displayedValue: "121", state: "Error"}, {}, false),
 +					setTest("valid_min", 'q03', {value: 0}, {value: 0, displayedValue: "0", state: ""}, {}, true),
 +					setTest("out_of_range_min", 'q03', {value: -1}, {value: -1, displayedValue: "-1", state: "Error"}, {}, false),
 +					setTest("invalid", 'q03', {value: 'two'}, {value: undefined, displayedValue: 'two'}, {}, false),
 +					setTest("null_required", 'q03', {required: true, value: null},
 +							{required: true, value: NaN, displayedValue: ''}, {state: "Error"}, false),
 +					setTest("null_notrequired", 'q03', {required: false, value: null}, {}, {displayedValue: ''}, true),
 +
 +					// test formatting of value vs. displayed value
 +					setTest("number format", 'q05', {value: 1234}, {value: 1234, displayedValue: "1,234"}, {}, true),
 +					setTest("currency format", 'q08', {value: 1234}, {value: 1234, displayedValue: "$1,234.00"}, {}, true)
 +				]);
 +
 +				doh.register("set('displayedValue')", [
 +					// test formatting of value vs. displayed value, in reverse
 +					setTest("number format", 'q05', {displayedValue: "4,321"}, {value: 4321, displayedValue: "4,321"}, {}, true),
 +					setTest("european currency format", 'q08eurde', {displayedValue: "4.321,98"},
 +						{value: 4321.98, displayedValue: "4.321,98 €"}, {}, true)
 +				]);
 +
 +				doh.register("font inheritance", [
 +					{
 +						name: "with srcNodeRef, style on .dijitTextBox",
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("monospace", dojo.style('monospace1', "fontFamily"));
 +							}), 0);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "without srcNodeRef, style on .dijitTextBox",
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("monospace", dojo.style('monospace2', "fontFamily"));
 +							}), 0);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "with srcNodeRef, style inherited",
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("sans-serif", dojo.style('sans1', "fontFamily"));
 +							}), 0);
 +							return d;
 +						}
 +					},
 +					{
 +						name: "without srcNodeRef, style inherited",
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							setTimeout(d.getTestCallback(function(){
 +								doh.is("sans-serif", dojo.style('sans2', "fontFamily"));
 +							}), 0);
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("pattern", [
 +					{
 +						name: "pattern markup",
 +						runTest: function(){
 +							var w = dijit.byId('q22');
 +							w.set('value', 'someTestString');
 +							doh.is(true, w.isValid(false));
 +							doh.f(!!w.focusNode.getAttribute('pattern'), "native pattern");
 +						}
 +					},
 +					{
 +						name: "pattern string",
 +						runTest: function(){
 +							var w = dijit.byId('q22');
 +							w.set('value', 'someTestString');
 +							w.set('pattern', "some[a-z]+"); // invalid
 +							doh.is(false, w.isValid(false), "invalid");
 +							doh.is(false, w._isValidSubset(), "invalid partial");
 +							w.set('value', 'some');
 +							doh.is(true, w._isValidSubset(), "valid partial");
 +							w.set('value', 'someTestString');
 +							w.set('pattern', "someT[a-z]+S[a-z]+"); // valid
 +							doh.is(true, w.isValid(false), "valid");
 +							doh.is(true, w._isValidSubset(), "valid partial 2");
 +							doh.f(!!w.focusNode.getAttribute('pattern'), "native pattern");
 +						}
 +					},
 +					{
 +						name: "pattern function",
 +						runTest: function(){
 +							var w = dijit.byId('q22');
 +							w.set('value', 'someTestString');
 +							w.set('pattern', function(){ return "[a-z]+"; }); // invalid
 +							doh.is('Error', w.get('state'));
 +							doh.is(false, w.isValid(false), "invalid string");
 +							w.set('pattern', function(){ return "[a-zTS]+"; }); // valid
 +							doh.is('', w.get('state'));
 +							doh.is(true, w.isValid(false), "valid string");
 +						}
 +					}
 +				]);
 +
 +				doh.register("#11889", [
 +					{
 +						name: "non-focused places:5",
 +						timeout: 2000,
 +						textbox: "q05",
 +						setUp: function(){
 +							this.textbox = dijit.byId(this.textbox);
 +							this.textbox.set('constraints', { places: 5 });
 +							this.textbox.set('editOptions', { places: "2,4" });
 +						},
 +						runTest: function(){
 +							this.textbox.set('value', 1.625);
 +							doh.is(1.625, this.textbox.get('value'), 'numeric value is unchanged');
 +							doh.is('1.62500', this.textbox.get('displayedValue'), 'formatted displayed value');
 +							doh.t(this.textbox.isValid(false), 'nonfocused is valid');
 +						}
 +					},
 +					{
 +						name: "focused places:2,4. blur to places:5",
 +						timeout: 2000,
 +						textbox: "q05",
 +						runTest: function(){
 +							var d = new doh.Deferred();
 +							var textbox = this.textbox;
 +
 +							textbox = dijit.byId(this.textbox);
 +							textbox.set('constraints', { places: 5 });
 +							textbox.set('editOptions', { places: "2,4" });
 +							textbox.focus();
 +
 +							setTimeout(d.getTestErrback(function(){	// allow time for async focus on IE9+
 +								textbox.set('value', 1.125);
 +
 +								doh.is(1.125, textbox.get('value'), 'numeric value is unchanged');
 +								doh.is('1.125', textbox.get('displayedValue'), 'formatted displayed value');
 +								doh.t(textbox.isValid(true), 'focused is valid');
 +
 +
 +								var handler = textbox.on("blur", function(){
 +									handler.remove();
 +									setTimeout(d.getTestCallback(function(){
 +										doh.is(1.125, textbox.get('value'), 'numeric value is unchanged #2');
 +										doh.is('1.12500', textbox.get('displayedValue'), 'formatted displayed value #2');
 +										doh.t(textbox.isValid(false), 'nonfocused is valid #2');
 +									}), 150);
 +								});
 +								dojo.byId("q01").focus();
 +							}), 0);
 +
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.register("validation in dialog", [
 +					{
 +						name: "showDialog",
 +						timeout: 5000,
 +						runTest: function() {
 +							// Show dialog, returning Deferred that fires when show dialog is finished.
 +							// Before showing, focus the open button, so that Dialog knows where to return focus to.
 +
 +							var d = new doh.Deferred();
 +
 +							dijit.byId("dlgOpenBtn").focus();
 +
 +							// Focus on IE9+ is asynchronous, so wait for it to occur before showing Dialog.   Otherwise,
 +							// when Dialog closes it restores focus somewhere strange.
 +							setTimeout(function () {
 +								dijit.byId("dlg").show().then(function () {
 +									d.resolve(true);
 +								});
 +							}, 0);
 +
 +							return d;
 +						}
 +					},
 +					function enterInvalidValue(){
 +						// Entering invalid value should make validation tooltip appear
 +						var d = new doh.Deferred();
 +						dijit.byId("dlgNTB").set("value", "invalid");
 +						dijit.byId("dlgNTB").focus();
 +						setTimeout(d.getTestCallback(function(){
 +							masterTT = dojo.global.dijit._masterTT;
 +							doh.t(masterTT && isVisible(masterTT.domNode), "tooltip shown");
 +						}), 300);
 +						return d;
 +					},
 +					{
 +						name: "hideDialog",
 +						timeout: 5000,
 +						runTest: function () {
 +							// Hiding the dialog should make the validation tooltip disappear
 +							var d = new doh.Deferred();
 +							dijit.byId("dlg").hide().then(function () {
 +								setTimeout(d.getTestCallback(function () {
 +									doh.t(isHidden(masterTT.domNode), "tooltip hidden");
 +								}), 300);
 +							});
 +							return d;
 +						}
 +					}
 +				]);
 +
 +				doh.run();
 +			});
 +		</script>
 +	</head>
 +
 +	<body class="claro" role="main">
 +		<h1 class="testTitle">Dijit Validation Widgets</h1>
 +		<!--	to test form submission, you'll need to create an action handler similar to
 +			http://www.utexas.edu/teamweb/cgi-bin/generic.cgi -->
 +		<form id="form1" action="" name="example" method="GET">
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q01">First Name:  </label>
 +				<span class="noticeMessage"> TextBox class, <b>tabIndex=2</b>, Attributes: {trim: true, propercase: true, intermediateChanges: true, style: 'width:700px', selectOnClick: true}, First letter of each word is upper case.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q01" data-dojo-type="dijit/form/TextBox"
 +					name="firstname" value="testing testing" style="width:700px" tabIndex="2"
 +					data-dojo-props='
 +					trim:true,
 +					selectOnClick:true,
 +					onFocus:function(){ console.log("user onfocus handler"); },
 +					onBlur:function(){ console.log("user onblur handler"); },
 +					onChange:function(v){ dojo.byId("oc1").value=v; },
 +					intermediateChanges:true,
 +					propercase:true '/>
 +				<br><label for="oc1">onChange:</label><input id="oc1" size="34" disabled value="not fired yet!" autocomplete="off"/>
 +				<input tabIndex="-1" type="button" onclick="dijit.byId('q01').set('disabled', true);" value="Disable"/>
 +				<input tabIndex="-1" type="button" onclick="dijit.byId('q01').set('disabled', false);" value="Enable"/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q02">Last Name:  </label>
 +				<span class="noticeMessage"> TextBox class, Attributes: {trim: true, uppercase: true, "class": 'verylong'}, all letters converted to upper case. </span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q02" data-dojo-type="dijit/form/TextBox"
 +					name="lastname" value="testing testing" class="verylong"
 +					data-dojo-props='trim:true, uppercase:true '/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q03">Age:  </label>
 +				<span class="noticeMessage"> NumberTextBox class, <b>tabIndex=1</b>, Attributes: {trim: true}, no initial value specified, tooltipPosition=[above, below].  Displays a prompt message if field is blank.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q03" data-dojo-type="dijit/form/NumberTextBox"
 +					name="age" tabIndex="1" class="small"
 +					data-dojo-props='
 +					promptMessage:"(optional) Enter an age between 0 and 120",
 +					maxLength:"3",
 +					constraints:{places:0,min:0,max:120},
 +					onChange:function(val){ dojo.byId("oc3").value=""+val; },
 +					tooltipPosition:["above", "below"]
 +				'/>
 +				<label for="oc3">onChange:</label><input id="oc3" size="14" disabled value="not fired yet!" autocomplete="off"/>
 +				<span style='white-space:nowrap;'>
 +				<button id="q03_valid" type=button onclick="dijit.byId('q03').set('value', 120);">set value to 120</button>
 +				<button id="q03_outofrange" type=button onclick="dijit.byId('q03').set('value', 121);">set value to 121</button>
 +				<button id="q03_invalid" type=button onclick="dijit.byId('q03').set('value', 'two');">set value to two</button>
 +				<button id="q03_null" type=button onclick="dijit.byId('q03').set('value', null);">set value to null</button>
 +				<button type=button onclick="dojo.byId('gv3').value=''+dijit.byId('q03').get('value');">get value</button>
 +				<input id="gv3" aria-label="get value 3" size="10" disabled value="" autocomplete="off"/>
 +				</span>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="fav">Favorite Number (1-100):  </label>
 +				<span class="noticeMessage"> NumberTextBox class, Attributes: required=true, must be integer, no messages provided,  no initial value specified, maxlength=3</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="fav" data-dojo-type="dijit/form/NumberTextBox"
 +					name="fav" class="small" required
 +					data-dojo-props='maxLength:"3",constraints:{places:0,min:1,max:100},pattern:"\\d+"'/>
 +					<button onclick="dijit.byId('fav').set('required', true); return false;">attr(required, true)</button>
 +					<button onclick="dijit.byId('fav').set('required', false); return false;">attr(required, false)</button>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q04">Occupation:  </label>
 +				<span class="noticeMessage">ValidationTextBox class,
 +					Attributes: {lowercase: true, required: true, "class": verylong, style: font-size: 15pt;}. Displays a prompt message if field is blank.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q04" data-dojo-type="dijit/form/ValidationTextBox"
 +					name="occupation" class="verylong" style="fontSize:15pt" required="true"
 +					data-dojo-props='lowercase:true, promptMessage:"Enter an occupation" '/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q05">Elevation:  </label>
 +				<span class="noticeMessage">IntegerTextBox class,
 +					Attributes: {required: true, min:-20000, max:+20000 }, displays a prompt message if field is blank, thousands separator remains during editing.</span>
 +				<span class="noticeMessage">Enter feet above sea level with a sign.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q05" class="small"/>
 +				<label for="oc5">onChange:</label><input id="oc5" size="10" disabled value="not fired yet!" autocomplete="off"/>
 +			</div>
 +<!--
 +			<div class="dojoTitlePaneLabel">
 +				<label for="attach-here">Population:  </label>
 +				<span class="noticeMessage">IntegerTextBox class,
 +					Attributes: {trim: true, required: true, signed: false, separator: ","}. <br><b> This widget was added in script, not markup. </b> </span>
 +			</div>
 +			<div class="testExample" >
 +				<input id="attach-here" type="text" name="population" class="medium" value="1500000"/>
 +			</div>
 +
 +			<script>
 +				// See if we can make a widget in script and attach it to the DOM ourselves.
 +				dojo.ready(function(){
 +					var props = {
 +						name: "population",
 +						value: "1,500,000",
 +						trim: "true",
 +						required: "true",
 +						pattern: dojo.regexp.integer,
 +						constraints: {signed:false, separator: ","},
 +						invalidMessage: "Invalid population.  Enter a number."
 +					};
 +					var w = new dijit.form.ValidationTextBox(props, "attach-here");
 +					});
 +			</script>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q06">Real Number:  </label>
 +				<span class="noticeMessage">RealNumberTextBox class,
 +					Attributes: {trim: true, required: true}. Enter any sort of real number.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q06" type="text" name="real1" class="medium" value="+0.1234"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.realNumber"
 +					trim="true"
 +					required="true"
 +					invalidMessage="This is not a valid real number." />
 +			</div>
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q07">Exponential Notation:  </label>
 +				<span class="noticeMessage">RealNumberTextBox class,
 +					Attributes: {exponent: true}. Enter a real number in exponential notation.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q07" type="text" name="real2" class="medium" value="+0.12"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.realNumber"
 +					trim="true"
 +					required="true"
 +					constraints="{exponent:true}"
 +					invalidMessage="Number must be in exponential notation. Example +5E-28" />
 +			</div>
 +			-->
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q08">Annual Income:  </label>
 +				<span class="noticeMessage">CurrencyTextBox class,
 +					Attributes: {fractional: true}.  Enter whole and cents.  Currency symbol is optional. Cents are MANDATORY.</span>
 +			</div>
 +
 +			<div class="testExample">
 +				<input id="q08" data-dojo-type="dijit/form/CurrencyTextBox"
 +					name="income1" class="medium" value="54775.53" required
 +					data-dojo-props='constraints:{fractional:true},
 +						currency:"USD",
 +						onChange:function(val){ dojo.byId("oc8").value = val; },
 +						invalidMessage:"Invalid amount.  Cents are MANDATORY." '/>USD
 +				 <label for="oc8">onChange:</label><input id="oc8" size="15" disabled value="not fired yet!" autocomplete="off"/>
 +			</div>
 +
 +			<div class="testExample">
 +				<label for="q08eur">euro currency (local format) fractional part is optional:</label>
 +				<input id="q08eur" data-dojo-type="dijit/form/CurrencyTextBox"
 +					name="income2" class="medium" value="54775.53" required
 +					data-dojo-props='currency:"EUR", invalidMessage:"Invalid amount.  Include cents." '/>EUR
 +				<button onclick="dijit.byId('q08eur').set('disabled',true); return false;">Disable</button>
 +				<button onclick="dijit.byId('q08eur').set('disabled',false); return false;">Enable</button>
 +				<button onclick="dijit.byId('q08eur').reset(); return false;">Reset</button>
 +			</div>
 +
 +			<!--
 +				It is unusual to override the lang properties on individual
 +				widgets.  Usually it should be the user's default or set on
 +				a page basis by the server.  This is for testing purposes
 +			-->
 +			<div class="testExample">
 +				euro currency (fixed lang: de-de) programmatically created, fractional part is optional: <input id="q08eurde" class="medium"/>EUR
 +			</div>
 +
 +			<script>
 +				// See if we can make a widget in script and attach it
 +				// to the DOM ourselves.
 +				dojo.ready(function(){
 +					var example = dojo.currency.format(54775.53, {locale: 'de-de', currency: "EUR"});
 +					var props = {
 +						name: "income3",
 +						value: 54775.53,
 +						lang: 'de-de',
 +						required: true,
 +						currency: "EUR",
 +						invalidMessage: "Invalid amount.  Example: " + example,
 +						"aria-label": "income3 de-de"
 +					};
 +					var w = new dijit.form.CurrencyTextBox(props, "q08eurde");
 +				});
 +			</script>
 +
 +			<!--
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q08a">Annual Income:  </label>
 +				<span class="noticeMessage">Old regexp currency textbox,
 +							Attributes: {fractional: true}. Enter dollars and cents.</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q08a" type="text" name="income3" class="medium" value="$54,775.53"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.currency"
 +					trim="true"
 +					required="true"
 +					constraints="{fractional:true}"
 +					invalidMessage="Invalid amount.  Include cents. Example: $12,000.00" />
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q09">IPv4 Address:  </label>
 +				<span class="noticeMessage">IpAddressTextBox class,
 +					Attributes: {allowIPv6: false, allowHybrid: false}. Also Dotted Hex works, 0x18.0x11.0x9b.0x28</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q09" type="text" name="ipv4" class="medium" value="24.17.155.40"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.ipAddress"
 +					trim="true"
 +					required="true"
 +					constraints="{allowIPv6:false,allowHybrid:false}"
 +					invalidMessage="Invalid IPv4 address."/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q10"> IPv6 Address:  </label>
 +				<span class="noticeMessage">IpAddressTextBox class,
 +					Attributes: {allowDottedDecimal: false, allowDottedHex: false}.
 +							Also hybrid works, x:x:x:x:x:x:d.d.d.d</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q10" type="text" name="ipv6" class="long" value="0000:0000:0000:0000:0000:0000:0000:0000"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.ipAddress"
 +					trim="true"
 +					uppercase = "true"
 +					required="true"
 +					constraints="{allowDottedDecimal:false, allowDottedHex:false, allowDottedOctal:false}"
 +					invalidMessage="Invalid IPv6 address, please enter eight groups of four hexadecimal digits. x:x:x:x:x:x:x:x"/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q11"> URL:  </label>
 +				<span class="noticeMessage">UrlTextBox class,
 +					Attributes: {required: true, trim: true, scheme: true}. </span>
 +			</div>
 +
 +			<div class="testExample">
 +				<input id="q11" type="text" name="url" class="long" value="http://www.xyz.com/a/b/c?x=2#p3"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.url"
 +					trim="true"
 +					required="true"
 +					constraints="{scheme:true}"
 +					invalidMessage="Invalid URL.  Be sure to include the scheme, http://..." />
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q12"> Email Address  </label>
 +				<span class="noticeMessage">EmailTextBox class,
 +					Attributes: {required: true, trim: true}. </span>
 +			</div>
 +
 +			<div class="testExample">
 +				<input id="q12" type="text" name="email" class="long" value="fred&barney at stonehenge.com"
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.emailAddress"
 +					trim="true"
 +					required="true"
 +					invalidMessage="Invalid Email Address." />
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q13"> Email Address List </label>
 +				<span class="noticeMessage">EmailListTextBox class,
 +					Attributes: {required: true, trim: true}. </span>
 +			</div>
 +
 +			<div class="testExample">
 +				<input id="q13" type="text" name="email" class="long" value="a at xyz.com; b at xyz.com; c at xyz.com; "
 +					data-dojo-type="dijit/form/ValidationTextBox"
 +					pattern="dojo.regexp.emailAddressList"
 +					trim="true"
 +					required="true"
 +					invalidMessage="Invalid Email Address List." />
 +			</div>
 +			-->
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q22">Regular Expression </label>
 +				<span class="noticeMessage">RegexpTextBox class,
 +					Attributes: {required: true} </span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q22" data-dojo-type="dijit/form/ValidationTextBox"
 +					name="phone" class="medium" value="someTestString" required
 +					data-dojo-props='pattern:"[\\w]+", invalidMessage:"Invalid Non-Space Text."'/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q23"> Password </label>
 +				<span class="noticeMessage">(just a test that type attribute is obeyed) </span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q23" data-dojo-type="dijit/form/TextBox" type="password" name="password" class="medium"/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label id="l_ticket1651">Trac ticket 1651:  </label>
 +				<span class="noticeMessage">value: null should show up as empty</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="ticket1651" class="medium" value="not null"/>
 +			</div>
 +
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q24">initially readOnly TextBox</label>
 +				<span class="noticeMessage">a test that readOnly and disabled are understood for TextBox</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q24" data-dojo-type="dijit/form/TextBox"
 +					name="readOnly" class="medium" readOnly value="cannot type here" title="hint text"/>
 +				<input type="button" id="removereadonly" onclick="dijit.byId('q24').set('readOnly',false);" value="Remove readOnly" tabIndex="-1"/>
 +				<input type="button" onclick="dijit.byId('q24').set('readOnly',true);" value="Set readOnly" tabIndex="-1"/>
 +				<input type="button" id="removedisabled" onclick="dijit.byId('q24').set('disabled',false);" value="Remove disabled" tabIndex="-1"/>
 +				<input type="button" onclick="dijit.byId('q24').set('disabled',true);" value="Set disabled" tabIndex="-1"/>
 +			</div>
 +			<div class="dojoTitlePaneLabel">
 +				<label for="q25">initially disabled ValidationTextBox</label>
 +				<span class="noticeMessage">a test that disabled is understood for ValidationTextBox</span>
 +			</div>
 +			<div class="testExample">
 +				<input id="q25" data-dojo-type="dijit/form/ValidationTextBox"
 +					name="disabled" class="medium" disabled value="cannot type here"/>
 +				<input type="button" onclick="dijit.byId('q25').set('readOnly',false);" value="Remove readOnly" tabIndex="-1"/>
 +				<input type="button" onclick="dijit.byId('q25').set('readOnly',true);" value="Set readOnly" tabIndex="-1"/>
 +				<input type="button" onclick="dijit.byId('q25').set('disabled',false);" value="Remove disabled" tabIndex="-1"/>
 +				<input type="button" onclick="dijit.byId('q25').set('disabled',true);" value="Set disabled" tabIndex="-1"/>
 +			</div>
 +
 +			<script>
 +				// so robot can get to it easily
 +				document.displayData=function(){
 +					var f = document.getElementById("form1");
 +					var s = "";
 +					for(var i = 0; i < f.elements.length; i++){
 +						var elem = f.elements[i];
 +						if(elem.nodeName.toLowerCase() == "button" || elem.type=="submit" || elem.type=="button")  { continue; }
 +						s += elem.name + ": " + elem.value + "\n";
 +					}
 +					return s;
 +				}
 +			</script>
 +
 +			<div>
 +				<button name="button" onclick="alert(document.displayData()); return false;" tabIndex="-1">view data</button>
 +				<input type="submit" name="submit"  tabIndex="-1"/>
 +			</div>
 +
 +		</form>
 +		<div class="dojoTitlePaneLabel">
 +				<label for="q26">TextBox with placeholder</label>
 +				<span class="noticeMessage">a test that placeholder works for TextBox.  10px padding added for testing.</span>
 +		</div>
 +		<div class="testExample">
 +			<input id="q26" data-dojo-type="dijit/form/TextBox" placeHolder="placeholder is here" name="placeHolder" value=""/>
 +			<button type="button" onclick="dijit.byId('q26').set('placeHolder', 'this is a very long placeholder that will overflow the input');">
 +				set long placeholder
 +			</button>
 +		</div>
 +		<h2>Tooltip positioning</h2>
 +		<p>
 +		These buttons switch the positions searched to try to place the validation error tooltips.
 +		Note that setting tooltip positioning to "above" or "below" is dangerous if
 +		you have a node with a dropdown, but the drop down might overlap the tooltip.
 +		</p>
 +		<button onclick="dijit.Tooltip.defaultPosition=['above', 'below'];">above, below</button>
 +		<button onclick="dijit.Tooltip.defaultPosition=['after', 'before'];">after, before (default)</button>
 +
 +		<div class="monospace">
 +			<input id="monospace1"/>
 +		</div>
 +		<div class="sans">
 +			<input id="sans1"/>
 +		</div>
 +
 +		<h2>Validation tooltips inside of dialogs:</h2>
 +		<div dojoType="dijit.Dialog" id="dlg">
 +			<label for="dlgNTB">NumberTextBox:</label>
 +			<div dojoType="dijit.form.NumberTextBox" id="dlgNTB"></div>
 +		</div>
 +		<button dojoType="dijit.form.Button" id="dlgOpenBtn" onclick="dijit.byId('dlg').show();">show dialog</button>
 +
 +		<div class="dojoTitlePaneLabel">
 +			<label id="l_ticket17923">Trac ticket 17923</label>
 +			<span class="noticeMessage">Partial numeric input should be validated with respect to the min/max constraints and available space</span>
 +		</div>
 +
 +		<div class="testExample">
 +			<input id="ticket17923" class="medium"/>
 +			<input type="button" id="ticket17923-maxLength5" value="maxLength: 5" tabIndex="-1"/>
 +			<input type="button" id="ticket17923-maxLength3" value="maxLength: 3" tabIndex="-1"/>
 +			<input type="button" id="ticket17923-min1:max15" value="min: 12, max: 142" tabIndex="-1"/>
 +			<input type="button" id="ticket17923-min-15:max15" value="min: -15, max: -5" tabIndex="-1"/>
 +		</div>
 +		<script>
 +		require(["dijit/form/NumberTextBox", "dojo/dom", "dojo/on", "dojo/_base/lang"], function(NumberTextBox, dom, on, lang){
 +
 +			var fld = new NumberTextBox({
 +					name: "ticket17923",
 +					constraints: {pattern: '#000.#', min: 12, max: 142 },
 +					maxLength: 5
 +			}, "ticket17923");
 +
 +			on(dom.byId("ticket17923-maxLength5"), 'click', function(){
 +					fld.set('maxLength', 5);
 +			});
 +
 +			on(dom.byId("ticket17923-maxLength3"), 'click', function(){
 +					fld.set('maxLength', 3);
 +			});
 +
 +			on(dom.byId("ticket17923-min1:max15"), 'click', function(){
 +					fld.set('constraints', lang.mixin({}, fld.get('constraints'), {min: 12, max: 142}));
 +			});
 +
 +			on(dom.byId("ticket17923-min-15:max15"), 'click', function(){
 +					fld.set('constraints', lang.mixin({}, fld.get('constraints'), {min: -15, max: -5}));
 +			});
 +
 +			fld.startup();
 +		});
 +		</script>
 +
 +		<div class="dojoTitlePaneLabel">
 +			<label id="l_ticket17955">Trac ticket 17955</label>
 +			<span class="noticeMessage">Ensure setting the formattedValue does not alter the entered value on blur</span>
 +		</div>
 +		<div class="testExample">
 +			<input id="ticket17955" class="medium"/>
 +		</div>
 +		<script>
 +		require(["dijit/form/NumberTextBox"], function(NumberTextBox){
 +
 +			var fld = new NumberTextBox({
 +				name: "ticket17955",
 +				constraints: {pattern: '000.#', min: 12, max: 142 },
 +				placeHolder: "Number Pattern 000.#",
 +				maxLength: 5
 +			}, "ticket17955");
 +			fld.startup();
 +		});
 +		</script>
 +		<div class="dojoTitlePaneLabel">
- 			<label id="l_ticket8260">Trac ticket 18260</label>
++			<label id="l_ticket18260">Trac ticket 18260</label>
 +			<span class="noticeMessage">Make sure rounding works with different locale's</span>
 +		</div>
 +
 +		<div class="testExample">
 +			<input id="ticket18260" class="medium"/>
 +		</div>
 +		<script>
 +		require(["dijit/form/NumberTextBox"], function(NumberTextBox){
 +
 +			var fld = new NumberTextBox({
 +				name: "ticket18260",
 +				lang: 'de-de',
 +				value: 12.4,
 +				constraints: {pattern: '000.#', min: 12, max: 142 },
 +				maxLength: 5
 +			}, "ticket18260");
 +			fld.startup();
 +		});
 +		</script>
++
++		<div class="dojoTitlePaneLabel">
++			<label id="l_ticket18367">Trac ticket 18367</label>
++			<span class="noticeMessage">Make sure precision ranges work as expected</span>
++		</div>
++		<div class="testExample">
++			<input id="ticket18367" class="medium"/>
++		</div>
++		<script>
++		require(["dijit/form/NumberTextBox"], function(NumberTextBox){
++
++			var fld = new NumberTextBox({
++				name: "ticket18367",
++				value: 8.0153234,
++				constraints: { min: 1, max: 10 , places: "0,5"},
++				maxLength: 5
++			}, "ticket18367");
++			fld.startup();
++		});
++		</script>
++
 +	</body>
 +</html>
diff --cc dijit/themes/tundra/Menu.css
index bcc60c4,0000000..055d021
mode 100644,000000..100644
--- a/dijit/themes/tundra/Menu.css
+++ b/dijit/themes/tundra/Menu.css
@@@ -1,82 -1,0 +1,88 @@@
 +.tundra .dijitMenu,
 +.tundra .dijitMenuBar {
 +	border: 1px solid #7eabcd;
 +	margin: 0;
 +	padding: 0;
 +	background-color: #f7f7f7;
 +}
 +
++.tundra .dijitMenuTable {
++  border-collapse: separate;
++  border-spacing: 0 0;
++  padding: 0;
++}
++
 +.tundra .dijitBorderContainer .dijitMenuBar {
 +	border:1px solid #ccc;
 +}
 +
 +.tundra .dijitMenuItem {
 +	font-family: sans-serif;
 +	margin: 0;
 +}
 +
 +.tundra .dijitMenuItem {
 +	padding: 4px 5px;
 +}
 +
 +.tundra .dijitMenuPreviousButton, .tundra .dijitMenuNextButton {
 +	font-style: italic;
 +}
 +.tundra .dijitMenuItem td {
 +	padding: 2px;
 +}
 +
 +.tundra .dijitMenuPassive .dijitMenuItemHover,
 +.tundra .dijitComboBoxMenu .dijitMenuItemHover,
 +.tundra .dijitMenuItemSelected {
 +	background-color: #3559ac;
 +	color:#fff;
 +}
 +
 +.tundra .dijitMenuItemIcon {
 +	width: 16px;
 +	height: 16px;
 +}
 +
 +.tundra .dijitMenuExpand {
 +	/* arrow to indicate this MenuItem opens a sub-menu */
 +	width: 7px;
 +	height: 7px;
 +	background-image: url('images/spriteArrows.png');
 +	background-position: -14px 0;
 +}
 +.dj_ie6 .tundra .dijitMenuExpand {
 +	background-image: url('images/spriteArrows.gif');
 +}
 +
 +/* separator can be two pixels -- set border of either one to 0 to have only one */
 +.tundra .dijitMenuSeparatorTop {
 +	border-bottom: 1px solid #9b9b9b;
 +}
 +
 +.tundra .dijitMenuSeparatorBottom {
 +	border-top: 1px solid #e8e8e8;
 +}
 +
 +/* the checked menu item */
 +.tundra .dijitCheckedMenuItem .dijitMenuItemIcon,
 +.tundra .dijitRadioMenuItem .dijitMenuItemIcon {
 +	background-image: url('images/checkmark.png');
 +	background-position: -80px;
 +}
 +
 +.dj_ie6 .tundra .dijitCheckedMenuItem .dijitMenuItemIcon,
 +.dj_ie6 .tundra .dijitRadioMenuItem .dijitMenuItemIcon {
 +	background-image: url('images/checkmark.gif');
 +}
 +
 +.tundra .dijitCheckedMenuItemChecked .dijitMenuItemIcon {
 +	background-position: -64px;
 +}
 +
 +.tundra .dijitRadioMenuItem .dijitMenuItemIcon {
 +	background-position: -110px; /* 15px * 8 */
 +}
 +.tundra .dijitRadioMenuItemChecked .dijitMenuItemIcon {
 +	background-position: -95px;	/* 15px * 7 */
 +}
diff --cc dojox/calendar/CONTRIBUTING.md
index 5ae012f,0000000..5ae012f
mode 100644,000000..100755
--- a/dojox/calendar/CONTRIBUTING.md
+++ b/dojox/calendar/CONTRIBUTING.md
diff --cc dojox/calendar/LICENSE
index b064ab1,0000000..b064ab1
mode 100644,000000..100755
--- a/dojox/calendar/LICENSE
+++ b/dojox/calendar/LICENSE
diff --cc dojox/calendar/nls/bg/buttons.js
index 61eb2fc,0000000..61eb2fc
mode 100644,000000..100755
--- a/dojox/calendar/nls/bg/buttons.js
+++ b/dojox/calendar/nls/bg/buttons.js
diff --cc dojox/calendar/nls/he/buttons.js
index 9271cba,0000000..9271cba
mode 100644,000000..100755
--- a/dojox/calendar/nls/he/buttons.js
+++ b/dojox/calendar/nls/he/buttons.js
diff --cc dojox/calendar/nls/hr/buttons.js
index 93aadf2,0000000..93aadf2
mode 100644,000000..100755
--- a/dojox/calendar/nls/hr/buttons.js
+++ b/dojox/calendar/nls/hr/buttons.js
diff --cc dojox/calendar/nls/uk/buttons.js
index 76522dc,0000000..76522dc
mode 100644,000000..100755
--- a/dojox/calendar/nls/uk/buttons.js
+++ b/dojox/calendar/nls/uk/buttons.js
diff --cc dojox/calendar/tests/hcalendar.html
index 2721891,0000000..2721891
mode 100644,000000..100755
--- a/dojox/calendar/tests/hcalendar.html
+++ b/dojox/calendar/tests/hcalendar.html
diff --cc dojox/dgauges/CONTRIBUTING.md
index 5ae012f,0000000..5ae012f
mode 100644,000000..100755
--- a/dojox/dgauges/CONTRIBUTING.md
+++ b/dojox/dgauges/CONTRIBUTING.md
diff --cc dojox/dojox.profile.js
index 427e26b,0000000..465ae6c
mode 100644,000000..100644
--- a/dojox/dojox.profile.js
+++ b/dojox/dojox.profile.js
@@@ -1,65 -1,0 +1,63 @@@
 +var profile = (function(){
 +	var testResourceRe = /\/tests\//,
 +
 +		copyOnly = function(filename, mid){
 +			var list = {
 +				"dojox/dojox.profile":1,
 +				"dojox/package.json":1,
 +				"dojox/mobile/themes/utils/compile":1,
 +				"dojox/mobile/themes/utils/cleanup":1,
 +				"dojox/app/tests/layoutApp/build.profile": 1,
 +				"dojox/app/tests/globalizedApp/build.profile": 1
 +			};
 +			return (mid in list) || /^dojox\/resources\//.test(mid) || /(png|jpg|jpeg|gif|tiff)$/.test(filename) || /dojox\/app\/build\//.test(mid);
 +		},
 +
 +		excludes = [
 +			"secure",
 +			"data/(demos|ItemExplorer|StoreExplorer|restListener)",
 +			"drawing/plugins/drawing/Silverlight",
- 			"editor/plugins/(ResizeTableColumn|SpellCheck)",
 +			"embed/(IE)",
 +			"flash/_base",
 +			"help",
 +			"image/(Gallery|SlideShow|ThumbnailPicker)",
 +			"jq",
 +			"jsonPath/query",
 +			"lang/(aspect|async|docs|observable|oo|typed|functional/(binrec|curry|linrec|listcomp|multirec|numrec|tailrec|util|zip))",
 +			"layout/(BorderContainer|dnd|ext-dijit)",
 +			"mobile/app/",
 +			"rails",
 +			"robot",
- 			"socket/Reconnect",
 +			"sql/",
 +			"storage/",
 +			"widget/(AnalogGauge|BarGauge|DataPresentation|DocTester|DynamicTooltip|FeedPortlet|FilePicker|gauge|Iterator|Loader|RollingList|SortList)",
 +			"wire/",
 +			"xmpp"
 +		],
 +
 +		excludesRe = new RegExp(("^dojox/(" + excludes.join("|") + ")").replace(/\//, "\\/")),
 +
 +		usesDojoProvideEtAl = function(mid){
 +			return excludesRe.test(mid);
 +		};
 +
 +	return {
 +		resourceTags:{
 +			test: function(filename, mid){
 +				return testResourceRe.test(mid);
 +			},
 +
 +			copyOnly: function(filename, mid){
 +				return copyOnly(filename, mid);
 +			},
 +
 +			amd: function(filename, mid){
 +				return !testResourceRe.test(mid) && !copyOnly(filename, mid) && !usesDojoProvideEtAl(mid) && /\.js$/.test(filename);
 +			},
 +
 +			miniExclude: function(filename, mid){
 +				return /\/demos\//.test(mid);
 +			}
 +		}
 +	};
 +})();
diff --cc dojox/embed/Flash.js
index 9d0a1fd,0000000..01b094c
mode 100644,000000..100644
--- a/dojox/embed/Flash.js
+++ b/dojox/embed/Flash.js
@@@ -1,504 -1,0 +1,505 @@@
 +define([
-     	"dojo/_base/lang",
-     	"dojo/_base/unload",
-     	"dojo/_base/array",
-     	"dojo/query",
-     	"dojo/has",
-     	"dojo/dom",
-     	"dojo/on",
-     	"dojo/window"
-         ], function(lang,unload,array,query,has,dom,on,win) {
++	"dojo/_base/lang",
++	"dojo/_base/unload",
++	"dojo/_base/array",
++	"dojo/query",
++	"dojo/has",
++	"dojo/dom",
++	"dojo/on",
++	"dojo/window",
++	"dojo/string"
++], function(lang,unload,array,query,has,dom,on,win,stringUtil) {
 +
 +	// module:
 +	//		dojox/embed/Flash
 +	// summary:
 +	//		Base functionality to insert a flash movie into
 +	//		a document on the fly.
 +	// example:
 +	//	|	var movie=new Flash({ args }, containerNode);
 +
 +
 +	var fMarkup, fVersion;
 +	var minimumVersion = 9; // anything below this will throw an error (may overwrite)
 +	var keyBase = "dojox-embed-flash-", keyCount=0;
 +	var _baseKwArgs = {
 +		expressInstall: false,
 +		width: 320,
 +		height: 240,
 +		swLiveConnect: "true",
 +		allowScriptAccess: "sameDomain",
 +		allowNetworking:"all",
 +		style: null,
 +		redirect: null
 +	};
 +
 +	function prep(kwArgs){
 +		kwArgs = lang.delegate(_baseKwArgs, kwArgs);
 +
 +		if(!("path" in kwArgs)){
 +			console.error("dojox.embed.Flash(ctor):: no path reference to a Flash movie was provided.");
 +			return null;
 +		}
 +
 +		if(!("id" in kwArgs)){
 +			kwArgs.id = (keyBase + keyCount++);
 +		}
 +		return kwArgs;
 +	}
 +
 +	if(has('ie')) {
 +		fMarkup = function(kwArgs){
 +			kwArgs = prep(kwArgs);
 +			if(!kwArgs){ return null; }
 +
 +			var p;
 +			var path = kwArgs.path;
 +			if(kwArgs.vars){
 +				var a = [];
 +				for(p in kwArgs.vars){
- 					a.push(p + '=' + kwArgs.vars[p]);
++					a.push(encodeURIComponent(p) + '=' + encodeURIComponent(kwArgs.vars[p]));
 +				}
 +				kwArgs.params.FlashVars = a.join("&");
 +				delete kwArgs.vars;
 +			}
- 			var s = '<object id="' + kwArgs.id + '" '
++			var s = '<object id="' + stringUtil.escape(String(kwArgs.id)) + '" '
 +				+ 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" '
- 				+ 'width="' + kwArgs.width + '" '
- 				+ 'height="' + kwArgs.height + '"'
- 				+ ((kwArgs.style)?' style="' + kwArgs.style + '"':'')
++				+ 'width="' + stringUtil.escape(String(kwArgs.width)) + '" '
++				+ 'height="' + stringUtil.escape(String(kwArgs.height)) + '"'
++				+ ((kwArgs.style)?' style="' + stringUtil.escape(String(kwArgs.style)) + '"':'')
 +				+ '>'
- 				+ '<param name="movie" value="' + path + '" />';
++				+ '<param name="movie" value="' + stringUtil.escape(String(path)) + '" />';
 +			if(kwArgs.params){
 +				for(p in kwArgs.params){
- 					s += '<param name="' + p + '" value="' + kwArgs.params[p] + '" />';
++					s += '<param name="' + stringUtil.escape(p) + '" value="' + stringUtil.escape(String(kwArgs.params[p])) + '" />';
 +				}
 +			}
 +			s += '</object>';
 +			return { id: kwArgs.id, markup: s };
 +		};
 +
 +		fVersion = (function(){
 +			var testVersion = 10, testObj = null;
 +			while(!testObj && testVersion > 7){
 +				try {
 +					testObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash." + testVersion--);
 +				}catch(e){ }
 +			}
 +			if(testObj){
 +				var v = testObj.GetVariable("$version").split(" ")[1].split(",");
 +				return {
 +					major: (v[0]!=null) ? parseInt(v[0]) : 0,
 +					minor: (v[1]!=null) ? parseInt(v[1]) : 0,
 +					rev: (v[2]!=null) ? parseInt(v[2]) : 0
 +				};
 +			}
 +			return { major: 0, minor: 0, rev: 0 };
 +		})();
 +
 +		//	attach some cleanup for IE, thanks to deconcept :)
 +		unload.addOnWindowUnload(function(){
 +			console.warn('***************UNLOAD');
 +			var dummy = function(){};
 +			var objs = query("object").
 +				reverse().
 +				style("display", "none").
 +				forEach(function(i){
 +					for(var p in i){
 +						if((p != "FlashVars") && typeof i[p] == "function"){
 +							try{
 +								i[p] = dummy;
 +							}catch(e){}
 +						}
 +					}
 +				});
 +		});
 +
 +	} else {
 +		//	*** Sane browsers branch ******************************************************************
 +		fMarkup = function(kwArgs){
 +			kwArgs = prep(kwArgs);
 +			if(!kwArgs){ return null; }
 +
 +			var p;
 +			var path = kwArgs.path;
 +			if(kwArgs.vars){
 +				var a = [];
 +				for(p in kwArgs.vars){
- 					a.push(p + '=' + kwArgs.vars[p]);
++					a.push(encodeURIComponent(p) + '=' + encodeURIComponent(kwArgs.vars[p]));
 +				}
 +				kwArgs.params.flashVars = a.join("&");
 +				delete kwArgs.vars;
 +			}
 +			var s = '<embed type="application/x-shockwave-flash" '
- 				+ 'src="' + path + '" '
- 				+ 'id="' + kwArgs.id + '" '
- 				+ 'width="' + kwArgs.width + '" '
- 				+ 'height="' + kwArgs.height + '"'
- 				+ ((kwArgs.style)?' style="' + kwArgs.style + '" ':'')
++				+ 'src="' + stringUtil.escape(String(path)) + '" '
++				+ 'id="' + stringUtil.escape(String(kwArgs.id)) + '" '
++				+ 'width="' + stringUtil.escape(String(kwArgs.width)) + '" '
++				+ 'height="' + stringUtil.escape(String(kwArgs.height)) + '"'
++				+ ((kwArgs.style)?' style="' + stringUtil.escape(String(kwArgs.style)) + '" ':'')
 +
 +				+ 'pluginspage="' + window.location.protocol + '//www.adobe.com/go/getflashplayer" ';
 +			if(kwArgs.params){
 +				for(p in kwArgs.params){
- 					s += ' ' + p + '="' + kwArgs.params[p] + '"';
++					s += ' ' + stringUtil.escape(p) + '="' + stringUtil.escape(String(kwArgs.params[p])) + '"';
 +				}
 +			}
 +			s += ' />';
 +			return { id: kwArgs.id, markup: s };
 +		};
 +
 +		fVersion=(function(){
 +			var plugin = navigator.plugins["Shockwave Flash"];
 +			if(plugin && plugin.description){
 +				var v = plugin.description.replace(/([a-zA-Z]|\s)+/, "").replace(/(\s+r|\s+b[0-9]+)/, ".").split(".");
 +				return {
 +					major: (v[0]!=null) ? parseInt(v[0]) : 0,
 +					minor: (v[1]!=null) ? parseInt(v[1]) : 0,
 +					rev: (v[2]!=null) ? parseInt(v[2]) : 0
 +				};
 +			}
 +			return { major: 0, minor: 0, rev: 0 };
 +		})();
 +	}
 +
 +
 +/*=====
 +var __flashArgs = {
 +	// path: String
 +	//		The URL of the movie to embed.
 +	// id: String?
 +	//		A unique key that will be used as the id of the created markup.  If you don't
 +	//		provide this, a unique key will be generated.
 +	// width: Number?
 +	//		The width of the embedded movie; the default value is 320px.
 +	// height: Number?
 +	//		The height of the embedded movie; the default value is 240px
 +	// minimumVersion: Number?
 +	//		The minimum targeted version of the Flash Player (defaults to 9)
 +	// style: String?
 +	//		Any CSS style information (i.e. style="background-color:transparent") you want
 +	//		to define on the markup.
 +	// params: Object?
 +	//		A set of key/value pairs that you want to define in the resultant markup.
 +	// vars: Object?
 +	//		A set of key/value pairs that the Flash movie will interpret as FlashVars.
 +	// expressInstall: Boolean?
 +	//		Whether or not to include any kind of expressInstall info. Default is false.
 +	// redirect: String?
 +	//		A url to redirect the browser to if the current Flash version is not supported.
 +};
 +=====*/
 +
 +	//	the main entry point
 +	var Flash = function(/*__flashArgs*/ kwArgs, /*DOMNode*/ node){
 +		// summary:
 +		//		Create a wrapper object around a Flash movie; this is the DojoX equivilent
 +		//		to SWFObject.
 +		//
 +		// description:
 +		//		Creates a wrapper object around a Flash movie.  Wrapper object will
 +		//		insert the movie reference in node; when the browser first starts
 +		//		grabbing the movie, onReady will be fired; when the movie has finished
 +		//		loading, it will fire onLoad.
 +		//
 +		//		If your movie uses ExternalInterface, you should use the onLoad event
 +		//		to do any kind of proxy setup (see dojox.embed.Flash.proxy); this seems
 +		//		to be the only consistent time calling EI methods are stable (since the
 +		//		Flash movie will shoot several methods into the window object before
 +		//		EI callbacks can be used properly).
 +		//
 +		// kwArgs: __flashArgs
 +		//		The various arguments that will be used to help define the Flash movie.
 +		// node: DomNode
 +		//		The node where the embed object will be placed
 +		//
 +		// example:
 +		//		Embed a flash movie in a document using the new operator, and get a reference to it.
 +		//	|	var movie = new dojox.embed.Flash({
 +		//	|		path: "path/to/my/movie.swf",
 +		//	|		width: 400,
 +		//	|		height: 300
 +		//	|	}, myWrapperNode, "testLoaded");
 +		//
 +		// example:
 +		//		Embed a flash movie in a document without using the new operator.
 +		//	|	var movie = dojox.embed.Flash({
 +		//	|		path: "path/to/my/movie.swf",
 +		//	|		width: 400,
 +		//	|		height: 300,
 +		//	|		style: "position:absolute;top:0;left:0"
 +		//	|	}, myWrapperNode, "testLoaded");
 +
 +		// File can only be run from a server, due to SWF dependency.
 +		if(location.href.toLowerCase().indexOf("file://")>-1){
 +			throw new Error("dojox.embed.Flash can't be run directly from a file. To instatiate the required SWF correctly it must be run from a server, like localHost.");
 +		}
 +
 +		// available: Number
 +		//		If there is a flash player available, and if so what version.
 +		this.available = fVersion.major;
 +
 +		// minimumVersion: Number
 +		//		The minimum version of Flash required to run this movie.
 +		this.minimumVersion = kwArgs.minimumVersion || minimumVersion;
 +
 +		// id: String
 +		//		The id of the DOMNode to be used for this movie.  Can be used with dojo.byId to get a reference.
 +		this.id = null;
 +
 +		// movie: FlashObject
 +		//		A reference to the movie itself.
 +		this.movie = null;
 +
 +		// domNode: DOMNode
 +		//		A reference to the DOMNode that contains this movie.
 +		this.domNode = null;
 +		if(node){
 +			node = dom.byId(node);
 +		}
 +		// setTimeout Fixes #8743 - creating double SWFs
 +		// also allows time for code to attach to onError
 +		setTimeout(lang.hitch(this, function(){
 +			if(kwArgs.expressInstall || this.available && this.available >= this.minimumVersion){
 +				if(kwArgs && node){
 +					this.init(kwArgs, node);
 +				}else{
 +					this.onError("embed.Flash was not provided with the proper arguments.");
 +				}
 +			}else{
 +				if(!this.available){
 +					this.onError("Flash is not installed.");
 +				}else{
 +					this.onError("Flash version detected: "+this.available+" is out of date. Minimum required: "+this.minimumVersion);
 +				}
 +			}
 +		}), 100);
 +	};
 +
 +	lang.extend(Flash, {
 +		onReady: function(/*HTMLObject*/ movie){
 +			// summary:
 +			//		Stub function for you to attach to when the movie reference is first
 +			//		pushed into the document.
 +		},
 +		onLoad: function(/*HTMLObject*/ movie){
 +			// summary:
 +			//		Stub function for you to attach to when the movie has finished downloading
 +			//		and is ready to be manipulated.
 +		},
 +		onError: function(msg){
 +
 +		},
 +		_onload: function(){
 +			// summary:
 +			//	Internal. Cleans up before calling onLoad.
 +			clearInterval(this._poller);
 +			delete this._poller;
 +			delete this._pollCount;
 +			delete this._pollMax;
 +			this.onLoad(this.movie);
 +		},
 +		init: function(/*__flashArgs*/ kwArgs, /*DOMNode?*/ node){
 +			// summary:
 +			//		Initialize (i.e. place and load) the movie based on kwArgs.
 +			this.destroy();		//	ensure we are clean first.
 +			node = dom.byId(node || this.domNode);
 +			if(!node){ throw new Error("dojox.embed.Flash: no domNode reference has been passed."); }
 +
 +			// vars to help determine load status
 +			var p = 0, testLoaded=false;
 +			this._poller = null; this._pollCount = 0; this._pollMax = 15; this.pollTime = 100;
 +
 +			if(Flash.initialized){
 +
 +				this.id = Flash.place(kwArgs, node);
 +				this.domNode = node;
 +
 +				setTimeout(lang.hitch(this, function(){
 +					this.movie = this.byId(this.id, kwArgs.doc);
 +					this.onReady(this.movie);
 +
 +					this._poller = setInterval(lang.hitch(this, function(){
 +
 +						// catch errors if not quite ready.
 +						try{
 +							p = this.movie.PercentLoaded();
 +						}catch(e){
 +							console.warn("this.movie.PercentLoaded() failed", e, this.movie);
 +						}
 +
 +						if(p == 100){
 +							// if percent = 100, movie is fully loaded and we're communicating
 +							this._onload();
 +
 +						}else if(p==0 && this._pollCount++ > this._pollMax){
 +							// after several attempts, we're not past zero.
 +							clearInterval(this._poller);
 +							throw new Error("Building SWF failed.");
 +						}
 +					}), this.pollTime);
 +				}), 1);
 +			}
 +		},
 +		_destroy: function(){
 +			// summary:
 +			//		Kill the movie and reset all the properties of this object.
 +			try{
 +				this.domNode.removeChild(this.movie);
 +			}catch(e){}
 +			this.id = this.movie = this.domNode = null;
 +		},
 +		destroy: function(){
 +			// summary:
 +			//		Public interface for destroying all the properties in this object.
 +			//		Will also clean all proxied methods.
 +			if(!this.movie){ return; }
 +
 +			//	remove any proxy functions
 +			var test = lang.delegate({
 +				id: true,
 +				movie: true,
 +				domNode: true,
 +				onReady: true,
 +				onLoad: true
 +			});
 +			for(var p in this){
 +				if(!test[p]){
 +					delete this[p];
 +				}
 +			}
 +
 +			//	poll the movie
 +			if(this._poller){
 +				//	wait until onLoad to destroy
 +				on(this, "Load", this, "_destroy");
 +			} else {
 +				this._destroy();
 +			}
 +		},
 +		byId: function (movieName, doc){
 +			// summary:
 +			//		Gets Flash movie by id.
 +			// description:
 +			//		Probably includes methods for outdated
 +			//		browsers, but this should catch all cases.
 +			// movieName: String
 +			//		The name of the SWF
 +			// doc: Object
 +			//		The document, if not current window
 +			//		(not fully supported)
 +			// example:
 +			//	|	var movie = dojox.embed.Flash.byId("myId");
 +
 +			doc = doc || document;
 +			if(doc.embeds[movieName]){
 +				return doc.embeds[movieName];
 +			}
 +			if(doc[movieName]){
 +				return doc[movieName];
 +			}
 +			if(window[movieName]){
 +				return window[movieName];
 +			}
 +			if(document[movieName]){
 +				return document[movieName];
 +			}
 +			return null;
 +		}
 +	});
 +
 +	//	expose information through the constructor function itself.
 +	lang.mixin(Flash, {
 +		// summary:
 +		//		A singleton object used internally to get information
 +		//		about the Flash player available in a browser, and
 +		//		as the factory for generating and placing markup in a
 +		//		document.
 +		//
 +		// minSupported: Number
 +		//		The minimum supported version of the Flash Player, defaults to 8.
 +		// available: Number
 +		//		Used as both a detection (i.e. if(dojox.embed.Flash.available){ })
 +		//		and as a variable holding the major version of the player installed.
 +		// supported: Boolean
 +		//		Whether or not the Flash Player installed is supported by dojox.embed.
 +		// version: Object
 +		//		The version of the installed Flash Player; takes the form of
 +		//		{ major, minor, rev }.  To get the major version, you'd do this:
 +		//		var v=dojox.embed.Flash.version.major;
 +		// initialized: Boolean
 +		//		Whether or not the Flash engine is available for use.
 +		// onInitialize: Function
 +		//		A stub you can connect to if you are looking to fire code when the
 +		//		engine becomes available.  A note: DO NOT use this event to
 +		//		place a movie in a document; it will usually fire before DOMContentLoaded
 +		//		is fired, and you will get an error.  Use dojo.addOnLoad instead.
 +		minSupported : 8,
 +		available: fVersion.major,
 +		supported: (fVersion.major >= fVersion.required),
 +		minimumRequired: fVersion.required,
 +		version: fVersion,
 +		initialized: false,
 +		onInitialize: function(){
 +			Flash.initialized = true;
 +		},
 +		__ie_markup__: function(kwArgs){
 +			return fMarkup(kwArgs);
 +		},
 +		proxy: function(/*Flash*/ obj, /*Array|String*/ methods){
 +			// summary:
 +			//		Create the set of passed methods on the Flash object
 +			//		so that you can call that object directly, as opposed to having to
 +			//		delve into the internal movie to do this.  Intended to make working
 +			//		with Flash movies that use ExternalInterface much easier to use.
 +			//
 +			// example:
 +			//		Create "setMessage" and "getMessage" methods on foo.
 +			//	|	var foo = new Flash(args, someNode);
 +			//	|	dojo.connect(foo, "onLoad", lang.hitch(foo, function(){
 +			//	|		Flash.proxy(this, [ "setMessage", "getMessage" ]);
 +			//	|		this.setMessage("Flash.proxy is pretty cool...");
 +			//	|		console.log(this.getMessage());
 +			//	|	}));
 +			array.forEach((methods instanceof Array ? methods : [ methods ]), function(item){
 +				this[item] = lang.hitch(this, function(){
 +					return (function(){
 +						return eval(this.movie.CallFunction(
 +							'<invoke name="' + item + '" returntype="javascript">'
 +							+ '<arguments>'
 +							+ array.map(arguments, function(item){
 +								// FIXME:
 +								//		investigate if __flash__toXML will
 +								//		accept direct application via map()
 +								//		(e.g., does it ignore args past the
 +								//		first? or does it blow up?)
 +								return __flash__toXML(item);
 +							}).join("")
 +							+ '</arguments>'
 +							+ '</invoke>'
 +						));
 +					}).apply(this, arguments||[]);
 +				});
 +			}, obj);
 +		}
 +	});
 +
 +	Flash.place = function(kwArgs, node){
 +		var o = fMarkup(kwArgs);
 +		node = dom.byId(node);
 +		if(!node){
 +			node = win.doc.createElement("div");
 +			node.id = o.id+"-container";
 +			win.body().appendChild(node);
 +		}
 +		if(o){
 +			node.innerHTML = o.markup;
 +			return o.id;
 +		}
 +		return null;
 +	}
 +	Flash.onInitialize();
 +
 +	lang.setObject("dojox.embed.Flash", Flash);
 +
 +	return Flash;
 +});
diff --cc dojox/grid/enhanced/plugins/filter/FilterDefDialog.js
index ae1bc69,0000000..9588ee9
mode 100644,000000..100644
--- a/dojox/grid/enhanced/plugins/filter/FilterDefDialog.js
+++ b/dojox/grid/enhanced/plugins/filter/FilterDefDialog.js
@@@ -1,1263 -1,0 +1,1266 @@@
 +define([
 +	"dojo/_base/declare",
 +	"dojo/_base/array",
 +	"dojo/_base/connect",
 +	"dojo/_base/lang",
 +	"dojo/_base/event",
 +	"dojo/_base/html",
 +	"dojo/_base/sniff",
 +	"dojo/keys",
++	"dojo/on",
 +	"dojo/string",
 +	"dojo/window",
 +	"dojo/date/locale",		
 +	"./FilterBuilder",
 +	"../Dialog",
 +	"dijit/form/ComboBox",
 +	"dijit/form/TextBox",
 +	"dijit/form/NumberTextBox",
 +	"dijit/form/DateTextBox",
 +	"dijit/form/TimeTextBox",
 +	"dijit/form/Button",
 +	"dijit/layout/AccordionContainer",
 +	"dijit/layout/ContentPane",
 +	"dijit/_Widget", 
 +	"dijit/_TemplatedMixin", 
 +	"dijit/_WidgetsInTemplateMixin",
 +	"dijit/focus",
 +	"dojox/html/metrics",
 +	"dijit/a11y",
 +	"dojo/text!../../templates/FilterDefPane.html",
 +	"dojo/text!../../templates/CriteriaBox.html",
 +	"dojo/text!../../templates/FilterBoolValueBox.html",	
 +	"dijit/Tooltip",
 +	"dijit/form/Select",
 +	"dijit/form/RadioButton",
 +	"dojox/html/ellipsis",
 +	"../../../cells/dijit"
- ], function(declare, array, connect, lang, event, html, has, keys, string, win, dateLocale, 
++], function(declare, array, connect, lang, event, html, has, keys, on, string, win, dateLocale, 
 +	FilterBuilder, Dialog, ComboBox, TextBox, NumberTextBox, DateTextBox, TimeTextBox, Button, 
 +	AccordionContainer, ContentPane, _Widget, _TemplatedMixin, _WidgetsInTemplateMixin, dijitFocus,
 +	metrics, dijitA11y, defPaneTemplate, criteriaTemplate, boolValueTemplate){
 +		
 +var _tabIdxes = {
 +		// summary:
 +		//		Define tabindexes for elements in the filter definition dialog
 +		relSelect: 60,
 +		accordionTitle: 70,
 +		removeCBoxBtn: -1,
 +		colSelect: 90,
 +		condSelect: 95,
 +		valueBox: 10,
 +		addCBoxBtn: 20,
 +		filterBtn: 30,
 +		clearBtn: 40,
 +		cancelBtn: 50
 +	};
 +var FilterAccordionContainer = declare("dojox.grid.enhanced.plugins.filter.AccordionContainer", AccordionContainer, {
 +	nls: null,
 +	addChild: function(/*dijit._Widget*/ child, /*Integer?*/ insertIndex){
 +		var pane = arguments[0] = child._pane = new ContentPane({
 +			content: child
 +		});
 +		this.inherited(arguments);
 +		this._modifyChild(pane);
 +	},
 +	removeChild: function(child){
 +		var pane = child, isRemoveByUser = false;
 +		if(child._pane){
 +			isRemoveByUser = true;
 +			pane = arguments[0] = child._pane;
 +		}
 +		this.inherited(arguments);
 +		if(isRemoveByUser){
 +			this._hackHeight(false, this._titleHeight);
 +			var children = this.getChildren();
 +			if(children.length === 1){
 +				html.style(children[0]._removeCBoxBtn.domNode, "display", "none");
 +			}
 +		}
 +		pane.destroyRecursive();
 +	},
 +	selectChild: function(child){
 +		if(child._pane){
 +			arguments[0] = child._pane;
 +		}
 +		this.inherited(arguments);
 +	},
 +	resize: function(){
 +		this.inherited(arguments);
 +		array.forEach(this.getChildren(), this._setupTitleDom);
 +	},
 +	startup: function(){
 +		if(this._started){
 +			return;
 +		}
 +		this.inherited(arguments);
 +		if(parseInt(has('ie'), 10) == 7){
 +			//IE7 will fire a lot of "onresize" event during initialization.
 +			array.some(this._connects, function(cnnt){
 +				if((cnnt[0] || {})[1] == "onresize"){
 +					this.disconnect(cnnt);
 +					return true;
 +				}
 +			}, this);
 +		}
 +		array.forEach(this.getChildren(), function(child){
 +			this._modifyChild(child, true);
 +		}, this);
 +	},
 +	_onKeyPress: function(/*Event*/ e, /*dijit._Widget*/ fromTitle){
 +		// summary:
 +		//		Overrides base class method, make left/right button do other things.
 +		if(this.disabled || e.altKey || !(fromTitle || e.ctrlKey)){
 +			return;
 +		}
 +		var k = keys, c = e.charOrCode, ltr = html._isBodyLtr(), toNext = null;
 +		if((fromTitle && c == k.UP_ARROW) || (e.ctrlKey && c == k.PAGE_UP)){
 +			toNext = false;
 +		}else if((fromTitle && c == k.DOWN_ARROW) || (e.ctrlKey && (c == k.PAGE_DOWN || c == k.TAB))){
 +			toNext = true;
 +		}else if(c == (ltr ? k.LEFT_ARROW : k.RIGHT_ARROW)){
 +			toNext = this._focusOnRemoveBtn ? null : false;
 +			this._focusOnRemoveBtn = !this._focusOnRemoveBtn;
 +		}else if(c == (ltr ? k.RIGHT_ARROW : k.LEFT_ARROW)){
 +			toNext = this._focusOnRemoveBtn ? true : null;
 +			this._focusOnRemoveBtn = !this._focusOnRemoveBtn;
 +		}else{
 +			return;
 +		}
 +		if(toNext !== null){
 +			this._adjacent(toNext)._buttonWidget._onTitleClick();
 +		}
 +		event.stop(e);
 +		win.scrollIntoView(this.selectedChildWidget._buttonWidget.domNode.parentNode);
 +		if(has('ie')){
 +			//IE will not show focus indicator if tabIndex is -1
 +			this.selectedChildWidget._removeCBoxBtn.focusNode.setAttribute("tabIndex", this._focusOnRemoveBtn ? _tabIdxes.accordionTitle : -1);
 +		}
 +		dijitFocus.focus(this.selectedChildWidget[this._focusOnRemoveBtn ? "_removeCBoxBtn" : "_buttonWidget"].focusNode);
 +	},
 +	_modifyChild: function(child, isFirst){
 +		if(!child || !this._started){
 +			return;
 +		}
 +		html.style(child.domNode, "overflow", "hidden");
 +		child._buttonWidget.connect(child._buttonWidget, "_setSelectedAttr", function(){
 +			this.focusNode.setAttribute("tabIndex", this.selected ? _tabIdxes.accordionTitle : "-1");
 +		});
 +		var _this = this;
 +		child._buttonWidget.connect(child._buttonWidget.domNode, "onclick", function(){
 +			_this._focusOnRemoveBtn = false;
 +		});
 +		(child._removeCBoxBtn = new Button({
 +			label: this.nls.removeRuleButton,
 +			showLabel: false,
 +			iconClass: "dojoxGridFCBoxRemoveCBoxBtnIcon",
 +			tabIndex: _tabIdxes.removeCBoxBtn,
 +			onClick: lang.hitch(child.content, "onRemove"),
 +			onKeyPress: function(e){
 +				_this._onKeyPress(e, child._buttonWidget.contentWidget);
 +			}
 +		})).placeAt(child._buttonWidget.domNode);
 +		var i, children = this.getChildren();
 +		if(children.length === 1){
 +			child._buttonWidget.set("selected", true);
 +			html.style(child._removeCBoxBtn.domNode, "display", "none");
 +		}else{
 +			for(i = 0; i < children.length; ++i){
 +				if(children[i]._removeCBoxBtn){
 +					html.style(children[i]._removeCBoxBtn.domNode, "display", "");
 +				}
 +			}
 +		}
 +		this._setupTitleDom(child);
 +		if(!this._titleHeight){
 +			for(i = 0; i < children.length; ++i){
 +				if(children[i] != this.selectedChildWidget){
 +					this._titleHeight = html.marginBox(children[i]._buttonWidget.domNode.parentNode).h;
 +					break;
 +				}
 +			}
 +		}
 +		if(!isFirst){
 +			this._hackHeight(true, this._titleHeight);
 +		}
 +	},
 +	_hackHeight: function(/* bool */toGrow,/* int */heightDif){
 +		var children = this.getChildren(),
 +			dn = this.domNode, h = html.style(dn, "height");
 +		if(!toGrow){
 +			dn.style.height = (h - heightDif) + 'px';
 +		}else if(children.length > 1){
 +			dn.style.height = (h + heightDif) + 'px';
 +		}else{
 +			//Only one rule, no need to do anything.
 +			return;
 +		}
 +		this.resize();
 +	},
 +	_setupTitleDom: function(child){
 +		var w = html.contentBox(child._buttonWidget.titleNode).w;
 +		if(has('ie') < 8){ w -= 8; }
 +		html.style(child._buttonWidget.titleTextNode, "width", w + "px");
 +	}
 +});
 +var FilterDefPane = declare("dojox.grid.enhanced.plugins.filter.FilterDefPane",[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{
 +	templateString: defPaneTemplate,
 +	widgetsInTemplate: true,
 +	dlg: null,
 +	postMixInProperties: function(){
 +		this.plugin = this.dlg.plugin;
 +		var nls = this.plugin.nls;
 +		this._addRuleBtnLabel = nls.addRuleButton;
 +		this._cancelBtnLabel = nls.cancelButton;
 +		this._clearBtnLabel = nls.clearButton;
 +		this._filterBtnLabel = nls.filterButton;
 +		this._relAll = nls.relationAll;
 +		this._relAny = nls.relationAny;
 +		this._relMsgFront = nls.relationMsgFront;
 +		this._relMsgTail = nls.relationMsgTail;
 +	},
 +	postCreate: function(){
 +		this.inherited(arguments);
- 		this.connect(this.domNode, "onkeypress", "_onKey");
++		// this.connect(this.domNode, "onkeypress", "_onKey");
++		on(this.domNode, "keydown", lang.hitch(this, "_onKey"));
++		
 +		(this.cboxContainer = new FilterAccordionContainer({
 +			nls: this.plugin.nls
 +		})).placeAt(this.criteriaPane);
 +		
 +		this._relSelect.set("tabIndex", _tabIdxes.relSelect);
 +		this._addCBoxBtn.set("tabIndex", _tabIdxes.addCBoxBtn);
 +		this._cancelBtn.set("tabIndex", _tabIdxes.cancelBtn);
 +		this._clearFilterBtn.set("tabIndex", _tabIdxes.clearBtn);
 +		this._filterBtn.set("tabIndex", _tabIdxes.filterBtn);
 +		
 +		var nls = this.plugin.nls;
 +		this._relSelect.domNode.setAttribute("aria-label", nls.waiRelAll);
 +		this._addCBoxBtn.domNode.setAttribute("aria-label", nls.waiAddRuleButton);
 +		this._cancelBtn.domNode.setAttribute("aria-label", nls.waiCancelButton);
 +		this._clearFilterBtn.domNode.setAttribute("aria-label", nls.waiClearButton);
 +		this._filterBtn.domNode.setAttribute("aria-label", nls.waiFilterButton);
 +		
 +		this._relSelect.set("value", this.dlg._relOpCls === "logicall" ? "0" : "1");
 +	},
 +	uninitialize: function(){
 +		this.cboxContainer.destroyRecursive();
 +		this.plugin = null;
 +		this.dlg = null;
 +	},
 +	_onRelSelectChange: function(val){
 +		this.dlg._relOpCls = val == "0" ? "logicall" : "logicany";
 +		this._relSelect.domNode.setAttribute("aria-label", this.plugin.nls[val == "0" ? "waiRelAll" : "waiRelAny"]);
 +	},
 +	_onAddCBox: function(){
 +		this.dlg.addCriteriaBoxes(1);
 +	},
 +	_onCancel: function(){
 +		this.dlg.onCancel();
 +	},
 +	_onClearFilter: function(){
 +		this.dlg.onClearFilter();
 +	},
 +	_onFilter: function(){
 +		this.dlg.onFilter();
 +	},
 +	_onKey: function(e){
 +		if(e.keyCode == keys.ENTER){
 +			this.dlg.onFilter();
 +		}
 +	}
 +});
 +var CriteriaBox = declare("dojox.grid.enhanced.plugins.filter.CriteriaBox",[_Widget, _TemplatedMixin, _WidgetsInTemplateMixin],{
 +	templateString: criteriaTemplate,
 +	widgetsInTemplate: true,
 +	dlg: null,
 +	postMixInProperties: function(){
 +		this.plugin = this.dlg.plugin;
 +		this._curValueBox = null;
 +		
 +		var nls = this.plugin.nls;
 +		this._colSelectLabel = nls.columnSelectLabel;
 +		this._condSelectLabel = nls.conditionSelectLabel;
 +		this._valueBoxLabel = nls.valueBoxLabel;
 +		this._anyColumnOption = nls.anyColumnOption;
 +	},
 +	postCreate: function(){
 +		var dlg = this.dlg, g = this.plugin.grid;
 +		//Select Column
 +		this._colSelect.set("tabIndex", _tabIdxes.colSelect);
 +		this._colOptions = this._getColumnOptions();
 +		this._colSelect.addOption([
 +			{label: this.plugin.nls.anyColumnOption, value: "anycolumn", selected: dlg.curColIdx < 0},
 +			{value: ""}
 +		].concat(this._colOptions));
 +		//Select Condition
 +		this._condSelect.set("tabIndex", _tabIdxes.condSelect);
 +		this._condSelect.addOption(this._getUsableConditions(dlg.getColumnType(dlg.curColIdx)));
 +		this._showSelectOrLabel(this._condSelect, this._condSelectAlt);
 +		
 +		this.connect(g.layout, "moveColumn", "onMoveColumn");
 +		var _this = this;
 +		setTimeout(function(){
 +			var type = dlg.getColumnType(dlg.curColIdx);
 +			_this._setValueBoxByType(type);
 +		}, 0);
 +	},
 +	_getColumnOptions: function(){
 +		var colIdx = this.dlg.curColIdx >= 0 ? String(this.dlg.curColIdx) : "anycolumn";
 +		return array.map(array.filter(this.plugin.grid.layout.cells, function(cell){
 +			return !(cell.filterable === false || cell.hidden);
 +		}), function(cell){
 +			return {
 +				label: cell.name || cell.field,
 +				value: String(cell.index),
 +				selected: colIdx == String(cell.index)
 +			};
 +		});
 +	},
 +	onMoveColumn: function(){
 +		var tmp = this._onChangeColumn;
 +		this._onChangeColumn = function(){};
 +		var option = this._colSelect.get("selectedOptions");
 +		this._colSelect.removeOption(this._colOptions);
 +		this._colOptions = this._getColumnOptions();
 +		this._colSelect.addOption(this._colOptions);
 +		var i = 0;
 +		for(; i < this._colOptions.length; ++i){
 +			if(this._colOptions[i].label == option.label){
 +				break;
 +			}
 +		}
 +		if(i < this._colOptions.length){
 +			this._colSelect.set("value", this._colOptions[i].value);
 +		}
 +		var _this = this;
 +		setTimeout(function(){
 +			_this._onChangeColumn = tmp;
 +		}, 0);
 +	},
 +	onRemove: function(){
 +		this.dlg.removeCriteriaBoxes(this);
 +	},
 +	uninitialize: function(){
 +		if(this._curValueBox){
 +			this._curValueBox.destroyRecursive();
 +			this._curValueBox = null;
 +		}
 +		this.plugin = null;
 +		this.dlg = null;
 +	},
 +	_showSelectOrLabel: function(sel, alt){
 +		var options = sel.getOptions();
 +		if(options.length == 1){
 +			alt.innerHTML = options[0].label;
 +			html.style(sel.domNode, "display", "none");
 +			html.style(alt, "display", "");
 +		}else{
 +			html.style(sel.domNode, "display", "");
 +			html.style(alt, "display", "none");
 +		}
 +	},
 +	_onChangeColumn: function(val){
 +		this._checkValidCriteria();
 +		var type = this.dlg.getColumnType(val);
 +		this._setConditionsByType(type);
 +		this._setValueBoxByType(type);
 +		this._updateValueBox();
 +	},
 +	_onChangeCondition: function(val){
 +		this._checkValidCriteria();
 +		var f = (val == "range");
 +		if(f ^ this._isRange){
 +			this._isRange = f;
 +			this._setValueBoxByType(this.dlg.getColumnType(this._colSelect.get("value")));
 +		}
 +		this._updateValueBox();
 +	},
 +	_updateValueBox: function(cond){
 +		this._curValueBox.set("disabled", this._condSelect.get("value") == "isempty");
 +	},
 +	_checkValidCriteria: function(){
 +		// summary:
 +		//		Check whether the given criteria box is completed. If it is, mark it.
 +		setTimeout(lang.hitch(this, function(){
 +			this.updateRuleTitle();
 +			this.dlg._updatePane();
 +		}),0);
 +	},
 +	_createValueBox: function(/* widget constructor */cls,/* object */arg){
 +		// summary:
 +		//		Create a value input box with given class and arguments
 +		var func = lang.hitch(arg.cbox, "_checkValidCriteria");
 +		return new cls(lang.mixin(arg,{
 +			tabIndex: _tabIdxes.valueBox,
 +			onKeyPress: func,
 +			onChange: func,
 +			"class": "dojoxGridFCBoxValueBox"
 +		}));
 +	},
 +	_createRangeBox: function(/* widget constructor */cls,/* object */arg){
 +		// summary:
 +		//		Create a DIV containing 2 input widgets, which represents a range, with the given class and arguments
 +		var func = lang.hitch(arg.cbox, "_checkValidCriteria");
 +		lang.mixin(arg,{
 +			tabIndex: _tabIdxes.valueBox,
 +			onKeyPress: func,
 +			onChange: func
 +		});
 +		var div = html.create("div", {"class": "dojoxGridFCBoxValueBox"}),
 +			start = new cls(arg),
 +			txt = html.create("span", {"class": "dojoxGridFCBoxRangeValueTxt", "innerHTML": this.plugin.nls.rangeTo}),
 +			end = new cls(arg);
 +		html.addClass(start.domNode, "dojoxGridFCBoxStartValue");
 +		html.addClass(end.domNode, "dojoxGridFCBoxEndValue");
 +		div.appendChild(start.domNode);
 +		div.appendChild(txt);
 +		div.appendChild(end.domNode);
 +		div.domNode = div;
 +		//Mock functions for set and get (in place of the old attr function)
 +		div.set = function(dummy, args){
 +			if(lang.isObject(args)){
 +				start.set("value", args.start);
 +				end.set("value", args.end);
 +			}
 +		};
 +		div.get = function(){
 +			var s = start.get("value"),
 +				e = end.get("value");
 +			return s && e ? {start: s, end: e} : "";
 +		};
 +		return div;
 +	},
 +	changeCurrentColumn: function(/* bool */selectCurCol){
 +		var colIdx = this.dlg.curColIdx;
 +		//Re-populate the columns in case some of them are set to hidden.
 +		this._colSelect.removeOption(this._colOptions);
 +		this._colOptions = this._getColumnOptions();
 +		this._colSelect.addOption(this._colOptions);
 +		this._colSelect.set('value', colIdx >= 0 ? String(colIdx) : "anycolumn");
 +		this.updateRuleTitle(true);
 +	},
 +	curColumn: function(){
 +		return this._colSelect.getOptions(this._colSelect.get("value")).label;
 +	},
 +	curCondition: function(){
 +		return this._condSelect.getOptions(this._condSelect.get("value")).label;
 +	},
 +	curValue: function(){
 +		var cond = this._condSelect.get("value");
 +		if(cond == "isempty"){return "";}
 +		return this._curValueBox ? this._curValueBox.get("value") : "";
 +	},
 +	save: function(){
 +		if(this.isEmpty()){
 +			return null;
 +		}
 +		var colIdx = this._colSelect.get("value"),
 +			type = this.dlg.getColumnType(colIdx),
 +			value = this.curValue(),
 +			cond = this._condSelect.get("value");
 +		return {
 +			"column": colIdx,
 +			"condition": cond,
 +			"value": value,
 +			"formattedVal": this.formatValue(type, cond, value),
 +			"type": type,
 +			"colTxt": this.curColumn(),
 +			"condTxt": this.curCondition()
 +		};
 +	},
 +	load: function(obj){
 +		var tmp = [
 +			this._onChangeColumn,
 +			this._onChangeCondition
 +		];
 +		this._onChangeColumn = this._onChangeCondition = function(){};
 +		if(obj.column){
 +			this._colSelect.set("value", obj.column);
 +		}
 +		if(obj.type){
 +			this._setConditionsByType(obj.type);
 +			this._setValueBoxByType(obj.type);
 +		}else{
 +			obj.type = this.dlg.getColumnType(this._colSelect.get("value"));
 +		}
 +		if(obj.condition){
 +			this._condSelect.set("value", obj.condition);
 +		}
 +		var value = obj.value || "";
 +		if(value || (obj.type != "date" && obj.type != "time")){
 +			this._curValueBox.set("value", value);
 +		}
 +		this._updateValueBox();
 +		setTimeout(lang.hitch(this, function(){
 +			this._onChangeColumn = tmp[0];
 +			this._onChangeCondition = tmp[1];
 +		}), 0);
 +	},
 +	getExpr: function(){
 +		if(this.isEmpty()){
 +			return null;
 +		}
 +		var colval = this._colSelect.get("value");
 +		return this.dlg.getExprForCriteria({
 +			"type": this.dlg.getColumnType(colval),
 +			"column": colval,
 +			"condition": this._condSelect.get("value"),
 +			"value": this.curValue()
 +		});
 +	},
 +	isEmpty: function(){
 +		var cond = this._condSelect.get("value");
 +		if(cond == "isempty"){return false;}
 +		var v = this.curValue();
 +		return v === "" || v === null || typeof v == "undefined" || (typeof v == "number" && isNaN(v));
 +	},
 +	updateRuleTitle: function(isEmpty){
 +		var node = this._pane._buttonWidget.titleTextNode;
 +		var title = [
 +			"<div class='dojoxEllipsis'>"
 +		];
 +		if(isEmpty || this.isEmpty()){
 +			node.title = string.substitute(this.plugin.nls.ruleTitleTemplate, [this._ruleIndex || 1]);
 +			title.push(node.title);
 +		}else{
 +			var type = this.dlg.getColumnType(this._colSelect.get("value"));
 +			var column = this.curColumn();
 +			var condition = this.curCondition();
 +			var value = this.formatValue(type, this._condSelect.get("value"), this.curValue());
 +			title.push(
 +				column,
 +				" <span class='dojoxGridRuleTitleCondition'>",
 +				condition,
 +				"</span> ",
 +				value
 +			);
 +			node.title = [column, " ", condition, " ", value].join('');
 +		}
 +		node.innerHTML = title.join('');
 +		if(has('mozilla')){
 +			var tt = html.create("div", {
 +				"style": "width: 100%; height: 100%; position: absolute; top: 0; left: 0; z-index: 9999;"
 +			}, node);
 +			tt.title = node.title;
 +		}
 +	},
 +	updateRuleIndex: function(index){
 +		if(this._ruleIndex != index){
 +			this._ruleIndex = index;
 +			if(this.isEmpty()){
 +				this.updateRuleTitle();
 +			}
 +		}
 +	},
 +	setAriaInfo: function(idx){
 +		var dss = string.substitute, nls = this.plugin.nls;
 +		this._colSelect.domNode.setAttribute("aria-label", dss(nls.waiColumnSelectTemplate, [idx]));
 +		this._condSelect.domNode.setAttribute("aria-label", dss(nls.waiConditionSelectTemplate, [idx]));
 +		this._pane._removeCBoxBtn.domNode.setAttribute("aria-label", dss(nls.waiRemoveRuleButtonTemplate, [idx]));
 +		this._index = idx;
 +	},
 +	_getUsableConditions: function(type){
 +		var conditions = lang.clone(this.dlg._dataTypeMap[type].conditions);
 +		var typeDisabledConds = (this.plugin.args.disabledConditions || {})[type];
 +		var colIdx = parseInt(this._colSelect.get("value"), 10);
 +		var colDisabledConds = isNaN(colIdx) ?
 +			(this.plugin.args.disabledConditions || {})["anycolumn"] :
 +			this.plugin.grid.layout.cells[colIdx].disabledConditions;
 +		if(!lang.isArray(typeDisabledConds)){
 +			typeDisabledConds = [];
 +		}
 +		if(!lang.isArray(colDisabledConds)){
 +			colDisabledConds = [];
 +		}
 +		var arr = typeDisabledConds.concat(colDisabledConds);
 +		if(arr.length){
 +			var disabledConds = {};
 +			array.forEach(arr, function(c){
 +				if(lang.isString(c)){
 +					disabledConds[c.toLowerCase()] = true;
 +				}
 +			});
 +			return array.filter(conditions, function(condOption){
 +				return !(condOption.value in disabledConds);
 +			});
 +		}
 +		return conditions;
 +	},
 +	_setConditionsByType: function(/* string */type){
 +		var condSelect = this._condSelect;
 +		condSelect.removeOption(condSelect.options);
 +		condSelect.addOption(this._getUsableConditions(type));
 +		this._showSelectOrLabel(this._condSelect, this._condSelectAlt);
 +	},
 +	_setValueBoxByType: function(/* string */type){
 +		if(this._curValueBox){
 +			this.valueNode.removeChild(this._curValueBox.domNode);
 +			try{
 +				this._curValueBox.destroyRecursive();
 +			}catch(e){}
 +			delete this._curValueBox;
 +		}
 +		//value box class
 +		var vbcls = this.dlg._dataTypeMap[type].valueBoxCls[this._getValueBoxClsInfo(this._colSelect.get("value"), type)],
 +			vboxArg = this._getValueBoxArgByType(type);
 +		this._curValueBox = this[this._isRange ? "_createRangeBox" : "_createValueBox"](vbcls, vboxArg);
 +		this.valueNode.appendChild(this._curValueBox.domNode);
 +		
 +		//Can not move to setAriaInfo, 'cause the value box is created after the defpane is loaded.
 +		this._curValueBox.focusNode.setAttribute("aria-label", string.substitute(this.plugin.nls.waiValueBoxTemplate,[this._index]));
 +		//Now our cbox is completely ready
 +		this.dlg.onRendered(this);
 +	},
 +	//--------------------------UI Configuration--------------------------------------
 +	_getValueBoxArgByType: function(/* string */type){
 +		// summary:
 +		//		Get the arguments for the value box construction.
 +		var g = this.plugin.grid,
 +			cell = g.layout.cells[parseInt(this._colSelect.get("value"), 10)],
 +			res = {
 +				cbox: this
 +			};
 +		if(type == "string"){
 +			if(cell && (cell.suggestion || cell.autoComplete)){
 +				lang.mixin(res, {
 +					store: g.store,
 +					searchAttr: cell.field || cell.name,
 +					query: g.query || {},
 +					fetchProperties: {
 +						sort: [{"attribute": cell.field || cell.name}],
 +						queryOptions: lang.mixin({
 +							ignoreCase: true,
 +							deep: true
 +						}, g.queryOptions || {})
 +					}
 +				});
 +			}
 +		}else if(type == "boolean"){
 +			lang.mixin(res, this.dlg.builder.defaultArgs["boolean"]);
 +		}
 +		if(cell && cell.dataTypeArgs){
 +			lang.mixin(res, cell.dataTypeArgs);
 +		}
 +		return res;
 +	},
 +	formatValue: function(type, cond, v){
 +		// summary:
 +		//		Format the value to be shown in tooltip.
 +		if(cond == "isempty"){return "";}
 +		if(type == "date" || type == "time"){
 +			var opt = {selector: type},
 +				fmt = dateLocale.format;
 +			if(cond == "range"){
 +				return string.substitute(this.plugin.nls.rangeTemplate, [fmt(v.start, opt), fmt(v.end, opt)]);
 +			}
 +			return fmt(v, opt);
 +		}else if(type == "boolean"){
 +			return v ? this._curValueBox._lblTrue : this._curValueBox._lblFalse;
 +		}
 +		return v;
 +	},
 +	_getValueBoxClsInfo: function(/* int|string */colIndex, /* string */type){
 +		// summary:
 +		//		Decide which value box to use given data type and column index.
 +		var cell = this.plugin.grid.layout.cells[parseInt(colIndex, 10)];
 +		//Now we only need to handle string. But maybe we need to handle more types here in the future.
 +		if(type == "string"){
 +			return (cell && (cell.suggestion || cell.autoComplete)) ? "ac" : "dft";
 +		}
 +		return "dft";
 +	}
 +});
 +
 +
 +var UniqueComboBox = declare("dojox.grid.enhanced.plugins.filter.UniqueComboBox", ComboBox, {
 +	_openResultList: function(results){
 +		var cache = {}, s = this.store, colName = this.searchAttr;
 +		arguments[0] = array.filter(results, function(item){
 +			var key = s.getValue(item, colName), existed = cache[key];
 +			cache[key] = true;
 +			return !existed;
 +		});
 +		this.inherited(arguments);
 +	},
 +	_onKey: function(evt){
 +		if(evt.charOrCode === keys.ENTER && this._opened){
 +			event.stop(evt);
 +		}
 +		this.inherited(arguments);
 +	}
 +});
 +var BooleanValueBox = declare("dojox.grid.enhanced.plugins.filter.BooleanValueBox", [_Widget, _TemplatedMixin, _WidgetsInTemplateMixin], {
 +	templateString: boolValueTemplate,
 +	widgetsInTemplate: true,
 +	constructor: function(args){
 +		var nls = args.cbox.plugin.nls;
 +		this._baseId = args.cbox.id;
 +		this._lblTrue = args.trueLabel || nls.trueLabel || "true";
 +		this._lblFalse = args.falseLabel || nls.falseLabel || "false";
 +		this.args = args;
 +	},
 +	postCreate: function(){
 +		this.onChange();
 +	},
 +	onChange: function(){},
 +	
 +	get: function(prop){
 +		return this.rbTrue.get("checked");
 +	},
 +	set: function(prop, v){
 +		this.inherited(arguments);
 +		if(prop == "value"){
 +			this.rbTrue.set("checked", !!v);
 +			this.rbFalse.set("checked", !v);
 +		}
 +	}
 +});
 +var FilterDefDialog = declare("dojox.grid.enhanced.plugins.filter.FilterDefDialog", null, {
 +	// summary:
 +	//		Create the filter definition UI.
 +	curColIdx: -1,
 +	_relOpCls: "logicall",
 +	_savedCriterias: null,
 +	plugin: null,
 +	constructor: function(args){
 +		var plugin = this.plugin = args.plugin;
 +		this.builder = new FilterBuilder();
 +		this._setupData();
 +		this._cboxes = [];
 +		this.defaultType = plugin.args.defaultType || "string";
 +		
 +		(this.filterDefPane = new FilterDefPane({
 +			"dlg": this
 +		})).startup();
 +		(this._defPane = new Dialog({
 +			"refNode": this.plugin.grid.domNode,
 +			"title": plugin.nls.filterDefDialogTitle,
 +			"class": "dojoxGridFDTitlePane",
 +			"iconClass": "dojoxGridFDPaneIcon",
 +			"content": this.filterDefPane
 +		})).startup();
 +		
 +		this._defPane.connect(plugin.grid.layer('filter'), "filterDef", lang.hitch(this, "_onSetFilter"));
 +		plugin.grid.setFilter = lang.hitch(this, "setFilter");
 +		plugin.grid.getFilter = lang.hitch(this, "getFilter");
 +		plugin.grid.getFilterRelation = lang.hitch(this, function(){
 +			return this._relOpCls;
 +		});
 +		
 +		plugin.connect(plugin.grid.layout, "moveColumn", lang.hitch(this, "onMoveColumn"));
 +	},
 +	onMoveColumn: function(sourceViewIndex, destViewIndex, cellIndex, targetIndex, before){
 +		if(this._savedCriterias && cellIndex != targetIndex){
 +			if(before){ --targetIndex; }
 +			var min = cellIndex < targetIndex ? cellIndex : targetIndex;
 +			var max = cellIndex < targetIndex ? targetIndex : cellIndex;
 +			var dir = targetIndex > min ? 1 : -1;
 +			array.forEach(this._savedCriterias, function(sc){
 +				var idx = parseInt(sc.column, 10);
 +				if(!isNaN(idx) && idx >= min && idx <= max){
 +					sc.column = String(idx == cellIndex ? idx + (max - min) * dir : idx - dir);
 +				}
 +			});
 +		}
 +	},
 +	destroy: function(){
 +		this._defPane.destroyRecursive();
 +		this._defPane = null;
 +		this.filterDefPane = null;
 +		this.builder = null;
 +		this._dataTypeMap = null;
 +		this._cboxes = null;
 +		var g = this.plugin.grid;
 +		g.setFilter = null;
 +		g.getFilter = null;
 +		g.getFilterRelation = null;
 +		this.plugin = null;
 +	},
 +	_setupData: function(){
 +		var nls = this.plugin.nls;
 +		this._dataTypeMap = {
 +		// summary:
 +		//		All supported data types
 +			"number":{
 +				valueBoxCls: {
 +					dft: NumberTextBox
 +				},
 +				conditions:[
 +					{label: nls.conditionEqual, value: "equalto", selected: true},
 +					{label: nls.conditionNotEqual, value: "notequalto"},
 +					{label: nls.conditionLess, value: "lessthan"},
 +					{label: nls.conditionLessEqual, value: "lessthanorequalto"},
 +					{label: nls.conditionLarger, value: "largerthan"},
 +					{label: nls.conditionLargerEqual, value: "largerthanorequalto"},
 +					{label: nls.conditionIsEmpty, value: "isempty"}
 +				]
 +			},
 +			"string":{
 +				valueBoxCls: {
 +					dft: TextBox,
 +					ac: UniqueComboBox		//For autoComplete
 +				},
 +				conditions:[
 +					{label: nls.conditionContains, value: "contains", selected: true},
 +					{label: nls.conditionIs, value: "equalto"},
 +					{label: nls.conditionStartsWith, value: "startswith"},
 +					{label: nls.conditionEndWith, value: "endswith"},
 +					{label: nls.conditionNotContain, value: "notcontains"},
 +					{label: nls.conditionIsNot, value: "notequalto"},
 +					{label: nls.conditionNotStartWith, value: "notstartswith"},
 +					{label: nls.conditionNotEndWith, value: "notendswith"},
 +					{label: nls.conditionIsEmpty, value: "isempty"}
 +				]
 +			},
 +			"date":{
 +				valueBoxCls: {
 +					dft: DateTextBox
 +				},
 +				conditions:[
 +					{label: nls.conditionIs, value: "equalto", selected: true},
 +					{label: nls.conditionBefore, value: "lessthan"},
 +					{label: nls.conditionAfter, value: "largerthan"},
 +					{label: nls.conditionRange, value: "range"},
 +					{label: nls.conditionIsEmpty, value: "isempty"}
 +				]
 +			},
 +			"time":{
 +				valueBoxCls: {
 +					dft: TimeTextBox
 +				},
 +				conditions:[
 +					{label: nls.conditionIs, value: "equalto", selected: true},
 +					{label: nls.conditionBefore, value: "lessthan"},
 +					{label: nls.conditionAfter, value: "largerthan"},
 +					{label: nls.conditionRange, value: "range"},
 +					{label: nls.conditionIsEmpty, value: "isempty"}
 +				]
 +			},
 +			"boolean": {
 +				valueBoxCls: {
 +					dft: BooleanValueBox
 +				},
 +				conditions: [
 +					{label: nls.conditionIs, value: "equalto", selected: true},
 +					{label: nls.conditionIsEmpty, value: "isempty"}
 +				]
 +			}
 +		};
 +	},
 +	setFilter: function(rules, ruleRelation){
 +		rules = rules || [];
 +		if(!lang.isArray(rules)){
 +			rules = [rules];
 +		}
 +		var func = function(){
 +			if(rules.length){
 +				this._savedCriterias = array.map(rules, function(rule){
 +					var type = rule.type || this.defaultType;
 +					return {
 +						"type": type,
 +						"column": String(rule.column),
 +						"condition": rule.condition,
 +						"value": rule.value,
 +						"colTxt": this.getColumnLabelByValue(String(rule.column)),
 +						"condTxt": this.getConditionLabelByValue(type, rule.condition),
 +						"formattedVal": rule.formattedVal || rule.value
 +					};
 +				}, this);
 +				this._criteriasChanged = true;
 +				if(ruleRelation === "logicall" || ruleRelation === "logicany"){
 +					this._relOpCls = ruleRelation;
 +				}
 +				var exprs = array.map(rules, this.getExprForCriteria, this);
 +				exprs = this.builder.buildExpression(exprs.length == 1 ? exprs[0] : {
 +					"op": this._relOpCls,
 +					"data": exprs
 +				});
 +				this.plugin.grid.layer("filter").filterDef(exprs);
 +				this.plugin.filterBar.toggleClearFilterBtn(false);
 +			}
 +			this._closeDlgAndUpdateGrid();
 +		};
 +		if(this._savedCriterias){
 +			this._clearWithoutRefresh = true;
 +			var handle = connect.connect(this, "clearFilter", this, function(){
 +				connect.disconnect(handle);
 +				this._clearWithoutRefresh = false;
 +				func.apply(this);
 +			});
 +			this.onClearFilter();
 +		}else{
 +			func.apply(this);
 +		}
 +	},
 +	getFilter: function(){
 +		return lang.clone(this._savedCriterias) || [];
 +	},
 +	getColumnLabelByValue: function(v){
 +		var nls = this.plugin.nls;
 +		if(v.toLowerCase() == "anycolumn"){
 +			return nls["anyColumnOption"];
 +		}else{
 +			var cell = this.plugin.grid.layout.cells[parseInt(v, 10)];
 +			return cell ? (cell.name || cell.field) : "";
 +		}
 +	},
 +	getConditionLabelByValue: function(type, c){
 +		var conditions = this._dataTypeMap[type].conditions;
 +		for(var i = conditions.length - 1; i >= 0; --i){
 +			var cond = conditions[i];
 +			if(cond.value == c.toLowerCase()){
 +				return cond.label;
 +			}
 +		}
 +		return "";
 +	},
 +	addCriteriaBoxes: function(/* int */cnt){
 +		// summary:
 +		//		Add *cnt* criteria boxes to the filter definition pane.
 +		//		Check overflow if necessary.
 +		if(typeof cnt != "number" || cnt <= 0){
 +			return;
 +		}
 +		var cbs = this._cboxes,
 +			cc = this.filterDefPane.cboxContainer,
 +			total = this.plugin.args.ruleCount,
 +			len = cbs.length, cbox;
 +		//If overflow, add to max rule count.
 +		if(total > 0 && len + cnt > total){
 +			cnt = total - len;
 +		}
 +		for(; cnt > 0; --cnt){
 +			cbox = new CriteriaBox({
 +				dlg: this
 +			});
 +			cbs.push(cbox);
 +			cc.addChild(cbox);
 +		}
 +		//If there's no content box in it , AccordionContainer can not startup
 +		cc.startup();
 +		this._updatePane();
 +		this._updateCBoxTitles();
 +		cc.selectChild(cbs[cbs.length-1]);
 +		//Asign an impossibly large scrollTop to scroll the criteria pane to the bottom.
 +		this.filterDefPane.criteriaPane.scrollTop = 1000000;
 +		if(cbs.length === 4){
 +			if(has('ie') <= 6 && !this.__alreadyResizedForIE6){
 +				var size = html.position(cc.domNode);
 +				size.w -= metrics.getScrollbar().w;
 +				cc.resize(size);
 +				this.__alreadyResizedForIE6 = true;
 +			}else{
 +				cc.resize();
 +			}
 +		}
 +	},
 +	removeCriteriaBoxes: function(/* int|CriteriaBox|int[] */cnt,/* bool? */isIdx){
 +		// summary:
 +		//		Remove criteria boxes from the filter definition pane.
 +		var cbs = this._cboxes, cc = this.filterDefPane.cboxContainer,
 +			len = cbs.length, start = len - cnt,
 +			end = len - 1, cbox,
 +			curIdx = array.indexOf(cbs, cc.selectedChildWidget.content);
 +		if(lang.isArray(cnt)){
 +			var i, idxes = cnt;
 +			idxes.sort();
 +			cnt = idxes.length;
 +			//find a rule that's not deleted.
 +			//must find and focus the last one, or the hack will not work.
 +			for(i = len - 1; i >= 0 && array.indexOf(idxes, i) >= 0; --i){}
 +			if(i >= 0){
 +				//must select before remove
 +				if(i != curIdx){
 +					cc.selectChild(cbs[i]);
 +				}
 +				//idxes is sorted from small to large,
 +				//so travel reversely won't need change index after delete from array.
 +				for(i = cnt-1; i >= 0; --i){
 +					if(idxes[i] >= 0 && idxes[i] < len){
 +						cc.removeChild(cbs[idxes[i]]);
 +						cbs.splice(idxes[i],1);
 +					}
 +				}
 +			}
 +			start = cbs.length;
 +		}else{
 +			if(isIdx === true){
 +				if(cnt >= 0 && cnt < len){
 +					start = end = cnt;
 +					cnt = 1;
 +				}else{
 +					return;
 +				}
 +			}else{
 +				if(cnt instanceof CriteriaBox){
 +					cbox = cnt;
 +					cnt = 1;
 +					start = end = array.indexOf(cbs, cbox);
 +				}else if(typeof cnt != "number" || cnt <= 0){
 +					return;
 +				}else if(cnt >= len){
 +					cnt = end;
 +					start = 1;
 +				}
 +			}
 +			if(end < start){
 +				return;
 +			}
 +			//must select before remove
 +			if(curIdx >= start && curIdx <= end){
 +				cc.selectChild(cbs[start ? start-1 : end+1]);
 +			}
 +			for(; end >= start; --end){
 +				cc.removeChild(cbs[end]);
 +			}
 +			cbs.splice(start, cnt);
 +		}
 +		this._updatePane();
 +		this._updateCBoxTitles();
 +		if(cbs.length === 3){
 +			//In ie6, resize back to the normal width will cause the title button look strange.
 +			cc.resize();
 +		}
 +	},
 +	getCriteria: function(/* int */idx){
 +		// summary:
 +		//		Get the *idx*-th criteria.
 +		if(typeof idx != "number"){
 +			return this._savedCriterias ? this._savedCriterias.length : 0;
 +		}
 +		if(this._savedCriterias && this._savedCriterias[idx]){
 +			return lang.mixin({
 +				relation: this._relOpCls == "logicall" ? this.plugin.nls.and : this.plugin.nls.or
 +			},this._savedCriterias[idx]);
 +		}
 +		return null;
 +	},
 +	getExprForCriteria: function(rule){
 +		if(rule.column == "anycolumn"){
 +			var cells = array.filter(this.plugin.grid.layout.cells, function(cell){
 +				return !(cell.filterable === false || cell.hidden);
 +			});
 +			return {
 +				"op": "logicany",
 +				"data": array.map(cells, function(cell){
 +					return this.getExprForColumn(rule.value, cell.index, rule.type, rule.condition);
 +				}, this)
 +			};
 +		}else{
 +			return this.getExprForColumn(rule.value, rule.column, rule.type, rule.condition);
 +		}
 +	},
 +	getExprForColumn: function(value, colIdx, type, condition){
 +		colIdx = parseInt(colIdx, 10);
 +		var cell = this.plugin.grid.layout.cells[colIdx],
 +			colName = cell.field || cell.name,
 +			obj = {
 +				"datatype": type || this.getColumnType(colIdx),
 +				"args": cell.dataTypeArgs,
 +				"isColumn": true
 +			},
 +			operands = [lang.mixin({"data": this.plugin.args.isServerSide ? colName : cell}, obj)];
 +		obj.isColumn = false;
 +		if(condition == "range"){
 +			operands.push(lang.mixin({"data": value.start}, obj),
 +				lang.mixin({"data": value.end}, obj));
 +		}else if(condition != "isempty"){
 +			operands.push(lang.mixin({"data": value}, obj));
 +		}
 +		return {
 +			"op": condition,
 +			"data": operands
 +		};
 +	},
 +	getColumnType: function(/* int */colIndex){
 +		var cell = this.plugin.grid.layout.cells[parseInt(colIndex, 10)];
 +		if(!cell || !cell.datatype){
 +			return this.defaultType;
 +		}
 +		var type = String(cell.datatype).toLowerCase();
 +		return this._dataTypeMap[type] ? type : this.defaultType;
 +	},
 +	//////////////////////////////////////////////////////////////////////////////////////////////////////////
 +	clearFilter: function(noRefresh){
 +		// summary:
 +		//		Clear filter definition.
 +		if(!this._savedCriterias){
 +			return;
 +		}
 +		this._savedCriterias = null;
 +		this.plugin.grid.layer("filter").filterDef(null);
 +		try{
 +			this.plugin.filterBar.toggleClearFilterBtn(true);
 +			this.filterDefPane._clearFilterBtn.set("disabled", true);
 +			this.removeCriteriaBoxes(this._cboxes.length-1);
 +			this._cboxes[0].load({});
 +		}catch(e){
 +			//Any error means the filter is defined outside this plugin.
 +		}
 +		if(noRefresh){
 +			this.closeDialog();
 +		}else{
 +			this._closeDlgAndUpdateGrid();
 +		}
 +	},
 +	showDialog: function(/* int */colIndex){
 +		// summary:
 +		//		Show the filter definition dialog.
 +		this._defPane.show();
 +		this.plugin.filterStatusTip.closeDialog();
 +		this._prepareDialog(colIndex);
 +	},
 +	closeDialog: function(){
 +		// summary:
 +		//		Close the filter definition dialog.
 +		if(this._defPane.open){
 +			this._defPane.hide();
 +		}
 +	},
 +	onFilter: function(e){
 +		// summary:
 +		//		Triggered when the "Filter" button is clicked.
 +		if(this.canFilter()){
 +			this._defineFilter();
 +			this._closeDlgAndUpdateGrid();
 +			this.plugin.filterBar.toggleClearFilterBtn(false);
 +		}
 +	},
 +	onClearFilter: function(e){
 +		// summary:
 +		//		Triggered when the "Clear" button is clicked.
 +		if(this._savedCriterias){
 +			if(this._savedCriterias.length >= this.plugin.ruleCountToConfirmClearFilter){
 +				this.plugin.clearFilterDialog.show();
 +			}else{
 +				this.clearFilter(this._clearWithoutRefresh);
 +			}
 +		}
 +	},
 +	onCancel: function(e){
 +		// summary:
 +		//		Triggered when the "Cancel" buttton is clicked.
 +		var sc = this._savedCriterias;
 +		var cbs = this._cboxes;
 +		if(sc){
 +			this.addCriteriaBoxes(sc.length - cbs.length);
 +			this.removeCriteriaBoxes(cbs.length - sc.length);
 +			array.forEach(sc, function(c, i){
 +				cbs[i].load(c);
 +			});
 +		}else{
 +			this.removeCriteriaBoxes(cbs.length - 1);
 +			cbs[0].load({});
 +		}
 +		this.closeDialog();
 +	},
 +	onRendered: function(cbox){
 +		// summary:
 +		//		Triggered when the rendering of the filter definition dialog is completely finished.
 +		// cbox:
 +		//		Current visible criteria box
 +		if(!has('ff')){
 +			var elems = dijitA11y._getTabNavigable(html.byId(cbox.domNode));
 +			dijitFocus.focus(elems.lowest || elems.first);
 +		}else{
 +			var dp = this._defPane;
 +			dp._getFocusItems(dp.domNode);
 +			dijitFocus.focus(dp._firstFocusItem);
 +		}
 +	},
 +	_onSetFilter: function(filterDef){
 +		// summary:
 +		//		If someone clear the filter def in the store directly, we must clear it in the UI.
 +		//		If someone defines a filter, don't know how to handle it!
 +		if(filterDef === null && this._savedCriterias){
 +			this.clearFilter();
 +		}
 +	},
 +	_prepareDialog: function(/* int */colIndex){
 +		var sc = this._savedCriterias,
 +			cbs = this._cboxes, i, cbox;
 +		this.curColIdx = colIndex;
 +		if(!sc){
 +			if(cbs.length === 0){
 +				this.addCriteriaBoxes(1);
 +			}else{
 +				//Re-populate columns anyway, because we don't know when the column is set to hidden.
 +				for(i = 0; (cbox = cbs[i]); ++i){
 +					cbox.changeCurrentColumn();
 +				}
 +			}
 +		}else if(this._criteriasChanged){
 +			this.filterDefPane._relSelect.set("value", this._relOpCls === "logicall" ? "0" : "1");
 +			this._criteriasChanged = false;
 +			var needNewCBox = sc.length > cbs.length ? sc.length - cbs.length : 0;
 +			this.addCriteriaBoxes(needNewCBox);
 +			this.removeCriteriaBoxes(cbs.length - sc.length);
 +			this.filterDefPane._clearFilterBtn.set("disabled", false);
 +			for(i = 0; i < cbs.length - needNewCBox; ++i){
 +				cbs[i].load(sc[i]);
 +			}
 +			if(needNewCBox > 0){
 +				var handled = [], handle = connect.connect(this, "onRendered", function(cbox){
 +					var i = array.indexOf(cbs, cbox);
 +					if(!handled[i]){
 +						handled[i] = true;
 +						if(--needNewCBox === 0){
 +							connect.disconnect(handle);
 +						}
 +						cbox.load(sc[i]);
 +					}
 +				});
 +			}
 +		}
 +		//Since we're allowed to remove cboxes when the definition pane is not shown,
 +		//we have to resize the container to have a correct _verticalSpace.
 +		this.filterDefPane.cboxContainer.resize();
 +	},
 +	_defineFilter: function(){
 +		var cbs = this._cboxes,
 +			filterCboxes = function(method){
 +				return array.filter(array.map(cbs, function(cbox){
 +					return cbox[method]();
 +				}), function(result){
 +					return !!result;
 +				});
 +			},
 +			exprs = filterCboxes("getExpr");
 +		this._savedCriterias = filterCboxes("save");
 +		exprs = exprs.length == 1 ? exprs[0] : {
 +			"op": this._relOpCls,
 +			"data": exprs
 +		};
 +		exprs = this.builder.buildExpression(exprs);
 +		
 +		this.plugin.grid.layer("filter").filterDef(exprs);
 +		this.filterDefPane._clearFilterBtn.set("disabled", false);
 +	},
 +	_updateCBoxTitles: function(){
 +		for(var cbs = this._cboxes, i = cbs.length; i > 0; --i){
 +			cbs[i - 1].updateRuleIndex(i);
 +			cbs[i - 1].setAriaInfo(i);
 +		}
 +	},
 +	_updatePane: function(){
 +		var cbs = this._cboxes,
 +			defPane = this.filterDefPane;
 +		defPane._addCBoxBtn.set("disabled", cbs.length == this.plugin.args.ruleCount);
 +		defPane._filterBtn.set("disabled", !this.canFilter());
 +	},
 +	canFilter: function(){
 +		return array.filter(this._cboxes, function(cbox){
 +			return !cbox.isEmpty();
 +		}).length > 0;
 +	},
 +	_closeDlgAndUpdateGrid: function(){
 +		this.closeDialog();
 +		var g = this.plugin.grid;
 +		g.showMessage(g.loadingMessage);
 +		setTimeout(lang.hitch(g, g._refresh), this._defPane.duration + 10);
 +	}
 +});
 +
 +return FilterDefDialog;
 +});
diff --cc dojox/layout/ContentPane.js
index 875aabe,0000000..62d43a9
mode 100644,000000..100644
--- a/dojox/layout/ContentPane.js
+++ b/dojox/layout/ContentPane.js
@@@ -1,102 -1,0 +1,102 @@@
 +define([
 +	"dojo/_base/lang",
 +	"dojo/_base/xhr",
 +	"dijit/layout/ContentPane",
 +	"dojox/html/_base",
 +	"dojo/_base/declare"
 +], function (lang, xhrUtil, ContentPane, htmlUtil, declare) {
 +
 +return declare("dojox.layout.ContentPane", ContentPane, {
 +	// summary:
 +	//		An extended version of dijit.layout.ContentPane.
 +	//		Supports infile scripts and external ones declared by `<script src=''...>`
 +	//		relative path adjustments (content fetched from a different folder)
 +	//		`<style>` and `<link rel='stylesheet' href='..'>` tags,
 +	//		css paths inside cssText is adjusted (if you set adjustPaths = true)
 +	//
 +	//		NOTE that dojo.require in script in the fetched file isn't recommended
 +	//		Many widgets need to be required at page load to work properly
 +
 +	// adjustPaths: Boolean
 +	//		Adjust relative paths in html string content to point to this page.
 +	//		Only useful if you grab content from a another folder then the current one
 +	adjustPaths: false,
 +
 +	// cleanContent: Boolean
 +	//		Cleans content to make it less likely to generate DOM/JS errors.
 +	//		Useful if you send ContentPane a complete page, instead of a html fragment
 +	//		scans for:
 +	//
 +	//		- title Node, remove
 +	//		- DOCTYPE tag, remove
 +	cleanContent: false,
 +
 +	// renderStyles: Boolean
 +	//		trigger/load styles in the content
 +	renderStyles: false,
 +
 +	// executeScripts: Boolean
 +	//		Execute (eval) scripts that is found in the content
 +	executeScripts: true,
 +
 +	// scriptHasHooks: Boolean
 +	//		replace keyword '_container_' in scripts with 'dijit.byId(this.id)'
 +	//		NOTE this name might change in the near future
 +	scriptHasHooks: false,
 +
 +	ioMethod: xhrUtil.get,
 +
 +	ioArgs: {},
 +
 +	onExecError: function(/*Event*/ e){
 +		// summary:
 +		//		event callback, called on script error or on java handler error
 +		//		override and return your own html string if you want a some text
 +		//		displayed within the ContentPane
 +	},
 +
 +	_setContent: function(cont){
 +		// override dijit.layout.ContentPane._setContent, to enable path adjustments
 +		
 +		var setter = this._contentSetter;
 +		if(! (setter && setter instanceof htmlUtil._ContentSetter)) {
 +			setter = this._contentSetter = new htmlUtil._ContentSetter({
 +				node: this.containerNode,
 +				_onError: lang.hitch(this, this._onError),
 +				onContentError: lang.hitch(this, function(e){
 +					// fires if a domfault occurs when we are appending this.errorMessage
 +					// like for instance if domNode is a UL and we try append a DIV
 +					var errMess = this.onContentError(e);
 +					try{
 +						this.containerNode.innerHTML = errMess;
 +					}catch(e){
 +						console.error('Fatal '+this.id+' could not change content due to '+e.message, e);
 +					}
 +				})/*,
 +				_onError */
 +			});
 +		};
 +
 +		// stash the params for the contentSetter to allow inheritance to work for _setContent
 +		this._contentSetterParams = {
 +			adjustPaths: Boolean(this.adjustPaths && (this.href||this.referencePath)),
 +			referencePath: this.href || this.referencePath,
 +			renderStyles: this.renderStyles,
 +			executeScripts: this.executeScripts,
 +			scriptHasHooks: this.scriptHasHooks,
 +			scriptHookReplacement: "dijit.byId('"+this.id+"')"
 +		};
 +
- 		this.inherited("_setContent", arguments);
++		return this.inherited("_setContent", arguments);
 +	},
 +	// could put back _renderStyles by wrapping/aliasing dojox.html._ContentSetter.prototype._renderStyles
 +
 +	destroy: function () {
 +		var setter = this._contentSetter;
 +		if (setter) {
 +			setter.tearDown();
 +		}
 +		this.inherited(arguments);
 +	}
 +});
 +});
diff --cc dojox/layout/ResizeHandle.js
index 937244d,0000000..e8b52c2
mode 100644,000000..100644
--- a/dojox/layout/ResizeHandle.js
+++ b/dojox/layout/ResizeHandle.js
@@@ -1,388 -1,0 +1,389 @@@
 +define(["dojo/_base/kernel","dojo/_base/lang","dojo/_base/connect","dojo/_base/array","dojo/_base/event",
 +	"dojo/_base/fx","dojo/_base/window","dojo/fx","dojo/dom","dojo/dom-class",
- 	"dojo/dom-geometry","dojo/dom-style","dijit/_base/manager","dijit/_Widget","dijit/_TemplatedMixin",
- 	"dojo/_base/declare"], function (
++	"dojo/dom-geometry","dojo/dom-style","dojo/_base/declare", "dojo/touch",
++	"dijit/_base/manager","dijit/_Widget","dijit/_TemplatedMixin",
++	], function (
 +	kernel, lang, connect, arrayUtil, eventUtil, fxBase, windowBase, fxUtil, 
- 	domUtil, domClass, domGeometry, domStyle, manager, Widget, TemplatedMixin, declare) {
++	domUtil, domClass, domGeometry, domStyle, declare, touch, manager, Widget, TemplatedMixin) {
 +
 +kernel.experimental("dojox.layout.ResizeHandle");
 +
 +var _ResizeHelper = declare("dojox.layout._ResizeHelper", Widget, {
 +	// summary:
 +	//		A global private resize helper shared between any
 +	//		`dojox.layout.ResizeHandle` with activeSizing off.
 +	
 +	show: function(){
 +		// summary:
 +		//		show helper to start resizing
 +		domStyle.set(this.domNode, "display", "");
 +	},
 +	
 +	hide: function(){
 +		// summary:
 +		//		hide helper after resizing is complete
 +		domStyle.set(this.domNode, "display", "none");
 +	},
 +	
 +	resize: function(/* Object */dim){
 +		// summary:
 +		//		size the widget and place accordingly
 +		domGeometry.setMarginBox(this.domNode, dim);
 +	}
 +	
 +});
 +
 +var ResizeHandle = declare("dojox.layout.ResizeHandle",[Widget, TemplatedMixin],
 +	{
 +	// summary:
 +	//		A draggable handle used to resize an attached node.
 +	//
 +	// description:
 +	//		The handle on the bottom-right corner of FloatingPane or other widgets that allows
 +	//		the widget to be resized.
 +	//		Typically not used directly.
 +
 +	// targetId: String
 +	//		id of the Widget OR DomNode that I will size
 +	targetId: "",
 +	
 +	// targetContainer: DomNode
 +	//		over-ride targetId and attch this handle directly to a reference of a DomNode
 +	targetContainer: null,
 +	
 +	// resizeAxis: String
 +	//		one of: x|y|xy limit resizing to a single axis, default to xy ...
 +	resizeAxis: "xy",
 +	
 +	// activeResize: Boolean
 +	//		if true, node will size realtime with mouse movement,
 +	//		if false, node will create virtual node, and only resize target on mouseUp
 +	activeResize: false,
 +	
 +	// activeResizeClass: String
 +	//		css class applied to virtual resize node.
 +	activeResizeClass: "dojoxResizeHandleClone",
 +	
 +	// animateSizing: Boolean
 +	//		only applicable if activeResize = false. onMouseup, animate the node to the
 +	//		new size                
 +	animateSizing: true,
 +	
 +	// animateMethod: String
 +	//		one of "chain" or "combine" ... visual effect only. combine will "scale"
 +	//		node to size, "chain" will alter width, then height
 +	animateMethod: "chain",
 +
 +	// animateDuration: Integer
 +	//		time in MS to run sizing animation. if animateMethod="chain", total animation
 +	//		playtime is 2*animateDuration
 +	animateDuration: 225,
 +
 +	// minHeight: Integer
 +	//		smallest height in px resized node can be
 +	minHeight: 100,
 +
 +	// minWidth: Integer
 +	//		smallest width in px resize node can be
 +	minWidth: 100,
 +
 +	// constrainMax: Boolean
 +	//		Toggle if this widget cares about the maxHeight and maxWidth
 +	//		parameters.
 +	constrainMax: false,
 +
 +	// maxHeight: Integer
 +	//		Largest height size in px the resize node can become.
 +	maxHeight:0,
 +	
 +	// maxWidth: Integer
 +	//		Largest width size in px the resize node can become.
 +	maxWidth:0,
 +
 +	// fixedAspect: Boolean
 +	//		Toggle to enable this widget to maintain the aspect
 +	//		ratio of the attached node.
 +	fixedAspect: false,
 +
 +	// intermediateChanges: Boolean
 +	//		Toggle to enable/disable this widget from firing onResize
 +	//		events at every step of a resize. If `activeResize` is true,
 +	//		and this is false, onResize only fires _after_ the drop
 +	//		operation. Animated resizing is not affected by this setting.
 +	intermediateChanges: false,
 +
 +	// startTopic: String
 +	//		The name of the topic this resizehandle publishes when resize is starting
 +	startTopic: "/dojo/resize/start",
 +	
 +	// endTopic: String
 +	//		The name of the topic this resizehandle publishes when resize is complete
 +	endTopic:"/dojo/resize/stop",
 +
 +	templateString: '<div dojoAttachPoint="resizeHandle" class="dojoxResizeHandle"><div></div></div>',
 +
 +	postCreate: function(){
 +		// summary:
 +		//		setup our one major listener upon creation
- 		this.connect(this.resizeHandle, "onmousedown", "_beginSizing");
++		this.connect(this.resizeHandle, touch.press, "_beginSizing");
 +		if(!this.activeResize){
 +			// there shall be only a single resize rubberbox that at the top
 +			// level so that we can overlay it on anything whenever the user
 +			// resizes something. Since there is only one mouse pointer he
 +			// can't at once resize multiple things interactively.
 +			this._resizeHelper = manager.byId('dojoxGlobalResizeHelper');
 +			if(!this._resizeHelper){
 +				this._resizeHelper = new _ResizeHelper({
 +						id: 'dojoxGlobalResizeHelper'
 +				}).placeAt(windowBase.body());
 +				domClass.add(this._resizeHelper.domNode, this.activeResizeClass);
 +			}
 +		}else{ this.animateSizing = false; }
 +
 +		if(!this.minSize){
 +			this.minSize = { w: this.minWidth, h: this.minHeight };
 +		}
 +		
 +		if(this.constrainMax){
 +			this.maxSize = { w: this.maxWidth, h: this.maxHeight };
 +		}
 +		
 +		// should we modify the css for the cursor hover to n-resize nw-resize and w-resize?
 +		this._resizeX = this._resizeY = false;
 +		var addClass = lang.partial(domClass.add, this.resizeHandle);
 +		switch(this.resizeAxis.toLowerCase()){
 +			case "xy" :
 +				this._resizeX = this._resizeY = true;
 +				// FIXME: need logic to determine NW or NE class to see
 +				// based on which [todo] corner is clicked
 +				addClass("dojoxResizeNW");
 +				break;
 +			case "x" :
 +				this._resizeX = true;
 +				addClass("dojoxResizeW");
 +				break;
 +			case "y" :
 +				this._resizeY = true;
 +				addClass("dojoxResizeN");
 +				break;
 +		}
 +	},
 +
 +	_beginSizing: function(/*Event*/ e){
 +		// summary:
 +		//		setup movement listeners and calculate initial size
- 		
++
 +		if(this._isSizing){ return; }
 +
 +		connect.publish(this.startTopic, [ this ]);
 +		this.targetWidget = manager.byId(this.targetId);
 +
 +		this.targetDomNode = this.targetWidget ? this.targetWidget.domNode : domUtil.byId(this.targetId);
 +		if(this.targetContainer){ this.targetDomNode = this.targetContainer; }
 +		if(!this.targetDomNode){ return; }
 +
 +		if(!this.activeResize){
 +			var c = domGeometry.position(this.targetDomNode, true);
 +			this._resizeHelper.resize({l: c.x, t: c.y, w: c.w, h: c.h});
 +			this._resizeHelper.show();
 +			if(!this.isLeftToRight()){
 +				this._resizeHelper.startPosition = {l: c.x, t: c.y};
 +			}
 +		}
 +
 +		this._isSizing = true;
 +		this.startPoint  = { x:e.clientX, y:e.clientY };
 +
 +		// widget.resize() or setting style.width/height expects native box model dimension 
 +		// (in most cases content-box, but it may be border-box if in backcompact mode)
 +		var style = domStyle.getComputedStyle(this.targetDomNode), 
 +			borderModel = domGeometry.boxModel==='border-model',
 +			padborder = borderModel?{w:0,h:0}:domGeometry.getPadBorderExtents(this.targetDomNode, style),
 +			margin = domGeometry.getMarginExtents(this.targetDomNode, style);
 +		this.startSize = { 
 +				w: domStyle.get(this.targetDomNode, 'width', style), 
 +				h: domStyle.get(this.targetDomNode, 'height', style),
 +				//ResizeHelper.resize expects a bounding box of the
 +				//border box, so let's keep track of padding/border
 +				//width/height as well
 +				pbw: padborder.w, pbh: padborder.h,
 +				mw: margin.w, mh: margin.h};
 +		if(!this.isLeftToRight() && domStyle.get(this.targetDomNode, "position") == "absolute"){
 +			var p = domGeometry.position(this.targetDomNode, true);
 +			this.startPosition = {l: p.x, t: p.y};
 +		}
 +		
 +		this._pconnects = [
- 			connect.connect(windowBase.doc,"onmousemove",this,"_updateSizing"),
- 			connect.connect(windowBase.doc,"onmouseup", this, "_endSizing")
++			connect.connect(windowBase.doc, touch.move, this,"_updateSizing"),
++			connect.connect(windowBase.doc, touch.release, this, "_endSizing")
 +		];
 +		
 +		eventUtil.stop(e);
 +	},
 +
 +	_updateSizing: function(/*Event*/ e){
 +		// summary:
 +		//		called when moving the ResizeHandle ... determines
 +		//		new size based on settings/position and sets styles.
 +
 +		if(this.activeResize){
 +			this._changeSizing(e);
 +		}else{
 +			var tmp = this._getNewCoords(e, 'border', this._resizeHelper.startPosition);
 +			if(tmp === false){ return; }
 +			this._resizeHelper.resize(tmp);
 +		}
 +		e.preventDefault();
 +	},
 +
 +	_getNewCoords: function(/* Event */ e, /* String */ box, /* Object */startPosition){
 +		
 +		// On IE, if you move the mouse above/to the left of the object being resized,
 +		// sometimes clientX/Y aren't set, apparently.  Just ignore the event.
 +		try{
 +			if(!e.clientX  || !e.clientY){ return false; }
 +		}catch(err){
 +			// sometimes you get an exception accessing above fields...
 +			return false;
 +		}
 +		this._activeResizeLastEvent = e;
 +
 +		var dx = (this.isLeftToRight()?1:-1) * (this.startPoint.x - e.clientX),
 +			dy = this.startPoint.y - e.clientY,
 +			newW = this.startSize.w - (this._resizeX ? dx : 0),
 +			newH = this.startSize.h - (this._resizeY ? dy : 0),
 +			r = this._checkConstraints(newW, newH)
 +		;
 +		
 +		startPosition = (startPosition || this.startPosition);
 +		if(startPosition && this._resizeX){
 +			// adjust x position for RtoL
 +			r.l = startPosition.l + dx;
 +			if(r.w != newW){
 +				r.l += (newW - r.w);
 +			}
 +			r.t = startPosition.t;
 +		}
 +
 +		switch(box){
 +			case 'margin':
 +				r.w += this.startSize.mw;
 +				r.h += this.startSize.mh;
 +				//pass through
 +			case "border":
 +				r.w += this.startSize.pbw;
 +				r.h += this.startSize.pbh;
 +				break;
 +			//default: //native, do nothing
 +		}
 +
 +		return r; // Object
 +	},
 +	
 +	_checkConstraints: function(newW, newH){
 +		// summary:
 +		//		filter through the various possible constaint possibilities.
 +				
 +		// minimum size check
 +		if(this.minSize){
 +			var tm = this.minSize;
 +			if(newW < tm.w){
 +				newW = tm.w;
 +			}
 +			if(newH < tm.h){
 +				newH = tm.h;
 +			}
 +		}
 +		
 +		// maximum size check:
 +		if(this.constrainMax && this.maxSize){
 +			var ms = this.maxSize;
 +			if(newW > ms.w){
 +				newW = ms.w;
 +			}
 +			if(newH > ms.h){
 +				newH = ms.h;
 +			}
 +		}
 +		
 +		if(this.fixedAspect){
 +			var w = this.startSize.w, h = this.startSize.h,
 +				delta = w * newH - h * newW;
 +			if(delta<0){
 +				newW = newH * w / h;
 +			}else if(delta>0){
 +				newH = newW * h / w;
 +			}
 +		}
 +		
 +		return { w: newW, h: newH }; // Object
 +	},
 +		
 +	_changeSizing: function(/*Event*/ e){
 +		// summary:
 +		//		apply sizing information based on information in (e) to attached node
 +		
 +		var isWidget = this.targetWidget && lang.isFunction(this.targetWidget.resize),
 +			tmp = this._getNewCoords(e, isWidget && 'margin');
 +		if(tmp === false){ return; }
 +
 +		if(isWidget){
 +			this.targetWidget.resize(tmp);
 +		}else{
 +			if(this.animateSizing){
 +				var anim = fxUtil[this.animateMethod]([
 +					fxBase.animateProperty({
 +						node: this.targetDomNode,
 +						properties: {
 +							width: { start: this.startSize.w, end: tmp.w }
 +						},
 +						duration: this.animateDuration
 +					}),
 +					fxBase.animateProperty({
 +						node: this.targetDomNode,
 +						properties: {
 +							height: { start: this.startSize.h, end: tmp.h }
 +						},
 +						duration: this.animateDuration
 +					})
 +				]);
 +				anim.play();
 +			}else{
 +				domStyle.set(this.targetDomNode,{
 +					width: tmp.w + "px",
 +					height: tmp.h + "px"
 +				});
 +			}
 +		}
 +		if(this.intermediateChanges){
 +			this.onResize(e);
 +		}
 +	},
 +
 +	_endSizing: function(/*Event*/ e){
 +		// summary:
 +		//		disconnect listenrs and cleanup sizing
 +		arrayUtil.forEach(this._pconnects, connect.disconnect);
 +		var pub = lang.partial(connect.publish, this.endTopic, [ this ]);
 +		if(!this.activeResize){
 +			this._resizeHelper.hide();
 +			this._changeSizing(e);
 +			setTimeout(pub, this.animateDuration + 15);
 +		}else{
 +			pub();
 +		}
 +		this._isSizing = false;
 +		this.onResize(e);
 +	},
 +	
 +	onResize: function(e){
 +		// summary:
 +		//		Stub fired when sizing is done. Fired once
 +		//		after resize, or often when `intermediateChanges` is
 +		//		set to true.
 +	}
 +	
 +});
 +
 +return ResizeHandle;
 +});
diff --cc dojox/mobile/ComboBox.js
index 95efd77,0000000..161285d
mode 100644,000000..100644
--- a/dojox/mobile/ComboBox.js
+++ b/dojox/mobile/ComboBox.js
@@@ -1,332 -1,0 +1,332 @@@
 +define([
 +	"dojo/_base/kernel",
 +	"dojo/_base/declare",
 +	"dojo/_base/lang",
 +	"dojo/_base/window",
 +	"dojo/dom-geometry",
 +	"dojo/dom-style",
 +	"dojo/dom-attr",
 +	"dojo/window",
 +	"dojo/touch",
 +	"dijit/form/_AutoCompleterMixin",
 +	"dijit/popup",
 +	"./_ComboBoxMenu",
 +	"./TextBox",
 +	"./sniff"
 +], function(kernel, declare, lang, win, domGeometry, domStyle, domAttr, windowUtils, touch, AutoCompleterMixin, popup, ComboBoxMenu, TextBox, has){
 +	kernel.experimental("dojox.mobile.ComboBox"); // should be using a more native search-type UI
 +
 +	return declare("dojox.mobile.ComboBox", [TextBox, AutoCompleterMixin], {
 +		// summary:
 +		//		A non-templated auto-completing text box widget.
 +
 +		// dropDownClass: [protected extension] String
 +		//		Name of the drop-down widget class used to select a date/time.
 +		//		Should be specified by subclasses.
 +		dropDownClass: "dojox.mobile._ComboBoxMenu",
 +
 +		// initially disable selection since iphone displays selection handles
 +		// that makes it hard to pick from the list
 +		
 +		// selectOnClick: Boolean
 +		//		Flag which enables the selection on click.
 +		selectOnClick: false,
 +		
 +		// autoComplete: Boolean
 +		//		Flag which enables the auto-completion.
 +		autoComplete: false,
 +
 +		// dropDown: [protected] Widget
 +		//		The widget to display as a popup. This widget *must* be
 +		//		defined before the startup function is called.
 +		dropDown: null,
 +
 +		// maxHeight: [protected] int
 +		//		The maximum height for the drop-down.
 +		//		Any drop-down taller than this value will have scrollbars.
 +		//		Set to -1 to limit the height to the available space in the viewport.
 +		maxHeight: -1,
 +
 +		// dropDownPosition: [const] String[]
 +		//		This variable controls the position of the drop-down.
 +		//		It is an array of strings with the following values:
 +		//
 +		//		- before: places drop down to the left of the target node/widget, or to the right in
 +		//		  the case of RTL scripts like Hebrew and Arabic
 +		//		- after: places drop down to the right of the target node/widget, or to the left in
 +		//		  the case of RTL scripts like Hebrew and Arabic
 +		//		- above: drop down goes above target node
 +		//		- below: drop down goes below target node
 +		//
 +		//		The list is positions is tried, in order, until a position is found where the drop down fits
 +		//		within the viewport.
 +		dropDownPosition: ["below","above"],
 +
 +		_throttleOpenClose: function(){
 +			// summary:
 +			//		Prevents the open/close in rapid succession.
 +			// tags:
 +			//		private
 +			if(this._throttleHandler){
 +				this._throttleHandler.remove();
 +			}
 +			this._throttleHandler = this.defer(function(){ this._throttleHandler = null; }, 500);
 +		},
 +
 +		_onFocus: function(){
 +			// summary:
 +			//		Shows drop-down if the user is selecting Next/Previous from the virtual keyboard.
 +			// tags:
 +			//		private
 +			this.inherited(arguments);
 +			if(!this._opened && !this._throttleHandler){
 +				this._startSearchAll(); 
 +			}
 +
 +			if(has("windows-theme")) {
 +				this.domNode.blur();
 +			}
 +		},
 +
 +		onInput: function(e){
 +			if(!e || e.charCode !== 0){ // #18047
 +				this._onKey(e);
 +				this.inherited(arguments);
 +			}
 +		},
 +
 +		_setListAttr: function(v){
 +			// tags:
 +			//		private
 +			this._set('list', v); // needed for Firefox 4+ to prevent HTML5 mode
 +		},
 +
 +		closeDropDown: function(){
 +			// summary:
 +			//		Closes the drop down on this widget
 +			// tags:
 +			//		protected
 +
 +			this._throttleOpenClose();
 +			if(this.endHandler){
 +				this.disconnect(this.startHandler);
 +				this.disconnect(this.endHandler);
 +				this.disconnect(this.moveHandler);
 +				clearInterval(this.repositionTimer);
 +				this.repositionTimer = this.endHandler = null;
 +			}
 +			this.inherited(arguments);
 +			domAttr.remove(this.domNode, "aria-owns");
 +			domAttr.set(this.domNode, "aria-expanded", "false");
 +			popup.close(this.dropDown);
 +			this._opened = false;
 +
 +			// Remove disable attribute to make input element clickable after context menu closed
 +			if(has("windows-theme") && this.domNode.disabled){
 +				this.defer(function(){
 +					this.domNode.removeAttribute("disabled");
 +				}, 300);
 +			}
 +
 +		},
 +
 +		openDropDown: function(){
 +			// summary:
 +			//		Opens the dropdown for this widget. To be called only when this.dropDown
 +			//		has been created and is ready to display (that is, its data is loaded).
 +			// returns:
 +			//		Returns the value of popup.open().
 +			// tags:
 +			//		protected
 +
 +			var wasClosed = !this._opened;
 +			var dropDown = this.dropDown,
 +				ddNode = dropDown.domNode,
 +				aroundNode = this.domNode,
 +				self = this;
 +				
 +			domAttr.set(dropDown.domNode, "role", "listbox");
 +			domAttr.set(this.domNode, "aria-expanded", "true");
 +			if(dropDown.id){
 +				domAttr.set(this.domNode, "aria-owns", dropDown.id);
 +			}
 +
- 			if(has("touch")){
++			if(has("touch") && (!has("ios") || has("ios") < 8)){
 +				win.global.scrollBy(0, domGeometry.position(aroundNode, false).y); // don't call scrollIntoView since it messes up ScrollableView
 +			}
 +
 +			// TODO: isn't maxHeight dependent on the return value from popup.open(),
 +			// i.e., dependent on how much space is available (BK)
 +
 +			if(!this._preparedNode){
 +				this._preparedNode = true;
 +				// Check if we have explicitly set width and height on the dropdown widget dom node
 +				if(ddNode.style.width){
 +					this._explicitDDWidth = true;
 +				}
 +				if(ddNode.style.height){
 +					this._explicitDDHeight = true;
 +				}
 +			}
 +
 +			// Code for resizing dropdown (height limitation, or increasing width to match my width)
 +			var myStyle = {
 +				display: "",
 +				overflow: "hidden",
 +				visibility: "hidden"
 +			};
 +			if(!this._explicitDDWidth){
 +				myStyle.width = "";
 +			}
 +			if(!this._explicitDDHeight){
 +				myStyle.height = "";
 +			}
 +			domStyle.set(ddNode, myStyle);
 +
 +			// Figure out maximum height allowed (if there is a height restriction)
 +			var maxHeight = this.maxHeight;
 +			if(maxHeight == -1){
 +				// limit height to space available in viewport either above or below my domNode
 +				// (whichever side has more room)
 +				var viewport = windowUtils.getBox(),
 +					position = domGeometry.position(aroundNode, false);
 +				maxHeight = Math.floor(Math.max(position.y, viewport.h - (position.y + position.h)));
 +			}
 +
 +			// Attach dropDown to DOM and make make visibility:hidden rather than display:none
 +			// so we call startup() and also get the size
 +			popup.moveOffScreen(dropDown);
 +
 +			if(dropDown.startup && !dropDown._started){
 +				dropDown.startup(); // this has to be done after being added to the DOM
 +			}
 +			// Get size of drop down, and determine if vertical scroll bar needed
 +			var mb = domGeometry.position(this.dropDown.containerNode, false);
 +			var overHeight = (maxHeight && mb.h > maxHeight);
 +			if(overHeight){
 +				mb.h = maxHeight;
 +			}
 +
 +			// Adjust dropdown width to match or be larger than my width
 +			mb.w = Math.max(mb.w, aroundNode.offsetWidth);
 +			domGeometry.setMarginBox(ddNode, mb);
 +
 +			var retVal = popup.open({
 +				parent: this,
 +				popup: dropDown,
 +				around: aroundNode,
 +				orient: has("windows-theme") ? ["above"] : this.dropDownPosition,
 +				onExecute: function(){
 +					self.closeDropDown();
 +				},
 +				onCancel: function(){
 +					self.closeDropDown();
 +				},
 +				onClose: function(){
 +					self._opened = false;
 +				}
 +			});
 +			this._opened=true;
 +
 +			if(wasClosed){
 +				var	isGesture = false,
 +					skipReposition = false,
 +					active = false,
 +					wrapper = dropDown.domNode.parentNode,
 +					aroundNodePos = domGeometry.position(aroundNode, false),
 +					popupPos = domGeometry.position(wrapper, false),
 +					deltaX = popupPos.x - aroundNodePos.x,
 +					deltaY = popupPos.y - aroundNodePos.y,
 +					startX = -1, startY = -1;
 +
 +				// touchstart isn't really needed since touchmove implies touchstart, but
 +				// mousedown is needed since mousemove doesn't know if the left button is down or not
 +				this.startHandler = this.connect(win.doc.documentElement, touch.press,
 +					function(e){
 +						skipReposition = true;
 +						active = true;
 +						isGesture = false;
 +						startX = e.clientX;
 +						startY = e.clientY;
 +					}
 +				);
 +				this.moveHandler = this.connect(win.doc.documentElement, touch.move,
 +					function(e){
 +						skipReposition = true;
 +						if(e.touches){
 +							active = isGesture = true; // touchmove implies touchstart
 +						}else if(active && (e.clientX != startX || e.clientY != startY)){
 +							isGesture = true;
 +						}
 +					}
 +				);
 +				this.clickHandler = this.connect(dropDown.domNode, "onclick",
 +					function(){
 +						skipReposition = true;
 +						active = isGesture = false; // click implies no gesture movement
 +					}
 +				);
 +				this.endHandler = this.connect(win.doc.documentElement, touch.release,
 +					function(){
 +						this.defer(function(){ // allow onclick to go first
 +							skipReposition = true;
 +							if(!isGesture && active){ // if click without move, then close dropdown
 +								this.closeDropDown();
 +							}
 +							active = false;
 +						});
 +					}
 +				);
 +				this.repositionTimer = setInterval(lang.hitch(this, function(){
 +					if(skipReposition){ // don't reposition if busy
 +						skipReposition = false;
 +						return;
 +					}
 +					var	currentAroundNodePos = domGeometry.position(aroundNode, false),
 +						currentPopupPos = domGeometry.position(wrapper, false),
 +						currentDeltaX = currentPopupPos.x - currentAroundNodePos.x,
 +						currentDeltaY = currentPopupPos.y - currentAroundNodePos.y;
 +					// if the popup is no longer placed correctly, relocate it
 +					if(Math.abs(currentDeltaX - deltaX) >= 1 || Math.abs(currentDeltaY - deltaY) >= 1){ // Firefox plays with partial pixels
 +						domStyle.set(wrapper, { left: parseInt(domStyle.get(wrapper, "left")) + deltaX - currentDeltaX + 'px', top: parseInt(domStyle.get(wrapper, "top")) + deltaY - currentDeltaY + 'px' });
 +					}
 +				}), 50); // yield a short time to allow for consolidation for better CPU throughput
 +			}
 +
 +			// We need to disable input control in order to prevent opening the soft keyboard in IE
 +			if(has("windows-theme")){
 +				this.domNode.setAttribute("disabled", true);
 +			}
 +
 +			return retVal;
 +		},
 +
 +		postCreate: function(){
 +			this.inherited(arguments);
 +			this.connect(this.domNode, "onclick", "_onClick");
 +			domAttr.set(this.domNode, "role", "combobox");
 +			domAttr.set(this.domNode, "aria-expanded", "false");
 +		},
 +
 +		destroy: function(){
 +			if(this.repositionTimer){
 +				clearInterval(this.repositionTimer);
 +			}
 +			this.inherited(arguments);
 +		},
 +
 +		_onClick: function(/*Event*/ e){
 +			// tags:
 +			//		private
 +			
 +			// throttle clicks to prevent double click from doing double actions
 +			if(!this._throttleHandler){
 +				if(this.opened){
 +					this.closeDropDown();
 +				}else{
 +					this._startSearchAll();
 +				}
 +			}
 +		}
 +	});
 +});
diff --cc dojox/package.json
index 699c5b6,0000000..f0211ba
mode 100644,000000..100644
--- a/dojox/package.json
+++ b/dojox/package.json
@@@ -1,27 -1,0 +1,27 @@@
 +{
 +	"name": "dojox",
- 	"version":"1.10.2",
++	"version":"1.10.3",
 +	"directories": {
 +		"lib": "."
 +	},
 +	"main": "main",
 +	"dependencies": {
- 		"dojo":"1.10.2",
- 		"dijit":"1.10.2"
++		"dojo":"1.10.3",
++		"dijit":"1.10.3"
 +	},
 +	"description": "Dojo eXtensions, a rollup of many useful sub-projects and varying states of maturity – from very stable and robust, to alpha and experimental. See individual projects contain README files for details.",
 +	"licenses": [
 +		 {
 +				 "type": "AFLv2.1",
 +				 "url": "http://trac.dojotoolkit.org/browser/dojox/trunk/LICENSE#L43"
 +		 },
 +		 {
 +				 "type": "BSD",
 +				 "url": "http://trac.dojotoolkit.org/browser/dojox/trunk/LICENSE#L13"
 +		 }
 +	],
 +	"bugs": "http://bugs.dojotoolkit.org/",
 +	"keywords": ["JavaScript", "Dojo", "Toolkit", "DojoX"],
 +	"homepage": "http://dojotoolkit.org/",
 +	"dojoBuild": "dojox.profile.js"
 +}
diff --cc dojox/socket.js
index 0b575a5,0000000..d2654f2
mode 100644,000000..100644
--- a/dojox/socket.js
+++ b/dojox/socket.js
@@@ -1,215 -1,0 +1,228 @@@
- define("dojox/socket", ["dojo", "dojo/on", "dojo/Evented", "dojo/cookie", "dojo/_base/url"], function(dojo, on, Evented) {
++define([
++	"dojo/_base/array",
++	"dojo/_base/lang",
++	"dojo/_base/xhr",
++	"dojo/aspect",
++	"dojo/on",
++	"dojo/Evented",
++	"dojo/_base/url"
++], function(array, lang, xhr, aspect, on, Evented, dBaseUrl) {
 +
 +var WebSocket = window.WebSocket;
 +
- function Socket(/*dojo.__XhrArgs*/ argsOrUrl){
++var Socket = function(/*dojo.__XhrArgs*/ argsOrUrl){
 +	// summary:
 +	//		Provides a simple socket connection using WebSocket, or alternate
 +	//		communication mechanisms in legacy browsers for comet-style communication. This is based
 +	//		on the WebSocket API and returns an object that implements the WebSocket interface:
 +	//		http://dev.w3.org/html5/websockets/#websocket
 +	// description:
 +	//		Provides socket connections. This can be used with virtually any Comet protocol.
 +	// argsOrUrl:
 +	//		This uses the same arguments as the other I/O functions in Dojo, or a
 +	//		URL to connect to. The URL should be a relative URL in order to properly
 +	//		work with WebSockets (it can still be host relative, like //other-site.org/endpoint)
 +	// returns:
 +	//		An object that implements the WebSocket API
 +	// example:
- 	//		| dojo.require("dojox.socket");
- 	//		| var socket = dojox.socket({"url://comet-server/comet");
- 	//		| // we could also add auto-reconnect support
- 	//		| // now we can connect to standard HTML5 WebSocket-style events
- 	//		| dojo.connect(socket, "onmessage", function(event){
- 	//		|    var message = event.data;
- 	//		|    // do something with the message
- 	//		| });
- 	//		| // send something
- 	//		| socket.send("hi there");
- 	//		| whenDone(function(){
- 	//		|   socket.close();
++	//		| require(["dojox/socket", "dojo/aspect"], function(socket, aspect) {
++	//		|    var sock = socket({"url://comet-server/comet");
++	//		|    // we could also add auto-reconnect support
++	//		|    // now we can connect to standard HTML5 WebSocket-style events
++	//		|    aspect.after(socket, "onmessage", function(event){
++	//		|       var message = event.data;
++	//		|       // do something with the message
++	//		|    });
++	//		|    // send something
++	//		|    sock.send("hi there");
++	//		|    ...
 +	//		| });
 +	//		You can also use the Reconnect module:
- 	//		| dojo.require("dojox.socket");
- 	//		| dojo.require("dojox.socket.Reconnect");
- 	//		| var socket = dojox.socket({url:"/comet"});
- 	//		| // add auto-reconnect support
- 	//		| socket = dojox.socket.Reconnect(socket);
++	//		| require["dojox/socket", "dojox/socket/Reconnect"], function(dxSocket, reconnect){
++	//		|    var socket = dxSocket({url:"/comet"});
++	//		|    // add auto-reconnect support
++	//		|    socket = reconnect(socket);
 +	if(typeof argsOrUrl == "string"){
 +		argsOrUrl = {url: argsOrUrl};
 +	}
- 	return WebSocket ? dojox.socket.WebSocket(argsOrUrl, true) : dojox.socket.LongPoll(argsOrUrl);
++	return WebSocket ? Socket.WebSocket(argsOrUrl, true) : Socket.LongPoll(argsOrUrl);
 +};
- dojox.socket = Socket;
 +
 +Socket.WebSocket = function(args, fallback){
 +	// summary:
 +	//		A wrapper for WebSocket, than handles standard args and relative URLs
- 	var ws = new WebSocket(new dojo._Url(document.baseURI.replace(/^http/i,'ws'), args.url));
++	var baseURI = document.baseURI || window.location.href;
++	var ws = new WebSocket(new dBaseUrl(baseURI.replace(/^http/i,'ws'), args.url));
 +	ws.on = function(type, listener){
 +		ws.addEventListener(type, listener, true);
 +	};
 +	var opened;
- 	dojo.connect(ws, "onopen", function(event){
++	aspect.after(ws, "onopen", function(event){
 +		opened = true;
- 	});
- 	dojo.connect(ws, "onclose", function(event){
++	}, true);
++	aspect.after(ws, "onclose", function(event){
 +		if(opened){
 +			return;
 +		}
 +		if(fallback){
- 			Socket.replace(ws, dojox.socket.LongPoll(args), true);
++			Socket.replace(ws, Socket.LongPoll(args), true);
 +		}
- 	});
++	}, true);
 +	return ws;
 +};
++
 +Socket.replace = function(socket, newSocket, listenForOpen){
 +	// make the original socket a proxy for the new socket
- 	socket.send = dojo.hitch(newSocket, "send");
- 	socket.close = dojo.hitch(newSocket, "close");
- 	if(listenForOpen){
- 		proxyEvent("open");
- 	}
- 	// redirect the events as well
- 	dojo.forEach(["message", "close", "error"], proxyEvent);
- 	function proxyEvent(type){
++	socket.send = lang.hitch(newSocket, "send");
++	socket.close = lang.hitch(newSocket, "close");
++	var proxyEvent = function(type){
 +		(newSocket.addEventListener || newSocket.on).call(newSocket, type, function(event){
 +			on.emit(socket, event.type, event);
 +		}, true);
++	};
++
++	if(listenForOpen){
++		proxyEvent("open");
 +	}
++	// redirect the events as well
++	array.forEach(["message", "close", "error"], proxyEvent);
 +};
++
 +Socket.LongPoll = function(/*dojo.__XhrArgs*/ args){
 +	// summary:
 +	//		Provides a simple long-poll based comet-style socket/connection to a server and returns an
 +	//		object implementing the WebSocket interface:
 +	//		http://dev.w3.org/html5/websockets/#websocket
 +	// args:
 +	//		This uses the same arguments as the other I/O functions in Dojo, with this addition:
 +	//	args.interval:
 +	//		Indicates the amount of time (in milliseconds) after a response was received
 +	//		before another request is made. By default, a request is made immediately
 +	//		after getting a response. The interval can be increased to reduce load on the
 +	//		server or to do simple time-based polling where the server always responds
 +	//		immediately.
 +	//	args.transport:
 +	//		Provide an alternate transport like dojo.io.script.get
 +	// returns:
 +	//		An object that implements the WebSocket API
 +	// example:
 +	//		| dojo.require("dojox.socket.LongPoll");
 +	//		| var socket = dojox.socket.LongPoll({url:"/comet"});
 +	//		or:
 +	//		| dojo.require("dojox.socket.LongPoll");
 +	//		| dojox.socket.LongPoll.add();
 +	//		| var socket = dojox.socket({url:"/comet"});
 +
- var cancelled = false,
++	var cancelled = false,
 +		first = true,
 +		timeoutId,
 +		connections = [];
 +
 +	// create the socket object
++	var fire, connect;
 +	var socket = {
 +		send: function(data){
 +			// summary:
 +			//		Send some data using XHR or provided transport
- 			var sendArgs = dojo.delegate(args);
++			var sendArgs = lang.delegate(args);
 +			sendArgs.rawBody = data;
 +			clearTimeout(timeoutId);
 +			var deferred = first ? (first = false) || socket.firstRequest(sendArgs) :
 +				socket.transport(sendArgs);
 +			connections.push(deferred);
 +			deferred.then(function(response){
 +				// got a response
 +				socket.readyState = 1;
 +				// remove the current connection
- 				connections.splice(dojo.indexOf(connections, deferred), 1);
++				connections.splice(array.indexOf(connections, deferred), 1);
 +				// reconnect to listen for the next message if there are no active connections,
 +				// we queue it up in case one of the onmessage handlers has a message to send
 +				if(!connections.length){
 +					timeoutId = setTimeout(connect, args.interval);
 +				}
 +				if(response){
 +					// now send the message along to listeners
 +					fire("message", {data: response}, deferred);
 +				}
 +			}, function(error){
- 				connections.splice(dojo.indexOf(connections, deferred), 1);
++				connections.splice(array.indexOf(connections, deferred), 1);
 +				// an error occurred, fire the appropriate event listeners
 +				if(!cancelled){
 +					fire("error", {error:error}, deferred);
 +					if(!connections.length){
 +						socket.readyState = 3;
 +						fire("close", {wasClean:false}, deferred);
 +					}
 +				}
 +			});
 +			return deferred;
 +		},
 +		close: function(){
 +			// summary:
 +			//		Close the connection
 +			socket.readyState = 2;
 +			cancelled = true;
- 			for(var i = 0; i < connections.length; i++){
++			var i;
++			for(i = 0; i < connections.length; i++){
 +				connections[i].cancel();
 +			}
 +			socket.readyState = 3;
 +			fire("close", {wasClean:true});
 +		},
- 		transport: args.transport || dojo.xhrPost,
++		transport: args.transport || xhr.post,
 +		args: args,
 +		url: args.url,
 +		readyState: 0,
 +		CONNECTING: 0,
 +		OPEN: 1,
 +		CLOSING: 2,
 +		CLOSED: 3,
 +		on: Evented.prototype.on,
 +		firstRequest: function(args){
 +			// summary:
 +			//		This allows for special handling for the first request. This is useful for
 +			//		providing information to disambiguate between the first request and
 +			//		subsequent long-poll requests so the server can properly setup a
 +			//		connection on the first connection or reject a request for an expired
 +			//		connection if the request is not expecting to be the first for a connection.
 +			//		This method can be overriden. The default behavior is to include a Pragma
 +			//		header with a value of "start-long-poll"
 +			var headers = (args.headers || (args.headers = {}));
 +			headers.Pragma = "start-long-poll";
 +			try{
 +				return this.transport(args);
 +			}finally{
 +				// cleanup the header so it is not used on subsequent requests
 +				delete headers.Pragma;
 +			}
 +		}
 +	};
- 	function connect(){
++	fire = function(type, object, deferred){
++		if(socket["on" + type]){
++			object.ioArgs = deferred && deferred.ioArgs;
++			object.type = type;
++			on.emit(socket, type, object);
++		}
++	};
++	connect = function(){
 +		if(socket.readyState == 0){
 +			// we fire the open event now because we really don't know when the "socket"
 +			// is truly open, and this gives us a to do a send() and get it included in the
 +			// HTTP request
 +			fire("open",{});
 +		}
 +		// make the long-poll connection, to wait for response from the server
 +		if(!connections.length){
 +			socket.send();
 +		}
- 	}
- 	function fire(type, object, deferred){
- 		if(socket["on" + type]){
- 			object.ioArgs = deferred && deferred.ioArgs;
- 			object.type = type;
- 			on.emit(socket, type, object);
- 		}
- 	}
++	};
 +	// provide an alias for Dojo's connect method
 +	socket.connect = socket.on;
 +	// do the initial connection
 +	setTimeout(connect);
 +	return socket;
 +};
++
 +return Socket;
- });
++
++});
diff --cc dojox/socket/Reconnect.js
index 6ae4ad2,0000000..440a32a
mode 100644,000000..100644
--- a/dojox/socket/Reconnect.js
+++ b/dojox/socket/Reconnect.js
@@@ -1,54 -1,0 +1,59 @@@
- dojo.provide("dojox.socket.Reconnect");
++define([
++	"dojox/socket",
++	"dojo/aspect"
++], function(dxSocket, aspect) {
 +
- dojox.socket.Reconnect = function(socket, options){
- 	// summary:
- 	//		Provides auto-reconnection to a websocket after it has been closed
- 	// socket:
- 	//		Socket to add reconnection support to.
- 	// returns:
- 	//		An object that implements the WebSocket API
- 	// example:
- 	//		You can use the Reconnect module:
- 	//		| dojo.require("dojox.socket");
- 	//		| dojo.require("dojox.socket.Reconnect");
- 	//		| var socket = dojox.socket({url:"/comet"});
- 	//		| // add auto-reconnect support
- 	//		| socket = dojox.socket.Reconnect(socket);
- 	options = options || {};
- 	var reconnectTime = options.reconnectTime || 10000;
- 	
- 	var connectHandle = dojo.connect(socket, "onclose", function(event){
- 		clearTimeout(checkForOpen);
- 		if(!event.wasClean){
- 			socket.disconnected(function(){
- 				dojox.socket.replace(socket, newSocket = socket.reconnect());
- 			});
++	dxSocket.Reconnect = function(socket, options){
++		// summary:
++		//		Provides auto-reconnection to a websocket after it has been closed
++		// socket:
++		//		Socket to add reconnection support to.
++		// returns:
++		//		An object that implements the WebSocket API
++		// example:
++		//		You can use the Reconnect module:
++		//		| require["dojox/socket", "dojox/socket/Reconnect"], function(dxSocket, reconnect){
++		//		|    var socket = dxSocket({url:"/comet"});
++		//		|    // add auto-reconnect support
++		//		|    socket = reconnect(socket);
++		var reconnectTime = options.reconnectTime || 10000;
++		var checkForOpen, newSocket;
++		options = options || {};
++
++		aspect.after(socket, "onclose", function(event){
++			clearTimeout(checkForOpen);
++			if(!event.wasClean){
++				socket.disconnected(function(){
++					dxSocket.replace(socket, newSocket = socket.reconnect());
++				});
++			}
++		}, true);
++		if(!socket.disconnected){
++			// add a default impl if it doesn't exist
++			socket.disconnected = function(reconnect){
++				setTimeout(function(){
++					reconnect();
++					checkForOpen = setTimeout(function(){
++						//reset the backoff
++						if(newSocket.readyState < 2){
++							reconnectTime = options.reconnectTime || 10000;
++						}
++					}, 10000);
++				}, reconnectTime);
++				// backoff each time
++				reconnectTime *= options.backoffRate || 2;
++			};
++		}
++		if(!socket.reconnect){
++			// add a default impl if it doesn't exist
++			socket.reconnect = function(){
++				return socket.args ?
++					dxSocket.LongPoll(socket.args) :
++					dxSocket.WebSocket({url: socket.URL || socket.url}); // different cases for different impls
++			};
 +		}
- 	});
- 	var checkForOpen, newSocket;
- 	if(!socket.disconnected){
- 		// add a default impl if it doesn't exist
- 		socket.disconnected = function(reconnect){
- 			setTimeout(function(){
- 				reconnect();
- 				checkForOpen = setTimeout(function(){
- 					//reset the backoff
- 					if(newSocket.readyState < 2){
- 						reconnectTime = options.reconnectTime || 10000;
- 					}
- 				}, 10000);
- 			}, reconnectTime);
- 			// backoff each time
- 			reconnectTime *= options.backoffRate || 2;
- 		};
- 	}
- 	if(!socket.reconnect){
- 		// add a default impl if it doesn't exist
- 		socket.reconnect = function(){
- 			return socket.args ?
- 				dojox.socket.LongPoll(socket.args) :
- 				dojox.socket.WebSocket({url: socket.URL || socket.url}); // different cases for different impls
- 		};
- 	}
- 	return socket;
- };
++		return socket;
++	};
++
++	return dxSocket.Reconnect;
++});
diff --cc dojox/storage/Storage.swf
index 0000000,0000000..9a09f70
new file mode 100644
Binary files differ
diff --cc util/build/transforms/optimizer/uglify.js
index 0450c52,0000000..9fb14e8
mode 100644,000000..100644
--- a/util/build/transforms/optimizer/uglify.js
+++ b/util/build/transforms/optimizer/uglify.js
@@@ -1,90 -1,0 +1,95 @@@
 +/*jshint node:true */
 +define([
 +	"../../buildControl",
 +	"../../fs",
 +	"./stripConsole",
 +	"dojo/_base/lang",
 +	"./uglify_worker",
++	"../writeAmd",
 +	"require"
- ], function(bc, fs, stripConsole, lang, uglify, require){
++], function(bc, fs, stripConsole, lang, uglify, writeAmd, require){
 +	if(!uglify){
 +		throw new Error("Unknown host environment: only nodejs is supported by uglify optimizer.");
 +	}
 +
 +	if(bc.maxOptimizationProcesses){
 +		var nodeReq = require.nodeRequire,
 +			processes = [],
 +			fork = nodeReq("child_process").fork,
 +			proc, jobs = {}, currentIndex = 0, queue = [],
 +			worker = require.toUrl("./uglify_worker.js");
 +		if(bc.maxOptimizationProcesses<0){
 +			bc.maxOptimizationProcesses = nodeReq('os').cpus().length;
 +		}
 +		for(var i = 0; i < bc.maxOptimizationProcesses; i++){
 +			proc = fork(worker);
 +			proc.on("message", function(data){
 +				if(jobs[data.dest]){
 +					var func = jobs[data.dest];
 +					delete jobs[data.dest];
 +					func(data);
 +				}
 +			});
 +			processes.push(proc);
 +		}
 +	}
 +
 +	return function(resource, text, copyright, optimizeSwitch, callback){
 +		copyright = copyright || "";
 +
 +		var options = bc.optimizeOptions || {};
 +
- 		options.filename = resource.src;
++		options.filename = writeAmd.getDestFilename(resource).split("/").pop();
 +
 +		if(optimizeSwitch.indexOf(".keeplines") > -1){
 +			options.gen_options = options.gen_options || {};
 +			options.gen_options.beautify = true;
 +			options.gen_options.indent_level = 0; //don't indent, just keep new lines
 +		}
 +		if(optimizeSwitch.indexOf(".comments") > -1){
 +			throw new Error("'comments' option is not supported by uglify optimizer.");
 +		}
 +
 +		var handleResult = function(data){
 +			try{
 +				if(data.error){
 +					throw data.error;
 +				}
 +				var result = copyright + "//>>built" + bc.newline + data.text;
 +
 +				fs.writeFile(resource.dest, result, resource.encoding, function(err){
 +					if(err){
 +						bc.log("optimizeFailedWrite", ["filename", resource.dest]);
 +					}
 +					callback(resource, err);
 +				});
 +			}catch(e){
 +				bc.log("optimizeFailed", ["module identifier", resource.mid, "exception", e + ""]);
 +				callback(resource, 0);
 +			}
 +		};
 +
 +		if(bc.maxOptimizationProcesses){
 +			jobs[resource.dest] = handleResult;
- 			processes[currentIndex].send({text: stripConsole(text),
- 				options: options, src: resource.src, dest: resource.dest});
++			processes[currentIndex].send({
++				text: stripConsole(text),
++				options: options,
++				dest: resource.dest,
++				useSourceMaps: bc.useSourceMaps
++			});
 +			currentIndex = (currentIndex+1) % processes.length;
 +		} else {
 +			process.nextTick(function(){
 +				var o = {};
 +				try{
- 					o.text = uglify(stripConsole(text), options);
++					o.text = uglify(stripConsole(text), options, resource.dest, bc.useSourceMaps);
 +				}catch(e){
 +					o.error = e;
 +				}
 +				handleResult(o);
 +			});
 +		}
 +
 +		return callback;
 +	};
 +});
diff --cc util/build/transforms/optimizer/uglify_worker.js
index 06a3325,0000000..ff21275
mode 100644,000000..100644
--- a/util/build/transforms/optimizer/uglify_worker.js
+++ b/util/build/transforms/optimizer/uglify_worker.js
@@@ -1,50 -1,0 +1,67 @@@
- function factory(uglify){
++function factory(uglify, fs){
 +	if(!uglify){
 +		throw new Error("Unknown host environment: only nodejs is supported by uglify optimizer.");
 +	}
 +	if(uglify.minify){
 +		//uglify2, provide a uglify-1 compatible uglify function
 +		var UglifyJS = uglify;
- 		uglify = function(code, options){
++		uglify = function(code, options, dest, useSourceMaps){
 +			//parse
 +			var ast = UglifyJS.parse(code, options);
 +			ast.figure_out_scope();
 +
 +			//by default suppress warnings from uglify2
 +			var compress_options = options.compress_options || {};
 +			if(!('warnings' in compress_options)){
 +				compress_options.warnings = false;
 +			}
 +			var compressor = UglifyJS.Compressor(compress_options);
 +			compressed_ast = ast.transform(compressor);
 +			compressed_ast.figure_out_scope();
 +
 +			//mangle
 +			compressed_ast.compute_char_frequency();
 +			compressed_ast.mangle_names();
 +
- 			return compressed_ast.print_to_string(options.gen_options);
++			var gen_options = options.gen_options || {};
++			if (useSourceMaps) {
++				var source_map = gen_options.source_map || {};
++				source_map.file = options.filename.split("/").pop();
++				// account for the //>> built line
++				source_map.dest_line_diff = 1;
++				gen_options.source_map = UglifyJS.SourceMap(source_map);
++			}
++
++			var output = compressed_ast.print_to_string(gen_options);
++
++			if (useSourceMaps) {
++				output += "//# sourceMappingURL=" + dest.split("/").pop() + ".map";
++				fs.writeFile(dest + ".map", gen_options.source_map.toString(), "utf-8");
++			}
++
++			return output;
 +		}
 +	}
 +	return uglify;
 +}
 +
 +if(global.define){
 +	//loaded by dojo AMD loader
- 	define(["dojo/has!host-node?dojo/node!uglify-js:"], factory);
++	define(["dojo/has!host-node?dojo/node!uglify-js:", "../../fs"], factory);
 +}else{
 +	//loaded in a node sub process
 +	try{
 +		var uglify = require("uglify-js");
++		var fs = require("fs");
 +	}catch(e){}
- 	uglify = factory(uglify);
++	uglify = factory(uglify, fs);
 +	process.on("message", function(data){
 +		var result = "", error = "";
 +		try{
- 			var result = uglify(data.text, data.options);
++			var result = uglify(data.text, data.options, data.dest, data.useSourceMaps);
 +		}catch(e){
 +			error = e.toString() + " " + e.stack;
 +		}
 +		process.send({text: result, dest: data.dest, error: error});
 +	});
 +}
diff --cc util/build/version.js
index d7e3ab3,0000000..07310a7
mode 100644,000000..100644
--- a/util/build/version.js
+++ b/util/build/version.js
@@@ -1,13 -1,0 +1,13 @@@
 +define([], function(){
 +	var
- 		rev = "$Rev: 00884aa $".match(/[0-9a-f]{7,}/),
++		rev = "$Rev: 920a75b $".match(/[0-9a-f]{7,}/),
 +		version= {
- 			major: 1, minor: 10, patch: 2, flag: "",
++			major: 1, minor: 10, patch: 3, flag: "",
 +			revision: rev ? rev[0] : NaN,
 +			toString: function(){
 +				var v= version;
 +				return v.major + "." + v.minor + "." + v.patch + v.flag + " (" + v.revision + ")";
 +			}
 +		};
 +	return version;
 +});
diff --cc util/doh/_nodeRunner.js
index c372b15,0000000..3007a5a
mode 100644,000000..100644
--- a/util/doh/_nodeRunner.js
+++ b/util/doh/_nodeRunner.js
@@@ -1,40 -1,0 +1,40 @@@
 +define(["doh/runner", "require", "dojo/_base/config"], function(doh, require, config){
 +	/*=====
 +	return {
 +		// summary:
 +		//		Module for running DOH tests in node (as opposed to a browser).
 +		//		Augments return value from doh/runner.
 +	};
 +	=====*/
 +
 +	doh.debug= console.log;
 +	doh.error= console.log;
 +
 +	// Override the doh._report method to make it quit with an
 +	// appropriate exit code in case of test failures.
 +	var oldReport = doh._report;
 +	doh._report = function(){
 +		oldReport.apply(doh, arguments);
 +		if(this._failureCount > 0 || this._errorCount > 0){
 +			process.exit(1);
 +		}
 +	};
 +
 +	console.log("\n"+doh._line);
- 	console.log("The Dojo Unit Test Harness, $Rev: 00884aa $");
++	console.log("The Dojo Unit Test Harness, $Rev: 920a75b $");
 +	console.log("Copyright (c) 2011, The Dojo Foundation, All Rights Reserved");
 +	console.log("Running with node.js");
 +	for (var tests= [], args= config["commandLineArgs"], i= 0, arg; i<args.length; i++) {
 +		arg= args[i];
 +		if (arg.length==2 && arg[0]=="test") {
 +			var test= arg[1];
 +			console.log("loading test " + test);
 +			tests.push(test);
 +		}
 +	}
 +	console.log(doh._line, "\n");
 +
 +	require(tests, function() {
 +		doh.run();
 +	});
 +});
diff --cc util/doh/_rhinoRunner.js
index b729e82,0000000..68574ad
mode 100644,000000..100644
--- a/util/doh/_rhinoRunner.js
+++ b/util/doh/_rhinoRunner.js
@@@ -1,39 -1,0 +1,39 @@@
 +define(["doh/runner", "require", "dojo/_base/config"], function(doh, require, config){
 +	/*=====
 +	 return {
 +	 // summary:
 +	 //		Module for running DOH tests in rhino (as opposed to a browser).
 +	 //		Augments return value from doh/runner.
 +	 };
 +	 =====*/
 +
 +	doh.debug= print;
 +	doh.error= print;
 +
 +	// Override the doh._report method to make it quit with an
 +	// appropriate exit code in case of test failures.
 +	var oldReport = doh._report;
 +	doh._report = function(){
 +		oldReport.apply(doh, arguments);
 +		if(this._failureCount > 0 || this._errorCount > 0){
 +			quit(1);
 +		}
 +	};
 +
 +	print("\n"+doh._line);
- 	print("The Dojo Unit Test Harness, $Rev: 00884aa $");
++	print("The Dojo Unit Test Harness, $Rev: 920a75b $");
 +	print("Copyright (c) 2011, The Dojo Foundation, All Rights Reserved");
 +	for (var tests= [], args= config["commandLineArgs"], i= 0, arg; i<args.length; i++) {
 +		arg= (args[i]+"").split("=");
 +		if (arg.length==2 && arg[0]=="test") {
 +			var test= arg[1];
 +			print("loading test " + test);
 +			tests.push(test);
 +		}
 +	}
 +	print(doh._line, "\n");
 +
 +	require(tests, function() {
 +		doh.run();
 +	});
 +});
diff --cc util/doh/mobileRunner.html
index 349a139,0000000..2a27e89
mode 100644,000000..100644
--- a/util/doh/mobileRunner.html
+++ b/util/doh/mobileRunner.html
@@@ -1,88 -1,0 +1,88 @@@
 +<!DOCTYPE html>
 +<html style="height:100%;">
 +	<head>
- 		<title>The Dojo Unit Test Harness, $Rev: 00884aa $</title>
++		<title>The Dojo Unit Test Harness, $Rev: 920a75b $</title>
 +		<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
 +		<meta name="apple-mobile-web-app-capable" content="yes">
 +		<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
 +		<script type="text/javascript">
 +			// workaround for bug in Safari 3.	See #7189
 +			if (/3[\.0-9]+ Safari/.test(navigator.appVersion))
 +			{
 +				window.console = {
 +					origConsole: window.console,
 +						log: function(s){
 +						this.origConsole.log(s);
 +					},
 +					info: function(s){
 +						this.origConsole.info(s);
 +					},
 +					error: function(s){
 +						this.origConsole.error(s);
 +					},
 +					warn: function(s){
 +						this.origConsole.warn(s);
 +					}
 +				};
 +			}
 +		</script>
 +
 +		<script type="text/javascript" src="_parseURLargs.js"></script>
 +	
 +		<style type="text/css">
 +			@import "../../dojo/resources/dojo.css";
 +
 +			#testLayout {
 +				position: relative;
 +				left: 0px;
 +				top: 0px;
 +				width: 100%;
 +				height: 100%;
 +				border: 1px solid black;
 +				border: 0px;
 +			}
 +
 +			.tabBody {
 +				margin: 0px;
 +				padding: 0px;
 +				/*
 +				border: 1px solid black;
 +				*/
 +				background-color: #DEDEDE;
 +				border: 0px;
 +				width: 100%;
 +				height: 100%;
 +				position: absolute;
 +				left: 0px; 
 +				top: 0px;
 +				overflow: auto;
 +			}
 +
 +			#logBody {
 +				padding-left: 5px;
 +				padding-top: 5px;
 +				font-family: Monaco, monospace;
 +				font-size: 11px;
 +				white-space: pre;
 +			}
 +
 +
 +		</style>
 +	</head>
 +	<body style="height: 100%;">
 +		<div style="position: relative; width: 100%; height: 100%; top: 0px; left: 0px;">
 +			<div class="tabBody"
 +				style="z-index: 1;">
 +				<pre id="logBody"></pre>
 +				<div id="perfTestsBody" style="background-color: white;"></div>
 +			</div>
 +			<iframe id="testBody" class="tabBody"
 +				style="z-index: -1;"></iframe>
 +			<!--
 +				src="http://redesign.dojotoolkit.org"></iframe>
 +			-->
 +		</div>
 +		<span id="hiddenAudio"></span>
 +	</body>
 +</html>
 +
diff --cc util/doh/package.json
index 1d6eae4,0000000..b945259
mode 100644,000000..100644
--- a/util/doh/package.json
+++ b/util/doh/package.json
@@@ -1,23 -1,0 +1,23 @@@
 +{
 +	"name": "doh",
- 	"version":"1.10.2",
++	"version":"1.10.3",
 +	"directories": {
 +		"lib": "."
 +	},
 +	"main": "main",
 +	"description": "DOH is a unit test framework developed by the Dojo Toolkit Community.",
 +	"licenses": [
 +		 {
 +				 "type": "AFLv2.1",
 +				 "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L43"
 +		 },
 +		 {
 +				 "type": "BSD",
 +				 "url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L13"
 +		 }
 +	],
 +	"bugs": "http://bugs.dojotoolkit.org/",
 +	"keywords": ["JavaScript", "Dojo", "Toolkit", "DOH"],
 +	"homepage": "http://dojotoolkit.org/",
 +	"dojoBuild": "doh.profile.js"
 +}
diff --cc util/doh/runner.html
index 2fd95eb,0000000..5b06bef
mode 100644,000000..100644
--- a/util/doh/runner.html
+++ b/util/doh/runner.html
@@@ -1,251 -1,0 +1,251 @@@
 +<!DOCTYPE html>
 +<html style="height:100%;">
 +	<head>
- 		<title>The Dojo Unit Test Harness, $Rev: 00884aa $</title>
++		<title>The Dojo Unit Test Harness, $Rev: 920a75b $</title>
 +
 +		<script type="text/javascript" src="_parseURLargs.js"></script>
 +	
 +		<style type="text/css">
 +			@import "../../dojo/resources/dojo.css";
 +			/*
 +			body {
 +				margin: 0px;
 +				padding: 0px;
 +				font-size: 13px;
 +				color: #292929;
 +				font-family: Myriad, Lucida Grande, Bitstream Vera Sans, Arial, Helvetica, sans-serif;
 +				*font-size: small;
 +				*font: x-small;
 +			}
 +
 +			th, td {
 +				font-size: 13px;
 +				color: #292929;
 +				font-family: Myriad, Lucida Grande, Bitstream Vera Sans, Arial, Helvetica, sans-serif;
 +				font-weight: normal;
 +			}
 +
 +			* body {
 +				line-height: 1.25em;
 +			}
 +			
 +			table {
 +				border-collapse: collapse;
 +			}
 +			*/
 +
 +			#testLayout {
 +				position: relative;
 +				left: 0px;
 +				top: 0px;
 +				width: 100%;
 +				height: 100%;
 +				border: 1px solid black;
 +				border: 0px;
 +			}
 +
 +			.tabBody {
 +				margin: 0px;
 +				padding: 0px;
 +				/*
 +				border: 1px solid black;
 +				*/
 +				background-color: #DEDEDE;
 +				border: 0px;
 +				width: 100%;
 +				height: 100%;
 +				position: absolute;
 +				left: 0px; 
 +				top: 0px;
 +				overflow: auto;
 +			}
 +
 +			#logBody {
 +				padding-left: 5px;
 +				padding-top: 5px;
 +				font-family: Monaco, monospace;
 +				font-size: 11px;
 +				white-space: pre;
 +			}
 +
 +			#progressOuter {
 +				background-color: #e9e9e9;
 +				background-image: -webkit-linear-gradient(rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
 +				background-image: -moz-linear-gradient(rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
 +				background-image: -ms-linear-gradient(rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
 +				background-image: -o-linear-gradient(rgba(255,255,255,1) 0%, rgba(255,255,255,0) 100%);
 +
 +				height: 1em;
 +				/*the following trick is necessary to prevent IE from wrapping the last piece of progress bar into a new line*/
 +				_margin:1px;
 +				_padding: -1px;
 +			}
 +
 +			#progressOuter .success, #progressOuter .failure{
 +				float: left;
 +				height: 1em;
 +			}
 +
 +			#play, #pause {
 +				font-family: Arial;
 +				font-size: 1.4em;
 +				border: 1px solid #DEDEDE;
 +				cursor: pointer;
 +				padding-right: 0.5em;
 +			}
 +
 +			.header {
 +				border: 1px solid #DEDEDE;
 +			}
 +
 +			button.tab {
 +				border-width: 1px 1px 0px 1px;
 +				border-style: solid;
 +				border-color: #DEDEDE;
 +				margin-right: 5px;
 +			}
 +
 +			#testListContainer {
 +				/*
 +				border: 1px solid black;
 +				*/
 +				position: relative;
 +				height: 99%;
 +				width: 100%;
 +				overflow: auto;
 +			}
 +
 +			#testList {
 +				border-collapse: collapse;
 +				position: absolute;
 +				left: 0px;
 +				width: 100%;
 +			}
 +
 +			#testList td {
 +				border-bottom: 1px solid #DEDEDE;
 +				border-right : 1px solid #DEDEDE;
 +				padding: 3px;
 +			}
 +
 +			#testListHeader th {
 +				border-bottom: 1px solid #DEDEDE;
 +				border-right : 1px solid #DEDEDE;
 +				padding: 3px;
 +				font-weight: bolder;
 +				font-style: italic;
 +			}
 +			
 +			#testList tfoot {
 +				font-weight: bold;
 +			}
 +
 +			#toggleButtons {
 +				float: left;
 +				background-color: #DEDEDE;
 +			}
 +
 +			div.testGroupName {
 +				position: absolute;
 +			}
 +
 +			.inProgress {
 +				background-color: #85afde;
 +			}
 +
 +			.success {
 +				background-color: #7cdea7;
 +			}
 +
 +			.failure {
 +				background-color: #de827b;
 +			}
 +		</style>
 +	</head>
 +	<body style="height: 100%;">
 +		<table id="testLayout" cellpadding="0" cellspacing="0" style="margin: 0;">
 +			<tr valign="top" height="40">
 +				<td colspan="2" id="logoBar">
 +					<h3 style="margin: 5px 5px 0px 5px; float: left;">D.O.H.: The Dojo Objective Harness</h3>
 +					<img src="small_logo.png" height="40" style="margin: 0px 5px 0px 5px; float: right;">
 +					<span style="margin: 10px 5px 0px 5px; float: right;">
 +						<input type="checkbox" id="audio" name="audio">
 +						<label for="audio">sounds?</label>
 +					</span>
 +				</td>
 +			</tr>
 +			<tr valign="top" height="10">
 +				<td colspan="2"><div id="progressOuter" onclick="doh._jumpToSuite(arguments[0]);"></div></td>
 +			</tr>
 +			<tr valign="top" height="30">
 +				<td width="30%" class="header">
 +					<span id="toggleButtons" onclick="doh.togglePaused();">
 +						<button id="play">►</button>
 +						<button id="pause" style="display: none;">║</button>
 +					</span>
 +					<span id="runningStatus">
 +						<span id="pausedMsg">Stopped</span>
 +						<span id="playingMsg" style="display: none;">Tests Running</span>
 +					</span>
 +				</td>
 +				<td width="*" class="header" valign="bottom">
 +					<button class="tab" onclick="doh.showTestPage();">Test Page</button>
 +					<button class="tab" onclick="doh.showLogPage();">Log</button>
 +										<button class="tab" onclick="doh.showPerfTestsPage();">Performance Tests Results</button>
 +				</td>
 +			</tr>
 +			<tr valign="top" style="border: 0; padding: 0; margin: 0;">
 +				<td style="border: 0; padding: 0; margin: 0; height:100%;">
 +					<div id="testListContainer">
 +						<table cellpadding="0" cellspacing="0" border="0"
 +							width="100%" id="testList" style="margin: 0;" onclick="doh._jumpToLog(arguments[0]);">
 +							<thead>
 +								<tr id="testListHeader" style="border: 0; padding: 0; margin: 0;" >
 +									<th> </th>
 +									<th width="20">
 +										<input type="checkbox" checked 
 +											onclick="doh.toggleRunAll();">
 +									</th>
 +									<th width="*" style="text-align: left;">test</th>
 +									<th width="50">time</th>
 +								</tr>
 +							</thead>
 +							<tbody valign="top">
 +								<tr id="groupTemplate" style="display: none;">
 +									<td style="font-family: Arial; width: 15px;">►</td>
 +									<td>
 +										<input type="checkbox" checked>
 +									</td>
 +									<td>group name</td>
 +									<td>10ms</td>
 +								</tr>
 +								<tr id="testTemplate" style="display: none;">
 +									<td> </td>
 +									<td> </td>
 +									<td style="padding-left: 20px;">test name</td>
 +									<td>10ms</td>
 +								</tr>
 +							</tbody>
 +						</table>
 +					</div>
 +				</td>
 +				<td style="height: 100%;">
 +					<div style="position: relative; width: 100%; height: 100%; top: 0px; left: 0px;">
 +						<div class="tabBody"
 +							style="z-index: 1;">
 +							<pre id="logBody"></pre>
 +							<div id="perfTestsBody" style="background-color: white;"></div>
 +						</div>
 +						<iframe id="testBody" class="tabBody"
 +							style="z-index: -1;"></iframe>
 +						<!--
 +							src="http://redesign.dojotoolkit.org"></iframe>
 +						-->
 +					</div>
 +				</td>
 +			</tr>
 +		</table>
 +		<span id="hiddenAudio"></span>
 +	</body>
 +</html>
 +
diff --cc util/package.json
index a81bfed,0000000..a8865b7
mode 100644,000000..100644
--- a/util/package.json
+++ b/util/package.json
@@@ -1,20 -1,0 +1,20 @@@
 +{
 +	"name": "dojo-util",
- 	"version":"1.10.2",
++	"version":"1.10.3",
 +	"licenses": [
 +		{
 +			"type": "AFLv2.1",
 +			"url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L43"
 +		},
 +		{
 +			"type": "BSD",
 +			"url": "http://trac.dojotoolkit.org/browser/dojo/trunk/LICENSE#L13"
 +		}
 +	],
 +	"description": "Dojo utilities including build system for optimizing JavaScript application performance, and DOH testing tool",
 +	"bugs": {
 +		"url": "http://bugs.dojotoolkit.org/"
 +	},
 +	"keywords": ["JavaScript", "Dojo", "Toolkit", "util"],
 +	"homepage": "http://dojotoolkit.org/"
 +}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/dojo.git



More information about the Pkg-javascript-commits mailing list