diff options
author | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
---|---|---|
committer | Andrew Dolgov <[email protected]> | 2011-11-08 20:40:44 +0400 |
commit | 81bea17aefb26859f825b9293c7c99192874806e (patch) | |
tree | fb244408ca271affa2899adb634788802c9a89d8 /lib/dijit/form | |
parent | 870a70e109ac9e80a88047044530de53d0404ec7 (diff) |
upgrade Dojo to 1.6.1
Diffstat (limited to 'lib/dijit/form')
42 files changed, 6581 insertions, 2856 deletions
diff --git a/lib/dijit/form/Button.js b/lib/dijit/form/Button.js index 741062022..77846776f 100644 --- a/lib/dijit/form/Button.js +++ b/lib/dijit/form/Button.js @@ -1,127 +1,360 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.Button"]){ -dojo._hasResource["dijit.form.Button"]=true; +if(!dojo._hasResource["dijit.form.Button"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Button"] = true; dojo.provide("dijit.form.Button"); dojo.require("dijit.form._FormWidget"); dojo.require("dijit._Container"); dojo.require("dijit._HasDropDown"); -dojo.declare("dijit.form.Button",dijit.form._FormWidget,{label:"",showLabel:true,iconClass:"",type:"button",baseClass:"dijitButton",templateString:dojo.cache("dijit.form","templates/Button.html","<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),attributeMap:dojo.delegate(dijit.form._FormWidget.prototype.attributeMap,{value:"valueNode",iconClass:{node:"iconNode",type:"class"}}),_onClick:function(e){ -if(this.disabled){ -return false; -} -this._clicked(); -return this.onClick(e); -},_onButtonClick:function(e){ -if(this._onClick(e)===false){ -e.preventDefault(); -}else{ -if(this.type=="submit"&&!(this.valueNode||this.focusNode).form){ -for(var _1=this.domNode;_1.parentNode;_1=_1.parentNode){ -var _2=dijit.byNode(_1); -if(_2&&typeof _2._onSubmit=="function"){ -_2._onSubmit(e); -break; -} -} -}else{ -if(this.valueNode){ -this.valueNode.click(); -e.preventDefault(); -} -} -} -},_fillContent:function(_3){ -if(_3&&(!this.params||!("label" in this.params))){ -this.set("label",_3.innerHTML); -} -},postCreate:function(){ -dojo.setSelectable(this.focusNode,false); -this.inherited(arguments); -},_setShowLabelAttr:function(_4){ -if(this.containerNode){ -dojo.toggleClass(this.containerNode,"dijitDisplayNone",!_4); -} -this.showLabel=_4; -},onClick:function(e){ -return true; -},_clicked:function(e){ -},setLabel:function(_5){ -dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.","","2.0"); -this.set("label",_5); -},_setLabelAttr:function(_6){ -this.containerNode.innerHTML=this.label=_6; -if(this.showLabel==false&&!this.params.title){ -this.titleNode.title=dojo.trim(this.containerNode.innerText||this.containerNode.textContent||""); -} -}}); -dojo.declare("dijit.form.DropDownButton",[dijit.form.Button,dijit._Container,dijit._HasDropDown],{baseClass:"dijitDropDownButton",templateString:dojo.cache("dijit.form","templates/DropDownButton.html","<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true,labelledby-${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"),_fillContent:function(){ -if(this.srcNodeRef){ -var _7=dojo.query("*",this.srcNodeRef); -dijit.form.DropDownButton.superclass._fillContent.call(this,_7[0]); -this.dropDownContainer=this.srcNodeRef; -} -},startup:function(){ -if(this._started){ -return; -} -if(!this.dropDown){ -var _8=dojo.query("[widgetId]",this.dropDownContainer)[0]; -this.dropDown=dijit.byNode(_8); -delete this.dropDownContainer; -} -dijit.popup.moveOffScreen(this.dropDown.domNode); -this.inherited(arguments); -},isLoaded:function(){ -var _9=this.dropDown; -return (!_9.href||_9.isLoaded); -},loadDropDown:function(){ -var _a=this.dropDown; -if(!_a){ -return; -} -if(!this.isLoaded()){ -var _b=dojo.connect(_a,"onLoad",this,function(){ -dojo.disconnect(_b); -this.openDropDown(); + + +dojo.declare("dijit.form.Button", + dijit.form._FormWidget, + { + // summary: + // Basically the same thing as a normal HTML button, but with special styling. + // description: + // Buttons can display a label, an icon, or both. + // A label should always be specified (through innerHTML) or the label + // attribute. It can be hidden via showLabel=false. + // example: + // | <button dojoType="dijit.form.Button" onClick="...">Hello world</button> + // + // example: + // | var button1 = new dijit.form.Button({label: "hello world", onClick: foo}); + // | dojo.body().appendChild(button1.domNode); + + // label: HTML String + // Text to display in button. + // If the label is hidden (showLabel=false) then and no title has + // been specified, then label is also set as title attribute of icon. + label: "", + + // showLabel: Boolean + // Set this to true to hide the label text and display only the icon. + // (If showLabel=false then iconClass must be specified.) + // Especially useful for toolbars. + // If showLabel=true, the label will become the title (a.k.a. tooltip/hint) of the icon. + // + // The exception case is for computers in high-contrast mode, where the label + // will still be displayed, since the icon doesn't appear. + showLabel: true, + + // iconClass: String + // Class to apply to DOMNode in button to make it display an icon + iconClass: "", + + // type: String + // Defines the type of button. "button", "submit", or "reset". + type: "button", + + baseClass: "dijitButton", + + templateString: dojo.cache("dijit.form", "templates/Button.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class=\"dijitReset dijitInline dijitButtonNode\"\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode,focusNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\"></span\n\t\t\t><span class=\"dijitReset dijitToggleButtonIconChar\">●</span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t\tdojoAttachPoint=\"containerNode\"\n\t\t\t></span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"), + + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + value: "valueNode" + }), + + _onClick: function(/*Event*/ e){ + // summary: + // Internal function to handle click actions + if(this.disabled){ + return false; + } + this._clicked(); // widget click actions + return this.onClick(e); // user click actions + }, + + _onButtonClick: function(/*Event*/ e){ + // summary: + // Handler when the user activates the button portion. + if(this._onClick(e) === false){ // returning nothing is same as true + e.preventDefault(); // needed for checkbox + }else if(this.type == "submit" && !(this.valueNode||this.focusNode).form){ // see if a nonform widget needs to be signalled + for(var node=this.domNode; node.parentNode/*#5935*/; node=node.parentNode){ + var widget=dijit.byNode(node); + if(widget && typeof widget._onSubmit == "function"){ + widget._onSubmit(e); + break; + } + } + }else if(this.valueNode){ + this.valueNode.click(); + e.preventDefault(); // cancel BUTTON click and continue with hidden INPUT click + } + }, + + buildRendering: function(){ + this.inherited(arguments); + dojo.setSelectable(this.focusNode, false); + }, + + _fillContent: function(/*DomNode*/ source){ + // Overrides _Templated._fillContent(). + // If button label is specified as srcNodeRef.innerHTML rather than + // this.params.label, handle it here. + // TODO: remove the method in 2.0, parser will do it all for me + if(source && (!this.params || !("label" in this.params))){ + this.set('label', source.innerHTML); + } + }, + + _setShowLabelAttr: function(val){ + if(this.containerNode){ + dojo.toggleClass(this.containerNode, "dijitDisplayNone", !val); + } + this._set("showLabel", val); + }, + + onClick: function(/*Event*/ e){ + // summary: + // Callback for when button is clicked. + // If type="submit", return true to perform submit, or false to cancel it. + // type: + // callback + return true; // Boolean + }, + + _clicked: function(/*Event*/ e){ + // summary: + // Internal overridable function for when the button is clicked + }, + + setLabel: function(/*String*/ content){ + // summary: + // Deprecated. Use set('label', ...) instead. + dojo.deprecated("dijit.form.Button.setLabel() is deprecated. Use set('label', ...) instead.", "", "2.0"); + this.set("label", content); + }, + + _setLabelAttr: function(/*String*/ content){ + // summary: + // Hook for set('label', ...) to work. + // description: + // Set the label (text) of the button; takes an HTML string. + this._set("label", content); + this.containerNode.innerHTML = content; + if(this.showLabel == false && !this.params.title){ + this.titleNode.title = dojo.trim(this.containerNode.innerText || this.containerNode.textContent || ''); + } + }, + + _setIconClassAttr: function(/*String*/ val){ + // Custom method so that icon node is hidden when not in use, to avoid excess padding/margin + // appearing around it (even if it's a 0x0 sized <img> node) + + var oldVal = this.iconClass || "dijitNoIcon", + newVal = val || "dijitNoIcon"; + dojo.replaceClass(this.iconNode, newVal, oldVal); + this._set("iconClass", val); + } }); -_a.refresh(); -}else{ -this.openDropDown(); -} -},isFocusable:function(){ -return this.inherited(arguments)&&!this._mouseDown; -}}); -dojo.declare("dijit.form.ComboButton",dijit.form.DropDownButton,{templateString:dojo.cache("dijit.form","templates/ComboButton.html","<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' waiRole=\"presentation\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\twaiRole=\"button\" waiState=\"labelledby-${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" waiRole=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\twaiRole=\"button\" waiState=\"haspopup-true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">▼</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"),attributeMap:dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap),{id:"",tabIndex:["focusNode","titleNode"],title:"titleNode"}),optionsTitle:"",baseClass:"dijitComboButton",cssStateNodes:{"buttonNode":"dijitButtonNode","titleNode":"dijitButtonContents","_popupStateNode":"dijitDownArrowButton"},_focusedNode:null,_onButtonKeyPress:function(_c){ -if(_c.charOrCode==dojo.keys[this.isLeftToRight()?"RIGHT_ARROW":"LEFT_ARROW"]){ -dijit.focus(this._popupStateNode); -dojo.stopEvent(_c); -} -},_onArrowKeyPress:function(_d){ -if(_d.charOrCode==dojo.keys[this.isLeftToRight()?"LEFT_ARROW":"RIGHT_ARROW"]){ -dijit.focus(this.titleNode); -dojo.stopEvent(_d); -} -},focus:function(_e){ -dijit.focus(_e=="start"?this.titleNode:this._popupStateNode); -}}); -dojo.declare("dijit.form.ToggleButton",dijit.form.Button,{baseClass:"dijitToggleButton",checked:false,attributeMap:dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap),{checked:"focusNode"}),_clicked:function(_f){ -this.set("checked",!this.checked); -},_setCheckedAttr:function(_10,_11){ -this.checked=_10; -dojo.attr(this.focusNode||this.domNode,"checked",_10); -dijit.setWaiState(this.focusNode||this.domNode,"pressed",_10); -this._handleOnChange(_10,_11); -},setChecked:function(_12){ -dojo.deprecated("setChecked("+_12+") is deprecated. Use set('checked',"+_12+") instead.","","2.0"); -this.set("checked",_12); -},reset:function(){ -this._hasBeenBlurred=false; -this.set("checked",this.params.checked||false); -}}); + + +dojo.declare("dijit.form.DropDownButton", [dijit.form.Button, dijit._Container, dijit._HasDropDown], { + // summary: + // A button with a drop down + // + // example: + // | <button dojoType="dijit.form.DropDownButton" label="Hello world"> + // | <div dojotype="dijit.Menu">...</div> + // | </button> + // + // example: + // | var button1 = new dijit.form.DropDownButton({ label: "hi", dropDown: new dijit.Menu(...) }); + // | dojo.body().appendChild(button1); + // + + baseClass : "dijitDropDownButton", + + templateString: dojo.cache("dijit.form", "templates/DropDownButton.html", "<span class=\"dijit dijitReset dijitInline\"\n\t><span class='dijitReset dijitInline dijitButtonNode'\n\t\tdojoAttachEvent=\"ondijitclick:_onButtonClick\" dojoAttachPoint=\"_buttonNode\"\n\t\t><span class=\"dijitReset dijitStretch dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"focusNode,titleNode,_arrowWrapperNode\"\n\t\t\trole=\"button\" aria-haspopup=\"true\" aria-labelledby=\"${id}_label\"\n\t\t\t><span class=\"dijitReset dijitInline dijitIcon\"\n\t\t\t\tdojoAttachPoint=\"iconNode\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\"\n\t\t\t\tdojoAttachPoint=\"containerNode,_popupStateNode\"\n\t\t\t\tid=\"${id}_label\"\n\t\t\t></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonInner\"></span\n\t\t\t><span class=\"dijitReset dijitInline dijitArrowButtonChar\">▼</span\n\t\t></span\n\t></span\n\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" class=\"dijitOffScreen\" tabIndex=\"-1\"\n\t\tdojoAttachPoint=\"valueNode\"\n/></span>\n"), + + _fillContent: function(){ + // Overrides Button._fillContent(). + // + // My inner HTML contains both the button contents and a drop down widget, like + // <DropDownButton> <span>push me</span> <Menu> ... </Menu> </DropDownButton> + // The first node is assumed to be the button content. The widget is the popup. + + if(this.srcNodeRef){ // programatically created buttons might not define srcNodeRef + //FIXME: figure out how to filter out the widget and use all remaining nodes as button + // content, not just nodes[0] + var nodes = dojo.query("*", this.srcNodeRef); + dijit.form.DropDownButton.superclass._fillContent.call(this, nodes[0]); + + // save pointer to srcNode so we can grab the drop down widget after it's instantiated + this.dropDownContainer = this.srcNodeRef; + } + }, + + startup: function(){ + if(this._started){ return; } + + // the child widget from srcNodeRef is the dropdown widget. Insert it in the page DOM, + // make it invisible, and store a reference to pass to the popup code. + if(!this.dropDown && this.dropDownContainer){ + var dropDownNode = dojo.query("[widgetId]", this.dropDownContainer)[0]; + this.dropDown = dijit.byNode(dropDownNode); + delete this.dropDownContainer; + } + if(this.dropDown){ + dijit.popup.hide(this.dropDown); + } + + this.inherited(arguments); + }, + + isLoaded: function(){ + // Returns whether or not we are loaded - if our dropdown has an href, + // then we want to check that. + var dropDown = this.dropDown; + return (!!dropDown && (!dropDown.href || dropDown.isLoaded)); + }, + + loadDropDown: function(){ + // Loads our dropdown + var dropDown = this.dropDown; + if(!dropDown){ return; } + if(!this.isLoaded()){ + var handler = dojo.connect(dropDown, "onLoad", this, function(){ + dojo.disconnect(handler); + this.openDropDown(); + }); + dropDown.refresh(); + }else{ + this.openDropDown(); + } + }, + + isFocusable: function(){ + // Overridden so that focus is handled by the _HasDropDown mixin, not by + // the _FormWidget mixin. + return this.inherited(arguments) && !this._mouseDown; + } +}); + +dojo.declare("dijit.form.ComboButton", dijit.form.DropDownButton, { + // summary: + // A combination button and drop-down button. + // Users can click one side to "press" the button, or click an arrow + // icon to display the drop down. + // + // example: + // | <button dojoType="dijit.form.ComboButton" onClick="..."> + // | <span>Hello world</span> + // | <div dojoType="dijit.Menu">...</div> + // | </button> + // + // example: + // | var button1 = new dijit.form.ComboButton({label: "hello world", onClick: foo, dropDown: "myMenu"}); + // | dojo.body().appendChild(button1.domNode); + // + + templateString: dojo.cache("dijit.form", "templates/ComboButton.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tcellspacing='0' cellpadding='0' role=\"presentation\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonNode\" dojoAttachPoint=\"buttonNode\" dojoAttachEvent=\"ondijitclick:_onButtonClick,onkeypress:_onButtonKeyPress\"\n\t\t><div id=\"${id}_button\" class=\"dijitReset dijitButtonContents\"\n\t\t\tdojoAttachPoint=\"titleNode\"\n\t\t\trole=\"button\" aria-labelledby=\"${id}_label\"\n\t\t\t><div class=\"dijitReset dijitInline dijitIcon\" dojoAttachPoint=\"iconNode\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitInline dijitButtonText\" id=\"${id}_label\" dojoAttachPoint=\"containerNode\" role=\"presentation\"></div\n\t\t></div\n\t\t></td\n\t\t><td id=\"${id}_arrow\" class='dijitReset dijitRight dijitButtonNode dijitArrowButton'\n\t\t\tdojoAttachPoint=\"_popupStateNode,focusNode,_buttonNode\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onArrowKeyPress\"\n\t\t\ttitle=\"${optionsTitle}\"\n\t\t\trole=\"button\" aria-haspopup=\"true\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">▼</div\n\t\t></td\n\t\t><td style=\"display:none !important;\"\n\t\t\t><input ${!nameAttrSetting} type=\"${type}\" value=\"${value}\" dojoAttachPoint=\"valueNode\"\n\t\t/></td></tr></tbody\n></table>\n"), + + attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { + id: "", + tabIndex: ["focusNode", "titleNode"], + title: "titleNode" + }), + + // optionsTitle: String + // Text that describes the options menu (accessibility) + optionsTitle: "", + + baseClass: "dijitComboButton", + + // Set classes like dijitButtonContentsHover or dijitArrowButtonActive depending on + // mouse action over specified node + cssStateNodes: { + "buttonNode": "dijitButtonNode", + "titleNode": "dijitButtonContents", + "_popupStateNode": "dijitDownArrowButton" + }, + + _focusedNode: null, + + _onButtonKeyPress: function(/*Event*/ evt){ + // summary: + // Handler for right arrow key when focus is on left part of button + if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "RIGHT_ARROW" : "LEFT_ARROW"]){ + dijit.focus(this._popupStateNode); + dojo.stopEvent(evt); + } + }, + + _onArrowKeyPress: function(/*Event*/ evt){ + // summary: + // Handler for left arrow key when focus is on right part of button + if(evt.charOrCode == dojo.keys[this.isLeftToRight() ? "LEFT_ARROW" : "RIGHT_ARROW"]){ + dijit.focus(this.titleNode); + dojo.stopEvent(evt); + } + }, + + focus: function(/*String*/ position){ + // summary: + // Focuses this widget to according to position, if specified, + // otherwise on arrow node + // position: + // "start" or "end" + if(!this.disabled){ + dijit.focus(position == "start" ? this.titleNode : this._popupStateNode); + } + } +}); + +dojo.declare("dijit.form.ToggleButton", dijit.form.Button, { + // summary: + // A button that can be in two states (checked or not). + // Can be base class for things like tabs or checkbox or radio buttons + + baseClass: "dijitToggleButton", + + // checked: Boolean + // Corresponds to the native HTML <input> element's attribute. + // In markup, specified as "checked='checked'" or just "checked". + // True if the button is depressed, or the checkbox is checked, + // or the radio button is selected, etc. + checked: false, + + attributeMap: dojo.mixin(dojo.clone(dijit.form.Button.prototype.attributeMap), { + checked:"focusNode" + }), + + _clicked: function(/*Event*/ evt){ + this.set('checked', !this.checked); + }, + + _setCheckedAttr: function(/*Boolean*/ value, /*Boolean?*/ priorityChange){ + this._set("checked", value); + dojo.attr(this.focusNode || this.domNode, "checked", value); + dijit.setWaiState(this.focusNode || this.domNode, "pressed", value); + this._handleOnChange(value, priorityChange); + }, + + setChecked: function(/*Boolean*/ checked){ + // summary: + // Deprecated. Use set('checked', true/false) instead. + dojo.deprecated("setChecked("+checked+") is deprecated. Use set('checked',"+checked+") instead.", "", "2.0"); + this.set('checked', checked); + }, + + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time + + this._hasBeenBlurred = false; + + // set checked state to original setting + this.set('checked', this.params.checked || false); + } +}); + } diff --git a/lib/dijit/form/CheckBox.js b/lib/dijit/form/CheckBox.js index ea5cdd170..939f6e8bb 100644 --- a/lib/dijit/form/CheckBox.js +++ b/lib/dijit/form/CheckBox.js @@ -1,76 +1,204 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.CheckBox"]){ -dojo._hasResource["dijit.form.CheckBox"]=true; +if(!dojo._hasResource["dijit.form.CheckBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.CheckBox"] = true; dojo.provide("dijit.form.CheckBox"); dojo.require("dijit.form.ToggleButton"); -dojo.declare("dijit.form.CheckBox",dijit.form.ToggleButton,{templateString:dojo.cache("dijit.form","templates/CheckBox.html","<div class=\"dijit dijitReset dijitInline\" waiRole=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"),baseClass:"dijitCheckBox",type:"checkbox",value:"on",readOnly:false,attributeMap:dojo.delegate(dijit.form._FormWidget.prototype.attributeMap,{readOnly:"focusNode"}),_setReadOnlyAttr:function(_1){ -this.readOnly=_1; -dojo.attr(this.focusNode,"readOnly",_1); -dijit.setWaiState(this.focusNode,"readonly",_1); -},_setValueAttr:function(_2,_3){ -if(typeof _2=="string"){ -this.value=_2; -dojo.attr(this.focusNode,"value",_2); -_2=true; -} -if(this._created){ -this.set("checked",_2,_3); -} -},_getValueAttr:function(){ -return (this.checked?this.value:false); -},_setLabelAttr:undefined,postMixInProperties:function(){ -if(this.value==""){ -this.value="on"; -} -this.checkedAttrSetting=this.checked?"checked":""; -this.inherited(arguments); -},_fillContent:function(_4){ -},reset:function(){ -this._hasBeenBlurred=false; -this.set("checked",this.params.checked||false); -this.value=this.params.value||"on"; -dojo.attr(this.focusNode,"value",this.value); -},_onFocus:function(){ -if(this.id){ -dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); -} -this.inherited(arguments); -},_onBlur:function(){ -if(this.id){ -dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); -} -this.inherited(arguments); -},_onClick:function(e){ -if(this.readOnly){ -return false; -} -return this.inherited(arguments); -}}); -dojo.declare("dijit.form.RadioButton",dijit.form.CheckBox,{type:"radio",baseClass:"dijitRadio",_setCheckedAttr:function(_5){ -this.inherited(arguments); -if(!this._created){ -return; -} -if(_5){ -var _6=this; -dojo.query("INPUT[type=radio]",this.focusNode.form||dojo.doc).forEach(function(_7){ -if(_7.name==_6.name&&_7!=_6.focusNode&&_7.form==_6.focusNode.form){ -var _8=dijit.getEnclosingWidget(_7); -if(_8&&_8.checked){ -_8.set("checked",false); -} -} -}); -} -},_clicked:function(e){ -if(!this.checked){ -this.set("checked",true); -} -}}); + + +dojo.declare( + "dijit.form.CheckBox", + dijit.form.ToggleButton, + { + // summary: + // Same as an HTML checkbox, but with fancy styling. + // + // description: + // User interacts with real html inputs. + // On onclick (which occurs by mouse click, space-bar, or + // using the arrow keys to switch the selected radio button), + // we update the state of the checkbox/radio. + // + // There are two modes: + // 1. High contrast mode + // 2. Normal mode + // + // In case 1, the regular html inputs are shown and used by the user. + // In case 2, the regular html inputs are invisible but still used by + // the user. They are turned quasi-invisible and overlay the background-image. + + templateString: dojo.cache("dijit.form", "templates/CheckBox.html", "<div class=\"dijit dijitReset dijitInline\" role=\"presentation\"\n\t><input\n\t \t${!nameAttrSetting} type=\"${type}\" ${checkedAttrSetting}\n\t\tclass=\"dijitReset dijitCheckBoxInput\"\n\t\tdojoAttachPoint=\"focusNode\"\n\t \tdojoAttachEvent=\"onclick:_onClick\"\n/></div>\n"), + + baseClass: "dijitCheckBox", + + // type: [private] String + // type attribute on <input> node. + // Overrides `dijit.form.Button.type`. Users should not change this value. + type: "checkbox", + + // value: String + // As an initialization parameter, equivalent to value field on normal checkbox + // (if checked, the value is passed as the value when form is submitted). + // + // However, get('value') will return either the string or false depending on + // whether or not the checkbox is checked. + // + // set('value', string) will check the checkbox and change the value to the + // specified string + // + // set('value', boolean) will change the checked state. + value: "on", + + // readOnly: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "readOnly". + // Similar to disabled except readOnly form values are submitted. + readOnly: false, + + // the attributeMap should inherit from dijit.form._FormWidget.prototype.attributeMap + // instead of ToggleButton as the icon mapping has no meaning for a CheckBox + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + readOnly: "focusNode" + }), + + _setReadOnlyAttr: function(/*Boolean*/ value){ + this._set("readOnly", value); + dojo.attr(this.focusNode, 'readOnly', value); + dijit.setWaiState(this.focusNode, "readonly", value); + }, + + _setValueAttr: function(/*String|Boolean*/ newValue, /*Boolean*/ priorityChange){ + // summary: + // Handler for value= attribute to constructor, and also calls to + // set('value', val). + // description: + // During initialization, just saves as attribute to the <input type=checkbox>. + // + // After initialization, + // when passed a boolean, controls whether or not the CheckBox is checked. + // If passed a string, changes the value attribute of the CheckBox (the one + // specified as "value" when the CheckBox was constructed (ex: <input + // dojoType="dijit.CheckBox" value="chicken">) + if(typeof newValue == "string"){ + this._set("value", newValue); + dojo.attr(this.focusNode, 'value', newValue); + newValue = true; + } + if(this._created){ + this.set('checked', newValue, priorityChange); + } + }, + _getValueAttr: function(){ + // summary: + // Hook so get('value') works. + // description: + // If the CheckBox is checked, returns the value attribute. + // Otherwise returns false. + return (this.checked ? this.value : false); + }, + + // Override dijit.form.Button._setLabelAttr() since we don't even have a containerNode. + // Normally users won't try to set label, except when CheckBox or RadioButton is the child of a dojox.layout.TabContainer + _setLabelAttr: undefined, + + postMixInProperties: function(){ + if(this.value == ""){ + this.value = "on"; + } + + // Need to set initial checked state as part of template, so that form submit works. + // dojo.attr(node, "checked", bool) doesn't work on IEuntil node has been attached + // to <body>, see #8666 + this.checkedAttrSetting = this.checked ? "checked" : ""; + + this.inherited(arguments); + }, + + _fillContent: function(/*DomNode*/ source){ + // Override Button::_fillContent() since it doesn't make sense for CheckBox, + // since CheckBox doesn't even have a container + }, + + reset: function(){ + // Override ToggleButton.reset() + + this._hasBeenBlurred = false; + + this.set('checked', this.params.checked || false); + + // Handle unlikely event that the <input type=checkbox> value attribute has changed + this._set("value", this.params.value || "on"); + dojo.attr(this.focusNode, 'value', this.value); + }, + + _onFocus: function(){ + if(this.id){ + dojo.query("label[for='"+this.id+"']").addClass("dijitFocusedLabel"); + } + this.inherited(arguments); + }, + + _onBlur: function(){ + if(this.id){ + dojo.query("label[for='"+this.id+"']").removeClass("dijitFocusedLabel"); + } + this.inherited(arguments); + }, + + _onClick: function(/*Event*/ e){ + // summary: + // Internal function to handle click actions - need to check + // readOnly, since button no longer does that check. + if(this.readOnly){ + dojo.stopEvent(e); + return false; + } + return this.inherited(arguments); + } + } +); + +dojo.declare( + "dijit.form.RadioButton", + dijit.form.CheckBox, + { + // summary: + // Same as an HTML radio, but with fancy styling. + + type: "radio", + baseClass: "dijitRadio", + + _setCheckedAttr: function(/*Boolean*/ value){ + // If I am being checked then have to deselect currently checked radio button + this.inherited(arguments); + if(!this._created){ return; } + if(value){ + var _this = this; + // search for radio buttons with the same name that need to be unchecked + dojo.query("INPUT[type=radio]", this.focusNode.form || dojo.doc).forEach( // can't use name= since dojo.query doesn't support [] in the name + function(inputNode){ + if(inputNode.name == _this.name && inputNode != _this.focusNode && inputNode.form == _this.focusNode.form){ + var widget = dijit.getEnclosingWidget(inputNode); + if(widget && widget.checked){ + widget.set('checked', false); + } + } + } + ); + } + }, + + _clicked: function(/*Event*/ e){ + if(!this.checked){ + this.set('checked', true); + } + } + } +); + } diff --git a/lib/dijit/form/ComboBox.js b/lib/dijit/form/ComboBox.js index 694c43ed1..aecc5c4fc 100644 --- a/lib/dijit/form/ComboBox.js +++ b/lib/dijit/form/ComboBox.js @@ -1,12 +1,12 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.ComboBox"]){ -dojo._hasResource["dijit.form.ComboBox"]=true; +if(!dojo._hasResource["dijit.form.ComboBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ComboBox"] = true; dojo.provide("dijit.form.ComboBox"); dojo.require("dojo.window"); dojo.require("dojo.regexp"); @@ -15,601 +15,1217 @@ dojo.require("dojo.data.util.filter"); dojo.require("dijit._CssStateMixin"); dojo.require("dijit.form._FormWidget"); dojo.require("dijit.form.ValidationTextBox"); -dojo.requireLocalization("dijit.form","ComboBox",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.form.ComboBoxMixin",null,{item:null,pageSize:Infinity,store:null,fetchProperties:{},query:{},autoComplete:true,highlightMatch:"first",searchDelay:100,searchAttr:"name",labelAttr:"",labelType:"text",queryExpr:"${0}*",ignoreCase:true,hasDownArrow:true,templateString:dojo.cache("dijit.form","templates/ComboBox.html","<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\tdojoAttachPoint=\"comboNode\" waiRole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"downArrowNode\" waiRole=\"presentation\"\n\t\tdojoAttachEvent=\"onmousedown:_onArrowMouseDown\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachEvent=\"onkeypress:_onKeyPress,compositionend\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" waiRole=\"textbox\" waiState=\"haspopup-true,autocomplete-list\"\n\t/></div\n></div>\n"),baseClass:"dijitTextBox dijitComboBox",cssStateNodes:{"downArrowNode":"dijitDownArrowButton"},_getCaretPos:function(_1){ -var _2=0; -if(typeof (_1.selectionStart)=="number"){ -_2=_1.selectionStart; -}else{ -if(dojo.isIE){ -var tr=dojo.doc.selection.createRange().duplicate(); -var _3=_1.createTextRange(); -tr.move("character",0); -_3.move("character",0); -try{ -_3.setEndPoint("EndToEnd",tr); -_2=String(_3.text).replace(/\r/g,"").length; -} -catch(e){ -} -} -} -return _2; -},_setCaretPos:function(_4,_5){ -_5=parseInt(_5); -dijit.selectInputText(_4,_5,_5); -},_setDisabledAttr:function(_6){ -this.inherited(arguments); -dijit.setWaiState(this.comboNode,"disabled",_6); -},_abortQuery:function(){ -if(this.searchTimer){ -clearTimeout(this.searchTimer); -this.searchTimer=null; -} -if(this._fetchHandle){ -if(this._fetchHandle.abort){ -this._fetchHandle.abort(); -} -this._fetchHandle=null; -} -},_onInput:function(_7){ -if(!this.searchTimer&&(_7.type=="paste"||_7.type=="input")&&this._lastInput!=this.textbox.value){ -this.searchTimer=setTimeout(dojo.hitch(this,function(){ -this._onKeyPress({charOrCode:229}); -}),100); -} -this.inherited(arguments); -},_onKeyPress:function(_8){ -var _9=_8.charOrCode; -if(_8.altKey||((_8.ctrlKey||_8.metaKey)&&(_9!="x"&&_9!="v"))||_9==dojo.keys.SHIFT){ -return; -} -var _a=false; -var _b="_startSearchFromInput"; -var pw=this._popupWidget; -var dk=dojo.keys; -var _c=null; -this._prev_key_backspace=false; -this._abortQuery(); -if(this._isShowingNow){ -pw.handleKey(_9); -_c=pw.getHighlightedOption(); -} -switch(_9){ -case dk.PAGE_DOWN: -case dk.DOWN_ARROW: -case dk.PAGE_UP: -case dk.UP_ARROW: -if(!this._isShowingNow){ -_a=true; -_b="_startSearchAll"; -}else{ -this._announceOption(_c); -} -dojo.stopEvent(_8); -break; -case dk.ENTER: -if(_c){ -if(_c==pw.nextButton){ -this._nextSearch(1); -dojo.stopEvent(_8); -break; -}else{ -if(_c==pw.previousButton){ -this._nextSearch(-1); -dojo.stopEvent(_8); -break; -} -} -}else{ -this._setBlurValue(); -this._setCaretPos(this.focusNode,this.focusNode.value.length); -} -_8.preventDefault(); -case dk.TAB: -var _d=this.get("displayedValue"); -if(pw&&(_d==pw._messages["previousMessage"]||_d==pw._messages["nextMessage"])){ -break; -} -if(_c){ -this._selectOption(); -} -if(this._isShowingNow){ -this._lastQuery=null; -this._hideResultList(); -} -break; -case " ": -if(_c){ -dojo.stopEvent(_8); -this._selectOption(); -this._hideResultList(); -}else{ -_a=true; -} -break; -case dk.ESCAPE: -if(this._isShowingNow){ -dojo.stopEvent(_8); -this._hideResultList(); -} -break; -case dk.DELETE: -case dk.BACKSPACE: -this._prev_key_backspace=true; -_a=true; -break; -default: -_a=typeof _9=="string"||_9==229; -} -if(_a){ -this.item=undefined; -this.searchTimer=setTimeout(dojo.hitch(this,_b),1); -} -},_autoCompleteText:function(_e){ -var fn=this.focusNode; -dijit.selectInputText(fn,fn.value.length); -var _f=this.ignoreCase?"toLowerCase":"substr"; -if(_e[_f](0).indexOf(this.focusNode.value[_f](0))==0){ -var _10=this._getCaretPos(fn); -if((_10+1)>fn.value.length){ -fn.value=_e; -dijit.selectInputText(fn,_10); -} -}else{ -fn.value=_e; -dijit.selectInputText(fn); -} -},_openResultList:function(_11,_12){ -this._fetchHandle=null; -if(this.disabled||this.readOnly||(_12.query[this.searchAttr]!=this._lastQuery)){ -return; -} -this._popupWidget.clearResultList(); -if(!_11.length&&!this._maxOptions){ -this._hideResultList(); -return; -} -_12._maxOptions=this._maxOptions; -var _13=this._popupWidget.createOptions(_11,_12,dojo.hitch(this,"_getMenuLabelFromItem")); -this._showResultList(); -if(_12.direction){ -if(1==_12.direction){ -this._popupWidget.highlightFirstOption(); -}else{ -if(-1==_12.direction){ -this._popupWidget.highlightLastOption(); -} -} -this._announceOption(this._popupWidget.getHighlightedOption()); -}else{ -if(this.autoComplete&&!this._prev_key_backspace&&!/^[*]+$/.test(_12.query[this.searchAttr])){ -this._announceOption(_13[1]); -} -} -},_showResultList:function(){ -this._hideResultList(); -this.displayMessage(""); -dojo.style(this._popupWidget.domNode,{width:"",height:""}); -var _14=this.open(); -var _15=dojo.marginBox(this._popupWidget.domNode); -this._popupWidget.domNode.style.overflow=((_14.h==_15.h)&&(_14.w==_15.w))?"hidden":"auto"; -var _16=_14.w; -if(_14.h<this._popupWidget.domNode.scrollHeight){ -_16+=16; -} -dojo.marginBox(this._popupWidget.domNode,{h:_14.h,w:Math.max(_16,this.domNode.offsetWidth)}); -if(_16<this.domNode.offsetWidth){ -this._popupWidget.domNode.parentNode.style.left=dojo.position(this.domNode,true).x+"px"; -} -dijit.setWaiState(this.comboNode,"expanded","true"); -},_hideResultList:function(){ -this._abortQuery(); -if(this._isShowingNow){ -dijit.popup.close(this._popupWidget); -this._isShowingNow=false; -dijit.setWaiState(this.comboNode,"expanded","false"); -dijit.removeWaiState(this.focusNode,"activedescendant"); -} -},_setBlurValue:function(){ -var _17=this.get("displayedValue"); -var pw=this._popupWidget; -if(pw&&(_17==pw._messages["previousMessage"]||_17==pw._messages["nextMessage"])){ -this._setValueAttr(this._lastValueReported,true); -}else{ -if(typeof this.item=="undefined"){ -this.item=null; -this.set("displayedValue",_17); -}else{ -if(this.value!=this._lastValueReported){ -dijit.form._FormValueWidget.prototype._setValueAttr.call(this,this.value,true); -} -this._refreshState(); -} -} -},_onBlur:function(){ -this._hideResultList(); -this.inherited(arguments); -},_setItemAttr:function(_18,_19,_1a){ -if(!_1a){ -_1a=this.labelFunc(_18,this.store); -} -this.value=this._getValueField()!=this.searchAttr?this.store.getIdentity(_18):_1a; -this.item=_18; -dijit.form.ComboBox.superclass._setValueAttr.call(this,this.value,_19,_1a); -},_announceOption:function(_1b){ -if(!_1b){ -return; -} -var _1c; -if(_1b==this._popupWidget.nextButton||_1b==this._popupWidget.previousButton){ -_1c=_1b.innerHTML; -this.item=undefined; -this.value=""; -}else{ -_1c=this.labelFunc(_1b.item,this.store); -this.set("item",_1b.item,false,_1c); -} -this.focusNode.value=this.focusNode.value.substring(0,this._lastInput.length); -dijit.setWaiState(this.focusNode,"activedescendant",dojo.attr(_1b,"id")); -this._autoCompleteText(_1c); -},_selectOption:function(evt){ -if(evt){ -this._announceOption(evt.target); -} -this._hideResultList(); -this._setCaretPos(this.focusNode,this.focusNode.value.length); -dijit.form._FormValueWidget.prototype._setValueAttr.call(this,this.value,true); -},_onArrowMouseDown:function(evt){ -if(this.disabled||this.readOnly){ -return; -} -dojo.stopEvent(evt); -this.focus(); -if(this._isShowingNow){ -this._hideResultList(); -}else{ -this._startSearchAll(); -} -},_startSearchAll:function(){ -this._startSearch(""); -},_startSearchFromInput:function(){ -this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g,"\\$1")); -},_getQueryString:function(_1d){ -return dojo.string.substitute(this.queryExpr,[_1d]); -},_startSearch:function(key){ -if(!this._popupWidget){ -var _1e=this.id+"_popup"; -this._popupWidget=new dijit.form._ComboBoxMenu({onChange:dojo.hitch(this,this._selectOption),id:_1e,dir:this.dir}); -dijit.removeWaiState(this.focusNode,"activedescendant"); -dijit.setWaiState(this.textbox,"owns",_1e); -} -var _1f=dojo.clone(this.query); -this._lastInput=key; -this._lastQuery=_1f[this.searchAttr]=this._getQueryString(key); -this.searchTimer=setTimeout(dojo.hitch(this,function(_20,_21){ -this.searchTimer=null; -var _22={queryOptions:{ignoreCase:this.ignoreCase,deep:true},query:_20,onBegin:dojo.hitch(this,"_setMaxOptions"),onComplete:dojo.hitch(this,"_openResultList"),onError:function(_23){ -_21._fetchHandle=null; -console.error("dijit.form.ComboBox: "+_23); -dojo.hitch(_21,"_hideResultList")(); -},start:0,count:this.pageSize}; -dojo.mixin(_22,_21.fetchProperties); -this._fetchHandle=_21.store.fetch(_22); -var _24=function(_25,_26){ -_25.start+=_25.count*_26; -_25.direction=_26; -this._fetchHandle=this.store.fetch(_25); -}; -this._nextSearch=this._popupWidget.onPage=dojo.hitch(this,_24,this._fetchHandle); -},_1f,this),this.searchDelay); -},_setMaxOptions:function(_27,_28){ -this._maxOptions=_27; -},_getValueField:function(){ -return this.searchAttr; -},compositionend:function(evt){ -this._onKeyPress({charOrCode:229}); -},constructor:function(){ -this.query={}; -this.fetchProperties={}; -},postMixInProperties:function(){ -if(!this.store){ -var _29=this.srcNodeRef; -this.store=new dijit.form._ComboBoxDataStore(_29); -if(!("value" in this.params)){ -var _2a=this.store.fetchSelectedItem(); -if(_2a){ -var _2b=this._getValueField(); -this.value=_2b!=this.searchAttr?this.store.getValue(_2a,_2b):this.labelFunc(_2a,this.store); -} -} -} -this.inherited(arguments); -},postCreate:function(){ -if(!this.hasDownArrow){ -this.downArrowNode.style.display="none"; -} -var _2c=dojo.query("label[for=\""+this.id+"\"]"); -if(_2c.length){ -_2c[0].id=(this.id+"_label"); -var cn=this.comboNode; -dijit.setWaiState(cn,"labelledby",_2c[0].id); -} -this.inherited(arguments); -},uninitialize:function(){ -if(this._popupWidget&&!this._popupWidget._destroyed){ -this._hideResultList(); -this._popupWidget.destroy(); -} -this.inherited(arguments); -},_getMenuLabelFromItem:function(_2d){ -var _2e=this.labelAttr?this.store.getValue(_2d,this.labelAttr):this.labelFunc(_2d,this.store); -var _2f=this.labelType; -if(this.highlightMatch!="none"&&this.labelType=="text"&&this._lastInput){ -_2e=this.doHighlight(_2e,this._escapeHtml(this._lastInput)); -_2f="html"; -} -return {html:_2f=="html",label:_2e}; -},doHighlight:function(_30,_31){ -var _32="i"+(this.highlightMatch=="all"?"g":""); -var _33=this._escapeHtml(_30); -_31=dojo.regexp.escapeString(_31); -var ret=_33.replace(new RegExp("(^|\\s)("+_31+")",_32),"$1<span class=\"dijitComboBoxHighlightMatch\">$2</span>"); -return ret; -},_escapeHtml:function(str){ -str=String(str).replace(/&/gm,"&").replace(/</gm,"<").replace(/>/gm,">").replace(/"/gm,"""); -return str; -},open:function(){ -this._isShowingNow=true; -return dijit.popup.open({popup:this._popupWidget,around:this.domNode,parent:this}); -},reset:function(){ -this.item=null; -this.inherited(arguments); -},labelFunc:function(_34,_35){ -return _35.getValue(_34,this.searchAttr).toString(); -}}); -dojo.declare("dijit.form._ComboBoxMenu",[dijit._Widget,dijit._Templated,dijit._CssStateMixin],{templateString:"<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>"+"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>"+"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>"+"</ul>",_messages:null,baseClass:"dijitComboBoxMenu",postMixInProperties:function(){ -this._messages=dojo.i18n.getLocalization("dijit.form","ComboBox",this.lang); -this.inherited(arguments); -},_setValueAttr:function(_36){ -this.value=_36; -this.onChange(_36); -},onChange:function(_37){ -},onPage:function(_38){ -},postCreate:function(){ -this.previousButton.innerHTML=this._messages["previousMessage"]; -this.nextButton.innerHTML=this._messages["nextMessage"]; -this.inherited(arguments); -},onClose:function(){ -this._blurOptionNode(); -},_createOption:function(_39,_3a){ -var _3b=_3a(_39); -var _3c=dojo.doc.createElement("li"); -dijit.setWaiRole(_3c,"option"); -if(_3b.html){ -_3c.innerHTML=_3b.label; -}else{ -_3c.appendChild(dojo.doc.createTextNode(_3b.label)); -} -if(_3c.innerHTML==""){ -_3c.innerHTML=" "; -} -_3c.item=_39; -return _3c; -},createOptions:function(_3d,_3e,_3f){ -this.previousButton.style.display=(_3e.start==0)?"none":""; -dojo.attr(this.previousButton,"id",this.id+"_prev"); -dojo.forEach(_3d,function(_40,i){ -var _41=this._createOption(_40,_3f); -_41.className="dijitReset dijitMenuItem"+(this.isLeftToRight()?"":" dijitMenuItemRtl"); -dojo.attr(_41,"id",this.id+i); -this.domNode.insertBefore(_41,this.nextButton); -},this); -var _42=false; -if(_3e._maxOptions&&_3e._maxOptions!=-1){ -if((_3e.start+_3e.count)<_3e._maxOptions){ -_42=true; -}else{ -if((_3e.start+_3e.count)>_3e._maxOptions&&_3e.count==_3d.length){ -_42=true; -} -} -}else{ -if(_3e.count==_3d.length){ -_42=true; -} -} -this.nextButton.style.display=_42?"":"none"; -dojo.attr(this.nextButton,"id",this.id+"_next"); -return this.domNode.childNodes; -},clearResultList:function(){ -while(this.domNode.childNodes.length>2){ -this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); -} -},_onMouseDown:function(evt){ -dojo.stopEvent(evt); -},_onMouseUp:function(evt){ -if(evt.target===this.domNode||!this._highlighted_option){ -return; -}else{ -if(evt.target==this.previousButton){ -this.onPage(-1); -}else{ -if(evt.target==this.nextButton){ -this.onPage(1); -}else{ -var tgt=evt.target; -while(!tgt.item){ -tgt=tgt.parentNode; -} -this._setValueAttr({target:tgt},true); -} -} -} -},_onMouseOver:function(evt){ -if(evt.target===this.domNode){ -return; -} -var tgt=evt.target; -if(!(tgt==this.previousButton||tgt==this.nextButton)){ -while(!tgt.item){ -tgt=tgt.parentNode; -} -} -this._focusOptionNode(tgt); -},_onMouseOut:function(evt){ -if(evt.target===this.domNode){ -return; -} -this._blurOptionNode(); -},_focusOptionNode:function(_43){ -if(this._highlighted_option!=_43){ -this._blurOptionNode(); -this._highlighted_option=_43; -dojo.addClass(this._highlighted_option,"dijitMenuItemSelected"); -} -},_blurOptionNode:function(){ -if(this._highlighted_option){ -dojo.removeClass(this._highlighted_option,"dijitMenuItemSelected"); -this._highlighted_option=null; -} -},_highlightNextOption:function(){ -if(!this.getHighlightedOption()){ -var fc=this.domNode.firstChild; -this._focusOptionNode(fc.style.display=="none"?fc.nextSibling:fc); -}else{ -var ns=this._highlighted_option.nextSibling; -if(ns&&ns.style.display!="none"){ -this._focusOptionNode(ns); -}else{ -this.highlightFirstOption(); -} -} -dojo.window.scrollIntoView(this._highlighted_option); -},highlightFirstOption:function(){ -var _44=this.domNode.firstChild; -var _45=_44.nextSibling; -this._focusOptionNode(_45.style.display=="none"?_44:_45); -dojo.window.scrollIntoView(this._highlighted_option); -},highlightLastOption:function(){ -this._focusOptionNode(this.domNode.lastChild.previousSibling); -dojo.window.scrollIntoView(this._highlighted_option); -},_highlightPrevOption:function(){ -if(!this.getHighlightedOption()){ -var lc=this.domNode.lastChild; -this._focusOptionNode(lc.style.display=="none"?lc.previousSibling:lc); -}else{ -var ps=this._highlighted_option.previousSibling; -if(ps&&ps.style.display!="none"){ -this._focusOptionNode(ps); -}else{ -this.highlightLastOption(); -} -} -dojo.window.scrollIntoView(this._highlighted_option); -},_page:function(up){ -var _46=0; -var _47=this.domNode.scrollTop; -var _48=dojo.style(this.domNode,"height"); -if(!this.getHighlightedOption()){ -this._highlightNextOption(); -} -while(_46<_48){ -if(up){ -if(!this.getHighlightedOption().previousSibling||this._highlighted_option.previousSibling.style.display=="none"){ -break; -} -this._highlightPrevOption(); -}else{ -if(!this.getHighlightedOption().nextSibling||this._highlighted_option.nextSibling.style.display=="none"){ -break; -} -this._highlightNextOption(); -} -var _49=this.domNode.scrollTop; -_46+=(_49-_47)*(up?-1:1); -_47=_49; -} -},pageUp:function(){ -this._page(true); -},pageDown:function(){ -this._page(false); -},getHighlightedOption:function(){ -var ho=this._highlighted_option; -return (ho&&ho.parentNode)?ho:null; -},handleKey:function(key){ -switch(key){ -case dojo.keys.DOWN_ARROW: -this._highlightNextOption(); -break; -case dojo.keys.PAGE_DOWN: -this.pageDown(); -break; -case dojo.keys.UP_ARROW: -this._highlightPrevOption(); -break; -case dojo.keys.PAGE_UP: -this.pageUp(); -break; -} -}}); -dojo.declare("dijit.form.ComboBox",[dijit.form.ValidationTextBox,dijit.form.ComboBoxMixin],{_setValueAttr:function(_4a,_4b,_4c){ -this.item=null; -if(!_4a){ -_4a=""; -} -dijit.form.ValidationTextBox.prototype._setValueAttr.call(this,_4a,_4b,_4c); -}}); -dojo.declare("dijit.form._ComboBoxDataStore",null,{constructor:function(_4d){ -this.root=_4d; -if(_4d.tagName!="SELECT"&&_4d.firstChild){ -_4d=dojo.query("select",_4d); -if(_4d.length>0){ -_4d=_4d[0]; -}else{ -this.root.innerHTML="<SELECT>"+this.root.innerHTML+"</SELECT>"; -_4d=this.root.firstChild; -} -this.root=_4d; -} -dojo.query("> option",_4d).forEach(function(_4e){ -_4e.innerHTML=dojo.trim(_4e.innerHTML); -}); -},getValue:function(_4f,_50,_51){ -return (_50=="value")?_4f.value:(_4f.innerText||_4f.textContent||""); -},isItemLoaded:function(_52){ -return true; -},getFeatures:function(){ -return {"dojo.data.api.Read":true,"dojo.data.api.Identity":true}; -},_fetchItems:function(_53,_54,_55){ -if(!_53.query){ -_53.query={}; -} -if(!_53.query.name){ -_53.query.name=""; -} -if(!_53.queryOptions){ -_53.queryOptions={}; -} -var _56=dojo.data.util.filter.patternToRegExp(_53.query.name,_53.queryOptions.ignoreCase),_57=dojo.query("> option",this.root).filter(function(_58){ -return (_58.innerText||_58.textContent||"").match(_56); +dojo.require("dijit._HasDropDown"); +dojo.requireLocalization("dijit.form", "ComboBox", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +dojo.declare( + "dijit.form.ComboBoxMixin", + dijit._HasDropDown, + { + // summary: + // Implements the base functionality for `dijit.form.ComboBox`/`dijit.form.FilteringSelect` + // description: + // All widgets that mix in dijit.form.ComboBoxMixin must extend `dijit.form._FormValueWidget`. + // tags: + // protected + + // item: Object + // This is the item returned by the dojo.data.store implementation that + // provides the data for this ComboBox, it's the currently selected item. + item: null, + + // pageSize: Integer + // Argument to data provider. + // Specifies number of search results per page (before hitting "next" button) + pageSize: Infinity, + + // store: [const] Object + // Reference to data provider object used by this ComboBox + store: null, + + // fetchProperties: Object + // Mixin to the dojo.data store's fetch. + // For example, to set the sort order of the ComboBox menu, pass: + // | { sort: [{attribute:"name",descending: true}] } + // To override the default queryOptions so that deep=false, do: + // | { queryOptions: {ignoreCase: true, deep: false} } + fetchProperties:{}, + + // query: Object + // A query that can be passed to 'store' to initially filter the items, + // before doing further filtering based on `searchAttr` and the key. + // Any reference to the `searchAttr` is ignored. + query: {}, + + // autoComplete: Boolean + // If user types in a partial string, and then tab out of the `<input>` box, + // automatically copy the first entry displayed in the drop down list to + // the `<input>` field + autoComplete: true, + + // highlightMatch: String + // One of: "first", "all" or "none". + // + // If the ComboBox/FilteringSelect opens with the search results and the searched + // string can be found, it will be highlighted. If set to "all" + // then will probably want to change `queryExpr` parameter to '*${0}*' + // + // Highlighting is only performed when `labelType` is "text", so as to not + // interfere with any HTML markup an HTML label might contain. + highlightMatch: "first", + + // searchDelay: Integer + // Delay in milliseconds between when user types something and we start + // searching based on that value + searchDelay: 100, + + // searchAttr: String + // Search for items in the data store where this attribute (in the item) + // matches what the user typed + searchAttr: "name", + + // labelAttr: String? + // The entries in the drop down list come from this attribute in the + // dojo.data items. + // If not specified, the searchAttr attribute is used instead. + labelAttr: "", + + // labelType: String + // Specifies how to interpret the labelAttr in the data store items. + // Can be "html" or "text". + labelType: "text", + + // queryExpr: String + // This specifies what query ComboBox/FilteringSelect sends to the data store, + // based on what the user has typed. Changing this expression will modify + // whether the drop down shows only exact matches, a "starting with" match, + // etc. Use it in conjunction with highlightMatch. + // dojo.data query expression pattern. + // `${0}` will be substituted for the user text. + // `*` is used for wildcards. + // `${0}*` means "starts with", `*${0}*` means "contains", `${0}` means "is" + queryExpr: "${0}*", + + // ignoreCase: Boolean + // Set true if the ComboBox/FilteringSelect should ignore case when matching possible items + ignoreCase: true, + + // hasDownArrow: Boolean + // Set this textbox to have a down arrow button, to display the drop down list. + // Defaults to true. + hasDownArrow: true, + + templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"), + + baseClass: "dijitTextBox dijitComboBox", + + // dropDownClass: [protected extension] String + // Name of the dropdown widget class used to select a date/time. + // Subclasses should specify this. + dropDownClass: "dijit.form._ComboBoxMenu", + + // Set classes like dijitDownArrowButtonHover depending on + // mouse action over button node + cssStateNodes: { + "_buttonNode": "dijitDownArrowButton" + }, + + // Flags to _HasDropDown to limit height of drop down to make it fit in viewport + maxHeight: -1, + + // For backwards compatibility let onClick events propagate, even clicks on the down arrow button + _stopClickEvents: false, + + _getCaretPos: function(/*DomNode*/ element){ + // khtml 3.5.2 has selection* methods as does webkit nightlies from 2005-06-22 + var pos = 0; + if(typeof(element.selectionStart) == "number"){ + // FIXME: this is totally borked on Moz < 1.3. Any recourse? + pos = element.selectionStart; + }else if(dojo.isIE){ + // in the case of a mouse click in a popup being handled, + // then the dojo.doc.selection is not the textarea, but the popup + // var r = dojo.doc.selection.createRange(); + // hack to get IE 6 to play nice. What a POS browser. + var tr = dojo.doc.selection.createRange().duplicate(); + var ntr = element.createTextRange(); + tr.move("character",0); + ntr.move("character",0); + try{ + // If control doesn't have focus, you get an exception. + // Seems to happen on reverse-tab, but can also happen on tab (seems to be a race condition - only happens sometimes). + // There appears to be no workaround for this - googled for quite a while. + ntr.setEndPoint("EndToEnd", tr); + pos = String(ntr.text).replace(/\r/g,"").length; + }catch(e){ + // If focus has shifted, 0 is fine for caret pos. + } + } + return pos; + }, + + _setCaretPos: function(/*DomNode*/ element, /*Number*/ location){ + location = parseInt(location); + dijit.selectInputText(element, location, location); + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + // Additional code to set disabled state of ComboBox node. + // Overrides _FormValueWidget._setDisabledAttr() or ValidationTextBox._setDisabledAttr(). + this.inherited(arguments); + dijit.setWaiState(this.domNode, "disabled", value); + }, + + _abortQuery: function(){ + // stop in-progress query + if(this.searchTimer){ + clearTimeout(this.searchTimer); + this.searchTimer = null; + } + if(this._fetchHandle){ + if(this._fetchHandle.abort){ this._fetchHandle.abort(); } + this._fetchHandle = null; + } + }, + + _onInput: function(/*Event*/ evt){ + // summary: + // Handles paste events + if(!this.searchTimer && (evt.type == 'paste'/*IE|WebKit*/ || evt.type == 'input'/*Firefox*/) && this._lastInput != this.textbox.value){ + this.searchTimer = setTimeout(dojo.hitch(this, function(){ + this._onKey({charOrCode: 229}); // fake IME key to cause a search + }), 100); // long delay that will probably be preempted by keyboard input + } + this.inherited(arguments); + }, + + _onKey: function(/*Event*/ evt){ + // summary: + // Handles keyboard events + + var key = evt.charOrCode; + + // except for cutting/pasting case - ctrl + x/v + if(evt.altKey || ((evt.ctrlKey || evt.metaKey) && (key != 'x' && key != 'v')) || key == dojo.keys.SHIFT){ + return; // throw out weird key combinations and spurious events + } + + var doSearch = false; + var pw = this.dropDown; + var dk = dojo.keys; + var highlighted = null; + this._prev_key_backspace = false; + this._abortQuery(); + + // _HasDropDown will do some of the work: + // 1. when drop down is not yet shown: + // - if user presses the down arrow key, call loadDropDown() + // 2. when drop down is already displayed: + // - on ESC key, call closeDropDown() + // - otherwise, call dropDown.handleKey() to process the keystroke + this.inherited(arguments); + + if(this._opened){ + highlighted = pw.getHighlightedOption(); + } + switch(key){ + case dk.PAGE_DOWN: + case dk.DOWN_ARROW: + case dk.PAGE_UP: + case dk.UP_ARROW: + // Keystroke caused ComboBox_menu to move to a different item. + // Copy new item to <input> box. + if(this._opened){ + this._announceOption(highlighted); + } + dojo.stopEvent(evt); + break; + + case dk.ENTER: + // prevent submitting form if user presses enter. Also + // prevent accepting the value if either Next or Previous + // are selected + if(highlighted){ + // only stop event on prev/next + if(highlighted == pw.nextButton){ + this._nextSearch(1); + dojo.stopEvent(evt); + break; + }else if(highlighted == pw.previousButton){ + this._nextSearch(-1); + dojo.stopEvent(evt); + break; + } + }else{ + // Update 'value' (ex: KY) according to currently displayed text + this._setBlurValue(); // set value if needed + this._setCaretPos(this.focusNode, this.focusNode.value.length); // move cursor to end and cancel highlighting + } + // default case: + // if enter pressed while drop down is open, or for FilteringSelect, + // if we are in the middle of a query to convert a directly typed in value to an item, + // prevent submit, but allow event to bubble + if(this._opened || this._fetchHandle){ + evt.preventDefault(); + } + // fall through + + case dk.TAB: + var newvalue = this.get('displayedValue'); + // if the user had More Choices selected fall into the + // _onBlur handler + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"]) + ){ + break; + } + if(highlighted){ + this._selectOption(); + } + if(this._opened){ + this._lastQuery = null; // in case results come back later + this.closeDropDown(); + } + break; + + case ' ': + if(highlighted){ + // user is effectively clicking a choice in the drop down menu + dojo.stopEvent(evt); + this._selectOption(); + this.closeDropDown(); + }else{ + // user typed a space into the input box, treat as normal character + doSearch = true; + } + break; + + case dk.DELETE: + case dk.BACKSPACE: + this._prev_key_backspace = true; + doSearch = true; + break; + + default: + // Non char keys (F1-F12 etc..) shouldn't open list. + // Ascii characters and IME input (Chinese, Japanese etc.) should. + //IME input produces keycode == 229. + doSearch = typeof key == 'string' || key == 229; + } + if(doSearch){ + // need to wait a tad before start search so that the event + // bubbles through DOM and we have value visible + this.item = undefined; // undefined means item needs to be set + this.searchTimer = setTimeout(dojo.hitch(this, "_startSearchFromInput"),1); + } + }, + + _autoCompleteText: function(/*String*/ text){ + // summary: + // Fill in the textbox with the first item from the drop down + // list, and highlight the characters that were + // auto-completed. For example, if user typed "CA" and the + // drop down list appeared, the textbox would be changed to + // "California" and "ifornia" would be highlighted. + + var fn = this.focusNode; + + // IE7: clear selection so next highlight works all the time + dijit.selectInputText(fn, fn.value.length); + // does text autoComplete the value in the textbox? + var caseFilter = this.ignoreCase? 'toLowerCase' : 'substr'; + if(text[caseFilter](0).indexOf(this.focusNode.value[caseFilter](0)) == 0){ + var cpos = this._getCaretPos(fn); + // only try to extend if we added the last character at the end of the input + if((cpos+1) > fn.value.length){ + // only add to input node as we would overwrite Capitalisation of chars + // actually, that is ok + fn.value = text;//.substr(cpos); + // visually highlight the autocompleted characters + dijit.selectInputText(fn, cpos); + } + }else{ + // text does not autoComplete; replace the whole value and highlight + fn.value = text; + dijit.selectInputText(fn); + } + }, + + _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ + // summary: + // Callback when a search completes. + // description: + // 1. generates drop-down list and calls _showResultList() to display it + // 2. if this result list is from user pressing "more choices"/"previous choices" + // then tell screen reader to announce new option + this._fetchHandle = null; + if( this.disabled || + this.readOnly || + (dataObject.query[this.searchAttr] != this._lastQuery) + ){ + return; + } + var wasSelected = this.dropDown._highlighted_option && dojo.hasClass(this.dropDown._highlighted_option, "dijitMenuItemSelected"); + this.dropDown.clearResultList(); + if(!results.length && !this._maxOptions){ // if no results and not just the previous choices button + this.closeDropDown(); + return; + } + + // Fill in the textbox with the first item from the drop down list, + // and highlight the characters that were auto-completed. For + // example, if user typed "CA" and the drop down list appeared, the + // textbox would be changed to "California" and "ifornia" would be + // highlighted. + + dataObject._maxOptions = this._maxOptions; + var nodes = this.dropDown.createOptions( + results, + dataObject, + dojo.hitch(this, "_getMenuLabelFromItem") + ); + + // show our list (only if we have content, else nothing) + this._showResultList(); + + // #4091: + // tell the screen reader that the paging callback finished by + // shouting the next choice + if(dataObject.direction){ + if(1 == dataObject.direction){ + this.dropDown.highlightFirstOption(); + }else if(-1 == dataObject.direction){ + this.dropDown.highlightLastOption(); + } + if(wasSelected){ + this._announceOption(this.dropDown.getHighlightedOption()); + } + }else if(this.autoComplete && !this._prev_key_backspace + // when the user clicks the arrow button to show the full list, + // startSearch looks for "*". + // it does not make sense to autocomplete + // if they are just previewing the options available. + && !/^[*]+$/.test(dataObject.query[this.searchAttr])){ + this._announceOption(nodes[1]); // 1st real item + } + }, + + _showResultList: function(){ + // summary: + // Display the drop down if not already displayed, or if it is displayed, then + // reposition it if necessary (reposition may be necessary if drop down's height changed). + + this.closeDropDown(true); + + // hide the tooltip + this.displayMessage(""); + + this.openDropDown(); + + dijit.setWaiState(this.domNode, "expanded", "true"); + }, + + loadDropDown: function(/*Function*/ callback){ + // Overrides _HasDropDown.loadDropDown(). + // This is called when user has pressed button icon or pressed the down arrow key + // to open the drop down. + + this._startSearchAll(); + }, + + isLoaded: function(){ + // signal to _HasDropDown that it needs to call loadDropDown() to load the + // drop down asynchronously before displaying it + return false; + }, + + closeDropDown: function(){ + // Overrides _HasDropDown.closeDropDown(). Closes the drop down (assuming that it's open). + // This method is the callback when the user types ESC or clicking + // the button icon while the drop down is open. It's also called by other code. + this._abortQuery(); + if(this._opened){ + this.inherited(arguments); + dijit.setWaiState(this.domNode, "expanded", "false"); + dijit.removeWaiState(this.focusNode,"activedescendant"); + } + }, + + _setBlurValue: function(){ + // if the user clicks away from the textbox OR tabs away, set the + // value to the textbox value + // #4617: + // if value is now more choices or previous choices, revert + // the value + var newvalue = this.get('displayedValue'); + var pw = this.dropDown; + if(pw && ( + newvalue == pw._messages["previousMessage"] || + newvalue == pw._messages["nextMessage"] + ) + ){ + this._setValueAttr(this._lastValueReported, true); + }else if(typeof this.item == "undefined"){ + // Update 'value' (ex: KY) according to currently displayed text + this.item = null; + this.set('displayedValue', newvalue); + }else{ + if(this.value != this._lastValueReported){ + dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); + } + this._refreshState(); + } + }, + + _onBlur: function(){ + // summary: + // Called magically when focus has shifted away from this widget and it's drop down + this.closeDropDown(); + this.inherited(arguments); + }, + + _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Set the displayed valued in the input box, and the hidden value + // that gets submitted, based on a dojo.data store item. + // description: + // Users shouldn't call this function; they should be calling + // set('item', value) + // tags: + // private + if(!displayedValue){ + displayedValue = this.store.getValue(item, this.searchAttr); + } + var value = this._getValueField() != this.searchAttr? this.store.getIdentity(item) : displayedValue; + this._set("item", item); + dijit.form.ComboBox.superclass._setValueAttr.call(this, value, priorityChange, displayedValue); + }, + + _announceOption: function(/*Node*/ node){ + // summary: + // a11y code that puts the highlighted option in the textbox. + // This way screen readers will know what is happening in the + // menu. + + if(!node){ + return; + } + // pull the text value from the item attached to the DOM node + var newValue; + if(node == this.dropDown.nextButton || + node == this.dropDown.previousButton){ + newValue = node.innerHTML; + this.item = undefined; + this.value = ''; + }else{ + newValue = this.store.getValue(node.item, this.searchAttr).toString(); + this.set('item', node.item, false, newValue); + } + // get the text that the user manually entered (cut off autocompleted text) + this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); + // set up ARIA activedescendant + dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); + // autocomplete the rest of the option to announce change + this._autoCompleteText(newValue); + }, + + _selectOption: function(/*Event*/ evt){ + // summary: + // Menu callback function, called when an item in the menu is selected. + if(evt){ + this._announceOption(evt.target); + } + this.closeDropDown(); + this._setCaretPos(this.focusNode, this.focusNode.value.length); + dijit.form._FormValueWidget.prototype._setValueAttr.call(this, this.value, true); // set this.value and fire onChange + }, + + _startSearchAll: function(){ + this._startSearch(''); + }, + + _startSearchFromInput: function(){ + this._startSearch(this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); + }, + + _getQueryString: function(/*String*/ text){ + return dojo.string.substitute(this.queryExpr, [text]); + }, + + _startSearch: function(/*String*/ key){ + // summary: + // Starts a search for elements matching key (key=="" means to return all items), + // and calls _openResultList() when the search completes, to display the results. + if(!this.dropDown){ + var popupId = this.id + "_popup", + dropDownConstructor = dojo.getObject(this.dropDownClass, false); + this.dropDown = new dropDownConstructor({ + onChange: dojo.hitch(this, this._selectOption), + id: popupId, + dir: this.dir + }); + dijit.removeWaiState(this.focusNode,"activedescendant"); + dijit.setWaiState(this.textbox,"owns",popupId); // associate popup with textbox + } + // create a new query to prevent accidentally querying for a hidden + // value from FilteringSelect's keyField + var query = dojo.clone(this.query); // #5970 + this._lastInput = key; // Store exactly what was entered by the user. + this._lastQuery = query[this.searchAttr] = this._getQueryString(key); + // #5970: set _lastQuery, *then* start the timeout + // otherwise, if the user types and the last query returns before the timeout, + // _lastQuery won't be set and their input gets rewritten + this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ + this.searchTimer = null; + var fetch = { + queryOptions: { + ignoreCase: this.ignoreCase, + deep: true + }, + query: query, + onBegin: dojo.hitch(this, "_setMaxOptions"), + onComplete: dojo.hitch(this, "_openResultList"), + onError: function(errText){ + _this._fetchHandle = null; + console.error('dijit.form.ComboBox: ' + errText); + _this.closeDropDown(); + }, + start: 0, + count: this.pageSize + }; + dojo.mixin(fetch, _this.fetchProperties); + this._fetchHandle = _this.store.fetch(fetch); + + var nextSearch = function(dataObject, direction){ + dataObject.start += dataObject.count*direction; + // #4091: + // tell callback the direction of the paging so the screen + // reader knows which menu option to shout + dataObject.direction = direction; + this._fetchHandle = this.store.fetch(dataObject); + this.focus(); + }; + this._nextSearch = this.dropDown.onPage = dojo.hitch(this, nextSearch, this._fetchHandle); + }, query, this), this.searchDelay); + }, + + _setMaxOptions: function(size, request){ + this._maxOptions = size; + }, + + _getValueField: function(){ + // summary: + // Helper for postMixInProperties() to set this.value based on data inlined into the markup. + // Returns the attribute name in the item (in dijit.form._ComboBoxDataStore) to use as the value. + return this.searchAttr; + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.query={}; + this.fetchProperties={}; + }, + + postMixInProperties: function(){ + if(!this.store){ + var srcNodeRef = this.srcNodeRef; + + // if user didn't specify store, then assume there are option tags + this.store = new dijit.form._ComboBoxDataStore(srcNodeRef); + + // if there is no value set and there is an option list, set + // the value to the first value to be consistent with native + // Select + + // Firefox and Safari set value + // IE6 and Opera set selectedIndex, which is automatically set + // by the selected attribute of an option tag + // IE6 does not set value, Opera sets value = selectedIndex + if(!("value" in this.params)){ + var item = (this.item = this.store.fetchSelectedItem()); + if(item){ + var valueField = this._getValueField(); + this.value = this.store.getValue(item, valueField); + } + } + } + + this.inherited(arguments); + }, + + postCreate: function(){ + // summary: + // Subclasses must call this method from their postCreate() methods + // tags: + // protected + + // find any associated label element and add to ComboBox node. + var label=dojo.query('label[for="'+this.id+'"]'); + if(label.length){ + label[0].id = (this.id+"_label"); + dijit.setWaiState(this.domNode, "labelledby", label[0].id); + + } + this.inherited(arguments); + }, + + _setHasDownArrowAttr: function(val){ + this.hasDownArrow = val; + this._buttonNode.style.display = val ? "" : "none"; + }, + + _getMenuLabelFromItem: function(/*Item*/ item){ + var label = this.labelFunc(item, this.store), + labelType = this.labelType; + // If labelType is not "text" we don't want to screw any markup ot whatever. + if(this.highlightMatch != "none" && this.labelType == "text" && this._lastInput){ + label = this.doHighlight(label, this._escapeHtml(this._lastInput)); + labelType = "html"; + } + return {html: labelType == "html", label: label}; + }, + + doHighlight: function(/*String*/ label, /*String*/ find){ + // summary: + // Highlights the string entered by the user in the menu. By default this + // highlights the first occurrence found. Override this method + // to implement your custom highlighting. + // tags: + // protected + + var + // Add (g)lobal modifier when this.highlightMatch == "all" and (i)gnorecase when this.ignoreCase == true + modifiers = (this.ignoreCase ? "i" : "") + (this.highlightMatch == "all" ? "g" : ""), + i = this.queryExpr.indexOf("${0}"); + find = dojo.regexp.escapeString(find); // escape regexp special chars + return this._escapeHtml(label).replace( + // prepend ^ when this.queryExpr == "${0}*" and append $ when this.queryExpr == "*${0}" + new RegExp((i == 0 ? "^" : "") + "("+ find +")" + (i == (this.queryExpr.length - 4) ? "$" : ""), modifiers), + '<span class="dijitComboBoxHighlightMatch">$1</span>' + ); // returns String, (almost) valid HTML (entities encoded) + }, + + _escapeHtml: function(/*String*/ str){ + // TODO Should become dojo.html.entities(), when exists use instead + // summary: + // Adds escape sequences for special characters in XML: &<>"' + str = String(str).replace(/&/gm, "&").replace(/</gm, "<") + .replace(/>/gm, ">").replace(/"/gm, """); + return str; // string + }, + + reset: function(){ + // Overrides the _FormWidget.reset(). + // Additionally reset the .item (to clean up). + this.item = null; + this.inherited(arguments); + }, + + labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ + // summary: + // Computes the label to display based on the dojo.data store item. + // returns: + // The label that the ComboBox should display + // tags: + // private + + // Use toString() because XMLStore returns an XMLItem whereas this + // method is expected to return a String (#9354) + return store.getValue(item, this.labelAttr || this.searchAttr).toString(); // String + } + } +); + +dojo.declare( + "dijit.form._ComboBoxMenu", + [dijit._Widget, dijit._Templated, dijit._CssStateMixin], + { + // summary: + // Focus-less menu for internal use in `dijit.form.ComboBox` + // tags: + // private + + templateString: "<ul class='dijitReset dijitMenu' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' style='overflow: \"auto\"; overflow-x: \"hidden\";'>" + +"<li class='dijitMenuItem dijitMenuPreviousButton' dojoAttachPoint='previousButton' role='option'></li>" + +"<li class='dijitMenuItem dijitMenuNextButton' dojoAttachPoint='nextButton' role='option'></li>" + +"</ul>", + + // _messages: Object + // Holds "next" and "previous" text for paging buttons on drop down + _messages: null, + + baseClass: "dijitComboBoxMenu", + + postMixInProperties: function(){ + this.inherited(arguments); + this._messages = dojo.i18n.getLocalization("dijit.form", "ComboBox", this.lang); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // fill in template with i18n messages + this.previousButton.innerHTML = this._messages["previousMessage"]; + this.nextButton.innerHTML = this._messages["nextMessage"]; + }, + + _setValueAttr: function(/*Object*/ value){ + this.value = value; + this.onChange(value); + }, + + // stubs + onChange: function(/*Object*/ value){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked an option in the drop down menu. + // Probably should be called onSelect. + // tags: + // callback + }, + onPage: function(/*Number*/ direction){ + // summary: + // Notifies ComboBox/FilteringSelect that user clicked to advance to next/previous page. + // tags: + // callback + }, + + onClose: function(){ + // summary: + // Callback from dijit.popup code to this widget, notifying it that it closed + // tags: + // private + this._blurOptionNode(); + }, + + _createOption: function(/*Object*/ item, labelFunc){ + // summary: + // Creates an option to appear on the popup menu subclassed by + // `dijit.form.FilteringSelect`. + + var menuitem = dojo.create("li", { + "class": "dijitReset dijitMenuItem" +(this.isLeftToRight() ? "" : " dijitMenuItemRtl"), + role: "option" + }); + var labelObject = labelFunc(item); + if(labelObject.html){ + menuitem.innerHTML = labelObject.label; + }else{ + menuitem.appendChild( + dojo.doc.createTextNode(labelObject.label) + ); + } + // #3250: in blank options, assign a normal height + if(menuitem.innerHTML == ""){ + menuitem.innerHTML = " "; + } + menuitem.item=item; + return menuitem; + }, + + createOptions: function(results, dataObject, labelFunc){ + // summary: + // Fills in the items in the drop down list + // results: + // Array of dojo.data items + // dataObject: + // dojo.data store + // labelFunc: + // Function to produce a label in the drop down list from a dojo.data item + + //this._dataObject=dataObject; + //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList); + // display "Previous . . ." button + this.previousButton.style.display = (dataObject.start == 0) ? "none" : ""; + dojo.attr(this.previousButton, "id", this.id + "_prev"); + // create options using _createOption function defined by parent + // ComboBox (or FilteringSelect) class + // #2309: + // iterate over cache nondestructively + dojo.forEach(results, function(item, i){ + var menuitem = this._createOption(item, labelFunc); + dojo.attr(menuitem, "id", this.id + i); + this.domNode.insertBefore(menuitem, this.nextButton); + }, this); + // display "Next . . ." button + var displayMore = false; + //Try to determine if we should show 'more'... + if(dataObject._maxOptions && dataObject._maxOptions != -1){ + if((dataObject.start + dataObject.count) < dataObject._maxOptions){ + displayMore = true; + }else if((dataObject.start + dataObject.count) > dataObject._maxOptions && dataObject.count == results.length){ + //Weird return from a datastore, where a start + count > maxOptions + // implies maxOptions isn't really valid and we have to go into faking it. + //And more or less assume more if count == results.length + displayMore = true; + } + }else if(dataObject.count == results.length){ + //Don't know the size, so we do the best we can based off count alone. + //So, if we have an exact match to count, assume more. + displayMore = true; + } + + this.nextButton.style.display = displayMore ? "" : "none"; + dojo.attr(this.nextButton,"id", this.id + "_next"); + return this.domNode.childNodes; + }, + + clearResultList: function(){ + // summary: + // Clears the entries in the drop down list, but of course keeps the previous and next buttons. + while(this.domNode.childNodes.length>2){ + this.domNode.removeChild(this.domNode.childNodes[this.domNode.childNodes.length-2]); + } + this._blurOptionNode(); + }, + + _onMouseDown: function(/*Event*/ evt){ + dojo.stopEvent(evt); + }, + + _onMouseUp: function(/*Event*/ evt){ + if(evt.target === this.domNode || !this._highlighted_option){ + // !this._highlighted_option check to prevent immediate selection when menu appears on top + // of <input>, see #9898. Note that _HasDropDown also has code to prevent this. + return; + }else if(evt.target == this.previousButton){ + this._blurOptionNode(); + this.onPage(-1); + }else if(evt.target == this.nextButton){ + this._blurOptionNode(); + this.onPage(1); + }else{ + var tgt = evt.target; + // while the clicked node is inside the div + while(!tgt.item){ + // recurse to the top + tgt = tgt.parentNode; + } + this._setValueAttr({ target: tgt }, true); + } + }, + + _onMouseOver: function(/*Event*/ evt){ + if(evt.target === this.domNode){ return; } + var tgt = evt.target; + if(!(tgt == this.previousButton || tgt == this.nextButton)){ + // while the clicked node is inside the div + while(!tgt.item){ + // recurse to the top + tgt = tgt.parentNode; + } + } + this._focusOptionNode(tgt); + }, + + _onMouseOut: function(/*Event*/ evt){ + if(evt.target === this.domNode){ return; } + this._blurOptionNode(); + }, + + _focusOptionNode: function(/*DomNode*/ node){ + // summary: + // Does the actual highlight. + if(this._highlighted_option != node){ + this._blurOptionNode(); + this._highlighted_option = node; + dojo.addClass(this._highlighted_option, "dijitMenuItemSelected"); + } + }, + + _blurOptionNode: function(){ + // summary: + // Removes highlight on highlighted option. + if(this._highlighted_option){ + dojo.removeClass(this._highlighted_option, "dijitMenuItemSelected"); + this._highlighted_option = null; + } + }, + + _highlightNextOption: function(){ + // summary: + // Highlight the item just below the current selection. + // If nothing selected, highlight first option. + + // because each press of a button clears the menu, + // the highlighted option sometimes becomes detached from the menu! + // test to see if the option has a parent to see if this is the case. + if(!this.getHighlightedOption()){ + var fc = this.domNode.firstChild; + this._focusOptionNode(fc.style.display == "none" ? fc.nextSibling : fc); + }else{ + var ns = this._highlighted_option.nextSibling; + if(ns && ns.style.display != "none"){ + this._focusOptionNode(ns); + }else{ + this.highlightFirstOption(); + } + } + // scrollIntoView is called outside of _focusOptionNode because in IE putting it inside causes the menu to scroll up on mouseover + dojo.window.scrollIntoView(this._highlighted_option); + }, + + highlightFirstOption: function(){ + // summary: + // Highlight the first real item in the list (not Previous Choices). + var first = this.domNode.firstChild; + var second = first.nextSibling; + this._focusOptionNode(second.style.display == "none" ? first : second); // remotely possible that Previous Choices is the only thing in the list + dojo.window.scrollIntoView(this._highlighted_option); + }, + + highlightLastOption: function(){ + // summary: + // Highlight the last real item in the list (not More Choices). + this._focusOptionNode(this.domNode.lastChild.previousSibling); + dojo.window.scrollIntoView(this._highlighted_option); + }, + + _highlightPrevOption: function(){ + // summary: + // Highlight the item just above the current selection. + // If nothing selected, highlight last option (if + // you select Previous and try to keep scrolling up the list). + if(!this.getHighlightedOption()){ + var lc = this.domNode.lastChild; + this._focusOptionNode(lc.style.display == "none" ? lc.previousSibling : lc); + }else{ + var ps = this._highlighted_option.previousSibling; + if(ps && ps.style.display != "none"){ + this._focusOptionNode(ps); + }else{ + this.highlightLastOption(); + } + } + dojo.window.scrollIntoView(this._highlighted_option); + }, + + _page: function(/*Boolean*/ up){ + // summary: + // Handles page-up and page-down keypresses + + var scrollamount = 0; + var oldscroll = this.domNode.scrollTop; + var height = dojo.style(this.domNode, "height"); + // if no item is highlighted, highlight the first option + if(!this.getHighlightedOption()){ + this._highlightNextOption(); + } + while(scrollamount<height){ + if(up){ + // stop at option 1 + if(!this.getHighlightedOption().previousSibling || + this._highlighted_option.previousSibling.style.display == "none"){ + break; + } + this._highlightPrevOption(); + }else{ + // stop at last option + if(!this.getHighlightedOption().nextSibling || + this._highlighted_option.nextSibling.style.display == "none"){ + break; + } + this._highlightNextOption(); + } + // going backwards + var newscroll=this.domNode.scrollTop; + scrollamount+=(newscroll-oldscroll)*(up ? -1:1); + oldscroll=newscroll; + } + }, + + pageUp: function(){ + // summary: + // Handles pageup keypress. + // TODO: just call _page directly from handleKey(). + // tags: + // private + this._page(true); + }, + + pageDown: function(){ + // summary: + // Handles pagedown keypress. + // TODO: just call _page directly from handleKey(). + // tags: + // private + this._page(false); + }, + + getHighlightedOption: function(){ + // summary: + // Returns the highlighted option. + var ho = this._highlighted_option; + return (ho && ho.parentNode) ? ho : null; + }, + + handleKey: function(evt){ + // summary: + // Handle keystroke event forwarded from ComboBox, returning false if it's + // a keystroke I recognize and process, true otherwise. + switch(evt.charOrCode){ + case dojo.keys.DOWN_ARROW: + this._highlightNextOption(); + return false; + case dojo.keys.PAGE_DOWN: + this.pageDown(); + return false; + case dojo.keys.UP_ARROW: + this._highlightPrevOption(); + return false; + case dojo.keys.PAGE_UP: + this.pageUp(); + return false; + default: + return true; + } + } + } +); + +dojo.declare( + "dijit.form.ComboBox", + [dijit.form.ValidationTextBox, dijit.form.ComboBoxMixin], + { + // summary: + // Auto-completing text box, and base class for dijit.form.FilteringSelect. + // + // description: + // The drop down box's values are populated from an class called + // a data provider, which returns a list of values based on the characters + // that the user has typed into the input box. + // If OPTION tags are used as the data provider via markup, + // then the OPTION tag's child text node is used as the widget value + // when selected. The OPTION tag's value attribute is ignored. + // To set the default value when using OPTION tags, specify the selected + // attribute on 1 of the child OPTION tags. + // + // Some of the options to the ComboBox are actually arguments to the data + // provider. + + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Hook so set('value', value) works. + // description: + // Sets the value of the select. + this._set("item", null); // value not looked up in store + if(!value){ value = ''; } // null translates to blank + dijit.form.ValidationTextBox.prototype._setValueAttr.call(this, value, priorityChange, displayedValue); + } + } +); + +dojo.declare("dijit.form._ComboBoxDataStore", null, { + // summary: + // Inefficient but small data store specialized for inlined `dijit.form.ComboBox` data + // + // description: + // Provides a store for inlined data like: + // + // | <select> + // | <option value="AL">Alabama</option> + // | ... + // + // Actually. just implements the subset of dojo.data.Read/Notification + // needed for ComboBox and FilteringSelect to work. + // + // Note that an item is just a pointer to the <option> DomNode. + + constructor: function( /*DomNode*/ root){ + this.root = root; + if(root.tagName != "SELECT" && root.firstChild){ + root = dojo.query("select", root); + if(root.length > 0){ // SELECT is a child of srcNodeRef + root = root[0]; + }else{ // no select, so create 1 to parent the option tags to define selectedIndex + this.root.innerHTML = "<SELECT>"+this.root.innerHTML+"</SELECT>"; + root = this.root.firstChild; + } + this.root = root; + } + dojo.query("> option", root).forEach(function(node){ + // TODO: this was added in #3858 but unclear why/if it's needed; doesn't seem to be. + // If it is needed then can we just hide the select itself instead? + //node.style.display="none"; + node.innerHTML = dojo.trim(node.innerHTML); + }); + + }, + + getValue: function( /*item*/ item, + /*attribute-name-string*/ attribute, + /*value?*/ defaultValue){ + return (attribute == "value") ? item.value : (item.innerText || item.textContent || ''); + }, + + isItemLoaded: function(/*anything*/ something){ + return true; + }, + + getFeatures: function(){ + return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true}; + }, + + _fetchItems: function( /*Object*/ args, + /*Function*/ findCallback, + /*Function*/ errorCallback){ + // summary: + // See dojo.data.util.simpleFetch.fetch() + if(!args.query){ args.query = {}; } + if(!args.query.name){ args.query.name = ""; } + if(!args.queryOptions){ args.queryOptions = {}; } + var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase), + items = dojo.query("> option", this.root).filter(function(option){ + return (option.innerText || option.textContent || '').match(matcher); + } ); + if(args.sort){ + items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this)); + } + findCallback(items, args); + }, + + close: function(/*dojo.data.api.Request || args || null*/ request){ + return; + }, + + getLabel: function(/*item*/ item){ + return item.innerHTML; + }, + + getIdentity: function(/*item*/ item){ + return dojo.attr(item, "value"); + }, + + fetchItemByIdentity: function(/*Object*/ args){ + // summary: + // Given the identity of an item, this method returns the item that has + // that identity through the onItem callback. + // Refer to dojo.data.api.Identity.fetchItemByIdentity() for more details. + // + // description: + // Given arguments like: + // + // | {identity: "CA", onItem: function(item){...} + // + // Call `onItem()` with the DOM node `<option value="CA">California</option>` + var item = dojo.query("> option[value='" + args.identity + "']", this.root)[0]; + args.onItem(item); + }, + + fetchSelectedItem: function(){ + // summary: + // Get the option marked as selected, like `<option selected>`. + // Not part of dojo.data API. + var root = this.root, + si = root.selectedIndex; + return typeof si == "number" + ? dojo.query("> option:nth-child(" + (si != -1 ? si+1 : 1) + ")", root)[0] + : null; // dojo.data.Item + } }); -if(_53.sort){ -_57.sort(dojo.data.util.sorter.createSortFunction(_53.sort,this)); -} -_54(_57,_53); -},close:function(_59){ -return; -},getLabel:function(_5a){ -return _5a.innerHTML; -},getIdentity:function(_5b){ -return dojo.attr(_5b,"value"); -},fetchItemByIdentity:function(_5c){ -var _5d=dojo.query("> option[value='"+_5c.identity+"']",this.root)[0]; -_5c.onItem(_5d); -},fetchSelectedItem:function(){ -var _5e=this.root,si=_5e.selectedIndex; -return typeof si=="number"?dojo.query("> option:nth-child("+(si!=-1?si+1:1)+")",_5e)[0]:null; -}}); +//Mix in the simple fetch implementation to this class. dojo.extend(dijit.form._ComboBoxDataStore,dojo.data.util.simpleFetch); + } diff --git a/lib/dijit/form/ComboButton.js b/lib/dijit/form/ComboButton.js index db2d04aea..d311582ac 100644 --- a/lib/dijit/form/ComboButton.js +++ b/lib/dijit/form/ComboButton.js @@ -1,12 +1,15 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.ComboButton"]){ -dojo._hasResource["dijit.form.ComboButton"]=true; +if(!dojo._hasResource["dijit.form.ComboButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ComboButton"] = true; dojo.provide("dijit.form.ComboButton"); dojo.require("dijit.form.Button"); + + + } diff --git a/lib/dijit/form/CurrencyTextBox.js b/lib/dijit/form/CurrencyTextBox.js index a6519b2a7..cd0d389a0 100644 --- a/lib/dijit/form/CurrencyTextBox.js +++ b/lib/dijit/form/CurrencyTextBox.js @@ -1,27 +1,96 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.CurrencyTextBox"]){ -dojo._hasResource["dijit.form.CurrencyTextBox"]=true; +if(!dojo._hasResource["dijit.form.CurrencyTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.CurrencyTextBox"] = true; dojo.provide("dijit.form.CurrencyTextBox"); dojo.require("dojo.currency"); dojo.require("dijit.form.NumberTextBox"); -dojo.declare("dijit.form.CurrencyTextBox",dijit.form.NumberTextBox,{currency:"",baseClass:"dijitTextBox dijitCurrencyTextBox",regExpGen:function(_1){ -return "("+(this._focused?this.inherited(arguments,[dojo.mixin({},_1,this.editOptions)])+"|":"")+dojo.currency.regexp(_1)+")"; -},_formatter:dojo.currency.format,parse:function(_2,_3){ -var v=dojo.currency.parse(_2,_3); -if(isNaN(v)&&/\d+/.test(_2)){ -return this.inherited(arguments,[_2,dojo.mixin({},_3,this.editOptions)]); -} -return v; -},_setConstraintsAttr:function(_4){ -if(!_4.currency&&this.currency){ -_4.currency=this.currency; -} -this.inherited(arguments,[dojo.currency._mixInDefaults(dojo.mixin(_4,{exponent:false}))]); -}}); + + +/*===== +dojo.declare( + "dijit.form.CurrencyTextBox.__Constraints", + [dijit.form.NumberTextBox.__Constraints, dojo.currency.__FormatOptions, dojo.currency.__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 (currency symbol, + // etc.) + // description: + // Follows the pattern of `dijit.form.NumberTextBox.constraints`. + // In general developers won't need to set this parameter + // example: + // To ensure that the user types in the cents (for example, 1.00 instead of just 1): + // | {fractional:true} +}); +=====*/ + +dojo.declare( + "dijit.form.CurrencyTextBox", + dijit.form.NumberTextBox, + { + // summary: + // A validating currency textbox + // description: + // CurrencyTextBox is similar to `dijit.form.NumberTextBox` but has a few + // extra features related to currency: + // + // 1. After specifying the currency type (american dollars, euros, etc.) it automatically + // sets parse/format options such as how many decimal places to show. + // 2. The currency mark (dollar sign, euro mark, etc.) is displayed when the field is blurred + // but erased during editing, so that the user can just enter a plain number. + + // currency: [const] String + // the [ISO4217](http://en.wikipedia.org/wiki/ISO_4217) currency code, a three letter sequence like "USD" + currency: "", + + /*===== + // constraints: dijit.form.CurrencyTextBox.__Constraints + // Despite the name, this parameter specifies both constraints on the input + // (including minimum/maximum allowed values) as well as + // formatting options. See `dijit.form.CurrencyTextBox.__Constraints` for details. + constraints: {}, + ======*/ + + baseClass: "dijitTextBox dijitCurrencyTextBox", + + // Override regExpGen ValidationTextBox.regExpGen().... we use a reg-ex generating function rather + // than a straight regexp to deal with locale (plus formatting options too?) + regExpGen: function(constraints){ + // if focused, accept either currency data or NumberTextBox format + return '(' + (this._focused? this.inherited(arguments, [ dojo.mixin({}, constraints, this.editOptions) ]) + '|' : '') + + dojo.currency.regexp(constraints) + ')'; + }, + + // Override NumberTextBox._formatter to deal with currencies, ex: converts "123.45" to "$123.45" + _formatter: dojo.currency.format, + + _parser: dojo.currency.parse, + + parse: function(/*String*/ value, /*Object*/ constraints){ + // summary: + // Parses string value as a Currency, according to the constraints object + // tags: + // protected extension + var v = this.inherited(arguments); + if(isNaN(v) && /\d+/.test(value)){ // currency parse failed, but it could be because they are using NumberTextBox format so try its parse + v = dojo.hitch(dojo.mixin({}, this, { _parser: dijit.form.NumberTextBox.prototype._parser }), "inherited")(arguments); + } + return v; + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + if(!constraints.currency && this.currency){ + constraints.currency = this.currency; + } + this.inherited(arguments, [ dojo.currency._mixInDefaults(dojo.mixin(constraints, { exponent: false })) ]); // get places + } + } +); + } diff --git a/lib/dijit/form/DateTextBox.js b/lib/dijit/form/DateTextBox.js index d040be5f8..3d929ae76 100644 --- a/lib/dijit/form/DateTextBox.js +++ b/lib/dijit/form/DateTextBox.js @@ -1,14 +1,40 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.DateTextBox"]){ -dojo._hasResource["dijit.form.DateTextBox"]=true; +if(!dojo._hasResource["dijit.form.DateTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.DateTextBox"] = true; dojo.provide("dijit.form.DateTextBox"); dojo.require("dijit.Calendar"); dojo.require("dijit.form._DateTimeTextBox"); -dojo.declare("dijit.form.DateTextBox",dijit.form._DateTimeTextBox,{baseClass:"dijitTextBox dijitDateTextBox",popupClass:"dijit.Calendar",_selector:"date",value:new Date("")}); + + +dojo.declare( + "dijit.form.DateTextBox", + dijit.form._DateTimeTextBox, + { + // summary: + // A validating, serializable, range-bound date text box with a drop down calendar + // + // Example: + // | new dijit.form.DateTextBox({value: new Date(2009, 0, 20)}) + // + // Example: + // | <input dojotype='dijit.form.DateTextBox' value='2009-01-20'> + + baseClass: "dijitTextBox dijitComboBox dijitDateTextBox", + popupClass: "dijit.Calendar", + _selector: "date", + + // value: Date + // The value of this widget as a JavaScript Date object, with only year/month/day specified. + // If specified in markup, use the format specified in `dojo.date.stamp.fromISOString`. + // set("value", ...) accepts either a Date object or a string. + value: new Date("") // value.toString()="NaN" + } +); + } diff --git a/lib/dijit/form/DropDownButton.js b/lib/dijit/form/DropDownButton.js index 53f66278e..2489a4cdf 100644 --- a/lib/dijit/form/DropDownButton.js +++ b/lib/dijit/form/DropDownButton.js @@ -1,12 +1,15 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.DropDownButton"]){ -dojo._hasResource["dijit.form.DropDownButton"]=true; +if(!dojo._hasResource["dijit.form.DropDownButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.DropDownButton"] = true; dojo.provide("dijit.form.DropDownButton"); dojo.require("dijit.form.Button"); + + + } diff --git a/lib/dijit/form/FilteringSelect.js b/lib/dijit/form/FilteringSelect.js index 08703226c..81b44ff3b 100644 --- a/lib/dijit/form/FilteringSelect.js +++ b/lib/dijit/form/FilteringSelect.js @@ -1,92 +1,227 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.FilteringSelect"]){ -dojo._hasResource["dijit.form.FilteringSelect"]=true; +if(!dojo._hasResource["dijit.form.FilteringSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.FilteringSelect"] = true; dojo.provide("dijit.form.FilteringSelect"); dojo.require("dijit.form.ComboBox"); -dojo.declare("dijit.form.FilteringSelect",[dijit.form.MappedTextBox,dijit.form.ComboBoxMixin],{_isvalid:true,required:true,_lastDisplayedValue:"",isValid:function(){ -return this._isvalid||(!this.required&&this.get("displayedValue")==""); -},_refreshState:function(){ -if(!this.searchTimer){ -this.inherited(arguments); -} -},_callbackSetLabel:function(_1,_2,_3){ -if((_2&&_2.query[this.searchAttr]!=this._lastQuery)||(!_2&&_1.length&&this.store.getIdentity(_1[0])!=this._lastQuery)){ -return; -} -if(!_1.length){ -this.valueNode.value=""; -dijit.form.TextBox.superclass._setValueAttr.call(this,"",_3||(_3===undefined&&!this._focused)); -this._isvalid=false; -this.validate(this._focused); -this.item=null; -}else{ -this.set("item",_1[0],_3); -} -},_openResultList:function(_4,_5){ -if(_5.query[this.searchAttr]!=this._lastQuery){ -return; -} -if(this.item===undefined){ -this._isvalid=_4.length!=0||this._maxOptions!=0; -this.validate(true); -} -dijit.form.ComboBoxMixin.prototype._openResultList.apply(this,arguments); -},_getValueAttr:function(){ -return this.valueNode.value; -},_getValueField:function(){ -return "value"; -},_setValueAttr:function(_6,_7){ -if(!this._onChangeActive){ -_7=null; -} -this._lastQuery=_6; -if(_6===null||_6===""){ -this._setDisplayedValueAttr("",_7); -return; -} -var _8=this; -this.store.fetchItemByIdentity({identity:_6,onItem:function(_9){ -_8._callbackSetLabel(_9?[_9]:[],undefined,_7); -}}); -},_setItemAttr:function(_a,_b,_c){ -this._isvalid=true; -this.inherited(arguments); -this.valueNode.value=this.value; -this._lastDisplayedValue=this.textbox.value; -},_getDisplayQueryString:function(_d){ -return _d.replace(/([\\\*\?])/g,"\\$1"); -},_setDisplayedValueAttr:function(_e,_f){ -if(!this._created){ -_f=false; -} -if(this.store){ -this._hideResultList(); -var _10=dojo.clone(this.query); -this._lastQuery=_10[this.searchAttr]=this._getDisplayQueryString(_e); -this.textbox.value=_e; -this._lastDisplayedValue=_e; -var _11=this; -var _12={query:_10,queryOptions:{ignoreCase:this.ignoreCase,deep:true},onComplete:function(_13,_14){ -_11._fetchHandle=null; -dojo.hitch(_11,"_callbackSetLabel")(_13,_14,_f); -},onError:function(_15){ -_11._fetchHandle=null; -console.error("dijit.form.FilteringSelect: "+_15); -dojo.hitch(_11,"_callbackSetLabel")([],undefined,false); -}}; -dojo.mixin(_12,this.fetchProperties); -this._fetchHandle=this.store.fetch(_12); -} -},postMixInProperties:function(){ -this.inherited(arguments); -this._isvalid=!this.required; -},undo:function(){ -this.set("displayedValue",this._lastDisplayedValue); -}}); + + +dojo.declare( + "dijit.form.FilteringSelect", + [dijit.form.MappedTextBox, dijit.form.ComboBoxMixin], + { + // summary: + // An enhanced version of the HTML SELECT tag, populated dynamically + // + // description: + // An enhanced version of the HTML SELECT tag, populated dynamically. It works + // very nicely with very large data sets because it can load and page data as needed. + // It also resembles ComboBox, but does not allow values outside of the provided ones. + // If OPTION tags are used as the data provider via markup, then the + // OPTION tag's child text node is used as the displayed value when selected + // while the OPTION tag's value attribute is used as the widget value on form submit. + // To set the default value when using OPTION tags, specify the selected + // attribute on 1 of the child OPTION tags. + // + // Similar features: + // - There is a drop down list of possible values. + // - You can only enter a value from the drop down list. (You can't + // enter an arbitrary value.) + // - The value submitted with the form is the hidden value (ex: CA), + // not the displayed value a.k.a. label (ex: California) + // + // Enhancements over plain HTML version: + // - If you type in some text then it will filter down the list of + // possible values in the drop down list. + // - List can be specified either as a static list or via a javascript + // function (that can get the list from a server) + + // required: Boolean + // True (default) if user is required to enter a value into this field. + required: true, + + _lastDisplayedValue: "", + + _isValidSubset: function(){ + return this._opened; + }, + + isValid: function(){ + // Overrides ValidationTextBox.isValid() + return this.item || (!this.required && this.get('displayedValue') == ""); // #5974 + }, + + _refreshState: function(){ + if(!this.searchTimer){ // state will be refreshed after results are returned + this.inherited(arguments); + } + }, + + _callbackSetLabel: function( + /*Array*/ result, + /*Object*/ dataObject, + /*Boolean?*/ priorityChange){ + // summary: + // Callback from dojo.data after lookup of user entered value finishes + + // setValue does a synchronous lookup, + // so it calls _callbackSetLabel directly, + // and so does not pass dataObject + // still need to test against _lastQuery in case it came too late + if((dataObject && dataObject.query[this.searchAttr] != this._lastQuery) || (!dataObject && result.length && this.store.getIdentity(result[0]) != this._lastQuery)){ + return; + } + if(!result.length){ + //#3268: don't modify display value on bad input + //#3285: change CSS to indicate error + this.valueNode.value = ""; + dijit.form.TextBox.superclass._setValueAttr.call(this, "", priorityChange || (priorityChange === undefined && !this._focused)); + this._set("item", null); + this.validate(this._focused); + }else{ + this.set('item', result[0], priorityChange); + } + }, + + _openResultList: function(/*Object*/ results, /*Object*/ dataObject){ + // Callback when a data store query completes. + // Overrides ComboBox._openResultList() + + // #3285: tap into search callback to see if user's query resembles a match + if(dataObject.query[this.searchAttr] != this._lastQuery){ + return; + } + dijit.form.ComboBoxMixin.prototype._openResultList.apply(this, arguments); + + if(this.item === undefined){ // item == undefined for keyboard search + // If the search returned no items that means that the user typed + // in something invalid (and they can't make it valid by typing more characters), + // so flag the FilteringSelect as being in an invalid state + this.validate(true); + } + }, + + _getValueAttr: function(){ + // summary: + // Hook for get('value') to work. + + // don't get the textbox value but rather the previously set hidden value. + // Use this.valueNode.value which isn't always set for other MappedTextBox widgets until blur + return this.valueNode.value; + }, + + _getValueField: function(){ + // Overrides ComboBox._getValueField() + return "value"; + }, + + _setValueAttr: function(/*String*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', value) works. + // description: + // Sets the value of the select. + // Also sets the label to the corresponding value by reverse lookup. + if(!this._onChangeActive){ priorityChange = null; } + this._lastQuery = value; + + if(value === null || value === ''){ + this._setDisplayedValueAttr('', priorityChange); + return; + } + + //#3347: fetchItemByIdentity if no keyAttr specified + var self = this; + this.store.fetchItemByIdentity({ + identity: value, + onItem: function(item){ + self._callbackSetLabel(item? [item] : [], undefined, priorityChange); + } + }); + }, + + _setItemAttr: function(/*item*/ item, /*Boolean?*/ priorityChange, /*String?*/ displayedValue){ + // summary: + // Set the displayed valued in the input box, and the hidden value + // that gets submitted, based on a dojo.data store item. + // description: + // Users shouldn't call this function; they should be calling + // set('item', value) + // tags: + // private + this.inherited(arguments); + this.valueNode.value = this.value; + this._lastDisplayedValue = this.textbox.value; + }, + + _getDisplayQueryString: function(/*String*/ text){ + return text.replace(/([\\\*\?])/g, "\\$1"); + }, + + _setDisplayedValueAttr: function(/*String*/ label, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('displayedValue', label) works. + // description: + // Sets textbox to display label. Also performs reverse lookup + // to set the hidden value. label should corresponding to item.searchAttr. + + if(label == null){ label = ''; } + + // This is called at initialization along with every custom setter. + // Usually (or always?) the call can be ignored. If it needs to be + // processed then at least make sure that the XHR request doesn't trigger an onChange() + // event, even if it returns after creation has finished + if(!this._created){ + if(!("displayedValue" in this.params)){ + return; + } + priorityChange = false; + } + + // Do a reverse lookup to map the specified displayedValue to the hidden value. + // Note that if there's a custom labelFunc() this code + if(this.store){ + this.closeDropDown(); + var query = dojo.clone(this.query); // #6196: populate query with user-specifics + // escape meta characters of dojo.data.util.filter.patternToRegExp(). + this._lastQuery = query[this.searchAttr] = this._getDisplayQueryString(label); + // If the label is not valid, the callback will never set it, + // so the last valid value will get the warning textbox. Set the + // textbox value now so that the impending warning will make + // sense to the user + this.textbox.value = label; + this._lastDisplayedValue = label; + this._set("displayedValue", label); // for watch("displayedValue") notification + var _this = this; + var fetch = { + query: query, + queryOptions: { + ignoreCase: this.ignoreCase, + deep: true + }, + onComplete: function(result, dataObject){ + _this._fetchHandle = null; + dojo.hitch(_this, "_callbackSetLabel")(result, dataObject, priorityChange); + }, + onError: function(errText){ + _this._fetchHandle = null; + console.error('dijit.form.FilteringSelect: ' + errText); + dojo.hitch(_this, "_callbackSetLabel")([], undefined, false); + } + }; + dojo.mixin(fetch, this.fetchProperties); + this._fetchHandle = this.store.fetch(fetch); + } + }, + + undo: function(){ + this.set('displayedValue', this._lastDisplayedValue); + } + } +); + } diff --git a/lib/dijit/form/Form.js b/lib/dijit/form/Form.js index 618700b5d..8eba470b9 100644 --- a/lib/dijit/form/Form.js +++ b/lib/dijit/form/Form.js @@ -1,64 +1,189 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.Form"]){ -dojo._hasResource["dijit.form.Form"]=true; +if(!dojo._hasResource["dijit.form.Form"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Form"] = true; dojo.provide("dijit.form.Form"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.require("dijit.form._FormMixin"); -dojo.declare("dijit.form.Form",[dijit._Widget,dijit._Templated,dijit.form._FormMixin],{name:"",action:"",method:"",encType:"","accept-charset":"",accept:"",target:"",templateString:"<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>",attributeMap:dojo.delegate(dijit._Widget.prototype.attributeMap,{action:"",method:"",encType:"","accept-charset":"",accept:"",target:""}),postMixInProperties:function(){ -this.nameAttrSetting=this.name?("name='"+this.name+"'"):""; -this.inherited(arguments); -},execute:function(_1){ -},onExecute:function(){ -},_setEncTypeAttr:function(_2){ -this.encType=_2; -dojo.attr(this.domNode,"encType",_2); -if(dojo.isIE){ -this.domNode.encoding=_2; -} -},postCreate:function(){ -if(dojo.isIE&&this.srcNodeRef&&this.srcNodeRef.attributes){ -var _3=this.srcNodeRef.attributes.getNamedItem("encType"); -if(_3&&!_3.specified&&(typeof _3.value=="string")){ -this.set("encType",_3.value); -} -} -this.inherited(arguments); -},reset:function(e){ -var _4={returnValue:true,preventDefault:function(){ -this.returnValue=false; -},stopPropagation:function(){ -},currentTarget:e?e.target:this.domNode,target:e?e.target:this.domNode}; -if(!(this.onReset(_4)===false)&&_4.returnValue){ -this.inherited(arguments,[]); -} -},onReset:function(e){ -return true; -},_onReset:function(e){ -this.reset(e); -dojo.stopEvent(e); -return false; -},_onSubmit:function(e){ -var fp=dijit.form.Form.prototype; -if(this.execute!=fp.execute||this.onExecute!=fp.onExecute){ -dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.","","2.0"); -this.onExecute(); -this.execute(this.getValues()); -} -if(this.onSubmit(e)===false){ -dojo.stopEvent(e); -} -},onSubmit:function(e){ -return this.isValid(); -},submit:function(){ -if(!(this.onSubmit()===false)){ -this.containerNode.submit(); -} -}}); +dojo.require("dijit.layout._ContentPaneResizeMixin"); + + +dojo.declare( + "dijit.form.Form", + [dijit._Widget, dijit._Templated, dijit.form._FormMixin, dijit.layout._ContentPaneResizeMixin], + { + // summary: + // Widget corresponding to HTML form tag, for validation and serialization + // + // example: + // | <form dojoType="dijit.form.Form" id="myForm"> + // | Name: <input type="text" name="name" /> + // | </form> + // | myObj = {name: "John Doe"}; + // | dijit.byId('myForm').set('value', myObj); + // | + // | myObj=dijit.byId('myForm').get('value'); + + // HTML <FORM> attributes + + // name: String? + // Name of form for scripting. + name: "", + + // action: String? + // Server-side form handler. + action: "", + + // method: String? + // HTTP method used to submit the form, either "GET" or "POST". + method: "", + + // encType: String? + // Encoding type for the form, ex: application/x-www-form-urlencoded. + encType: "", + + // accept-charset: String? + // List of supported charsets. + "accept-charset": "", + + // accept: String? + // List of MIME types for file upload. + accept: "", + + // target: String? + // Target frame for the document to be opened in. + target: "", + + templateString: "<form dojoAttachPoint='containerNode' dojoAttachEvent='onreset:_onReset,onsubmit:_onSubmit' ${!nameAttrSetting}></form>", + + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + action: "", + method: "", + encType: "", + "accept-charset": "", + accept: "", + target: "" + }), + + postMixInProperties: function(){ + // Setup name=foo string to be referenced from the template (but only if a name has been specified) + // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 + this.nameAttrSetting = this.name ? ("name='" + this.name + "'") : ""; + this.inherited(arguments); + }, + + execute: function(/*Object*/ formContents){ + // summary: + // Deprecated: use submit() + // tags: + // deprecated + }, + + onExecute: function(){ + // summary: + // Deprecated: use onSubmit() + // tags: + // deprecated + }, + + _setEncTypeAttr: function(/*String*/ value){ + this.encType = value; + dojo.attr(this.domNode, "encType", value); + if(dojo.isIE){ this.domNode.encoding = value; } + }, + + postCreate: function(){ + // IE tries to hide encType + // TODO: remove in 2.0, no longer necessary with data-dojo-params + if(dojo.isIE && this.srcNodeRef && this.srcNodeRef.attributes){ + var item = this.srcNodeRef.attributes.getNamedItem('encType'); + if(item && !item.specified && (typeof item.value == "string")){ + this.set('encType', item.value); + } + } + this.inherited(arguments); + }, + + reset: function(/*Event?*/ e){ + // summary: + // restores all widget values back to their init values, + // calls onReset() which can cancel the reset by returning false + + // create fake event so we can know if preventDefault() is called + var faux = { + returnValue: true, // the IE way + preventDefault: function(){ // not IE + this.returnValue = false; + }, + stopPropagation: function(){}, + currentTarget: e ? e.target : this.domNode, + target: e ? e.target : this.domNode + }; + // if return value is not exactly false, and haven't called preventDefault(), then reset + if(!(this.onReset(faux) === false) && faux.returnValue){ + this.inherited(arguments, []); + } + }, + + onReset: function(/*Event?*/ e){ + // summary: + // Callback when user resets the form. This method is intended + // to be over-ridden. When the `reset` method is called + // programmatically, the return value from `onReset` is used + // to compute whether or not resetting should proceed + // tags: + // callback + return true; // Boolean + }, + + _onReset: function(e){ + this.reset(e); + dojo.stopEvent(e); + return false; + }, + + _onSubmit: function(e){ + var fp = dijit.form.Form.prototype; + // TODO: remove this if statement beginning with 2.0 + if(this.execute != fp.execute || this.onExecute != fp.onExecute){ + dojo.deprecated("dijit.form.Form:execute()/onExecute() are deprecated. Use onSubmit() instead.", "", "2.0"); + this.onExecute(); + this.execute(this.getValues()); + } + if(this.onSubmit(e) === false){ // only exactly false stops submit + dojo.stopEvent(e); + } + }, + + onSubmit: function(/*Event?*/ e){ + // summary: + // Callback when user submits the form. + // description: + // This method is intended to be over-ridden, but by default it checks and + // returns the validity of form elements. When the `submit` + // method is called programmatically, the return value from + // `onSubmit` is used to compute whether or not submission + // should proceed + // tags: + // extension + + return this.isValid(); // Boolean + }, + + submit: function(){ + // summary: + // programmatically submit form if and only if the `onSubmit` returns true + if(!(this.onSubmit() === false)){ + this.containerNode.submit(); + } + } + } +); + } diff --git a/lib/dijit/form/HorizontalRule.js b/lib/dijit/form/HorizontalRule.js index 944282d80..e81805e6e 100644 --- a/lib/dijit/form/HorizontalRule.js +++ b/lib/dijit/form/HorizontalRule.js @@ -1,38 +1,74 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.HorizontalRule"]){ -dojo._hasResource["dijit.form.HorizontalRule"]=true; +if(!dojo._hasResource["dijit.form.HorizontalRule"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.HorizontalRule"] = true; dojo.provide("dijit.form.HorizontalRule"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); -dojo.declare("dijit.form.HorizontalRule",[dijit._Widget,dijit._Templated],{templateString:"<div class=\"dijitRuleContainer dijitRuleContainerH\"></div>",count:3,container:"containerNode",ruleStyle:"",_positionPrefix:"<div class=\"dijitRuleMark dijitRuleMarkH\" style=\"left:",_positionSuffix:"%;",_suffix:"\"></div>",_genHTML:function(_1,_2){ -return this._positionPrefix+_1+this._positionSuffix+this.ruleStyle+this._suffix; -},_isHorizontal:true,postCreate:function(){ -var _3; -if(this.count==1){ -_3=this._genHTML(50,0); -}else{ -var i; -var _4=100/(this.count-1); -if(!this._isHorizontal||this.isLeftToRight()){ -_3=this._genHTML(0,0); -for(i=1;i<this.count-1;i++){ -_3+=this._genHTML(_4*i,i); -} -_3+=this._genHTML(100,this.count-1); -}else{ -_3=this._genHTML(100,0); -for(i=1;i<this.count-1;i++){ -_3+=this._genHTML(100-_4*i,i); -} -_3+=this._genHTML(0,this.count-1); -} -} -this.domNode.innerHTML=_3; -}}); + + +dojo.declare("dijit.form.HorizontalRule", [dijit._Widget, dijit._Templated], +{ + // summary: + // Hash marks for `dijit.form.HorizontalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerH"></div>', + + // count: Integer + // Number of hash marks to generate + count: 3, + + // container: String + // For HorizontalSlider, this is either "topDecoration" or "bottomDecoration", + // and indicates whether this rule goes above or below the slider. + container: "containerNode", + + // ruleStyle: String + // CSS style to apply to individual hash marks + ruleStyle: "", + + _positionPrefix: '<div class="dijitRuleMark dijitRuleMarkH" style="left:', + _positionSuffix: '%;', + _suffix: '"></div>', + + _genHTML: function(pos, ndx){ + return this._positionPrefix + pos + this._positionSuffix + this.ruleStyle + this._suffix; + }, + + // _isHorizontal: [protected extension] Boolean + // VerticalRule will override this... + _isHorizontal: true, + + buildRendering: function(){ + this.inherited(arguments); + + var innerHTML; + if(this.count == 1){ + innerHTML = this._genHTML(50, 0); + }else{ + var i; + var interval = 100 / (this.count-1); + if(!this._isHorizontal || this.isLeftToRight()){ + innerHTML = this._genHTML(0, 0); + for(i=1; i < this.count-1; i++){ + innerHTML += this._genHTML(interval*i, i); + } + innerHTML += this._genHTML(100, this.count-1); + }else{ + innerHTML = this._genHTML(100, 0); + for(i=1; i < this.count-1; i++){ + innerHTML += this._genHTML(100-interval*i, i); + } + innerHTML += this._genHTML(0, this.count-1); + } + } + this.domNode.innerHTML = innerHTML; + } +}); + } diff --git a/lib/dijit/form/HorizontalRuleLabels.js b/lib/dijit/form/HorizontalRuleLabels.js index 6cf6742c5..d923e8081 100644 --- a/lib/dijit/form/HorizontalRuleLabels.js +++ b/lib/dijit/form/HorizontalRuleLabels.js @@ -1,38 +1,97 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.HorizontalRuleLabels"]){ -dojo._hasResource["dijit.form.HorizontalRuleLabels"]=true; +if(!dojo._hasResource["dijit.form.HorizontalRuleLabels"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.HorizontalRuleLabels"] = true; dojo.provide("dijit.form.HorizontalRuleLabels"); dojo.require("dijit.form.HorizontalRule"); -dojo.declare("dijit.form.HorizontalRuleLabels",dijit.form.HorizontalRule,{templateString:"<div class=\"dijitRuleContainer dijitRuleContainerH dijitRuleLabelsContainer dijitRuleLabelsContainerH\"></div>",labelStyle:"",labels:[],numericMargin:0,minimum:0,maximum:1,constraints:{pattern:"#%"},_positionPrefix:"<div class=\"dijitRuleLabelContainer dijitRuleLabelContainerH\" style=\"left:",_labelPrefix:"\"><div class=\"dijitRuleLabel dijitRuleLabelH\">",_suffix:"</div></div>",_calcPosition:function(_1){ -return _1; -},_genHTML:function(_2,_3){ -return this._positionPrefix+this._calcPosition(_2)+this._positionSuffix+this.labelStyle+this._labelPrefix+this.labels[_3]+this._suffix; -},getLabels:function(){ -var _4=this.labels; -if(!_4.length){ -_4=dojo.query("> li",this.srcNodeRef).map(function(_5){ -return String(_5.innerHTML); + + +dojo.declare("dijit.form.HorizontalRuleLabels", dijit.form.HorizontalRule, +{ + // summary: + // Labels for `dijit.form.HorizontalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerH dijitRuleLabelsContainer dijitRuleLabelsContainerH"></div>', + + // labelStyle: String + // CSS style to apply to individual text labels + labelStyle: "", + + // labels: String[]? + // Array of text labels to render - evenly spaced from left-to-right or bottom-to-top. + // Alternately, minimum and maximum can be specified, to get numeric labels. + labels: [], + + // numericMargin: Integer + // Number of generated numeric labels that should be rendered as '' on the ends when labels[] are not specified + numericMargin: 0, + + // numericMinimum: Integer + // Leftmost label value for generated numeric labels when labels[] are not specified + minimum: 0, + + // numericMaximum: Integer + // Rightmost label value for generated numeric labels when labels[] are not specified + maximum: 1, + + // constraints: Object + // pattern, places, lang, et al (see dojo.number) for generated numeric labels when labels[] are not specified + constraints: {pattern:"#%"}, + + _positionPrefix: '<div class="dijitRuleLabelContainer dijitRuleLabelContainerH" style="left:', + _labelPrefix: '"><div class="dijitRuleLabel dijitRuleLabelH">', + _suffix: '</div></div>', + + _calcPosition: function(pos){ + // summary: + // Returns the value to be used in HTML for the label as part of the left: attribute + // tags: + // protected extension + return pos; + }, + + _genHTML: function(pos, ndx){ + return this._positionPrefix + this._calcPosition(pos) + this._positionSuffix + this.labelStyle + this._labelPrefix + this.labels[ndx] + this._suffix; + }, + + getLabels: function(){ + // summary: + // Overridable function to return array of labels to use for this slider. + // Can specify a getLabels() method instead of a labels[] array, or min/max attributes. + // tags: + // protected extension + + // if the labels array was not specified directly, then see if <li> children were + var labels = this.labels; + if(!labels.length){ + // for markup creation, labels are specified as child elements + labels = dojo.query("> li", this.srcNodeRef).map(function(node){ + return String(node.innerHTML); + }); + } + this.srcNodeRef.innerHTML = ''; + // if the labels were not specified directly and not as <li> children, then calculate numeric labels + if(!labels.length && this.count > 1){ + var start = this.minimum; + var inc = (this.maximum - start) / (this.count-1); + for(var i=0; i < this.count; i++){ + labels.push((i < this.numericMargin || i >= (this.count-this.numericMargin)) ? '' : dojo.number.format(start, this.constraints)); + start += inc; + } + } + return labels; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + this.labels = this.getLabels(); + this.count = this.labels.length; + } }); -} -this.srcNodeRef.innerHTML=""; -if(!_4.length&&this.count>1){ -var _6=this.minimum; -var _7=(this.maximum-_6)/(this.count-1); -for(var i=0;i<this.count;i++){ -_4.push((i<this.numericMargin||i>=(this.count-this.numericMargin))?"":dojo.number.format(_6,this.constraints)); -_6+=_7; -} -} -return _4; -},postMixInProperties:function(){ -this.inherited(arguments); -this.labels=this.getLabels(); -this.count=this.labels.length; -}}); + } diff --git a/lib/dijit/form/HorizontalSlider.js b/lib/dijit/form/HorizontalSlider.js index a0cb8cf2b..93dc4a0c2 100644 --- a/lib/dijit/form/HorizontalSlider.js +++ b/lib/dijit/form/HorizontalSlider.js @@ -1,209 +1,343 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.HorizontalSlider"]){ -dojo._hasResource["dijit.form.HorizontalSlider"]=true; +if(!dojo._hasResource["dijit.form.HorizontalSlider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.HorizontalSlider"] = true; dojo.provide("dijit.form.HorizontalSlider"); dojo.require("dijit.form._FormWidget"); dojo.require("dijit._Container"); dojo.require("dojo.dnd.move"); dojo.require("dijit.form.Button"); dojo.require("dojo.number"); -dojo.require("dojo._base.fx"); -dojo.declare("dijit.form.HorizontalSlider",[dijit.form._FormValueWidget,dijit._Container],{templateString:dojo.cache("dijit.form","templates/HorizontalSlider.html","<table class=\"dijit dijitReset dijitSlider dijitSliderH\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"topDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationT dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderDecrementIconH\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderLeftBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><div class=\"dijitReset dijitSliderBarContainerH\" waiRole=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderProgressBar dijitSliderProgressBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableH\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleH\" dojoAttachEvent=\"onmousedown:_onHandleClick\" waiRole=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderRemainingBar dijitSliderRemainingBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"></div\n\t\t\t></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderRightBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderIncrementIconH\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"containerNode,bottomDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationB dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n></table>\n"),value:0,showButtons:true,minimum:0,maximum:100,discreteValues:Infinity,pageIncrement:2,clickSelect:true,slideDuration:dijit.defaultDuration,widgetsInTemplate:true,attributeMap:dojo.delegate(dijit.form._FormWidget.prototype.attributeMap,{id:""}),baseClass:"dijitSlider",cssStateNodes:{incrementButton:"dijitSliderIncrementButton",decrementButton:"dijitSliderDecrementButton",focusNode:"dijitSliderThumb"},_mousePixelCoord:"pageX",_pixelCount:"w",_startingPixelCoord:"x",_startingPixelCount:"l",_handleOffsetCoord:"left",_progressPixelSize:"width",_onKeyUp:function(e){ -if(this.disabled||this.readOnly||e.altKey||e.ctrlKey||e.metaKey){ -return; -} -this._setValueAttr(this.value,true); -},_onKeyPress:function(e){ -if(this.disabled||this.readOnly||e.altKey||e.ctrlKey||e.metaKey){ -return; -} -switch(e.charOrCode){ -case dojo.keys.HOME: -this._setValueAttr(this.minimum,false); -break; -case dojo.keys.END: -this._setValueAttr(this.maximum,false); -break; -case ((this._descending||this.isLeftToRight())?dojo.keys.RIGHT_ARROW:dojo.keys.LEFT_ARROW): -case (this._descending===false?dojo.keys.DOWN_ARROW:dojo.keys.UP_ARROW): -case (this._descending===false?dojo.keys.PAGE_DOWN:dojo.keys.PAGE_UP): -this.increment(e); -break; -case ((this._descending||this.isLeftToRight())?dojo.keys.LEFT_ARROW:dojo.keys.RIGHT_ARROW): -case (this._descending===false?dojo.keys.UP_ARROW:dojo.keys.DOWN_ARROW): -case (this._descending===false?dojo.keys.PAGE_UP:dojo.keys.PAGE_DOWN): -this.decrement(e); -break; -default: -return; -} -dojo.stopEvent(e); -},_onHandleClick:function(e){ -if(this.disabled||this.readOnly){ -return; -} -if(!dojo.isIE){ -dijit.focus(this.sliderHandle); -} -dojo.stopEvent(e); -},_isReversed:function(){ -return !this.isLeftToRight(); -},_onBarClick:function(e){ -if(this.disabled||this.readOnly||!this.clickSelect){ -return; -} -dijit.focus(this.sliderHandle); -dojo.stopEvent(e); -var _1=dojo.position(this.sliderBarContainer,true); -var _2=e[this._mousePixelCoord]-_1[this._startingPixelCoord]; -this._setPixelValue(this._isReversed()?(_1[this._pixelCount]-_2):_2,_1[this._pixelCount],true); -this._movable.onMouseDown(e); -},_setPixelValue:function(_3,_4,_5){ -if(this.disabled||this.readOnly){ -return; -} -_3=_3<0?0:_4<_3?_4:_3; -var _6=this.discreteValues; -if(_6<=1||_6==Infinity){ -_6=_4; -} -_6--; -var _7=_4/_6; -var _8=Math.round(_3/_7); -this._setValueAttr((this.maximum-this.minimum)*_8/_6+this.minimum,_5); -},_setValueAttr:function(_9,_a){ -this.valueNode.value=this.value=_9; -dijit.setWaiState(this.focusNode,"valuenow",_9); -this.inherited(arguments); -var _b=(_9-this.minimum)/(this.maximum-this.minimum); -var _c=(this._descending===false)?this.remainingBar:this.progressBar; -var _d=(this._descending===false)?this.progressBar:this.remainingBar; -if(this._inProgressAnim&&this._inProgressAnim.status!="stopped"){ -this._inProgressAnim.stop(true); -} -if(_a&&this.slideDuration>0&&_c.style[this._progressPixelSize]){ -var _e=this; -var _f={}; -var _10=parseFloat(_c.style[this._progressPixelSize]); -var _11=this.slideDuration*(_b-_10/100); -if(_11==0){ -return; -} -if(_11<0){ -_11=0-_11; -} -_f[this._progressPixelSize]={start:_10,end:_b*100,units:"%"}; -this._inProgressAnim=dojo.animateProperty({node:_c,duration:_11,onAnimate:function(v){ -_d.style[_e._progressPixelSize]=(100-parseFloat(v[_e._progressPixelSize]))+"%"; -},onEnd:function(){ -delete _e._inProgressAnim; -},properties:_f}); -this._inProgressAnim.play(); -}else{ -_c.style[this._progressPixelSize]=(_b*100)+"%"; -_d.style[this._progressPixelSize]=((1-_b)*100)+"%"; -} -},_bumpValue:function(_12,_13){ -if(this.disabled||this.readOnly){ -return; -} -var s=dojo.getComputedStyle(this.sliderBarContainer); -var c=dojo._getContentBox(this.sliderBarContainer,s); -var _14=this.discreteValues; -if(_14<=1||_14==Infinity){ -_14=c[this._pixelCount]; -} -_14--; -var _15=(this.value-this.minimum)*_14/(this.maximum-this.minimum)+_12; -if(_15<0){ -_15=0; -} -if(_15>_14){ -_15=_14; -} -_15=_15*(this.maximum-this.minimum)/_14+this.minimum; -this._setValueAttr(_15,_13); -},_onClkBumper:function(val){ -if(this.disabled||this.readOnly||!this.clickSelect){ -return; -} -this._setValueAttr(val,true); -},_onClkIncBumper:function(){ -this._onClkBumper(this._descending===false?this.minimum:this.maximum); -},_onClkDecBumper:function(){ -this._onClkBumper(this._descending===false?this.maximum:this.minimum); -},decrement:function(e){ -this._bumpValue(e.charOrCode==dojo.keys.PAGE_DOWN?-this.pageIncrement:-1); -},increment:function(e){ -this._bumpValue(e.charOrCode==dojo.keys.PAGE_UP?this.pageIncrement:1); -},_mouseWheeled:function(evt){ -dojo.stopEvent(evt); -var _16=!dojo.isMozilla; -var _17=evt[(_16?"wheelDelta":"detail")]*(_16?1:-1); -this._bumpValue(_17<0?-1:1,true); -},startup:function(){ -if(this._started){ -return; -} -dojo.forEach(this.getChildren(),function(_18){ -if(this[_18.container]!=this.containerNode){ -this[_18.container].appendChild(_18.domNode); -} -},this); -this.inherited(arguments); -},_typematicCallback:function(_19,_1a,e){ -if(_19==-1){ -this._setValueAttr(this.value,true); -}else{ -this[(_1a==(this._descending?this.incrementButton:this.decrementButton))?"decrement":"increment"](e); -} -},postCreate:function(){ -if(this.showButtons){ -this.incrementButton.style.display=""; -this.decrementButton.style.display=""; -this._connects.push(dijit.typematic.addMouseListener(this.decrementButton,this,"_typematicCallback",25,500)); -this._connects.push(dijit.typematic.addMouseListener(this.incrementButton,this,"_typematicCallback",25,500)); -} -this.connect(this.domNode,!dojo.isMozilla?"onmousewheel":"DOMMouseScroll","_mouseWheeled"); -var _1b=dojo.declare(dijit.form._SliderMover,{widget:this}); -this._movable=new dojo.dnd.Moveable(this.sliderHandle,{mover:_1b}); -var _1c=dojo.query("label[for=\""+this.id+"\"]"); -if(_1c.length){ -_1c[0].id=(this.id+"_label"); -dijit.setWaiState(this.focusNode,"labelledby",_1c[0].id); -} -dijit.setWaiState(this.focusNode,"valuemin",this.minimum); -dijit.setWaiState(this.focusNode,"valuemax",this.maximum); -this.inherited(arguments); -this._layoutHackIE7(); -},destroy:function(){ -this._movable.destroy(); -if(this._inProgressAnim&&this._inProgressAnim.status!="stopped"){ -this._inProgressAnim.stop(true); -} -this._supportingWidgets=dijit.findWidgets(this.domNode); -this.inherited(arguments); -}}); -dojo.declare("dijit.form._SliderMover",dojo.dnd.Mover,{onMouseMove:function(e){ -var _1d=this.widget; -var _1e=_1d._abspos; -if(!_1e){ -_1e=_1d._abspos=dojo.position(_1d.sliderBarContainer,true); -_1d._setPixelValue_=dojo.hitch(_1d,"_setPixelValue"); -_1d._isReversed_=_1d._isReversed(); -} -var _1f=e[_1d._mousePixelCoord]-_1e[_1d._startingPixelCoord]; -_1d._setPixelValue_(_1d._isReversed_?(_1e[_1d._pixelCount]-_1f):_1f,_1e[_1d._pixelCount],false); -},destroy:function(e){ -dojo.dnd.Mover.prototype.destroy.apply(this,arguments); -var _20=this.widget; -_20._abspos=null; -_20._setValueAttr(_20.value,true); -}}); + + +dojo.declare( + "dijit.form.HorizontalSlider", + [dijit.form._FormValueWidget, dijit._Container], +{ + // summary: + // A form widget that allows one to select a value with a horizontally draggable handle + + templateString: dojo.cache("dijit.form", "templates/HorizontalSlider.html", "<table class=\"dijit dijitReset dijitSlider dijitSliderH\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"topDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationT dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderDecrementIconH\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderLeftBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><div class=\"dijitReset dijitSliderBarContainerH\" role=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div role=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderProgressBar dijitSliderProgressBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableH\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleH\" dojoAttachEvent=\"onmousedown:_onHandleClick\" role=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t\t><div role=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarH dijitSliderRemainingBar dijitSliderRemainingBarH\" dojoAttachEvent=\"onmousedown:_onBarClick\"></div\n\t\t\t></div\n\t\t></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperH dijitSliderRightBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div\n\t\t></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerH\"\n\t\t\t><div class=\"dijitSliderIncrementIconH\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t\t><td dojoAttachPoint=\"containerNode,bottomDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationB dijitSliderDecorationH\"></td\n\t\t><td class=\"dijitReset\" colspan=\"2\"></td\n\t></tr\n></table>\n"), + + // Overrides FormValueWidget.value to indicate numeric value + value: 0, + + // showButtons: [const] Boolean + // Show increment/decrement buttons at the ends of the slider? + showButtons: true, + + // minimum:: [const] Integer + // The minimum value the slider can be set to. + minimum: 0, + + // maximum: [const] Integer + // The maximum value the slider can be set to. + maximum: 100, + + // discreteValues: Integer + // If specified, indicates that the slider handle has only 'discreteValues' possible positions, + // and that after dragging the handle, it will snap to the nearest possible position. + // Thus, the slider has only 'discreteValues' possible values. + // + // For example, if minimum=10, maxiumum=30, and discreteValues=3, then the slider handle has + // three possible positions, representing values 10, 20, or 30. + // + // If discreteValues is not specified or if it's value is higher than the number of pixels + // in the slider bar, then the slider handle can be moved freely, and the slider's value will be + // computed/reported based on pixel position (in this case it will likely be fractional, + // such as 123.456789). + discreteValues: Infinity, + + // pageIncrement: Integer + // If discreteValues is also specified, this indicates the amount of clicks (ie, snap positions) + // that the slider handle is moved via pageup/pagedown keys. + // If discreteValues is not specified, it indicates the number of pixels. + pageIncrement: 2, + + // clickSelect: Boolean + // If clicking the slider bar changes the value or not + clickSelect: true, + + // slideDuration: Number + // The time in ms to take to animate the slider handle from 0% to 100%, + // when clicking the slider bar to make the handle move. + slideDuration: dijit.defaultDuration, + + // Flag to _Templated (TODO: why is this here? I see no widgets in the template.) + widgetsInTemplate: true, + + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + id: "" + }), + + baseClass: "dijitSlider", + + // Apply CSS classes to up/down arrows and handle per mouse state + cssStateNodes: { + incrementButton: "dijitSliderIncrementButton", + decrementButton: "dijitSliderDecrementButton", + focusNode: "dijitSliderThumb" + }, + + _mousePixelCoord: "pageX", + _pixelCount: "w", + _startingPixelCoord: "x", + _startingPixelCount: "l", + _handleOffsetCoord: "left", + _progressPixelSize: "width", + + _onKeyUp: function(/*Event*/ e){ + if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; } + this._setValueAttr(this.value, true); + }, + + _onKeyPress: function(/*Event*/ e){ + if(this.disabled || this.readOnly || e.altKey || e.ctrlKey || e.metaKey){ return; } + switch(e.charOrCode){ + case dojo.keys.HOME: + this._setValueAttr(this.minimum, false); + break; + case dojo.keys.END: + this._setValueAttr(this.maximum, false); + break; + // this._descending === false: if ascending vertical (min on top) + // (this._descending || this.isLeftToRight()): if left-to-right horizontal or descending vertical + case ((this._descending || this.isLeftToRight()) ? dojo.keys.RIGHT_ARROW : dojo.keys.LEFT_ARROW): + case (this._descending === false ? dojo.keys.DOWN_ARROW : dojo.keys.UP_ARROW): + case (this._descending === false ? dojo.keys.PAGE_DOWN : dojo.keys.PAGE_UP): + this.increment(e); + break; + case ((this._descending || this.isLeftToRight()) ? dojo.keys.LEFT_ARROW : dojo.keys.RIGHT_ARROW): + case (this._descending === false ? dojo.keys.UP_ARROW : dojo.keys.DOWN_ARROW): + case (this._descending === false ? dojo.keys.PAGE_UP : dojo.keys.PAGE_DOWN): + this.decrement(e); + break; + default: + return; + } + dojo.stopEvent(e); + }, + + _onHandleClick: function(e){ + if(this.disabled || this.readOnly){ return; } + if(!dojo.isIE){ + // make sure you get focus when dragging the handle + // (but don't do on IE because it causes a flicker on mouse up (due to blur then focus) + dijit.focus(this.sliderHandle); + } + dojo.stopEvent(e); + }, + + _isReversed: function(){ + // summary: + // Returns true if direction is from right to left + // tags: + // protected extension + return !this.isLeftToRight(); + }, + + _onBarClick: function(e){ + if(this.disabled || this.readOnly || !this.clickSelect){ return; } + dijit.focus(this.sliderHandle); + dojo.stopEvent(e); + var abspos = dojo.position(this.sliderBarContainer, true); + var pixelValue = e[this._mousePixelCoord] - abspos[this._startingPixelCoord]; + this._setPixelValue(this._isReversed() ? (abspos[this._pixelCount] - pixelValue) : pixelValue, abspos[this._pixelCount], true); + this._movable.onMouseDown(e); + }, + + _setPixelValue: function(/*Number*/ pixelValue, /*Number*/ maxPixels, /*Boolean?*/ priorityChange){ + if(this.disabled || this.readOnly){ return; } + pixelValue = pixelValue < 0 ? 0 : maxPixels < pixelValue ? maxPixels : pixelValue; + var count = this.discreteValues; + if(count <= 1 || count == Infinity){ count = maxPixels; } + count--; + var pixelsPerValue = maxPixels / count; + var wholeIncrements = Math.round(pixelValue / pixelsPerValue); + this._setValueAttr((this.maximum-this.minimum)*wholeIncrements/count + this.minimum, priorityChange); + }, + + _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', value) works. + this._set("value", value); + this.valueNode.value = value; + dijit.setWaiState(this.focusNode, "valuenow", value); + this.inherited(arguments); + var percent = (value - this.minimum) / (this.maximum - this.minimum); + var progressBar = (this._descending === false) ? this.remainingBar : this.progressBar; + var remainingBar = (this._descending === false) ? this.progressBar : this.remainingBar; + if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){ + this._inProgressAnim.stop(true); + } + if(priorityChange && this.slideDuration > 0 && progressBar.style[this._progressPixelSize]){ + // animate the slider + var _this = this; + var props = {}; + var start = parseFloat(progressBar.style[this._progressPixelSize]); + var duration = this.slideDuration * (percent-start/100); + if(duration == 0){ return; } + if(duration < 0){ duration = 0 - duration; } + props[this._progressPixelSize] = { start: start, end: percent*100, units:"%" }; + this._inProgressAnim = dojo.animateProperty({ node: progressBar, duration: duration, + onAnimate: function(v){ remainingBar.style[_this._progressPixelSize] = (100-parseFloat(v[_this._progressPixelSize])) + "%"; }, + onEnd: function(){ delete _this._inProgressAnim; }, + properties: props + }) + this._inProgressAnim.play(); + }else{ + progressBar.style[this._progressPixelSize] = (percent*100) + "%"; + remainingBar.style[this._progressPixelSize] = ((1-percent)*100) + "%"; + } + }, + + _bumpValue: function(signedChange, /*Boolean?*/ priorityChange){ + if(this.disabled || this.readOnly){ return; } + var s = dojo.getComputedStyle(this.sliderBarContainer); + var c = dojo._getContentBox(this.sliderBarContainer, s); + var count = this.discreteValues; + if(count <= 1 || count == Infinity){ count = c[this._pixelCount]; } + count--; + var value = (this.value - this.minimum) * count / (this.maximum - this.minimum) + signedChange; + if(value < 0){ value = 0; } + if(value > count){ value = count; } + value = value * (this.maximum - this.minimum) / count + this.minimum; + this._setValueAttr(value, priorityChange); + }, + + _onClkBumper: function(val){ + if(this.disabled || this.readOnly || !this.clickSelect){ return; } + this._setValueAttr(val, true); + }, + + _onClkIncBumper: function(){ + this._onClkBumper(this._descending === false ? this.minimum : this.maximum); + }, + + _onClkDecBumper: function(){ + this._onClkBumper(this._descending === false ? this.maximum : this.minimum); + }, + + decrement: function(/*Event*/ e){ + // summary: + // Decrement slider + // tags: + // private + this._bumpValue(e.charOrCode == dojo.keys.PAGE_DOWN ? -this.pageIncrement : -1); + }, + + increment: function(/*Event*/ e){ + // summary: + // Increment slider + // tags: + // private + this._bumpValue(e.charOrCode == dojo.keys.PAGE_UP ? this.pageIncrement : 1); + }, + + _mouseWheeled: function(/*Event*/ evt){ + // summary: + // Event handler for mousewheel where supported + dojo.stopEvent(evt); + var janky = !dojo.isMozilla; + var scroll = evt[(janky ? "wheelDelta" : "detail")] * (janky ? 1 : -1); + this._bumpValue(scroll < 0 ? -1 : 1, true); // negative scroll acts like a decrement + }, + + startup: function(){ + if(this._started){ return; } + + dojo.forEach(this.getChildren(), function(child){ + if(this[child.container] != this.containerNode){ + this[child.container].appendChild(child.domNode); + } + }, this); + + this.inherited(arguments); + }, + + _typematicCallback: function(/*Number*/ count, /*Object*/ button, /*Event*/ e){ + if(count == -1){ + this._setValueAttr(this.value, true); + }else{ + this[(button == (this._descending? this.incrementButton : this.decrementButton)) ? "decrement" : "increment"](e); + } + }, + + buildRendering: function(){ + this.inherited(arguments); + if(this.showButtons){ + this.incrementButton.style.display=""; + this.decrementButton.style.display=""; + } + + // find any associated label element and add to slider focusnode. + var label = dojo.query('label[for="'+this.id+'"]'); + if(label.length){ + label[0].id = (this.id+"_label"); + dijit.setWaiState(this.focusNode, "labelledby", label[0].id); + } + + dijit.setWaiState(this.focusNode, "valuemin", this.minimum); + dijit.setWaiState(this.focusNode, "valuemax", this.maximum); + }, + + postCreate: function(){ + this.inherited(arguments); + + if(this.showButtons){ + this._connects.push(dijit.typematic.addMouseListener( + this.decrementButton, this, "_typematicCallback", 25, 500)); + this._connects.push(dijit.typematic.addMouseListener( + this.incrementButton, this, "_typematicCallback", 25, 500)); + } + this.connect(this.domNode, !dojo.isMozilla ? "onmousewheel" : "DOMMouseScroll", "_mouseWheeled"); + + // define a custom constructor for a SliderMover that points back to me + var mover = dojo.declare(dijit.form._SliderMover, { + widget: this + }); + this._movable = new dojo.dnd.Moveable(this.sliderHandle, {mover: mover}); + + this._layoutHackIE7(); + }, + + destroy: function(){ + this._movable.destroy(); + if(this._inProgressAnim && this._inProgressAnim.status != "stopped"){ + this._inProgressAnim.stop(true); + } + this._supportingWidgets = dijit.findWidgets(this.domNode); // tells destroy about pseudo-child widgets (ruler/labels) + this.inherited(arguments); + } +}); + +dojo.declare("dijit.form._SliderMover", + dojo.dnd.Mover, +{ + onMouseMove: function(e){ + var widget = this.widget; + var abspos = widget._abspos; + if(!abspos){ + abspos = widget._abspos = dojo.position(widget.sliderBarContainer, true); + widget._setPixelValue_ = dojo.hitch(widget, "_setPixelValue"); + widget._isReversed_ = widget._isReversed(); + } + var coordEvent = e.touches ? e.touches[0] : e, // if multitouch take first touch for coords + pixelValue = coordEvent[widget._mousePixelCoord] - abspos[widget._startingPixelCoord]; + widget._setPixelValue_(widget._isReversed_ ? (abspos[widget._pixelCount]-pixelValue) : pixelValue, abspos[widget._pixelCount], false); + }, + + destroy: function(e){ + dojo.dnd.Mover.prototype.destroy.apply(this, arguments); + var widget = this.widget; + widget._abspos = null; + widget._setValueAttr(widget.value, true); + } +}); + } diff --git a/lib/dijit/form/MappedTextBox.js b/lib/dijit/form/MappedTextBox.js index e7dd213c0..ebdaaf15b 100644 --- a/lib/dijit/form/MappedTextBox.js +++ b/lib/dijit/form/MappedTextBox.js @@ -1,12 +1,15 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.MappedTextBox"]){ -dojo._hasResource["dijit.form.MappedTextBox"]=true; +if(!dojo._hasResource["dijit.form.MappedTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.MappedTextBox"] = true; dojo.provide("dijit.form.MappedTextBox"); dojo.require("dijit.form.ValidationTextBox"); + + + } diff --git a/lib/dijit/form/MultiSelect.js b/lib/dijit/form/MultiSelect.js index 8aacb4148..12f3b40e7 100644 --- a/lib/dijit/form/MultiSelect.js +++ b/lib/dijit/form/MultiSelect.js @@ -1,49 +1,119 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.MultiSelect"]){ -dojo._hasResource["dijit.form.MultiSelect"]=true; +if(!dojo._hasResource["dijit.form.MultiSelect"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.MultiSelect"] = true; dojo.provide("dijit.form.MultiSelect"); dojo.require("dijit.form._FormWidget"); -dojo.declare("dijit.form.MultiSelect",dijit.form._FormValueWidget,{size:7,templateString:"<select multiple='true' ${!nameAttrSetting} dojoAttachPoint='containerNode,focusNode' dojoAttachEvent='onchange: _onChange'></select>",attributeMap:dojo.delegate(dijit.form._FormWidget.prototype.attributeMap,{size:"focusNode"}),reset:function(){ -this._hasBeenBlurred=false; -this._setValueAttr(this._resetValue,true); -},addSelected:function(_1){ -_1.getSelected().forEach(function(n){ -this.containerNode.appendChild(n); -this.domNode.scrollTop=this.domNode.offsetHeight; -var _2=_1.domNode.scrollTop; -_1.domNode.scrollTop=0; -_1.domNode.scrollTop=_2; -},this); -},getSelected:function(){ -return dojo.query("option",this.containerNode).filter(function(n){ -return n.selected; -}); -},_getValueAttr:function(){ -return this.getSelected().map(function(n){ -return n.value; -}); -},multiple:true,_setValueAttr:function(_3){ -dojo.query("option",this.containerNode).forEach(function(n){ -n.selected=(dojo.indexOf(_3,n.value)!=-1); -}); -},invertSelection:function(_4){ -dojo.query("option",this.containerNode).forEach(function(n){ -n.selected=!n.selected; + + +dojo.declare("dijit.form.MultiSelect", dijit.form._FormValueWidget, { + // summary: + // Widget version of a <select multiple=true> element, + // for selecting multiple options. + + // size: Number + // Number of elements to display on a page + // NOTE: may be removed in version 2.0, since elements may have variable height; + // set the size via style="..." or CSS class names instead. + size: 7, + + templateString: "<select multiple='true' ${!nameAttrSetting} dojoAttachPoint='containerNode,focusNode' dojoAttachEvent='onchange: _onChange'></select>", + + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + size: "focusNode" + }), + + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time + + // TODO: once we inherit from FormValueWidget this won't be needed + this._hasBeenBlurred = false; + this._setValueAttr(this._resetValue, true); + }, + + addSelected: function(/*dijit.form.MultiSelect*/ select){ + // summary: + // Move the selected nodes of a passed Select widget + // instance to this Select widget. + // + // example: + // | // move all the selected values from "bar" to "foo" + // | dijit.byId("foo").addSelected(dijit.byId("bar")); + + select.getSelected().forEach(function(n){ + this.containerNode.appendChild(n); + // scroll to bottom to see item + // cannot use scrollIntoView since <option> tags don't support all attributes + // does not work on IE due to a bug where <select> always shows scrollTop = 0 + this.domNode.scrollTop = this.domNode.offsetHeight; // overshoot will be ignored + // scrolling the source select is trickier esp. on safari who forgets to change the scrollbar size + var oldscroll = select.domNode.scrollTop; + select.domNode.scrollTop = 0; + select.domNode.scrollTop = oldscroll; + },this); + }, + + getSelected: function(){ + // summary: + // Access the NodeList of the selected options directly + return dojo.query("option",this.containerNode).filter(function(n){ + return n.selected; // Boolean + }); // dojo.NodeList + }, + + _getValueAttr: function(){ + // summary: + // Hook so get('value') works. + // description: + // Returns an array of the selected options' values. + return this.getSelected().map(function(n){ + return n.value; + }); + }, + + multiple: true, // for Form + + _setValueAttr: function(/*Array*/ values){ + // summary: + // Hook so set('value', values) works. + // description: + // Set the value(s) of this Select based on passed values + dojo.query("option",this.containerNode).forEach(function(n){ + n.selected = (dojo.indexOf(values,n.value) != -1); + }); + }, + + invertSelection: function(onChange){ + // summary: + // Invert the selection + // onChange: Boolean + // If null, onChange is not fired. + dojo.query("option",this.containerNode).forEach(function(n){ + n.selected = !n.selected; + }); + this._handleOnChange(this.get('value'), onChange == true); + }, + + _onChange: function(/*Event*/ e){ + this._handleOnChange(this.get('value'), true); + }, + + // for layout widgets: + resize: function(/*Object*/ size){ + if(size){ + dojo.marginBox(this.domNode, size); + } + }, + + postCreate: function(){ + this._onChange(); + } }); -this._handleOnChange(this.get("value"),_4==true); -},_onChange:function(e){ -this._handleOnChange(this.get("value"),true); -},resize:function(_5){ -if(_5){ -dojo.marginBox(this.domNode,_5); -} -},postCreate:function(){ -this._onChange(); -}}); + } diff --git a/lib/dijit/form/NumberSpinner.js b/lib/dijit/form/NumberSpinner.js index 82a3c4024..ba5a782f5 100644 --- a/lib/dijit/form/NumberSpinner.js +++ b/lib/dijit/form/NumberSpinner.js @@ -1,38 +1,71 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.NumberSpinner"]){ -dojo._hasResource["dijit.form.NumberSpinner"]=true; +if(!dojo._hasResource["dijit.form.NumberSpinner"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.NumberSpinner"] = true; dojo.provide("dijit.form.NumberSpinner"); dojo.require("dijit.form._Spinner"); dojo.require("dijit.form.NumberTextBox"); -dojo.declare("dijit.form.NumberSpinner",[dijit.form._Spinner,dijit.form.NumberTextBoxMixin],{adjust:function(_1,_2){ -var tc=this.constraints,v=isNaN(_1),_3=!isNaN(tc.max),_4=!isNaN(tc.min); -if(v&&_2!=0){ -_1=(_2>0)?_4?tc.min:_3?tc.max:0:_3?this.constraints.max:_4?tc.min:0; -} -var _5=_1+_2; -if(v||isNaN(_5)){ -return _1; -} -if(_3&&(_5>tc.max)){ -_5=tc.max; -} -if(_4&&(_5<tc.min)){ -_5=tc.min; -} -return _5; -},_onKeyPress:function(e){ -if((e.charOrCode==dojo.keys.HOME||e.charOrCode==dojo.keys.END)&&!(e.ctrlKey||e.altKey||e.metaKey)&&typeof this.get("value")!="undefined"){ -var _6=this.constraints[(e.charOrCode==dojo.keys.HOME?"min":"max")]; -if(typeof _6=="number"){ -this._setValueAttr(_6,false); -} -dojo.stopEvent(e); -} -}}); + + +dojo.declare("dijit.form.NumberSpinner", + [dijit.form._Spinner, dijit.form.NumberTextBoxMixin], + { + // summary: + // Extends NumberTextBox to add up/down arrows and pageup/pagedown for incremental change to the value + // + // description: + // A `dijit.form.NumberTextBox` extension to provide keyboard accessible value selection + // as well as icons for spinning direction. When using the keyboard, the typematic rules + // apply, meaning holding the key will gradually increase or decrease the value and + // accelerate. + // + // example: + // | new dijit.form.NumberSpinner({ constraints:{ max:300, min:100 }}, "someInput"); + + adjust: function(/*Object*/ val, /*Number*/ delta){ + // summary: + // Change Number val by the given amount + // tags: + // protected + + var tc = this.constraints, + v = isNaN(val), + gotMax = !isNaN(tc.max), + gotMin = !isNaN(tc.min) + ; + if(v && delta != 0){ // blank or invalid value and they want to spin, so create defaults + val = (delta > 0) ? + gotMin ? tc.min : gotMax ? tc.max : 0 : + gotMax ? this.constraints.max : gotMin ? tc.min : 0 + ; + } + var newval = val + delta; + if(v || isNaN(newval)){ return val; } + if(gotMax && (newval > tc.max)){ + newval = tc.max; + } + if(gotMin && (newval < tc.min)){ + newval = tc.min; + } + return newval; + }, + + _onKeyPress: function(e){ + if((e.charOrCode == dojo.keys.HOME || e.charOrCode == dojo.keys.END) && !(e.ctrlKey || e.altKey || e.metaKey) + && typeof this.get('value') != 'undefined' /* gibberish, so HOME and END are default editing keys*/){ + var value = this.constraints[(e.charOrCode == dojo.keys.HOME ? "min" : "max")]; + if(typeof value == "number"){ + this._setValueAttr(value, false); + } + // eat home or end key whether we change the value or not + dojo.stopEvent(e); + } + } +}); + } diff --git a/lib/dijit/form/NumberTextBox.js b/lib/dijit/form/NumberTextBox.js index d477d1ba9..4d0d64f99 100644 --- a/lib/dijit/form/NumberTextBox.js +++ b/lib/dijit/form/NumberTextBox.js @@ -1,112 +1,278 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.NumberTextBox"]){ -dojo._hasResource["dijit.form.NumberTextBox"]=true; +if(!dojo._hasResource["dijit.form.NumberTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.NumberTextBox"] = true; dojo.provide("dijit.form.NumberTextBox"); dojo.require("dijit.form.ValidationTextBox"); dojo.require("dojo.number"); -dojo.declare("dijit.form.NumberTextBoxMixin",null,{regExpGen:dojo.number.regexp,value:NaN,editOptions:{pattern:"#.######"},_formatter:dojo.number.format,_setConstraintsAttr:function(_1){ -var _2=typeof _1.places=="number"?_1.places:0; -if(_2){ -_2++; -} -if(typeof _1.max!="number"){ -_1.max=9*Math.pow(10,15-_2); -} -if(typeof _1.min!="number"){ -_1.min=-9*Math.pow(10,15-_2); -} -this.inherited(arguments,[_1]); -if(this.focusNode&&this.focusNode.value&&!isNaN(this.value)){ -this.set("value",this.value); -} -},_onFocus:function(){ -if(this.disabled){ -return; -} -var _3=this.get("value"); -if(typeof _3=="number"&&!isNaN(_3)){ -var _4=this.format(_3,this.constraints); -if(_4!==undefined){ -this.textbox.value=_4; -} -} -this.inherited(arguments); -},format:function(_5,_6){ -var _7=String(_5); -if(typeof _5!="number"){ -return _7; -} -if(isNaN(_5)){ -return ""; -} -if(!("rangeCheck" in this&&this.rangeCheck(_5,_6))&&_6.exponent!==false&&/\de[-+]?\d/i.test(_7)){ -return _7; -} -if(this.editOptions&&this._focused){ -_6=dojo.mixin({},_6,this.editOptions); -} -return this._formatter(_5,_6); -},parse:dojo.number.parse,_getDisplayedValueAttr:function(){ -var v=this.inherited(arguments); -return isNaN(v)?this.textbox.value:v; -},filter:function(_8){ -return (_8===null||_8===""||_8===undefined)?NaN:this.inherited(arguments); -},serialize:function(_9,_a){ -return (typeof _9!="number"||isNaN(_9))?"":this.inherited(arguments); -},_setValueAttr:function(_b,_c,_d){ -if(_b!==undefined&&_d===undefined){ -_d=String(_b); -if(typeof _b=="number"){ -if(isNaN(_b)){ -_d=""; -}else{ -if(("rangeCheck" in this&&this.rangeCheck(_b,this.constraints))||this.constraints.exponent===false||!/\de[-+]?\d/i.test(_d)){ -_d=undefined; -} -} -}else{ -if(!_b){ -_d=""; -_b=NaN; -}else{ -_b=undefined; -} -} -} -this.inherited(arguments,[_b,_c,_d]); -},_getValueAttr:function(){ -var v=this.inherited(arguments); -if(isNaN(v)&&this.textbox.value!==""){ -if(this.constraints.exponent!==false&&/\de[-+]?\d/i.test(this.textbox.value)&&(new RegExp("^"+dojo.number._realNumberRegexp(dojo.mixin({},this.constraints))+"$").test(this.textbox.value))){ -var n=Number(this.textbox.value); -return isNaN(n)?undefined:n; -}else{ -return undefined; -} -}else{ -return v; -} -},isValid:function(_e){ -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)){ -return true; -}else{ -return this.inherited(arguments); -} -}else{ -return false; -} -} -}}); -dojo.declare("dijit.form.NumberTextBox",[dijit.form.RangeBoundTextBox,dijit.form.NumberTextBoxMixin],{}); + + +/*===== +dojo.declare( + "dijit.form.NumberTextBox.__Constraints", + [dijit.form.RangeBoundTextBox.__Constraints, dojo.number.__FormatOptions, dojo.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'} +}); +=====*/ + +dojo.declare("dijit.form.NumberTextBoxMixin", + null, + { + // summary: + // A mixin for all number textboxes + // tags: + // protected + + // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather + // than a straight regexp to deal with locale (plus formatting options too?) + regExpGen: dojo.number.regexp, + + /*===== + // constraints: dijit.form.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). See `dijit.form.NumberTextBox.__Constraints` for details. + 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: dojo.number.__FormatOptions? + // Formatting options + // tags: + // protected extension + + return "12345"; // String + }, + =====*/ + _formatter: dojo.number.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); + } + }, + + _onFocus: function(){ + if(this.disabled){ 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, /*dojo.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 = dojo.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: dojo.number.__ParseOptions + // Formatting options + // tags: + // protected + + return 123.45; // Number + }, + =====*/ + _parser: dojo.number.parse, + + parse: function(/*String*/ value, /*dojo.number.__FormatOptions*/ constraints){ + // summary: + // Replacable function to convert a formatted string to a number value + // tags: + // protected extension + + var v = this._parser(value, dojo.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. + return (value === null || value === '' || value === undefined) ? NaN : this.inherited(arguments); // set('value', null||''||undefined) should fire onChange(NaN) + }, + + 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 = dojo.hitch(dojo.mixin({}, 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 dojo.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 comnpute a real string value + } + }else if(!value){ // 0 processed in if branch above, ''|null|undefined flow thru 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 unparsable 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("^"+dojo.number._realNumberRegexp(dojo.mixin({}, 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 vaidation 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; + } + } + } + } +); + +dojo.declare("dijit.form.NumberTextBox", + [dijit.form.RangeBoundTextBox,dijit.form.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" + } +); + } diff --git a/lib/dijit/form/RadioButton.js b/lib/dijit/form/RadioButton.js index 154bdd4bc..295ec5ffd 100644 --- a/lib/dijit/form/RadioButton.js +++ b/lib/dijit/form/RadioButton.js @@ -1,12 +1,16 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.RadioButton"]){ -dojo._hasResource["dijit.form.RadioButton"]=true; +if(!dojo._hasResource["dijit.form.RadioButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.RadioButton"] = true; dojo.provide("dijit.form.RadioButton"); dojo.require("dijit.form.CheckBox"); + + +// TODO: for 2.0, move the RadioButton code into this file + } diff --git a/lib/dijit/form/RangeBoundTextBox.js b/lib/dijit/form/RangeBoundTextBox.js index b2e2f4ca9..589967f25 100644 --- a/lib/dijit/form/RangeBoundTextBox.js +++ b/lib/dijit/form/RangeBoundTextBox.js @@ -1,12 +1,15 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.RangeBoundTextBox"]){ -dojo._hasResource["dijit.form.RangeBoundTextBox"]=true; +if(!dojo._hasResource["dijit.form.RangeBoundTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.RangeBoundTextBox"] = true; dojo.provide("dijit.form.RangeBoundTextBox"); dojo.require("dijit.form.ValidationTextBox"); + + + } diff --git a/lib/dijit/form/Select.js b/lib/dijit/form/Select.js index 7caf553f2..a681cdf89 100644 --- a/lib/dijit/form/Select.js +++ b/lib/dijit/form/Select.js @@ -1,140 +1,305 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.Select"]){ -dojo._hasResource["dijit.form.Select"]=true; +if(!dojo._hasResource["dijit.form.Select"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Select"] = true; dojo.provide("dijit.form.Select"); dojo.require("dijit.form._FormSelectWidget"); dojo.require("dijit._HasDropDown"); dojo.require("dijit.Menu"); dojo.require("dijit.Tooltip"); -dojo.requireLocalization("dijit.form","validate",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.form._SelectMenu",dijit.Menu,{buildRendering:function(){ -this.inherited(arguments); -var o=(this.menuTableNode=this.domNode); -var n=(this.domNode=dojo.create("div",{style:{overflowX:"hidden",overflowY:"scroll"}})); -if(o.parentNode){ -o.parentNode.replaceChild(n,o); -} -dojo.removeClass(o,"dijitMenuTable"); -n.className=o.className+" dijitSelectMenu"; -o.className="dijitReset dijitMenuTable"; -dijit.setWaiRole(o,"listbox"); -dijit.setWaiRole(n,"presentation"); -n.appendChild(o); -},resize:function(mb){ -if(mb){ -dojo.marginBox(this.domNode,mb); -if("w" in mb){ -this.menuTableNode.style.width="100%"; -} -} -}}); -dojo.declare("dijit.form.Select",[dijit.form._FormSelectWidget,dijit._HasDropDown],{baseClass:"dijitSelect",templateString:dojo.cache("dijit.form","templates/Select.html","<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\twaiRole=\"combobox\" waiState=\"haspopup-true\"\n\t><tbody waiRole=\"presentation\"><tr waiRole=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" waiRole=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" waiState=\"hidden-true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" waiRole=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" waiRole=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" waiRole=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n"),attributeMap:dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode"}),required:false,state:"",tooltipPosition:[],emptyLabel:"",_isLoaded:false,_childrenLoaded:false,_fillContent:function(){ -this.inherited(arguments); -if(this.options.length&&!this.value&&this.srcNodeRef){ -var si=this.srcNodeRef.selectedIndex; -this.value=this.options[si!=-1?si:0].value; -} -this.dropDown=new dijit.form._SelectMenu({id:this.id+"_menu"}); -dojo.addClass(this.dropDown.domNode,this.baseClass+"Menu"); -},_getMenuItemForOption:function(_1){ -if(!_1.value){ -return new dijit.MenuSeparator(); -}else{ -var _2=dojo.hitch(this,"_setValueAttr",_1); -var _3=new dijit.MenuItem({option:_1,label:_1.label,onClick:_2,disabled:_1.disabled||false}); -dijit.setWaiRole(_3.focusNode,"listitem"); -return _3; -} -},_addOptionItem:function(_4){ -if(this.dropDown){ -this.dropDown.addChild(this._getMenuItemForOption(_4)); -} -},_getChildren:function(){ -if(!this.dropDown){ -return []; -} -return this.dropDown.getChildren(); -},_loadChildren:function(_5){ -if(_5===true){ -if(this.dropDown){ -delete this.dropDown.focusedChild; -} -if(this.options.length){ -this.inherited(arguments); -}else{ -dojo.forEach(this._getChildren(),function(_6){ -_6.destroyRecursive(); +dojo.requireLocalization("dijit.form", "validate", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +dojo.declare("dijit.form._SelectMenu", dijit.Menu, { + // summary: + // An internally-used menu for dropdown that allows us a vertical scrollbar + buildRendering: function(){ + // summary: + // Stub in our own changes, so that our domNode is not a table + // otherwise, we won't respond correctly to heights/overflows + this.inherited(arguments); + var o = (this.menuTableNode = this.domNode); + var n = (this.domNode = dojo.create("div", {style: {overflowX: "hidden", overflowY: "scroll"}})); + if(o.parentNode){ + o.parentNode.replaceChild(n, o); + } + dojo.removeClass(o, "dijitMenuTable"); + n.className = o.className + " dijitSelectMenu"; + o.className = "dijitReset dijitMenuTable"; + dijit.setWaiRole(o,"listbox"); + dijit.setWaiRole(n,"presentation"); + n.appendChild(o); + }, + + postCreate: function(){ + // summary: + // stop mousemove from selecting text on IE to be consistent with other browsers + + this.inherited(arguments); + + this.connect(this.domNode, "onmousemove", dojo.stopEvent); + }, + + resize: function(/*Object*/ mb){ + // summary: + // Overridden so that we are able to handle resizing our + // internal widget. Note that this is not a "full" resize + // implementation - it only works correctly if you pass it a + // marginBox. + // + // mb: Object + // The margin box to set this dropdown to. + if(mb){ + dojo.marginBox(this.domNode, mb); + if("w" in mb){ + // We've explicitly set the wrapper <div>'s width, so set <table> width to match. + // 100% is safer than a pixel value because there may be a scroll bar with + // browser/OS specific width. + this.menuTableNode.style.width = "100%"; + } + } + } }); -var _7=new dijit.MenuItem({label:" "}); -this.dropDown.addChild(_7); -} -}else{ -this._updateSelection(); -} -var _8=this.options.length; -this._isLoaded=false; -this._childrenLoaded=true; -if(!this._loadingStore){ -this._setValueAttr(this.value); -} -},_setValueAttr:function(_9){ -this.inherited(arguments); -dojo.attr(this.valueNode,"value",this.get("value")); -},_setDisplay:function(_a){ -this.containerNode.innerHTML="<span class=\"dijitReset dijitInline "+this.baseClass+"Label\">"+(_a||this.emptyLabel||" ")+"</span>"; -dijit.setWaiState(this.focusNode,"valuetext",(_a||this.emptyLabel||" ")); -},validate:function(_b){ -var _c=this.isValid(_b); -this.state=_c?"":"Error"; -this._setStateClass(); -dijit.setWaiState(this.focusNode,"invalid",_c?"false":"true"); -var _d=_c?"":this._missingMsg; -if(this._message!==_d){ -this._message=_d; -dijit.hideTooltip(this.domNode); -if(_d){ -dijit.showTooltip(_d,this.domNode,this.tooltipPosition,!this.isLeftToRight()); -} -} -return _c; -},isValid:function(_e){ -return (!this.required||!(/^\s*$/.test(this.value))); -},reset:function(){ -this.inherited(arguments); -dijit.hideTooltip(this.domNode); -this.state=""; -this._setStateClass(); -delete this._message; -},postMixInProperties:function(){ -this.inherited(arguments); -this._missingMsg=dojo.i18n.getLocalization("dijit.form","validate",this.lang).missingMessage; -},postCreate:function(){ -this.inherited(arguments); -if(this.tableNode.style.width){ -dojo.addClass(this.domNode,this.baseClass+"FixedWidth"); -} -},isLoaded:function(){ -return this._isLoaded; -},loadDropDown:function(_f){ -this._loadChildren(true); -this._isLoaded=true; -_f(); -},closeDropDown:function(){ -this.inherited(arguments); -if(this.dropDown&&this.dropDown.menuTableNode){ -this.dropDown.menuTableNode.style.width=""; -} -},uninitialize:function(_10){ -if(this.dropDown&&!this.dropDown._destroyed){ -this.dropDown.destroyRecursive(_10); -delete this.dropDown; -} -this.inherited(arguments); -}}); + +dojo.declare("dijit.form.Select", [dijit.form._FormSelectWidget, dijit._HasDropDown], { + // summary: + // This is a "styleable" select box - it is basically a DropDownButton which + // can take a <select> as its input. + + baseClass: "dijitSelect", + + templateString: dojo.cache("dijit.form", "templates/Select.html", "<table class=\"dijit dijitReset dijitInline dijitLeft\"\n\tdojoAttachPoint=\"_buttonNode,tableNode,focusNode\" cellspacing='0' cellpadding='0'\n\trole=\"combobox\" aria-haspopup=\"true\"\n\t><tbody role=\"presentation\"><tr role=\"presentation\"\n\t\t><td class=\"dijitReset dijitStretch dijitButtonContents dijitButtonNode\" role=\"presentation\"\n\t\t\t><span class=\"dijitReset dijitInline dijitButtonText\" dojoAttachPoint=\"containerNode,_popupStateNode\"></span\n\t\t\t><input type=\"hidden\" ${!nameAttrSetting} dojoAttachPoint=\"valueNode\" value=\"${value}\" aria-hidden=\"true\"\n\t\t/></td><td class=\"dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\t\tdojoAttachPoint=\"titleNode\" role=\"presentation\"\n\t\t\t><div class=\"dijitReset dijitArrowButtonInner\" role=\"presentation\"></div\n\t\t\t><div class=\"dijitReset dijitArrowButtonChar\" role=\"presentation\">▼</div\n\t\t></td\n\t></tr></tbody\n></table>\n"), + + // attributeMap: Object + // Add in our style to be applied to the focus node + attributeMap: dojo.mixin(dojo.clone(dijit.form._FormSelectWidget.prototype.attributeMap),{style:"tableNode"}), + + // required: Boolean + // Can be true or false, default is false. + required: false, + + // state: String + // Shows current state (ie, validation result) of input (Normal, Warning, or Error) + state: "", + + // message: String + // Currently displayed error/prompt message + message: "", + + // tooltipPosition: String[] + // See description of dijit.Tooltip.defaultPosition for details on this parameter. + tooltipPosition: [], + + // emptyLabel: string + // What to display in an "empty" dropdown + emptyLabel: " ", + + // _isLoaded: Boolean + // Whether or not we have been loaded + _isLoaded: false, + + // _childrenLoaded: Boolean + // Whether or not our children have been loaded + _childrenLoaded: false, + + _fillContent: function(){ + // summary: + // Set the value to be the first, or the selected index + this.inherited(arguments); + // set value from selected option + if(this.options.length && !this.value && this.srcNodeRef){ + var si = this.srcNodeRef.selectedIndex || 0; // || 0 needed for when srcNodeRef is not a SELECT + this.value = this.options[si >= 0 ? si : 0].value; + } + // Create the dropDown widget + this.dropDown = new dijit.form._SelectMenu({id: this.id + "_menu"}); + dojo.addClass(this.dropDown.domNode, this.baseClass + "Menu"); + }, + + _getMenuItemForOption: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // For the given option, return the menu item that should be + // used to display it. This can be overridden as needed + if(!option.value && !option.label){ + // We are a separator (no label set for it) + return new dijit.MenuSeparator(); + }else{ + // Just a regular menu option + var click = dojo.hitch(this, "_setValueAttr", option); + var item = new dijit.MenuItem({ + option: option, + label: option.label || this.emptyLabel, + onClick: click, + disabled: option.disabled || false + }); + dijit.setWaiRole(item.focusNode, "listitem"); + return item; + } + }, + + _addOptionItem: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // For the given option, add an option to our dropdown. + // If the option doesn't have a value, then a separator is added + // in that place. + if(this.dropDown){ + this.dropDown.addChild(this._getMenuItemForOption(option)); + } + }, + + _getChildren: function(){ + if(!this.dropDown){ + return []; + } + return this.dropDown.getChildren(); + }, + + _loadChildren: function(/*Boolean*/ loadMenuItems){ + // summary: + // Resets the menu and the length attribute of the button - and + // ensures that the label is appropriately set. + // loadMenuItems: Boolean + // actually loads the child menu items - we only do this when we are + // populating for showing the dropdown. + + if(loadMenuItems === true){ + // this.inherited destroys this.dropDown's child widgets (MenuItems). + // Avoid this.dropDown (Menu widget) having a pointer to a destroyed widget (which will cause + // issues later in _setSelected). (see #10296) + if(this.dropDown){ + delete this.dropDown.focusedChild; + } + if(this.options.length){ + this.inherited(arguments); + }else{ + // Drop down menu is blank but add one blank entry just so something appears on the screen + // to let users know that they are no choices (mimicing native select behavior) + dojo.forEach(this._getChildren(), function(child){ child.destroyRecursive(); }); + var item = new dijit.MenuItem({label: " "}); + this.dropDown.addChild(item); + } + }else{ + this._updateSelection(); + } + + this._isLoaded = false; + this._childrenLoaded = true; + + if(!this._loadingStore){ + // Don't call this if we are loading - since we will handle it later + this._setValueAttr(this.value); + } + }, + + _setValueAttr: function(value){ + this.inherited(arguments); + dojo.attr(this.valueNode, "value", this.get("value")); + }, + + _setDisplay: function(/*String*/ newDisplay){ + // summary: + // sets the display for the given value (or values) + var lbl = newDisplay || this.emptyLabel; + this.containerNode.innerHTML = '<span class="dijitReset dijitInline ' + this.baseClass + 'Label">' + lbl + '</span>'; + dijit.setWaiState(this.focusNode, "valuetext", lbl); + }, + + validate: function(/*Boolean*/ isFocused){ + // summary: + // Called by oninit, onblur, and onkeypress. + // description: + // Show missing or invalid messages if appropriate, and highlight textbox field. + // Used when a select is initially set to no value and the user is required to + // set the value. + + var isValid = this.isValid(isFocused); + this._set("state", isValid ? "" : "Error"); + dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); + var message = isValid ? "" : this._missingMsg; + if(this.message !== message){ + this._set("message", message); + dijit.hideTooltip(this.domNode); + if(message){ + dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + } + } + return isValid; + }, + + isValid: function(/*Boolean*/ isFocused){ + // summary: + // Whether or not this is a valid value. The only way a Select + // can be invalid is when it's required but nothing is selected. + return (!this.required || this.value === 0 || !(/^\s*$/.test(this.value || ""))); // handle value is null or undefined + }, + + reset: function(){ + // summary: + // Overridden so that the state will be cleared. + this.inherited(arguments); + dijit.hideTooltip(this.domNode); + this._set("state", ""); + this._set("message", "") + }, + + postMixInProperties: function(){ + // summary: + // set the missing message + this.inherited(arguments); + this._missingMsg = dojo.i18n.getLocalization("dijit.form", "validate", + this.lang).missingMessage; + }, + + postCreate: function(){ + // summary: + // stop mousemove from selecting text on IE to be consistent with other browsers + + this.inherited(arguments); + + this.connect(this.domNode, "onmousemove", dojo.stopEvent); + }, + + _setStyleAttr: function(/*String||Object*/ value){ + this.inherited(arguments); + dojo.toggleClass(this.domNode, this.baseClass + "FixedWidth", !!this.tableNode.style.width); + }, + + isLoaded: function(){ + return this._isLoaded; + }, + + loadDropDown: function(/*Function*/ loadCallback){ + // summary: + // populates the menu + this._loadChildren(true); + this._isLoaded = true; + loadCallback(); + }, + + closeDropDown: function(){ + // overriding _HasDropDown.closeDropDown() + this.inherited(arguments); + + if(this.dropDown && this.dropDown.menuTableNode){ + // Erase possible width: 100% setting from _SelectMenu.resize(). + // Leaving it would interfere with the next openDropDown() call, which + // queries the natural size of the drop down. + this.dropDown.menuTableNode.style.width = ""; + } + }, + + uninitialize: function(preserveDom){ + if(this.dropDown && !this.dropDown._destroyed){ + this.dropDown.destroyRecursive(preserveDom); + delete this.dropDown; + } + this.inherited(arguments); + } +}); + } diff --git a/lib/dijit/form/SimpleTextarea.js b/lib/dijit/form/SimpleTextarea.js index b4824c74e..6ff823105 100644 --- a/lib/dijit/form/SimpleTextarea.js +++ b/lib/dijit/form/SimpleTextarea.js @@ -1,59 +1,103 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ -dojo._hasResource["dijit.form.SimpleTextarea"]=true; +if(!dojo._hasResource["dijit.form.SimpleTextarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.SimpleTextarea"] = true; dojo.provide("dijit.form.SimpleTextarea"); dojo.require("dijit.form.TextBox"); -dojo.declare("dijit.form.SimpleTextarea",dijit.form.TextBox,{baseClass:"dijitTextBox dijitTextArea",attributeMap:dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap,{rows:"textbox",cols:"textbox"}),rows:"3",cols:"20",templateString:"<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>",postMixInProperties:function(){ -if(!this.value&&this.srcNodeRef){ -this.value=this.srcNodeRef.value; -} -this.inherited(arguments); -},filter:function(_1){ -if(_1){ -_1=_1.replace(/\r/g,""); -} -return this.inherited(arguments); -},postCreate:function(){ -this.inherited(arguments); -if(dojo.isIE&&this.cols){ -dojo.addClass(this.textbox,"dijitTextAreaCols"); -} -},_previousValue:"",_onInput:function(e){ -if(this.maxLength){ -var _2=parseInt(this.maxLength); -var _3=this.textbox.value.replace(/\r/g,""); -var _4=_3.length-_2; -if(_4>0){ -if(e){ -dojo.stopEvent(e); -} -var _5=this.textbox; -if(_5.selectionStart){ -var _6=_5.selectionStart; -var cr=0; -if(dojo.isOpera){ -cr=(this.textbox.value.substring(0,_6).match(/\r/g)||[]).length; -} -this.textbox.value=_3.substring(0,_6-_4-cr)+_3.substring(_6-cr); -_5.setSelectionRange(_6-_4,_6-_4); -}else{ -if(dojo.doc.selection){ -_5.focus(); -var _7=dojo.doc.selection.createRange(); -_7.moveStart("character",-_4); -_7.text=""; -_7.select(); -} -} -} -this._previousValue=this.textbox.value; -} -this.inherited(arguments); -}}); + + +dojo.declare("dijit.form.SimpleTextarea", + dijit.form.TextBox, + { + // summary: + // A simple textarea that degrades, and responds to + // minimal LayoutContainer usage, and works with dijit.form.Form. + // Doesn't automatically size according to input, like Textarea. + // + // example: + // | <textarea dojoType="dijit.form.SimpleTextarea" name="foo" value="bar" rows=30 cols=40></textarea> + // + // example: + // | new dijit.form.SimpleTextarea({ rows:20, cols:30 }, "foo"); + + baseClass: "dijitTextBox dijitTextArea", + + attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { + rows:"textbox", cols: "textbox" + }), + + // rows: Number + // The number of rows of text. + rows: "3", + + // rows: Number + // The number of characters per line. + cols: "20", + + templateString: "<textarea ${!nameAttrSetting} dojoAttachPoint='focusNode,containerNode,textbox' autocomplete='off'></textarea>", + + postMixInProperties: function(){ + // Copy value from srcNodeRef, unless user specified a value explicitly (or there is no srcNodeRef) + // TODO: parser will handle this in 2.0 + if(!this.value && this.srcNodeRef){ + this.value = this.srcNodeRef.value; + } + this.inherited(arguments); + }, + + buildRendering: function(){ + this.inherited(arguments); + if(dojo.isIE && this.cols){ // attribute selectors is not supported in IE6 + dojo.addClass(this.textbox, "dijitTextAreaCols"); + } + }, + + filter: function(/*String*/ value){ + // Override TextBox.filter to deal with newlines... specifically (IIRC) this is for IE which writes newlines + // as \r\n instead of just \n + if(value){ + value = value.replace(/\r/g,""); + } + return this.inherited(arguments); + }, + + _previousValue: "", + _onInput: function(/*Event?*/ e){ + // Override TextBox._onInput() to enforce maxLength restriction + if(this.maxLength){ + var maxLength = parseInt(this.maxLength); + var value = this.textbox.value.replace(/\r/g,''); + var overflow = value.length - maxLength; + if(overflow > 0){ + if(e){ dojo.stopEvent(e); } + var textarea = this.textbox; + if(textarea.selectionStart){ + var pos = textarea.selectionStart; + var cr = 0; + if(dojo.isOpera){ + cr = (this.textbox.value.substring(0,pos).match(/\r/g) || []).length; + } + this.textbox.value = value.substring(0,pos-overflow-cr)+value.substring(pos-cr); + textarea.setSelectionRange(pos-overflow, pos-overflow); + }else if(dojo.doc.selection){ //IE + textarea.focus(); + var range = dojo.doc.selection.createRange(); + // delete overflow characters + range.moveStart("character", -overflow); + range.text = ''; + // show cursor + range.select(); + } + } + this._previousValue = this.textbox.value; + } + this.inherited(arguments); + } +}); + } diff --git a/lib/dijit/form/Slider.js b/lib/dijit/form/Slider.js index 051d57929..80d4cfa98 100644 --- a/lib/dijit/form/Slider.js +++ b/lib/dijit/form/Slider.js @@ -1,18 +1,23 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.Slider"]){ -dojo._hasResource["dijit.form.Slider"]=true; +if(!dojo._hasResource["dijit.form.Slider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Slider"] = true; dojo.provide("dijit.form.Slider"); -dojo.deprecated("Call require() for HorizontalSlider / VerticalRule, explicitly rather than 'dijit.form.Slider' itself","","2.0"); dojo.require("dijit.form.HorizontalSlider"); dojo.require("dijit.form.VerticalSlider"); dojo.require("dijit.form.HorizontalRule"); dojo.require("dijit.form.VerticalRule"); dojo.require("dijit.form.HorizontalRuleLabels"); dojo.require("dijit.form.VerticalRuleLabels"); + + +dojo.deprecated("Call require() for HorizontalSlider / VerticalRule, explicitly rather than 'dijit.form.Slider' itself", "", "2.0"); + +// For back-compat, remove for 2.0 + } diff --git a/lib/dijit/form/TextBox.js b/lib/dijit/form/TextBox.js index 81c9b2a04..38a9c09d4 100644 --- a/lib/dijit/form/TextBox.js +++ b/lib/dijit/form/TextBox.js @@ -1,209 +1,427 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.TextBox"]){ -dojo._hasResource["dijit.form.TextBox"]=true; +if(!dojo._hasResource["dijit.form.TextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.TextBox"] = true; dojo.provide("dijit.form.TextBox"); dojo.require("dijit.form._FormWidget"); -dojo.declare("dijit.form.TextBox",dijit.form._FormValueWidget,{trim:false,uppercase:false,lowercase:false,propercase:false,maxLength:"",selectOnClick:false,placeHolder:"",templateString:dojo.cache("dijit.form","templates/TextBox.html","<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),_singleNodeTemplate:"<input class=\"dijit dijitReset dijitLeft dijitInputField\" dojoAttachPoint=\"textbox,focusNode\" autocomplete=\"off\" type=\"${type}\" ${!nameAttrSetting} />",_buttonInputDisabled:dojo.isIE?"disabled":"",baseClass:"dijitTextBox",attributeMap:dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap,{maxLength:"focusNode"}),postMixInProperties:function(){ -var _1=this.type.toLowerCase(); -if(this.templateString.toLowerCase()=="input"||((_1=="hidden"||_1=="file")&&this.templateString==dijit.form.TextBox.prototype.templateString)){ -this.templateString=this._singleNodeTemplate; -} -this.inherited(arguments); -},_setPlaceHolderAttr:function(v){ -this.placeHolder=v; -if(!this._phspan){ -this._attachPoints.push("_phspan"); -this._phspan=dojo.create("span",{className:"dijitPlaceHolder dijitInputField"},this.textbox,"after"); -} -this._phspan.innerHTML=""; -this._phspan.appendChild(document.createTextNode(v)); -this._updatePlaceHolder(); -},_updatePlaceHolder:function(){ -if(this._phspan){ -this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none"; -} -},_getValueAttr:function(){ -return this.parse(this.get("displayedValue"),this.constraints); -},_setValueAttr:function(_2,_3,_4){ -var _5; -if(_2!==undefined){ -_5=this.filter(_2); -if(typeof _4!="string"){ -if(_5!==null&&((typeof _5!="number")||!isNaN(_5))){ -_4=this.filter(this.format(_5,this.constraints)); -}else{ -_4=""; -} -} -} -if(_4!=null&&_4!=undefined&&((typeof _4)!="number"||!isNaN(_4))&&this.textbox.value!=_4){ -this.textbox.value=_4; -} -this._updatePlaceHolder(); -this.inherited(arguments,[_5,_3]); -},displayedValue:"",getDisplayedValue:function(){ -dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.","","2.0"); -return this.get("displayedValue"); -},_getDisplayedValueAttr:function(){ -return this.filter(this.textbox.value); -},setDisplayedValue:function(_6){ -dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.","","2.0"); -this.set("displayedValue",_6); -},_setDisplayedValueAttr:function(_7){ -if(_7===null||_7===undefined){ -_7=""; -}else{ -if(typeof _7!="string"){ -_7=String(_7); -} -} -this.textbox.value=_7; -this._setValueAttr(this.get("value"),undefined,_7); -},format:function(_8,_9){ -return ((_8==null||_8==undefined)?"":(_8.toString?_8.toString():_8)); -},parse:function(_a,_b){ -return _a; -},_refreshState:function(){ -},_onInput:function(e){ -if(e&&e.type&&/key/i.test(e.type)&&e.keyCode){ -switch(e.keyCode){ -case dojo.keys.SHIFT: -case dojo.keys.ALT: -case dojo.keys.CTRL: -case dojo.keys.TAB: -return; -} -} -if(this.intermediateChanges){ -var _c=this; -setTimeout(function(){ -_c._handleOnChange(_c.get("value"),false); -},0); -} -this._refreshState(); -},postCreate:function(){ -if(dojo.isIE){ -var s=dojo.getComputedStyle(this.domNode); -if(s){ -var ff=s.fontFamily; -if(ff){ -var _d=this.domNode.getElementsByTagName("INPUT"); -if(_d){ -for(var i=0;i<_d.length;i++){ -_d[i].style.fontFamily=ff; -} -} -} -} -} -this.textbox.setAttribute("value",this.textbox.value); -this.inherited(arguments); -if(dojo.isMoz||dojo.isOpera){ -this.connect(this.textbox,"oninput",this._onInput); -}else{ -this.connect(this.textbox,"onkeydown",this._onInput); -this.connect(this.textbox,"onkeyup",this._onInput); -this.connect(this.textbox,"onpaste",this._onInput); -this.connect(this.textbox,"oncut",this._onInput); -} -},_blankValue:"",filter:function(_e){ -if(_e===null){ -return this._blankValue; -} -if(typeof _e!="string"){ -return _e; -} -if(this.trim){ -_e=dojo.trim(_e); -} -if(this.uppercase){ -_e=_e.toUpperCase(); -} -if(this.lowercase){ -_e=_e.toLowerCase(); -} -if(this.propercase){ -_e=_e.replace(/[^\s]+/g,function(_f){ -return _f.substring(0,1).toUpperCase()+_f.substring(1); -}); -} -return _e; -},_setBlurValue:function(){ -this._setValueAttr(this.get("value"),true); -},_onBlur:function(e){ -if(this.disabled){ -return; -} -this._setBlurValue(); -this.inherited(arguments); -if(this._selectOnClickHandle){ -this.disconnect(this._selectOnClickHandle); -} -if(this.selectOnClick&&dojo.isMoz){ -this.textbox.selectionStart=this.textbox.selectionEnd=undefined; -} -this._updatePlaceHolder(); -},_onFocus:function(by){ -if(this.disabled||this.readOnly){ -return; -} -if(this.selectOnClick&&by=="mouse"){ -this._selectOnClickHandle=this.connect(this.domNode,"onmouseup",function(){ -this.disconnect(this._selectOnClickHandle); -var _10; -if(dojo.isIE){ -var _11=dojo.doc.selection.createRange(); -var _12=_11.parentElement(); -_10=_12==this.textbox&&_11.text.length==0; -}else{ -_10=this.textbox.selectionStart==this.textbox.selectionEnd; -} -if(_10){ -dijit.selectInputText(this.textbox); -} -}); -} -this._updatePlaceHolder(); -this._refreshState(); -this.inherited(arguments); -},reset:function(){ -this.textbox.value=""; -this.inherited(arguments); -}}); -dijit.selectInputText=function(_13,_14,_15){ -var _16=dojo.global; -var _17=dojo.doc; -_13=dojo.byId(_13); -if(isNaN(_14)){ -_14=0; -} -if(isNaN(_15)){ -_15=_13.value?_13.value.length:0; -} -dijit.focus(_13); -if(_17["selection"]&&dojo.body()["createTextRange"]){ -if(_13.createTextRange){ -var _18=_13.createTextRange(); -with(_18){ -collapse(true); -moveStart("character",-99999); -moveStart("character",_14); -moveEnd("character",_15-_14); -select(); -} -} -}else{ -if(_16["getSelection"]){ -if(_13.setSelectionRange){ -_13.setSelectionRange(_14,_15); -} -} -} + + +dojo.declare( + "dijit.form.TextBox", + dijit.form._FormValueWidget, + { + // summary: + // A base class for textbox form inputs + + // trim: Boolean + // Removes leading and trailing whitespace if true. Default is false. + trim: false, + + // uppercase: Boolean + // Converts all characters to uppercase if true. Default is false. + uppercase: false, + + // lowercase: Boolean + // Converts all characters to lowercase if true. Default is false. + lowercase: false, + + // propercase: Boolean + // Converts the first character of each word to uppercase if true. + propercase: false, + + // maxLength: String + // HTML INPUT tag maxLength declaration. + maxLength: "", + + // selectOnClick: [const] Boolean + // If true, all text will be selected when focused with mouse + selectOnClick: false, + + // placeHolder: String + // Defines a hint to help users fill out the input field (as defined in HTML 5). + // This should only contain plain text (no html markup). + placeHolder: "", + + templateString: dojo.cache("dijit.form", "templates/TextBox.html", "<div class=\"dijit dijitReset dijitInline dijitLeft\" id=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"), + _singleNodeTemplate: '<input class="dijit dijitReset dijitLeft dijitInputField" dojoAttachPoint="textbox,focusNode" autocomplete="off" type="${type}" ${!nameAttrSetting} />', + + _buttonInputDisabled: dojo.isIE ? "disabled" : "", // allows IE to disallow focus, but Firefox cannot be disabled for mousedown events + + baseClass: "dijitTextBox", + + attributeMap: dojo.delegate(dijit.form._FormValueWidget.prototype.attributeMap, { + maxLength: "focusNode" + }), + + postMixInProperties: function(){ + var type = this.type.toLowerCase(); + if(this.templateString && this.templateString.toLowerCase() == "input" || ((type == "hidden" || type == "file") && this.templateString == dijit.form.TextBox.prototype.templateString)){ + this.templateString = this._singleNodeTemplate; + } + this.inherited(arguments); + }, + + _setPlaceHolderAttr: function(v){ + this._set("placeHolder", v); + if(!this._phspan){ + this._attachPoints.push('_phspan'); + /* dijitInputField class gives placeHolder same padding as the input field + * parent node already has dijitInputField class but it doesn't affect this <span> + * since it's position: absolute. + */ + this._phspan = dojo.create('span',{className:'dijitPlaceHolder dijitInputField'},this.textbox,'after'); + } + this._phspan.innerHTML=""; + this._phspan.appendChild(document.createTextNode(v)); + + this._updatePlaceHolder(); + }, + + _updatePlaceHolder: function(){ + if(this._phspan){ + this._phspan.style.display=(this.placeHolder&&!this._focused&&!this.textbox.value)?"":"none"; + } + }, + + _getValueAttr: function(){ + // summary: + // Hook so get('value') works as we like. + // description: + // For `dijit.form.TextBox` this basically returns the value of the <input>. + // + // For `dijit.form.MappedTextBox` subclasses, which have both + // a "displayed value" and a separate "submit value", + // This treats the "displayed value" as the master value, computing the + // submit value from it via this.parse(). + return this.parse(this.get('displayedValue'), this.constraints); + }, + + _setValueAttr: function(value, /*Boolean?*/ priorityChange, /*String?*/ formattedValue){ + // summary: + // Hook so set('value', ...) works. + // + // description: + // Sets the value of the widget to "value" which can be of + // any type as determined by the widget. + // + // value: + // The visual element value is also set to a corresponding, + // but not necessarily the same, value. + // + // formattedValue: + // If specified, used to set the visual element value, + // otherwise a computed visual value is used. + // + // priorityChange: + // If true, an onChange event is fired immediately instead of + // waiting for the next blur event. + + var filteredValue; + if(value !== undefined){ + // TODO: this is calling filter() on both the display value and the actual value. + // I added a comment to the filter() definition about this, but it should be changed. + filteredValue = this.filter(value); + if(typeof formattedValue != "string"){ + if(filteredValue !== null && ((typeof filteredValue != "number") || !isNaN(filteredValue))){ + formattedValue = this.filter(this.format(filteredValue, this.constraints)); + }else{ formattedValue = ''; } + } + } + if(formattedValue != null && formattedValue != undefined && ((typeof formattedValue) != "number" || !isNaN(formattedValue)) && this.textbox.value != formattedValue){ + this.textbox.value = formattedValue; + this._set("displayedValue", this.get("displayedValue")); + } + + this._updatePlaceHolder(); + + this.inherited(arguments, [filteredValue, priorityChange]); + }, + + // displayedValue: String + // For subclasses like ComboBox where the displayed value + // (ex: Kentucky) and the serialized value (ex: KY) are different, + // this represents the displayed value. + // + // Setting 'displayedValue' through set('displayedValue', ...) + // updates 'value', and vice-versa. Otherwise 'value' is updated + // from 'displayedValue' periodically, like onBlur etc. + // + // TODO: move declaration to MappedTextBox? + // Problem is that ComboBox references displayedValue, + // for benefit of FilteringSelect. + displayedValue: "", + + getDisplayedValue: function(){ + // summary: + // Deprecated. Use get('displayedValue') instead. + // tags: + // deprecated + dojo.deprecated(this.declaredClass+"::getDisplayedValue() is deprecated. Use set('displayedValue') instead.", "", "2.0"); + return this.get('displayedValue'); + }, + + _getDisplayedValueAttr: function(){ + // summary: + // Hook so get('displayedValue') works. + // description: + // Returns the displayed value (what the user sees on the screen), + // after filtering (ie, trimming spaces etc.). + // + // For some subclasses of TextBox (like ComboBox), the displayed value + // is different from the serialized value that's actually + // sent to the server (see dijit.form.ValidationTextBox.serialize) + + // TODO: maybe we should update this.displayedValue on every keystroke so that we don't need + // this method + // TODO: this isn't really the displayed value when the user is typing + return this.filter(this.textbox.value); + }, + + setDisplayedValue: function(/*String*/ value){ + // summary: + // Deprecated. Use set('displayedValue', ...) instead. + // tags: + // deprecated + dojo.deprecated(this.declaredClass+"::setDisplayedValue() is deprecated. Use set('displayedValue', ...) instead.", "", "2.0"); + this.set('displayedValue', value); + }, + + _setDisplayedValueAttr: function(/*String*/ value){ + // summary: + // Hook so set('displayedValue', ...) works. + // description: + // Sets the value of the visual element to the string "value". + // The widget value is also set to a corresponding, + // but not necessarily the same, value. + + if(value === null || value === undefined){ value = '' } + else if(typeof value != "string"){ value = String(value) } + + this.textbox.value = value; + + // sets the serialized value to something corresponding to specified displayedValue + // (if possible), and also updates the textbox.value, for example converting "123" + // to "123.00" + this._setValueAttr(this.get('value'), undefined); + + this._set("displayedValue", this.get('displayedValue')); + }, + + format: function(/*String*/ value, /*Object*/ constraints){ + // summary: + // Replacable function to convert a value to a properly formatted string. + // tags: + // protected extension + return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value)); + }, + + parse: function(/*String*/ value, /*Object*/ constraints){ + // summary: + // Replacable function to convert a formatted string to a value + // tags: + // protected extension + + return value; // String + }, + + _refreshState: function(){ + // summary: + // After the user types some characters, etc., this method is + // called to check the field for validity etc. The base method + // in `dijit.form.TextBox` does nothing, but subclasses override. + // tags: + // protected + }, + + _onInput: function(e){ + if(e && e.type && /key/i.test(e.type) && e.keyCode){ + switch(e.keyCode){ + case dojo.keys.SHIFT: + case dojo.keys.ALT: + case dojo.keys.CTRL: + case dojo.keys.TAB: + return; + } + } + if(this.intermediateChanges){ + var _this = this; + // the setTimeout allows the key to post to the widget input box + setTimeout(function(){ _this._handleOnChange(_this.get('value'), false); }, 0); + } + this._refreshState(); + + // In case someone is watch()'ing for changes to displayedValue + this._set("displayedValue", this.get("displayedValue")); + }, + + postCreate: function(){ + if(dojo.isIE){ // IE INPUT tag fontFamily has to be set directly using STYLE + // the setTimeout gives IE a chance to render the TextBox and to deal with font inheritance + setTimeout(dojo.hitch(this, function(){ + var s = dojo.getComputedStyle(this.domNode); + if(s){ + var ff = s.fontFamily; + if(ff){ + var inputs = this.domNode.getElementsByTagName("INPUT"); + if(inputs){ + for(var i=0; i < inputs.length; i++){ + inputs[i].style.fontFamily = ff; + } + } + } + } + }), 0); + } + + // setting the value here is needed since value="" in the template causes "undefined" + // and setting in the DOM (instead of the JS object) helps with form reset actions + this.textbox.setAttribute("value", this.textbox.value); // DOM and JS values should be the same + + this.inherited(arguments); + + if(dojo.isMoz || dojo.isOpera){ + this.connect(this.textbox, "oninput", "_onInput"); + }else{ + this.connect(this.textbox, "onkeydown", "_onInput"); + this.connect(this.textbox, "onkeyup", "_onInput"); + this.connect(this.textbox, "onpaste", "_onInput"); + this.connect(this.textbox, "oncut", "_onInput"); + } + }, + + _blankValue: '', // if the textbox is blank, what value should be reported + filter: function(val){ + // summary: + // Auto-corrections (such as trimming) that are applied to textbox + // value on blur or form submit. + // description: + // For MappedTextBox subclasses, this is called twice + // - once with the display value + // - once the value as set/returned by set('value', ...) + // and get('value'), ex: a Number for NumberTextBox. + // + // In the latter case it does corrections like converting null to NaN. In + // the former case the NumberTextBox.filter() method calls this.inherited() + // to execute standard trimming code in TextBox.filter(). + // + // TODO: break this into two methods in 2.0 + // + // tags: + // protected extension + if(val === null){ return this._blankValue; } + if(typeof val != "string"){ return val; } + if(this.trim){ + val = dojo.trim(val); + } + if(this.uppercase){ + val = val.toUpperCase(); + } + if(this.lowercase){ + val = val.toLowerCase(); + } + if(this.propercase){ + val = val.replace(/[^\s]+/g, function(word){ + return word.substring(0,1).toUpperCase() + word.substring(1); + }); + } + return val; + }, + + _setBlurValue: function(){ + this._setValueAttr(this.get('value'), true); + }, + + _onBlur: function(e){ + if(this.disabled){ return; } + this._setBlurValue(); + this.inherited(arguments); + + if(this._selectOnClickHandle){ + this.disconnect(this._selectOnClickHandle); + } + if(this.selectOnClick && dojo.isMoz){ + this.textbox.selectionStart = this.textbox.selectionEnd = undefined; // clear selection so that the next mouse click doesn't reselect + } + + this._updatePlaceHolder(); + }, + + _onFocus: function(/*String*/ by){ + if(this.disabled || this.readOnly){ return; } + + // Select all text on focus via click if nothing already selected. + // Since mouse-up will clear the selection need to defer selection until after mouse-up. + // Don't do anything on focus by tabbing into the widget since there's no associated mouse-up event. + if(this.selectOnClick && by == "mouse"){ + this._selectOnClickHandle = this.connect(this.domNode, "onmouseup", function(){ + // Only select all text on first click; otherwise users would have no way to clear + // the selection. + this.disconnect(this._selectOnClickHandle); + + // Check if the user selected some text manually (mouse-down, mouse-move, mouse-up) + // and if not, then select all the text + var textIsNotSelected; + if(dojo.isIE){ + var range = dojo.doc.selection.createRange(); + var parent = range.parentElement(); + textIsNotSelected = parent == this.textbox && range.text.length == 0; + }else{ + textIsNotSelected = this.textbox.selectionStart == this.textbox.selectionEnd; + } + if(textIsNotSelected){ + dijit.selectInputText(this.textbox); + } + }); + } + + this._updatePlaceHolder(); + + // call this.inherited() before refreshState(), since this.inherited() will possibly scroll the viewport + // (to scroll the TextBox into view), which will affect how _refreshState() positions the tooltip + this.inherited(arguments); + + this._refreshState(); + }, + + reset: function(){ + // Overrides dijit._FormWidget.reset(). + // Additionally resets the displayed textbox value to '' + this.textbox.value = ''; + this.inherited(arguments); + } + } +); + +dijit.selectInputText = function(/*DomNode*/ element, /*Number?*/ start, /*Number?*/ stop){ + // summary: + // Select text in the input element argument, from start (default 0), to stop (default end). + + // TODO: use functions in _editor/selection.js? + var _window = dojo.global; + var _document = dojo.doc; + element = dojo.byId(element); + if(isNaN(start)){ start = 0; } + if(isNaN(stop)){ stop = element.value ? element.value.length : 0; } + dijit.focus(element); + if(_document["selection"] && dojo.body()["createTextRange"]){ // IE + if(element.createTextRange){ + var r = element.createTextRange(); + r.collapse(true); + r.moveStart("character", -99999); // move to 0 + r.moveStart("character", start); // delta from 0 is the correct position + r.moveEnd("character", stop-start); + r.select(); + } + }else if(_window["getSelection"]){ + if(element.setSelectionRange){ + element.setSelectionRange(start, stop); + } + } }; + } diff --git a/lib/dijit/form/Textarea.js b/lib/dijit/form/Textarea.js index 4dd9cd928..cc1ec917e 100644 --- a/lib/dijit/form/Textarea.js +++ b/lib/dijit/form/Textarea.js @@ -1,103 +1,167 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.Textarea"]){ -dojo._hasResource["dijit.form.Textarea"]=true; +if(!dojo._hasResource["dijit.form.Textarea"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.Textarea"] = true; dojo.provide("dijit.form.Textarea"); dojo.require("dijit.form.SimpleTextarea"); -dojo.declare("dijit.form.Textarea",dijit.form.SimpleTextarea,{cols:"",_previousNewlines:0,_strictMode:(dojo.doc.compatMode!="BackCompat"),_getHeight:function(_1){ -var _2=_1.scrollHeight; -if(dojo.isIE){ -_2+=_1.offsetHeight-_1.clientHeight-((dojo.isIE<8&&this._strictMode)?dojo._getPadBorderExtents(_1).h:0); -}else{ -if(dojo.isMoz){ -_2+=_1.offsetHeight-_1.clientHeight; -}else{ -if(dojo.isWebKit&&!(dojo.isSafari<4)){ -_2+=dojo._getBorderExtents(_1).h; -}else{ -_2+=dojo._getPadBorderExtents(_1).h; -} -} -} -return _2; -},_estimateHeight:function(_3){ -_3.style.maxHeight=""; -_3.style.height="auto"; -_3.rows=(_3.value.match(/\n/g)||[]).length+1; -},_needsHelpShrinking:dojo.isMoz||dojo.isWebKit,_onInput:function(){ -this.inherited(arguments); -if(this._busyResizing){ -return; -} -this._busyResizing=true; -var _4=this.textbox; -if(_4.scrollHeight&&_4.offsetHeight&&_4.clientHeight){ -var _5=this._getHeight(_4)+"px"; -if(_4.style.height!=_5){ -_4.style.maxHeight=_4.style.height=_5; -} -if(this._needsHelpShrinking){ -if(this._setTimeoutHandle){ -clearTimeout(this._setTimeoutHandle); -} -this._setTimeoutHandle=setTimeout(dojo.hitch(this,"_shrink"),0); -} -}else{ -this._estimateHeight(_4); -} -this._busyResizing=false; -},_busyResizing:false,_shrink:function(){ -this._setTimeoutHandle=null; -if(this._needsHelpShrinking&&!this._busyResizing){ -this._busyResizing=true; -var _6=this.textbox; -var _7=false; -if(_6.value==""){ -_6.value=" "; -_7=true; -} -var _8=_6.scrollHeight; -if(!_8){ -this._estimateHeight(_6); -}else{ -var _9=_6.style.paddingBottom; -var _a=dojo._getPadExtents(_6); -_a=_a.h-_a.t; -_6.style.paddingBottom=_a+1+"px"; -var _b=this._getHeight(_6)-1+"px"; -if(_6.style.maxHeight!=_b){ -_6.style.paddingBottom=_a+_8+"px"; -_6.scrollTop=0; -_6.style.maxHeight=this._getHeight(_6)-_8+"px"; -} -_6.style.paddingBottom=_9; -} -if(_7){ -_6.value=""; -} -this._busyResizing=false; -} -},resize:function(){ -this._onInput(); -},_setValueAttr:function(){ -this.inherited(arguments); -this.resize(); -},postCreate:function(){ -this.inherited(arguments); -dojo.style(this.textbox,{overflowY:"hidden",overflowX:"auto",boxSizing:"border-box",MsBoxSizing:"border-box",WebkitBoxSizing:"border-box",MozBoxSizing:"border-box"}); -this.connect(this.textbox,"onscroll",this._onInput); -this.connect(this.textbox,"onresize",this._onInput); -this.connect(this.textbox,"onfocus",this._onInput); -this._setTimeoutHandle=setTimeout(dojo.hitch(this,"resize"),0); -},uninitialize:function(){ -if(this._setTimeoutHandle){ -clearTimeout(this._setTimeoutHandle); -} -this.inherited(arguments); -}}); + + +dojo.declare( + "dijit.form.Textarea", + dijit.form.SimpleTextarea, + { + // summary: + // A textarea widget that adjusts it's height according to the amount of data. + // + // description: + // A textarea that dynamically expands/contracts (changing it's height) as + // the user types, to display all the text without requiring a scroll bar. + // + // Takes nearly all the parameters (name, value, etc.) that a vanilla textarea takes. + // Rows is not supported since this widget adjusts the height. + // + // example: + // | <textarea dojoType="dijit.form.TextArea">...</textarea> + + + // TODO: for 2.0, rename this to ExpandingTextArea, and rename SimpleTextarea to Textarea + + baseClass: "dijitTextBox dijitTextArea dijitExpandingTextArea", + + // Override SimpleTextArea.cols to default to width:100%, for backward compatibility + cols: "", + + _previousNewlines: 0, + _strictMode: (dojo.doc.compatMode != 'BackCompat'), // not the same as !dojo.isQuirks + + _getHeight: function(textarea){ + var newH = textarea.scrollHeight; + if(dojo.isIE){ + newH += textarea.offsetHeight - textarea.clientHeight - ((dojo.isIE < 8 && this._strictMode) ? dojo._getPadBorderExtents(textarea).h : 0); + }else if(dojo.isMoz){ + newH += textarea.offsetHeight - textarea.clientHeight; // creates room for horizontal scrollbar + }else if(dojo.isWebKit){ + newH += dojo._getBorderExtents(textarea).h; + }else{ // Opera 9.6 (TODO: test if this is still needed) + newH += dojo._getPadBorderExtents(textarea).h; + } + return newH; + }, + + _estimateHeight: function(textarea){ + // summary: + // Approximate the height when the textarea is invisible with the number of lines in the text. + // Fails when someone calls setValue with a long wrapping line, but the layout fixes itself when the user clicks inside so . . . + // In IE, the resize event is supposed to fire when the textarea becomes visible again and that will correct the size automatically. + // + textarea.style.maxHeight = ""; + textarea.style.height = "auto"; + // #rows = #newlines+1 + // Note: on Moz, the following #rows appears to be 1 too many. + // Actually, Moz is reserving room for the scrollbar. + // If you increase the font size, this behavior becomes readily apparent as the last line gets cut off without the +1. + textarea.rows = (textarea.value.match(/\n/g) || []).length + 1; + }, + + _needsHelpShrinking: dojo.isMoz || dojo.isWebKit, + + _onInput: function(){ + // Override SimpleTextArea._onInput() to deal with height adjustment + this.inherited(arguments); + if(this._busyResizing){ return; } + this._busyResizing = true; + var textarea = this.textbox; + if(textarea.scrollHeight && textarea.offsetHeight && textarea.clientHeight){ + var newH = this._getHeight(textarea) + "px"; + if(textarea.style.height != newH){ + textarea.style.maxHeight = textarea.style.height = newH; + } + if(this._needsHelpShrinking){ + if(this._setTimeoutHandle){ + clearTimeout(this._setTimeoutHandle); + } + this._setTimeoutHandle = setTimeout(dojo.hitch(this, "_shrink"), 0); // try to collapse multiple shrinks into 1 + } + }else{ + // hidden content of unknown size + this._estimateHeight(textarea); + } + this._busyResizing = false; + }, + + _busyResizing: false, + _shrink: function(){ + // grow paddingBottom to see if scrollHeight shrinks (when it is unneccesarily big) + this._setTimeoutHandle = null; + if(this._needsHelpShrinking && !this._busyResizing){ + this._busyResizing = true; + var textarea = this.textbox; + var empty = false; + if(textarea.value == ''){ + textarea.value = ' '; // prevent collapse all the way back to 0 + empty = true; + } + var scrollHeight = textarea.scrollHeight; + if(!scrollHeight){ + this._estimateHeight(textarea); + }else{ + var oldPadding = textarea.style.paddingBottom; + var newPadding = dojo._getPadExtents(textarea); + newPadding = newPadding.h - newPadding.t; + textarea.style.paddingBottom = newPadding + 1 + "px"; // tweak padding to see if height can be reduced + var newH = this._getHeight(textarea) - 1 + "px"; // see if the height changed by the 1px added + if(textarea.style.maxHeight != newH){ // if can be reduced, so now try a big chunk + textarea.style.paddingBottom = newPadding + scrollHeight + "px"; + textarea.scrollTop = 0; + textarea.style.maxHeight = this._getHeight(textarea) - scrollHeight + "px"; // scrollHeight is the added padding + } + textarea.style.paddingBottom = oldPadding; + } + if(empty){ + textarea.value = ''; + } + this._busyResizing = false; + } + }, + + resize: function(){ + // summary: + // Resizes the textarea vertically (should be called after a style/value change) + this._onInput(); + }, + + _setValueAttr: function(){ + this.inherited(arguments); + this.resize(); + }, + + buildRendering: function(){ + this.inherited(arguments); + + // tweak textarea style to reduce browser differences + dojo.style(this.textbox, { overflowY: 'hidden', overflowX: 'auto', boxSizing: 'border-box', MsBoxSizing: 'border-box', WebkitBoxSizing: 'border-box', MozBoxSizing: 'border-box' }); + }, + + postCreate: function(){ + this.inherited(arguments); + + this.connect(this.textbox, "onscroll", "_onInput"); + this.connect(this.textbox, "onresize", "_onInput"); + this.connect(this.textbox, "onfocus", "_onInput"); // useful when a previous estimate was off a bit + this._setTimeoutHandle = setTimeout(dojo.hitch(this, "resize"), 0); + }, + + uninitialize: function(){ + if(this._setTimeoutHandle){ + clearTimeout(this._setTimeoutHandle); + } + this.inherited(arguments); + } +}); + } diff --git a/lib/dijit/form/TimeTextBox.js b/lib/dijit/form/TimeTextBox.js index f5e7f99e3..81949c4a1 100644 --- a/lib/dijit/form/TimeTextBox.js +++ b/lib/dijit/form/TimeTextBox.js @@ -1,14 +1,87 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.TimeTextBox"]){ -dojo._hasResource["dijit.form.TimeTextBox"]=true; +if(!dojo._hasResource["dijit.form.TimeTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.TimeTextBox"] = true; dojo.provide("dijit.form.TimeTextBox"); dojo.require("dijit._TimePicker"); dojo.require("dijit.form._DateTimeTextBox"); -dojo.declare("dijit.form.TimeTextBox",dijit.form._DateTimeTextBox,{baseClass:"dijitTextBox dijitTimeTextBox",popupClass:"dijit._TimePicker",_selector:"time",value:new Date("")}); + + +/*===== +dojo.declare( + "dijit.form.TimeTextBox.__Constraints", + [dijit.form._DateTimeTextBox.__Constraints, dijit._TimePicker.__Constraints] +); +=====*/ + +dojo.declare( + "dijit.form.TimeTextBox", + dijit.form._DateTimeTextBox, + { + // summary: + // A validating, serializable, range-bound time text box with a drop down time picker + + baseClass: "dijitTextBox dijitComboBox dijitTimeTextBox", + popupClass: "dijit._TimePicker", + _selector: "time", + +/*===== + // constraints: dijit.form.TimeTextBox.__Constraints + constraints:{}, +=====*/ + + // value: Date + // The value of this widget as a JavaScript Date object. Note that the date portion implies time zone and daylight savings rules. + // + // Example: + // | new dijit.form.TimeTextBox({value: dojo.date.stamp.fromISOString("T12:59:59", new Date())}) + // + // When passed to the parser in markup, must be specified according to locale-independent + // `dojo.date.stamp.fromISOString` format. + // + // Example: + // | <input dojotype='dijit.form.TimeTextBox' value='T12:34:00'> + value: new Date(""), // value.toString()="NaN" + //FIXME: in markup, you have no control over daylight savings + + _onKey: function(evt){ + this.inherited(arguments); + + // If the user has backspaced or typed some numbers, then filter the result list + // by what they typed. Maybe there's a better way to detect this, like _handleOnChange()? + switch(evt.keyCode){ + case dojo.keys.ENTER: + case dojo.keys.TAB: + case dojo.keys.ESCAPE: + case dojo.keys.DOWN_ARROW: + case dojo.keys.UP_ARROW: + // these keys have special meaning + break; + default: + // setTimeout() because the keystroke hasn't yet appeared in the <input>, + // so the get('displayedValue') call below won't give the result we want. + setTimeout(dojo.hitch(this, function(){ + // set this.filterString to the filter to apply to the drop down list; + // it will be used in openDropDown() + var val = this.get('displayedValue'); + this.filterString = (val && !this.parse(val, this.constraints)) ? val.toLowerCase() : ""; + + // close the drop down and reopen it, in order to filter the items shown in the list + // and also since the drop down may need to be repositioned if the number of list items has changed + // and it's being displayed above the <input> + if(this._opened){ + this.closeDropDown(); + } + this.openDropDown(); + }), 0); + } + } + } +); + } diff --git a/lib/dijit/form/ToggleButton.js b/lib/dijit/form/ToggleButton.js index f67dd0880..27b836fbd 100644 --- a/lib/dijit/form/ToggleButton.js +++ b/lib/dijit/form/ToggleButton.js @@ -1,12 +1,15 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.ToggleButton"]){ -dojo._hasResource["dijit.form.ToggleButton"]=true; +if(!dojo._hasResource["dijit.form.ToggleButton"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ToggleButton"] = true; dojo.provide("dijit.form.ToggleButton"); dojo.require("dijit.form.Button"); + + + } diff --git a/lib/dijit/form/ValidationTextBox.js b/lib/dijit/form/ValidationTextBox.js index 2e0f17317..ee4292441 100644 --- a/lib/dijit/form/ValidationTextBox.js +++ b/lib/dijit/form/ValidationTextBox.js @@ -1,211 +1,486 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ -dojo._hasResource["dijit.form.ValidationTextBox"]=true; +if(!dojo._hasResource["dijit.form.ValidationTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.ValidationTextBox"] = true; dojo.provide("dijit.form.ValidationTextBox"); dojo.require("dojo.i18n"); dojo.require("dijit.form.TextBox"); dojo.require("dijit.Tooltip"); -dojo.requireLocalization("dijit.form","validate",null,"ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); -dojo.declare("dijit.form.ValidationTextBox",dijit.form.TextBox,{templateString:dojo.cache("dijit.form","templates/ValidationTextBox.html","<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"),baseClass:"dijitTextBox dijitValidationTextBox",required:false,promptMessage:"",invalidMessage:"$_unset_$",missingMessage:"$_unset_$",constraints:{},regExp:".*",regExpGen:function(_1){ -return this.regExp; -},state:"",tooltipPosition:[],_setValueAttr:function(){ -this.inherited(arguments); -this.validate(this._focused); -},validator:function(_2,_3){ -return (new RegExp("^(?:"+this.regExpGen(_3)+")"+(this.required?"":"?")+"$")).test(_2)&&(!this.required||!this._isEmpty(_2))&&(this._isEmpty(_2)||this.parse(_2,_3)!==undefined); -},_isValidSubset:function(){ -return this.textbox.value.search(this._partialre)==0; -},isValid:function(_4){ -return this.validator(this.textbox.value,this.constraints); -},_isEmpty:function(_5){ -return /^\s*$/.test(_5); -},getErrorMessage:function(_6){ -return (this.required&&this._isEmpty(this.textbox.value))?this.missingMessage:this.invalidMessage; -},getPromptMessage:function(_7){ -return this.promptMessage; -},_maskValidSubsetError:true,validate:function(_8){ -var _9=""; -var _a=this.disabled||this.isValid(_8); -if(_a){ -this._maskValidSubsetError=true; -} -var _b=this._isEmpty(this.textbox.value); -var _c=!_a&&!_b&&_8&&this._isValidSubset(); -this.state=((_a||((!this._hasBeenBlurred||_8)&&_b)||_c)&&this._maskValidSubsetError)?"":"Error"; -if(this.state=="Error"){ -this._maskValidSubsetError=_8; -} -this._setStateClass(); -dijit.setWaiState(this.focusNode,"invalid",_a?"false":"true"); -if(_8){ -if(this.state=="Error"){ -_9=this.getErrorMessage(true); -}else{ -_9=this.getPromptMessage(true); -} -this._maskValidSubsetError=true; -} -this.displayMessage(_9); -return _a; -},_message:"",displayMessage:function(_d){ -if(this._message==_d){ -return; -} -this._message=_d; -dijit.hideTooltip(this.domNode); -if(_d){ -dijit.showTooltip(_d,this.domNode,this.tooltipPosition,!this.isLeftToRight()); -} -},_refreshState:function(){ -this.validate(this._focused); -this.inherited(arguments); -},constructor:function(){ -this.constraints={}; -},_setConstraintsAttr:function(_e){ -if(!_e.locale&&this.lang){ -_e.locale=this.lang; -} -this.constraints=_e; -this._computePartialRE(); -},_computePartialRE:function(){ -var p=this.regExpGen(this.constraints); -this.regExp=p; -var _f=""; -if(p!=".*"){ -this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g,function(re){ -switch(re.charAt(0)){ -case "{": -case "+": -case "?": -case "*": -case "^": -case "$": -case "|": -case "(": -_f+=re; -break; -case ")": -_f+="|$)"; -break; -default: -_f+="(?:"+re+"|$)"; -break; -} -}); -} -try{ -"".search(_f); -} -catch(e){ -_f=this.regExp; -console.warn("RegExp error in "+this.declaredClass+": "+this.regExp); -} -this._partialre="^(?:"+_f+")$"; -},postMixInProperties:function(){ -this.inherited(arguments); -this.messages=dojo.i18n.getLocalization("dijit.form","validate",this.lang); -if(this.invalidMessage=="$_unset_$"){ -this.invalidMessage=this.messages.invalidMessage; -} -if(!this.invalidMessage){ -this.invalidMessage=this.promptMessage; -} -if(this.missingMessage=="$_unset_$"){ -this.missingMessage=this.messages.missingMessage; -} -if(!this.missingMessage){ -this.missingMessage=this.invalidMessage; -} -this._setConstraintsAttr(this.constraints); -},_setDisabledAttr:function(_10){ -this.inherited(arguments); -this._refreshState(); -},_setRequiredAttr:function(_11){ -this.required=_11; -dijit.setWaiState(this.focusNode,"required",_11); -this._refreshState(); -},reset:function(){ -this._maskValidSubsetError=true; -this.inherited(arguments); -},_onBlur:function(){ -this.displayMessage(""); -this.inherited(arguments); -}}); -dojo.declare("dijit.form.MappedTextBox",dijit.form.ValidationTextBox,{postMixInProperties:function(){ -this.inherited(arguments); -this.nameAttrSetting=""; -},serialize:function(val,_12){ -return val.toString?val.toString():""; -},toString:function(){ -var val=this.filter(this.get("value")); -return val!=null?(typeof val=="string"?val:this.serialize(val,this.constraints)):""; -},validate:function(){ -this.valueNode.value=this.toString(); -return this.inherited(arguments); -},buildRendering:function(){ -this.inherited(arguments); -this.valueNode=dojo.place("<input type='hidden'"+(this.name?" name='"+this.name+"'":"")+">",this.textbox,"after"); -},reset:function(){ -this.valueNode.value=""; -this.inherited(arguments); -}}); -dojo.declare("dijit.form.RangeBoundTextBox",dijit.form.MappedTextBox,{rangeMessage:"",rangeCheck:function(_13,_14){ -return ("min" in _14?(this.compare(_13,_14.min)>=0):true)&&("max" in _14?(this.compare(_13,_14.max)<=0):true); -},isInRange:function(_15){ -return this.rangeCheck(this.get("value"),this.constraints); -},_isDefinitelyOutOfRange:function(){ -var val=this.get("value"); -var _16=false; -var _17=false; -if("min" in this.constraints){ -var min=this.constraints.min; -min=this.compare(val,((typeof min=="number")&&min>=0&&val!=0)?0:min); -_16=(typeof min=="number")&&min<0; -} -if("max" in this.constraints){ -var max=this.constraints.max; -max=this.compare(val,((typeof max!="number")||max>0)?max:0); -_17=(typeof max=="number")&&max>0; -} -return _16||_17; -},_isValidSubset:function(){ -return this.inherited(arguments)&&!this._isDefinitelyOutOfRange(); -},isValid:function(_18){ -return this.inherited(arguments)&&((this._isEmpty(this.textbox.value)&&!this.required)||this.isInRange(_18)); -},getErrorMessage:function(_19){ -var v=this.get("value"); -if(v!==null&&v!==""&&v!==undefined&&(typeof v!="number"||!isNaN(v))&&!this.isInRange(_19)){ -return this.rangeMessage; -} -return this.inherited(arguments); -},postMixInProperties:function(){ -this.inherited(arguments); -if(!this.rangeMessage){ -this.messages=dojo.i18n.getLocalization("dijit.form","validate",this.lang); -this.rangeMessage=this.messages.rangeMessage; -} -},_setConstraintsAttr:function(_1a){ -this.inherited(arguments); -if(this.focusNode){ -if(this.constraints.min!==undefined){ -dijit.setWaiState(this.focusNode,"valuemin",this.constraints.min); -}else{ -dijit.removeWaiState(this.focusNode,"valuemin"); -} -if(this.constraints.max!==undefined){ -dijit.setWaiState(this.focusNode,"valuemax",this.constraints.max); -}else{ -dijit.removeWaiState(this.focusNode,"valuemax"); -} -} -},_setValueAttr:function(_1b,_1c){ -dijit.setWaiState(this.focusNode,"valuenow",_1b); -this.inherited(arguments); -}}); +dojo.requireLocalization("dijit.form", "validate", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw"); + + +/*===== + dijit.form.ValidationTextBox.__Constraints = function(){ + // locale: String + // locale used for validation, picks up value from this widget's lang attribute + // _flags_: anything + // various flags passed to regExpGen function + this.locale = ""; + this._flags_ = ""; + } +=====*/ + +dojo.declare( + "dijit.form.ValidationTextBox", + dijit.form.TextBox, + { + // summary: + // Base class for textbox widgets with the ability to validate content of various types and provide user feedback. + // tags: + // protected + + templateString: dojo.cache("dijit.form", "templates/ValidationTextBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class=\"dijitReset dijitInputInner\" dojoAttachPoint='textbox,focusNode' autocomplete=\"off\"\n\t\t\t${!nameAttrSetting} type='${type}'\n\t/></div\n></div>\n"), + baseClass: "dijitTextBox dijitValidationTextBox", + + // required: Boolean + // User is required to enter data into this field. + required: false, + + // promptMessage: String + // If defined, display this hint string immediately on focus to the textbox, if empty. + // Also displays if the textbox value is Incomplete (not yet valid but will be with additional input). + // Think of this like a tooltip that tells the user what to do, not an error message + // that tells the user what they've done wrong. + // + // Message disappears when user starts typing. + promptMessage: "", + + // invalidMessage: String + // The message to display if value is invalid. + // The translated string value is read from the message file by default. + // Set to "" to use the promptMessage instead. + invalidMessage: "$_unset_$", + + // missingMessage: String + // The message to display if value is empty and the field is required. + // The translated string value is read from the message file by default. + // Set to "" to use the invalidMessage instead. + missingMessage: "$_unset_$", + + // message: String + // Currently error/prompt message. + // When using the default tooltip implementation, this will only be + // displayed when the field is focused. + message: "", + + // constraints: dijit.form.ValidationTextBox.__Constraints + // user-defined object needed to pass parameters to the validator functions + constraints: {}, + + // regExp: [extension protected] String + // regular expression string used to validate the input + // Do not specify both regExp and regExpGen + regExp: ".*", + + regExpGen: function(/*dijit.form.ValidationTextBox.__Constraints*/ constraints){ + // summary: + // Overridable function used to generate regExp when dependent on constraints. + // Do not specify both regExp and regExpGen. + // tags: + // extension protected + return this.regExp; // String + }, + + // state: [readonly] String + // Shows current state (ie, validation result) of input (""=Normal, Incomplete, or Error) + state: "", + + // tooltipPosition: String[] + // See description of `dijit.Tooltip.defaultPosition` for details on this parameter. + tooltipPosition: [], + + _setValueAttr: function(){ + // summary: + // Hook so set('value', ...) works. + this.inherited(arguments); + this.validate(this._focused); + }, + + validator: function(/*anything*/ value, /*dijit.form.ValidationTextBox.__Constraints*/ constraints){ + // summary: + // Overridable function used to validate the text input against the regular expression. + // tags: + // protected + return (new RegExp("^(?:" + this.regExpGen(constraints) + ")"+(this.required?"":"?")+"$")).test(value) && + (!this.required || !this._isEmpty(value)) && + (this._isEmpty(value) || this.parse(value, constraints) !== undefined); // Boolean + }, + + _isValidSubset: function(){ + // summary: + // Returns true if the value is either already valid or could be made valid by appending characters. + // This is used for validation while the user [may be] still typing. + return this.textbox.value.search(this._partialre) == 0; + }, + + isValid: function(/*Boolean*/ isFocused){ + // summary: + // Tests if value is valid. + // Can override with your own routine in a subclass. + // tags: + // protected + return this.validator(this.textbox.value, this.constraints); + }, + + _isEmpty: function(value){ + // summary: + // Checks for whitespace + return (this.trim ? /^\s*$/ : /^$/).test(value); // Boolean + }, + + getErrorMessage: function(/*Boolean*/ isFocused){ + // summary: + // Return an error message to show if appropriate + // tags: + // protected + return (this.required && this._isEmpty(this.textbox.value)) ? this.missingMessage : this.invalidMessage; // String + }, + + getPromptMessage: function(/*Boolean*/ isFocused){ + // summary: + // Return a hint message to show when widget is first focused + // tags: + // protected + return this.promptMessage; // String + }, + + _maskValidSubsetError: true, + validate: function(/*Boolean*/ isFocused){ + // summary: + // Called by oninit, onblur, and onkeypress. + // description: + // Show missing or invalid messages if appropriate, and highlight textbox field. + // tags: + // protected + var message = ""; + var isValid = this.disabled || this.isValid(isFocused); + if(isValid){ this._maskValidSubsetError = true; } + var isEmpty = this._isEmpty(this.textbox.value); + var isValidSubset = !isValid && isFocused && this._isValidSubset(); + this._set("state", isValid ? "" : (((((!this._hasBeenBlurred || isFocused) && isEmpty) || isValidSubset) && this._maskValidSubsetError) ? "Incomplete" : "Error")); + dijit.setWaiState(this.focusNode, "invalid", isValid ? "false" : "true"); + + if(this.state == "Error"){ + this._maskValidSubsetError = isFocused && isValidSubset; // we want the error to show up after a blur and refocus + message = this.getErrorMessage(isFocused); + }else if(this.state == "Incomplete"){ + message = this.getPromptMessage(isFocused); // show the prompt whenever the value is not yet complete + this._maskValidSubsetError = !this._hasBeenBlurred || isFocused; // no Incomplete warnings while focused + }else if(isEmpty){ + message = this.getPromptMessage(isFocused); // show the prompt whenever there's no error and no text + } + this.set("message", message); + + return isValid; + }, + + displayMessage: function(/*String*/ message){ + // summary: + // Overridable method to display validation errors/hints. + // By default uses a tooltip. + // tags: + // extension + dijit.hideTooltip(this.domNode); + if(message && this._focused){ + dijit.showTooltip(message, this.domNode, this.tooltipPosition, !this.isLeftToRight()); + } + }, + + _refreshState: function(){ + // Overrides TextBox._refreshState() + this.validate(this._focused); + this.inherited(arguments); + }, + + //////////// INITIALIZATION METHODS /////////////////////////////////////// + + constructor: function(){ + this.constraints = {}; + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + if(!constraints.locale && this.lang){ + constraints.locale = this.lang; + } + this._set("constraints", constraints); + this._computePartialRE(); + }, + + _computePartialRE: function(){ + var p = this.regExpGen(this.constraints); + this.regExp = p; + var partialre = ""; + // parse the regexp and produce a new regexp that matches valid subsets + // if the regexp is .* then there's no use in matching subsets since everything is valid + if(p != ".*"){ this.regExp.replace(/\\.|\[\]|\[.*?[^\\]{1}\]|\{.*?\}|\(\?[=:!]|./g, + function (re){ + switch(re.charAt(0)){ + case '{': + case '+': + case '?': + case '*': + case '^': + case '$': + case '|': + case '(': + partialre += re; + break; + case ")": + partialre += "|$)"; + break; + default: + partialre += "(?:"+re+"|$)"; + break; + } + } + );} + try{ // this is needed for now since the above regexp parsing needs more test verification + "".search(partialre); + }catch(e){ // should never be here unless the original RE is bad or the parsing is bad + partialre = this.regExp; + console.warn('RegExp error in ' + this.declaredClass + ': ' + this.regExp); + } // should never be here unless the original RE is bad or the parsing is bad + this._partialre = "^(?:" + partialre + ")$"; + }, + + postMixInProperties: function(){ + this.inherited(arguments); + this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); + if(this.invalidMessage == "$_unset_$"){ this.invalidMessage = this.messages.invalidMessage; } + if(!this.invalidMessage){ this.invalidMessage = this.promptMessage; } + if(this.missingMessage == "$_unset_$"){ this.missingMessage = this.messages.missingMessage; } + if(!this.missingMessage){ this.missingMessage = this.invalidMessage; } + this._setConstraintsAttr(this.constraints); // this needs to happen now (and later) due to codependency on _set*Attr calls attachPoints + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + this.inherited(arguments); // call FormValueWidget._setDisabledAttr() + this._refreshState(); + }, + + _setRequiredAttr: function(/*Boolean*/ value){ + this._set("required", value); + dijit.setWaiState(this.focusNode, "required", value); + this._refreshState(); + }, + + _setMessageAttr: function(/*String*/ message){ + this._set("message", message); + this.displayMessage(message); + }, + + reset:function(){ + // Overrides dijit.form.TextBox.reset() by also + // hiding errors about partial matches + this._maskValidSubsetError = true; + this.inherited(arguments); + }, + + _onBlur: function(){ + // the message still exists but for back-compat, and to erase the tooltip + // (if the message is being displayed as a tooltip), call displayMessage('') + this.displayMessage(''); + + this.inherited(arguments); + } + } +); + +dojo.declare( + "dijit.form.MappedTextBox", + dijit.form.ValidationTextBox, + { + // summary: + // A dijit.form.ValidationTextBox subclass which provides a base class for widgets that have + // a visible formatted display value, and a serializable + // value in a hidden input field which is actually sent to the server. + // description: + // The visible display may + // be locale-dependent and interactive. The value sent to the server is stored in a hidden + // input field which uses the `name` attribute declared by the original widget. That value sent + // to the server is defined by the dijit.form.MappedTextBox.serialize method and is typically + // locale-neutral. + // tags: + // protected + + postMixInProperties: function(){ + this.inherited(arguments); + + // we want the name attribute to go to the hidden <input>, not the displayed <input>, + // so override _FormWidget.postMixInProperties() setting of nameAttrSetting + this.nameAttrSetting = ""; + }, + + serialize: function(/*anything*/ val, /*Object?*/ options){ + // summary: + // Overridable function used to convert the get('value') result to a canonical + // (non-localized) string. For example, will print dates in ISO format, and + // numbers the same way as they are represented in javascript. + // tags: + // protected extension + return val.toString ? val.toString() : ""; // String + }, + + toString: function(){ + // summary: + // Returns widget as a printable string using the widget's value + // tags: + // protected + var val = this.filter(this.get('value')); // call filter in case value is nonstring and filter has been customized + return val != null ? (typeof val == "string" ? val : this.serialize(val, this.constraints)) : ""; // String + }, + + validate: function(){ + // Overrides `dijit.form.TextBox.validate` + this.valueNode.value = this.toString(); + return this.inherited(arguments); + }, + + buildRendering: function(){ + // Overrides `dijit._Templated.buildRendering` + + this.inherited(arguments); + + // Create a hidden <input> node with the serialized value used for submit + // (as opposed to the displayed value). + // Passing in name as markup rather than calling dojo.create() with an attrs argument + // to make dojo.query(input[name=...]) work on IE. (see #8660) + this.valueNode = dojo.place("<input type='hidden'" + (this.name ? " name='" + this.name.replace(/'/g, """) + "'" : "") + "/>", this.textbox, "after"); + }, + + reset: function(){ + // Overrides `dijit.form.ValidationTextBox.reset` to + // reset the hidden textbox value to '' + this.valueNode.value = ''; + this.inherited(arguments); + } + } +); + +/*===== + dijit.form.RangeBoundTextBox.__Constraints = function(){ + // min: Number + // Minimum signed value. Default is -Infinity + // max: Number + // Maximum signed value. Default is +Infinity + this.min = min; + this.max = max; + } +=====*/ + +dojo.declare( + "dijit.form.RangeBoundTextBox", + dijit.form.MappedTextBox, + { + // summary: + // Base class for textbox form widgets which defines a range of valid values. + + // rangeMessage: String + // The message to display if value is out-of-range + rangeMessage: "", + + /*===== + // constraints: dijit.form.RangeBoundTextBox.__Constraints + constraints: {}, + ======*/ + + rangeCheck: function(/*Number*/ primitive, /*dijit.form.RangeBoundTextBox.__Constraints*/ constraints){ + // summary: + // Overridable function used to validate the range of the numeric input value. + // tags: + // protected + return ("min" in constraints? (this.compare(primitive,constraints.min) >= 0) : true) && + ("max" in constraints? (this.compare(primitive,constraints.max) <= 0) : true); // Boolean + }, + + isInRange: function(/*Boolean*/ isFocused){ + // summary: + // Tests if the value is in the min/max range specified in constraints + // tags: + // protected + return this.rangeCheck(this.get('value'), this.constraints); + }, + + _isDefinitelyOutOfRange: function(){ + // summary: + // Returns true if the value is out of range and will remain + // out of range even if the user types more characters + var val = this.get('value'); + var isTooLittle = false; + var isTooMuch = false; + if("min" in this.constraints){ + var min = this.constraints.min; + min = this.compare(val, ((typeof min == "number") && min >= 0 && val !=0) ? 0 : min); + isTooLittle = (typeof min == "number") && min < 0; + } + if("max" in this.constraints){ + var max = this.constraints.max; + max = this.compare(val, ((typeof max != "number") || max > 0) ? max : 0); + isTooMuch = (typeof max == "number") && max > 0; + } + return isTooLittle || isTooMuch; + }, + + _isValidSubset: function(){ + // summary: + // Overrides `dijit.form.ValidationTextBox._isValidSubset`. + // Returns true if the input is syntactically valid, and either within + // range or could be made in range by more typing. + return this.inherited(arguments) && !this._isDefinitelyOutOfRange(); + }, + + isValid: function(/*Boolean*/ isFocused){ + // Overrides dijit.form.ValidationTextBox.isValid to check that the value is also in range. + return this.inherited(arguments) && + ((this._isEmpty(this.textbox.value) && !this.required) || this.isInRange(isFocused)); // Boolean + }, + + getErrorMessage: function(/*Boolean*/ isFocused){ + // Overrides dijit.form.ValidationTextBox.getErrorMessage to print "out of range" message if appropriate + var v = this.get('value'); + if(v !== null && v !== '' && v !== undefined && (typeof v != "number" || !isNaN(v)) && !this.isInRange(isFocused)){ // don't check isInRange w/o a real value + return this.rangeMessage; // String + } + return this.inherited(arguments); + }, + + postMixInProperties: function(){ + this.inherited(arguments); + if(!this.rangeMessage){ + this.messages = dojo.i18n.getLocalization("dijit.form", "validate", this.lang); + this.rangeMessage = this.messages.rangeMessage; + } + }, + + _setConstraintsAttr: function(/*Object*/ constraints){ + this.inherited(arguments); + if(this.focusNode){ // not set when called from postMixInProperties + if(this.constraints.min !== undefined){ + dijit.setWaiState(this.focusNode, "valuemin", this.constraints.min); + }else{ + dijit.removeWaiState(this.focusNode, "valuemin"); + } + if(this.constraints.max !== undefined){ + dijit.setWaiState(this.focusNode, "valuemax", this.constraints.max); + }else{ + dijit.removeWaiState(this.focusNode, "valuemax"); + } + } + }, + + _setValueAttr: function(/*Number*/ value, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', ...) works. + + dijit.setWaiState(this.focusNode, "valuenow", value); + this.inherited(arguments); + } + } +); + } diff --git a/lib/dijit/form/VerticalRule.js b/lib/dijit/form/VerticalRule.js index 13576277f..98f612cf0 100644 --- a/lib/dijit/form/VerticalRule.js +++ b/lib/dijit/form/VerticalRule.js @@ -1,13 +1,35 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.VerticalRule"]){ -dojo._hasResource["dijit.form.VerticalRule"]=true; +if(!dojo._hasResource["dijit.form.VerticalRule"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.VerticalRule"] = true; dojo.provide("dijit.form.VerticalRule"); dojo.require("dijit.form.HorizontalRule"); -dojo.declare("dijit.form.VerticalRule",dijit.form.HorizontalRule,{templateString:"<div class=\"dijitRuleContainer dijitRuleContainerV\"></div>",_positionPrefix:"<div class=\"dijitRuleMark dijitRuleMarkV\" style=\"top:",_isHorizontal:false}); + + +dojo.declare("dijit.form.VerticalRule", dijit.form.HorizontalRule, +{ + // summary: + // Hash marks for the `dijit.form.VerticalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerV"></div>', + _positionPrefix: '<div class="dijitRuleMark dijitRuleMarkV" style="top:', + +/*===== + // container: String + // This is either "leftDecoration" or "rightDecoration", + // to indicate whether this rule goes to the left or to the right of the slider. + // Note that on RTL system, "leftDecoration" would actually go to the right, and vice-versa. + container: "", +=====*/ + + // Overrides HorizontalRule._isHorizontal + _isHorizontal: false + +}); + } diff --git a/lib/dijit/form/VerticalRuleLabels.js b/lib/dijit/form/VerticalRuleLabels.js index 9adac9056..57caba8b5 100644 --- a/lib/dijit/form/VerticalRuleLabels.js +++ b/lib/dijit/form/VerticalRuleLabels.js @@ -1,15 +1,33 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.VerticalRuleLabels"]){ -dojo._hasResource["dijit.form.VerticalRuleLabels"]=true; +if(!dojo._hasResource["dijit.form.VerticalRuleLabels"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.VerticalRuleLabels"] = true; dojo.provide("dijit.form.VerticalRuleLabels"); dojo.require("dijit.form.HorizontalRuleLabels"); -dojo.declare("dijit.form.VerticalRuleLabels",dijit.form.HorizontalRuleLabels,{templateString:"<div class=\"dijitRuleContainer dijitRuleContainerV dijitRuleLabelsContainer dijitRuleLabelsContainerV\"></div>",_positionPrefix:"<div class=\"dijitRuleLabelContainer dijitRuleLabelContainerV\" style=\"top:",_labelPrefix:"\"><span class=\"dijitRuleLabel dijitRuleLabelV\">",_calcPosition:function(_1){ -return 100-_1; -},_isHorizontal:false}); + + +dojo.declare("dijit.form.VerticalRuleLabels", dijit.form.HorizontalRuleLabels, +{ + // summary: + // Labels for the `dijit.form.VerticalSlider` + + templateString: '<div class="dijitRuleContainer dijitRuleContainerV dijitRuleLabelsContainer dijitRuleLabelsContainerV"></div>', + + _positionPrefix: '<div class="dijitRuleLabelContainer dijitRuleLabelContainerV" style="top:', + _labelPrefix: '"><span class="dijitRuleLabel dijitRuleLabelV">', + + _calcPosition: function(pos){ + // Overrides HorizontalRuleLabel._calcPosition() + return 100-pos; + }, + + // needed to prevent labels from being reversed in RTL mode + _isHorizontal: false +}); + } diff --git a/lib/dijit/form/VerticalSlider.js b/lib/dijit/form/VerticalSlider.js index 9e98cbe0f..f074c7cd1 100644 --- a/lib/dijit/form/VerticalSlider.js +++ b/lib/dijit/form/VerticalSlider.js @@ -1,15 +1,42 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form.VerticalSlider"]){ -dojo._hasResource["dijit.form.VerticalSlider"]=true; +if(!dojo._hasResource["dijit.form.VerticalSlider"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form.VerticalSlider"] = true; dojo.provide("dijit.form.VerticalSlider"); dojo.require("dijit.form.HorizontalSlider"); -dojo.declare("dijit.form.VerticalSlider",dijit.form.HorizontalSlider,{templateString:dojo.cache("dijit.form","templates/VerticalSlider.html","<table class=\"dijit dijitReset dijitSlider dijitSliderV\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderIncrementIconV\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderTopBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td dojoAttachPoint=\"leftDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationL dijitSliderDecorationV\"></td\n\t\t><td class=\"dijitReset\" style=\"height:100%;\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><center class=\"dijitReset dijitSliderBarContainerV\" waiRole=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderRemainingBar dijitSliderRemainingBarV\" dojoAttachEvent=\"onmousedown:_onBarClick\"><!--#5629--></div\n\t\t\t\t><div waiRole=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderProgressBar dijitSliderProgressBarV\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableV\" style=\"vertical-align:top;\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleV\" dojoAttachEvent=\"onmousedown:_onHandleClick\" waiRole=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t></center\n\t\t></td\n\t\t><td dojoAttachPoint=\"containerNode,rightDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationR dijitSliderDecorationV\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderBottomBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderDecrementIconV\" tabIndex=\"-1\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n></table>\n"),_mousePixelCoord:"pageY",_pixelCount:"h",_startingPixelCoord:"y",_startingPixelCount:"t",_handleOffsetCoord:"top",_progressPixelSize:"height",_descending:true,_isReversed:function(){ -return this._descending; -}}); + + +dojo.declare( + "dijit.form.VerticalSlider", + dijit.form.HorizontalSlider, +{ + // summary: + // A form widget that allows one to select a value with a vertically draggable handle + + templateString: dojo.cache("dijit.form", "templates/VerticalSlider.html", "<table class=\"dijit dijitReset dijitSlider dijitSliderV\" cellspacing=\"0\" cellpadding=\"0\" border=\"0\" rules=\"none\" dojoAttachEvent=\"onkeypress:_onKeyPress,onkeyup:_onKeyUp\"\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderIncrementIconV\" style=\"display:none\" dojoAttachPoint=\"decrementButton\"><span class=\"dijitSliderButtonInner\">+</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderTopBumper\" dojoAttachEvent=\"onmousedown:_onClkIncBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td dojoAttachPoint=\"leftDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationL dijitSliderDecorationV\"></td\n\t\t><td class=\"dijitReset dijitSliderDecorationC\" style=\"height:100%;\"\n\t\t\t><input dojoAttachPoint=\"valueNode\" type=\"hidden\" ${!nameAttrSetting}\n\t\t\t/><center class=\"dijitReset dijitSliderBarContainerV\" role=\"presentation\" dojoAttachPoint=\"sliderBarContainer\"\n\t\t\t\t><div role=\"presentation\" dojoAttachPoint=\"remainingBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderRemainingBar dijitSliderRemainingBarV\" dojoAttachEvent=\"onmousedown:_onBarClick\"><!--#5629--></div\n\t\t\t\t><div role=\"presentation\" dojoAttachPoint=\"progressBar\" class=\"dijitSliderBar dijitSliderBarV dijitSliderProgressBar dijitSliderProgressBarV\" dojoAttachEvent=\"onmousedown:_onBarClick\"\n\t\t\t\t\t><div class=\"dijitSliderMoveable dijitSliderMoveableV\" style=\"vertical-align:top;\"\n\t\t\t\t\t\t><div dojoAttachPoint=\"sliderHandle,focusNode\" class=\"dijitSliderImageHandle dijitSliderImageHandleV\" dojoAttachEvent=\"onmousedown:_onHandleClick\" role=\"slider\" valuemin=\"${minimum}\" valuemax=\"${maximum}\"></div\n\t\t\t\t\t></div\n\t\t\t\t></div\n\t\t\t></center\n\t\t></td\n\t\t><td dojoAttachPoint=\"containerNode,rightDecoration\" class=\"dijitReset dijitSliderDecoration dijitSliderDecorationR dijitSliderDecorationV\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset\"\n\t\t\t><center><div class=\"dijitSliderBar dijitSliderBumper dijitSliderBumperV dijitSliderBottomBumper\" dojoAttachEvent=\"onmousedown:_onClkDecBumper\"></div></center\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n\t><tr class=\"dijitReset\"\n\t\t><td class=\"dijitReset\"></td\n\t\t><td class=\"dijitReset dijitSliderButtonContainer dijitSliderButtonContainerV\"\n\t\t\t><div class=\"dijitSliderDecrementIconV\" style=\"display:none\" dojoAttachPoint=\"incrementButton\"><span class=\"dijitSliderButtonInner\">-</span></div\n\t\t></td\n\t\t><td class=\"dijitReset\"></td\n\t></tr\n></table>\n"), + _mousePixelCoord: "pageY", + _pixelCount: "h", + _startingPixelCoord: "y", + _startingPixelCount: "t", + _handleOffsetCoord: "top", + _progressPixelSize: "height", + + // _descending: Boolean + // Specifies if the slider values go from high-on-top (true), or low-on-top (false) + // TODO: expose this in 1.2 - the css progress/remaining bar classes need to be reversed + _descending: true, + + _isReversed: function(){ + // summary: + // Overrides HorizontalSlider._isReversed. + // Indicates if values are high on top (with low numbers on the bottom). + return this._descending; + } +}); + } diff --git a/lib/dijit/form/_DateTimeTextBox.js b/lib/dijit/form/_DateTimeTextBox.js index 72898e406..9d5c004c8 100644 --- a/lib/dijit/form/_DateTimeTextBox.js +++ b/lib/dijit/form/_DateTimeTextBox.js @@ -1,148 +1,250 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form._DateTimeTextBox"]){ -dojo._hasResource["dijit.form._DateTimeTextBox"]=true; +if(!dojo._hasResource["dijit.form._DateTimeTextBox"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._DateTimeTextBox"] = true; dojo.provide("dijit.form._DateTimeTextBox"); dojo.require("dojo.date"); dojo.require("dojo.date.locale"); dojo.require("dojo.date.stamp"); dojo.require("dijit.form.ValidationTextBox"); -new Date("X"); -dojo.declare("dijit.form._DateTimeTextBox",dijit.form.RangeBoundTextBox,{regExpGen:dojo.date.locale.regexp,datePackage:"dojo.date",compare:dojo.date.compare,format:function(_1,_2){ -if(!_1){ -return ""; -} -return this.dateLocaleModule.format(_1,_2); -},parse:function(_3,_4){ -return this.dateLocaleModule.parse(_3,_4)||(this._isEmpty(_3)?null:undefined); -},serialize:function(_5,_6){ -if(_5.toGregorian){ -_5=_5.toGregorian(); -} -return dojo.date.stamp.toISOString(_5,_6); -},value:new Date(""),_blankValue:null,popupClass:"",_selector:"",constructor:function(_7){ -var _8=_7.datePackage?_7.datePackage+".Date":"Date"; -this.dateClassObj=dojo.getObject(_8,false); -this.value=new this.dateClassObj(""); -this.datePackage=_7.datePackage||this.datePackage; -this.dateLocaleModule=dojo.getObject(this.datePackage+".locale",false); -this.regExpGen=this.dateLocaleModule.regexp; -},_setConstraintsAttr:function(_9){ -_9.selector=this._selector; -_9.fullYear=true; -var _a=dojo.date.stamp.fromISOString; -if(typeof _9.min=="string"){ -_9.min=_a(_9.min); -} -if(typeof _9.max=="string"){ -_9.max=_a(_9.max); -} -this.inherited(arguments,[_9]); -},_onFocus:function(_b){ -this._open(); -this.inherited(arguments); -},_setValueAttr:function(_c,_d,_e){ -if(_c!==undefined){ -if(!_c||_c.toString()==dijit.form._DateTimeTextBox.prototype.value.toString()){ -_c=null; -} -if(_c instanceof Date&&!(this.dateClassObj instanceof Date)){ -_c=new this.dateClassObj(_c); -} -} -this.inherited(arguments,[_c,_d,_e]); -if(this._picker){ -if(!_c){ -_c=new this.dateClassObj(); -} -this._picker.set("value",_c); -} -},_open:function(){ -if(this.disabled||this.readOnly||!this.popupClass){ -return; -} -var _f=this; -if(!this._picker){ -var _10=dojo.getObject(this.popupClass,false); -this._picker=new _10({onValueSelected:function(_11){ -if(_f._tabbingAway){ -delete _f._tabbingAway; -}else{ -_f.focus(); -} -setTimeout(dojo.hitch(_f,"_close"),1); -dijit.form._DateTimeTextBox.superclass._setValueAttr.call(_f,_11,true); -},id:this.id+"_popup",dir:_f.dir,lang:_f.lang,value:this.get("value")||new this.dateClassObj(),constraints:_f.constraints,datePackage:_f.datePackage,isDisabledDate:function(_12){ -var _13=dojo.date.compare; -var _14=_f.constraints; -return _14&&((_14.min&&_13(_14.min,_12,_f._selector)>0)||(_14.max&&_13(_14.max,_12,_f._selector)<0)); -}}); -} -if(!this._opened){ -dijit.popup.open({parent:this,popup:this._picker,orient:{"BL":"TL","TL":"BL"},around:this.domNode,onCancel:dojo.hitch(this,this._close),onClose:function(){ -_f._opened=false; -}}); -this._opened=true; -} -dojo.marginBox(this._picker.domNode,{w:this.domNode.offsetWidth}); -},_close:function(){ -if(this._opened){ -dijit.popup.close(this._picker); -this._opened=false; -} -},_onBlur:function(){ -this._close(); -if(this._picker){ -this._picker.destroy(); -delete this._picker; -} -this.inherited(arguments); -},_getDisplayedValueAttr:function(){ -return this.textbox.value; -},_setDisplayedValueAttr:function(_15,_16){ -this._setValueAttr(this.parse(_15,this.constraints),_16,_15); -},destroy:function(){ -if(this._picker){ -this._picker.destroy(); -delete this._picker; -} -this.inherited(arguments); -},postCreate:function(){ -this.inherited(arguments); -this.connect(this.focusNode,"onkeypress",this._onKeyPress); -this.connect(this.focusNode,"onclick",this._open); -},_onKeyPress:function(e){ -var p=this._picker,dk=dojo.keys; -if(p&&this._opened&&p.handleKey){ -if(p.handleKey(e)===false){ -return; -} -} -if(this._opened&&e.charOrCode==dk.ESCAPE&&!(e.shiftKey||e.ctrlKey||e.altKey||e.metaKey)){ -this._close(); -dojo.stopEvent(e); -}else{ -if(!this._opened&&e.charOrCode==dk.DOWN_ARROW){ -this._open(); -dojo.stopEvent(e); -}else{ -if(e.charOrCode===dk.TAB){ -this._tabbingAway=true; -}else{ -if(this._opened&&(e.keyChar||e.charOrCode===dk.BACKSPACE||e.charOrCode==dk.DELETE)){ -setTimeout(dojo.hitch(this,function(){ -if(this._picker&&this._opened){ -dijit.placeOnScreenAroundElement(p.domNode.parentNode,this.domNode,{"BL":"TL","TL":"BL"},p.orient?dojo.hitch(p,"orient"):null); -} -}),1); -} -} -} -} -}}); +dojo.require("dijit._HasDropDown"); + + +new Date("X"); // workaround for #11279, new Date("") == NaN + +/*===== +dojo.declare( + "dijit.form._DateTimeTextBox.__Constraints", + [dijit.form.RangeBoundTextBox.__Constraints, dojo.date.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'} +}); +=====*/ + +dojo.declare( + "dijit.form._DateTimeTextBox", + [ dijit.form.RangeBoundTextBox, dijit._HasDropDown ], + { + // summary: + // Base class for validating, serializable, range-bound date or time text box. + + templateString: dojo.cache("dijit.form", "templates/DropDownBox.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\"\n\trole=\"combobox\"\n\t><div class='dijitReset dijitRight dijitButtonNode dijitArrowButton dijitDownArrowButton dijitArrowButtonContainer'\n\t\tdojoAttachPoint=\"_buttonNode, _popupStateNode\" role=\"presentation\"\n\t\t><input class=\"dijitReset dijitInputField dijitArrowButtonInner\" value=\"▼ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t${_buttonInputDisabled}\n\t/></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' ${!nameAttrSetting} type=\"text\" autocomplete=\"off\"\n\t\t\tdojoAttachPoint=\"textbox,focusNode\" role=\"textbox\" aria-haspopup=\"true\"\n\t/></div\n></div>\n"), + + // hasDownArrow: [const] Boolean + // Set this textbox to display a down arrow button, to open the drop down list. + hasDownArrow: true, + + // openOnClick: [const] Boolean + // Set to true to open drop down upon clicking anywhere on the textbox. + openOnClick: true, + + /*===== + // constraints: dijit.form._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: {}, + ======*/ + + // Override ValidationTextBox.regExpGen().... we use a reg-ex generating function rather + // than a straight regexp to deal with locale (plus formatting options too?) + regExpGen: dojo.date.locale.regexp, + + // datePackage: String + // JavaScript namespace to find calendar routines. Uses Gregorian calendar routines + // at dojo.date, by default. + datePackage: "dojo.date", + + // Override _FormWidget.compare() to work for dates/times + compare: function(/*Date*/ val1, /*Date*/ val2){ + var isInvalid1 = this._isInvalidDate(val1); + var isInvalid2 = this._isInvalidDate(val2); + return isInvalid1 ? (isInvalid2 ? 0 : -1) : (isInvalid2 ? 1 : dojo.date.compare(val1, val2, this._selector)); + }, + + // flag to _HasDropDown to make drop down Calendar width == <input> width + forceWidth: true, + + format: function(/*Date*/ value, /*dojo.date.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, /*dojo.date.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 dojo.date.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(/*Object*/ args){ + var dateClass = args.datePackage ? args.datePackage + ".Date" : "Date"; + this.dateClassObj = dojo.getObject(dateClass, false); + this.value = new this.dateClassObj(""); + + this.datePackage = args.datePackage || this.datePackage; + this.dateLocaleModule = dojo.getObject(this.datePackage + ".locale", false); + this.regExpGen = this.dateLocaleModule.regexp; + this._invalidDate = dijit.form._DateTimeTextBox.prototype.value.toString(); + }, + + buildRendering: function(){ + this.inherited(arguments); + + if(!this.hasDownArrow){ + this._buttonNode.style.display = "none"; + } + + // If openOnClick is true, we basically just want to treat the whole widget as the + // button. We need to do that also if the actual drop down button will be hidden, + // so that there's a mouse method for opening the drop down. + if(this.openOnClick || !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 = dojo.date.stamp.fromISOString; + if(typeof constraints.min == "string"){ constraints.min = fromISO(constraints.min); } + if(typeof constraints.max == "string"){ constraints.max = fromISO(constraints.max); } + this.inherited(arguments); + }, + + _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 = dojo.date.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); + 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 + if(attr == "value" && this.value instanceof Date && this.compare(value, this.value) == 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.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 = dojo.getObject(this.popupClass, false), + 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 + dijit.form._DateTimeTextBox.superclass._setValueAttr.call(textBox, value, true); + }, + id: this.id + "_popup", + dir: textBox.dir, + lang: textBox.lang, + value: value, + 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); + } + } +); + } diff --git a/lib/dijit/form/_FormMixin.js b/lib/dijit/form/_FormMixin.js index 4c52ed9e2..e42702d31 100644 --- a/lib/dijit/form/_FormMixin.js +++ b/lib/dijit/form/_FormMixin.js @@ -1,158 +1,461 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form._FormMixin"]){ -dojo._hasResource["dijit.form._FormMixin"]=true; +if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._FormMixin"] = true; dojo.provide("dijit.form._FormMixin"); dojo.require("dojo.window"); -dojo.declare("dijit.form._FormMixin",null,{reset:function(){ -dojo.forEach(this.getDescendants(),function(_1){ -if(_1.reset){ -_1.reset(); -} -}); -},validate:function(){ -var _2=false; -return dojo.every(dojo.map(this.getDescendants(),function(_3){ -_3._hasBeenBlurred=true; -var _4=_3.disabled||!_3.validate||_3.validate(); -if(!_4&&!_2){ -dojo.window.scrollIntoView(_3.containerNode||_3.domNode); -_3.focus(); -_2=true; -} -return _4; -}),function(_5){ -return _5; -}); -},setValues:function(_6){ -dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.","","2.0"); -return this.set("value",_6); -},_setValueAttr:function(_7){ -var _8={}; -dojo.forEach(this.getDescendants(),function(_9){ -if(!_9.name){ -return; -} -var _a=_8[_9.name]||(_8[_9.name]=[]); -_a.push(_9); -}); -for(var _b in _8){ -if(!_8.hasOwnProperty(_b)){ -continue; -} -var _c=_8[_b],_d=dojo.getObject(_b,false,_7); -if(_d===undefined){ -continue; -} -if(!dojo.isArray(_d)){ -_d=[_d]; -} -if(typeof _c[0].checked=="boolean"){ -dojo.forEach(_c,function(w,i){ -w.set("value",dojo.indexOf(_d,w.value)!=-1); -}); -}else{ -if(_c[0].multiple){ -_c[0].set("value",_d); -}else{ -dojo.forEach(_c,function(w,i){ -w.set("value",_d[i]); -}); -} -} -} -},getValues:function(){ -dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.","","2.0"); -return this.get("value"); -},_getValueAttr:function(){ -var _e={}; -dojo.forEach(this.getDescendants(),function(_f){ -var _10=_f.name; -if(!_10||_f.disabled){ -return; -} -var _11=_f.get("value"); -if(typeof _f.checked=="boolean"){ -if(/Radio/.test(_f.declaredClass)){ -if(_11!==false){ -dojo.setObject(_10,_11,_e); -}else{ -_11=dojo.getObject(_10,false,_e); -if(_11===undefined){ -dojo.setObject(_10,null,_e); -} -} -}else{ -var ary=dojo.getObject(_10,false,_e); -if(!ary){ -ary=[]; -dojo.setObject(_10,ary,_e); -} -if(_11!==false){ -ary.push(_11); -} -} -}else{ -var _12=dojo.getObject(_10,false,_e); -if(typeof _12!="undefined"){ -if(dojo.isArray(_12)){ -_12.push(_11); -}else{ -dojo.setObject(_10,[_12,_11],_e); -} -}else{ -dojo.setObject(_10,_11,_e); -} -} -}); -return _e; -},isValid:function(){ -this._invalidWidgets=dojo.filter(this.getDescendants(),function(_13){ -return !_13.disabled&&_13.isValid&&!_13.isValid(); -}); -return !this._invalidWidgets.length; -},onValidStateChange:function(_14){ -},_widgetChange:function(_15){ -var _16=this._lastValidState; -if(!_15||this._lastValidState===undefined){ -_16=this.isValid(); -if(this._lastValidState===undefined){ -this._lastValidState=_16; -} -}else{ -if(_15.isValid){ -this._invalidWidgets=dojo.filter(this._invalidWidgets||[],function(w){ -return (w!=_15); -},this); -if(!_15.isValid()&&!_15.get("disabled")){ -this._invalidWidgets.push(_15); -} -_16=(this._invalidWidgets.length===0); -} -} -if(_16!==this._lastValidState){ -this._lastValidState=_16; -this.onValidStateChange(_16); -} -},connectChildren:function(){ -dojo.forEach(this._changeConnections,dojo.hitch(this,"disconnect")); -var _17=this; -var _18=(this._changeConnections=[]); -dojo.forEach(dojo.filter(this.getDescendants(),function(_19){ -return _19.validate; -}),function(_1a){ -_18.push(_17.connect(_1a,"validate",dojo.hitch(_17,"_widgetChange",_1a))); -_18.push(_17.connect(_1a,"_setDisabledAttr",dojo.hitch(_17,"_widgetChange",_1a))); -}); -this._widgetChange(null); -},startup:function(){ -this.inherited(arguments); -this._changeConnections=[]; -this.connectChildren(); -}}); + + +dojo.declare("dijit.form._FormMixin", null, { + // summary: + // Mixin for containers of form widgets (i.e. widgets that represent a single value + // and can be children of a <form> node or dijit.form.Form widget) + // description: + // Can extract all the form widgets + // values and combine them into a single javascript object, or alternately + // take such an object and set the values for all the contained + // form widgets + +/*===== + // value: Object + // Name/value hash for each child widget with a name and value. + // Child widgets without names are not part of the hash. + // + // If there are multiple child widgets w/the same name, value is an array, + // unless they are radio buttons in which case value is a scalar (since only + // one radio button can be checked at a time). + // + // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure. + // + // Example: + // | { name: "John Smith", interests: ["sports", "movies"] } +=====*/ + + // state: [readonly] String + // Will be "Error" if one or more of the child widgets has an invalid value, + // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "", + // which indicates that the form is ready to be submitted. + state: "", + + // TODO: + // * Repeater + // * better handling for arrays. Often form elements have names with [] like + // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...]) + // + // + + reset: function(){ + dojo.forEach(this.getDescendants(), function(widget){ + if(widget.reset){ + widget.reset(); + } + }); + }, + + validate: function(){ + // summary: + // returns if the form is valid - same as isValid - but + // provides a few additional (ui-specific) features. + // 1 - it will highlight any sub-widgets that are not + // valid + // 2 - it will call focus() on the first invalid + // sub-widget + var didFocus = false; + return dojo.every(dojo.map(this.getDescendants(), function(widget){ + // Need to set this so that "required" widgets get their + // state set. + widget._hasBeenBlurred = true; + var valid = widget.disabled || !widget.validate || widget.validate(); + if(!valid && !didFocus){ + // Set focus of the first non-valid widget + dojo.window.scrollIntoView(widget.containerNode || widget.domNode); + widget.focus(); + didFocus = true; + } + return valid; + }), function(item){ return item; }); + }, + + setValues: function(val){ + dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0"); + return this.set('value', val); + }, + _setValueAttr: function(/*Object*/ obj){ + // summary: + // Fill in form values from according to an Object (in the format returned by get('value')) + + // generate map from name --> [list of widgets with that name] + var map = { }; + dojo.forEach(this.getDescendants(), function(widget){ + if(!widget.name){ return; } + var entry = map[widget.name] || (map[widget.name] = [] ); + entry.push(widget); + }); + + for(var name in map){ + if(!map.hasOwnProperty(name)){ + continue; + } + var widgets = map[name], // array of widgets w/this name + values = dojo.getObject(name, false, obj); // list of values for those widgets + + if(values === undefined){ + continue; + } + if(!dojo.isArray(values)){ + values = [ values ]; + } + if(typeof widgets[0].checked == 'boolean'){ + // for checkbox/radio, values is a list of which widgets should be checked + dojo.forEach(widgets, function(w, i){ + w.set('value', dojo.indexOf(values, w.value) != -1); + }); + }else if(widgets[0].multiple){ + // it takes an array (e.g. multi-select) + widgets[0].set('value', values); + }else{ + // otherwise, values is a list of values to be assigned sequentially to each widget + dojo.forEach(widgets, function(w, i){ + w.set('value', values[i]); + }); + } + } + + /*** + * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets) + + dojo.forEach(this.containerNode.elements, function(element){ + if(element.name == ''){return}; // like "continue" + var namePath = element.name.split("."); + var myObj=obj; + var name=namePath[namePath.length-1]; + for(var j=1,len2=namePath.length;j<len2;++j){ + var p=namePath[j - 1]; + // repeater support block + var nameA=p.split("["); + if(nameA.length > 1){ + if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]]=[ ]; + } // if + + nameIndex=parseInt(nameA[1]); + if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ + myObj[nameA[0]][nameIndex] = { }; + } + myObj=myObj[nameA[0]][nameIndex]; + continue; + } // repeater support ends + + if(typeof(myObj[p]) == "undefined"){ + myObj=undefined; + break; + }; + myObj=myObj[p]; + } + + if(typeof(myObj) == "undefined"){ + return; // like "continue" + } + if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){ + return; // like "continue" + } + + // TODO: widget values (just call set('value', ...) on the widget) + + // TODO: maybe should call dojo.getNodeProp() instead + switch(element.type){ + case "checkbox": + element.checked = (name in myObj) && + dojo.some(myObj[name], function(val){ return val == element.value; }); + break; + case "radio": + element.checked = (name in myObj) && myObj[name] == element.value; + break; + case "select-multiple": + element.selectedIndex=-1; + dojo.forEach(element.options, function(option){ + option.selected = dojo.some(myObj[name], function(val){ return option.value == val; }); + }); + break; + case "select-one": + element.selectedIndex="0"; + dojo.forEach(element.options, function(option){ + option.selected = option.value == myObj[name]; + }); + break; + case "hidden": + case "text": + case "textarea": + case "password": + element.value = myObj[name] || ""; + break; + } + }); + */ + + // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events + // which I am monitoring. + }, + + getValues: function(){ + dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get('value'); + }, + _getValueAttr: function(){ + // summary: + // Returns Object representing form values. See description of `value` for details. + // description: + + // The value is updated into this.value every time a child has an onChange event, + // so in the common case this function could just return this.value. However, + // that wouldn't work when: + // + // 1. User presses return key to submit a form. That doesn't fire an onchange event, + // and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange() + // + // 2. app for some reason calls this.get("value") while the user is typing into a + // form field. Not sure if that case needs to be supported or not. + + // get widget values + var obj = { }; + dojo.forEach(this.getDescendants(), function(widget){ + var name = widget.name; + if(!name || widget.disabled){ return; } + + // Single value widget (checkbox, radio, or plain <input> type widget) + var value = widget.get('value'); + + // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays + if(typeof widget.checked == 'boolean'){ + if(/Radio/.test(widget.declaredClass)){ + // radio button + if(value !== false){ + dojo.setObject(name, value, obj); + }else{ + // give radio widgets a default of null + value = dojo.getObject(name, false, obj); + if(value === undefined){ + dojo.setObject(name, null, obj); + } + } + }else{ + // checkbox/toggle button + var ary=dojo.getObject(name, false, obj); + if(!ary){ + ary=[]; + dojo.setObject(name, ary, obj); + } + if(value !== false){ + ary.push(value); + } + } + }else{ + var prev=dojo.getObject(name, false, obj); + if(typeof prev != "undefined"){ + if(dojo.isArray(prev)){ + prev.push(value); + }else{ + dojo.setObject(name, [prev, value], obj); + } + }else{ + // unique name + dojo.setObject(name, value, obj); + } + } + }); + + /*** + * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code? + * but it doesn't understand [] notation, presumably) + var obj = { }; + dojo.forEach(this.containerNode.elements, function(elm){ + if(!elm.name) { + return; // like "continue" + } + var namePath = elm.name.split("."); + var myObj=obj; + var name=namePath[namePath.length-1]; + for(var j=1,len2=namePath.length;j<len2;++j){ + var nameIndex = null; + var p=namePath[j - 1]; + var nameA=p.split("["); + if(nameA.length > 1){ + if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]]=[ ]; + } // if + nameIndex=parseInt(nameA[1]); + if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){ + myObj[nameA[0]][nameIndex] = { }; + } + } else if(typeof(myObj[nameA[0]]) == "undefined"){ + myObj[nameA[0]] = { } + } // if + + if(nameA.length == 1){ + myObj=myObj[nameA[0]]; + } else{ + myObj=myObj[nameA[0]][nameIndex]; + } // if + } // for + + if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){ + if(name == name.split("[")[0]){ + myObj[name]=elm.value; + } else{ + // can not set value when there is no name + } + } else if(elm.type == "checkbox" && elm.checked){ + if(typeof(myObj[name]) == 'undefined'){ + myObj[name]=[ ]; + } + myObj[name].push(elm.value); + } else if(elm.type == "select-multiple"){ + if(typeof(myObj[name]) == 'undefined'){ + myObj[name]=[ ]; + } + for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){ + if(elm.options[jdx].selected){ + myObj[name].push(elm.options[jdx].value); + } + } + } // if + name=undefined; + }); // forEach + ***/ + return obj; + }, + + isValid: function(){ + // summary: + // Returns true if all of the widgets are valid. + // Deprecated, will be removed in 2.0. Use get("state") instead. + + return this.state == ""; + }, + + onValidStateChange: function(isValid){ + // summary: + // Stub function to connect to if you want to do something + // (like disable/enable a submit button) when the valid + // state changes on the form as a whole. + // + // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead. + }, + + _getState: function(){ + // summary: + // Compute what this.state should be based on state of children + var states = dojo.map(this._descendants, function(w){ + return w.get("state") || ""; + }); + + return dojo.indexOf(states, "Error") >= 0 ? "Error" : + dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : ""; + }, + + disconnectChildren: function(){ + // summary: + // Remove connections to monitor changes to children's value, error state, and disabled state, + // in order to update Form.value and Form.state. + dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect")); + dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); }); + }, + + connectChildren: function(/*Boolean*/ inStartup){ + // summary: + // Setup connections to monitor changes to children's value, error state, and disabled state, + // in order to update Form.value and Form.state. + // + // You can call this function directly, ex. in the event that you + // programmatically add a widget to the form *after* the form has been + // initialized. + + var _this = this; + + // Remove old connections, if any + this.disconnectChildren(); + + this._descendants = this.getDescendants(); + + // (Re)set this.value and this.state. Send watch() notifications but not on startup. + var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set"); + set("value", this.get("value")); + set("state", this._getState()); + + // Monitor changes to error state and disabled state in order to update + // Form.state + var conns = (this._childConnections = []), + watches = (this._childWatches = []); + dojo.forEach(dojo.filter(this._descendants, + function(item){ return item.validate; } + ), + function(widget){ + // We are interested in whenever the widget changes validity state - or + // whenever the disabled attribute on that widget is changed. + dojo.forEach(["state", "disabled"], function(attr){ + watches.push(widget.watch(attr, function(attr, oldVal, newVal){ + _this.set("state", _this._getState()); + })); + }); + }); + + // And monitor calls to child.onChange so we can update this.value + var onChange = function(){ + // summary: + // Called when child's value or disabled state changes + + // Use setTimeout() to collapse value changes in multiple children into a single + // update to my value. Multiple updates will occur on: + // 1. Form.set() + // 2. Form.reset() + // 3. user selecting a radio button (which will de-select another radio button, + // causing two onChange events) + if(_this._onChangeDelayTimer){ + clearTimeout(_this._onChangeDelayTimer); + } + _this._onChangeDelayTimer = setTimeout(function(){ + delete _this._onChangeDelayTimer; + _this._set("value", _this.get("value")); + }, 10); + }; + dojo.forEach( + dojo.filter(this._descendants, function(item){ return item.onChange; } ), + function(widget){ + // When a child widget's value changes, + // the efficient thing to do is to just update that one attribute in this.value, + // but that gets a little complicated when a checkbox is checked/unchecked + // since this.value["checkboxName"] contains an array of all the checkboxes w/the same name. + // Doing simple thing for now. + conns.push(_this.connect(widget, "onChange", onChange)); + + // Disabling/enabling a child widget should remove it's value from this.value. + // Again, this code could be more efficient, doing simple thing for now. + watches.push(widget.watch("disabled", onChange)); + } + ); + }, + + startup: function(){ + this.inherited(arguments); + + // Initialize value and valid/invalid state tracking. Needs to be done in startup() + // so that children are initialized. + this.connectChildren(true); + + // Make state change call onValidStateChange(), will be removed in 2.0 + this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); }); + }, + + destroy: function(){ + this.disconnectChildren(); + this.inherited(arguments); + } + + }); + } diff --git a/lib/dijit/form/_FormSelectWidget.js b/lib/dijit/form/_FormSelectWidget.js index 5905c0aef..fbbf7226b 100644 --- a/lib/dijit/form/_FormSelectWidget.js +++ b/lib/dijit/form/_FormSelectWidget.js @@ -1,307 +1,582 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form._FormSelectWidget"]){ -dojo._hasResource["dijit.form._FormSelectWidget"]=true; +if(!dojo._hasResource["dijit.form._FormSelectWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._FormSelectWidget"] = true; dojo.provide("dijit.form._FormSelectWidget"); dojo.require("dijit.form._FormWidget"); dojo.require("dojo.data.util.sorter"); -dojo.declare("dijit.form._FormSelectWidget",dijit.form._FormValueWidget,{multiple:false,options:null,store:null,query:null,queryOptions:null,onFetch:null,sortByLabel:true,loadChildrenOnOpen:false,getOptions:function(_1){ -var _2=_1,_3=this.options||[],l=_3.length; -if(_2===undefined){ -return _3; -} -if(dojo.isArray(_2)){ -return dojo.map(_2,"return this.getOptions(item);",this); -} -if(dojo.isObject(_1)){ -if(!dojo.some(this.options,function(o,_4){ -if(o===_2||(o.value&&o.value===_2.value)){ -_2=_4; -return true; -} -return false; -})){ -_2=-1; -} -} -if(typeof _2=="string"){ -for(var i=0;i<l;i++){ -if(_3[i].value===_2){ -_2=i; -break; -} -} -} -if(typeof _2=="number"&&_2>=0&&_2<l){ -return this.options[_2]; -} -return null; -},addOption:function(_5){ -if(!dojo.isArray(_5)){ -_5=[_5]; -} -dojo.forEach(_5,function(i){ -if(i&&dojo.isObject(i)){ -this.options.push(i); -} -},this); -this._loadChildren(); -},removeOption:function(_6){ -if(!dojo.isArray(_6)){ -_6=[_6]; -} -var _7=this.getOptions(_6); -dojo.forEach(_7,function(i){ -if(i){ -this.options=dojo.filter(this.options,function(_8,_9){ -return (_8.value!==i.value); -}); -this._removeOptionItem(i); -} -},this); -this._loadChildren(); -},updateOption:function(_a){ -if(!dojo.isArray(_a)){ -_a=[_a]; -} -dojo.forEach(_a,function(i){ -var _b=this.getOptions(i),k; -if(_b){ -for(k in i){ -_b[k]=i[k]; -} -} -},this); -this._loadChildren(); -},setStore:function(_c,_d,_e){ -var _f=this.store; -_e=_e||{}; -if(_f!==_c){ -dojo.forEach(this._notifyConnections||[],dojo.disconnect); -delete this._notifyConnections; -if(_c&&_c.getFeatures()["dojo.data.api.Notification"]){ -this._notifyConnections=[dojo.connect(_c,"onNew",this,"_onNewItem"),dojo.connect(_c,"onDelete",this,"_onDeleteItem"),dojo.connect(_c,"onSet",this,"_onSetItem")]; -} -this.store=_c; -} -this._onChangeActive=false; -if(this.options&&this.options.length){ -this.removeOption(this.options); -} -if(_c){ -var cb=function(_10){ -if(this.sortByLabel&&!_e.sort&&_10.length){ -_10.sort(dojo.data.util.sorter.createSortFunction([{attribute:_c.getLabelAttributes(_10[0])[0]}],_c)); -} -if(_e.onFetch){ -_10=_e.onFetch(_10); -} -dojo.forEach(_10,function(i){ -this._addOptionForItem(i); -},this); -this._loadingStore=false; -this.set("value",(("_pendingValue" in this)?this._pendingValue:_d)); -delete this._pendingValue; -if(!this.loadChildrenOnOpen){ -this._loadChildren(); -}else{ -this._pseudoLoadChildren(_10); -} -this._fetchedWith=_11; -this._lastValueReported=this.multiple?[]:null; -this._onChangeActive=true; -this.onSetStore(); -this._handleOnChange(this.value); -}; -var _11=dojo.mixin({onComplete:cb,scope:this},_e); -this._loadingStore=true; -_c.fetch(_11); -}else{ -delete this._fetchedWith; -} -return _f; -},_setValueAttr:function(_12,_13){ -if(this._loadingStore){ -this._pendingValue=_12; -return; -} -var _14=this.getOptions()||[]; -if(!dojo.isArray(_12)){ -_12=[_12]; -} -dojo.forEach(_12,function(i,idx){ -if(!dojo.isObject(i)){ -i=i+""; -} -if(typeof i==="string"){ -_12[idx]=dojo.filter(_14,function(_15){ -return _15.value===i; -})[0]||{value:"",label:""}; -} -},this); -_12=dojo.filter(_12,function(i){ -return i&&i.value; -}); -if(!this.multiple&&(!_12[0]||!_12[0].value)&&_14.length){ -_12[0]=_14[0]; -} -dojo.forEach(_14,function(i){ -i.selected=dojo.some(_12,function(v){ -return v.value===i.value; -}); -}); -var val=dojo.map(_12,function(i){ -return i.value; -}),_16=dojo.map(_12,function(i){ -return i.label; -}); -this.value=this.multiple?val:val[0]; -this._setDisplay(this.multiple?_16:_16[0]); -this._updateSelection(); -this._handleOnChange(this.value,_13); -},_getDisplayedValueAttr:function(){ -var val=this.get("value"); -if(!dojo.isArray(val)){ -val=[val]; -} -var ret=dojo.map(this.getOptions(val),function(v){ -if(v&&"label" in v){ -return v.label; -}else{ -if(v){ -return v.value; -} -} -return null; -},this); -return this.multiple?ret:ret[0]; -},_getValueDeprecated:false,getValue:function(){ -return this._lastValue; -},undo:function(){ -this._setValueAttr(this._lastValueReported,false); -},_loadChildren:function(){ -if(this._loadingStore){ -return; -} -dojo.forEach(this._getChildren(),function(_17){ -_17.destroyRecursive(); -}); -dojo.forEach(this.options,this._addOptionItem,this); -this._updateSelection(); -},_updateSelection:function(){ -this.value=this._getValueFromOpts(); -var val=this.value; -if(!dojo.isArray(val)){ -val=[val]; -} -if(val&&val[0]){ -dojo.forEach(this._getChildren(),function(_18){ -var _19=dojo.some(val,function(v){ -return _18.option&&(v===_18.option.value); + + +/*===== +dijit.form.__SelectOption = function(){ + // value: String + // The value of the option. Setting to empty (or missing) will + // place a separator at that location + // label: String + // The label for our option. It can contain html tags. + // selected: Boolean + // Whether or not we are a selected option + // disabled: Boolean + // Whether or not this specific option is disabled + this.value = value; + this.label = label; + this.selected = selected; + this.disabled = disabled; +} +=====*/ + +dojo.declare("dijit.form._FormSelectWidget", dijit.form._FormValueWidget, { + // summary: + // Extends _FormValueWidget in order to provide "select-specific" + // values - i.e., those values that are unique to <select> elements. + // This also provides the mechanism for reading the elements from + // a store, if desired. + + // multiple: [const] Boolean + // Whether or not we are multi-valued + multiple: false, + + // options: dijit.form.__SelectOption[] + // The set of options for our select item. Roughly corresponds to + // the html <option> tag. + options: null, + + // store: dojo.data.api.Identity + // A store which, at the very least impelements dojo.data.api.Identity + // to use for getting our list of options - rather than reading them + // from the <option> html tags. + store: null, + + // query: object + // A query to use when fetching items from our store + query: null, + + // queryOptions: object + // Query options to use when fetching from the store + queryOptions: null, + + // onFetch: Function + // A callback to do with an onFetch - but before any items are actually + // iterated over (i.e. to filter even futher what you want to add) + onFetch: null, + + // sortByLabel: Boolean + // Flag to sort the options returned from a store by the label of + // the store. + sortByLabel: true, + + + // loadChildrenOnOpen: Boolean + // By default loadChildren is called when the items are fetched from the + // store. This property allows delaying loadChildren (and the creation + // of the options/menuitems) until the user clicks the button to open the + // dropdown. + loadChildrenOnOpen: false, + + getOptions: function(/*anything*/ valueOrIdx){ + // summary: + // Returns a given option (or options). + // valueOrIdx: + // If passed in as a string, that string is used to look up the option + // in the array of options - based on the value property. + // (See dijit.form.__SelectOption). + // + // If passed in a number, then the option with the given index (0-based) + // within this select will be returned. + // + // If passed in a dijit.form.__SelectOption, the same option will be + // returned if and only if it exists within this select. + // + // If passed an array, then an array will be returned with each element + // in the array being looked up. + // + // If not passed a value, then all options will be returned + // + // returns: + // The option corresponding with the given value or index. null + // is returned if any of the following are true: + // - A string value is passed in which doesn't exist + // - An index is passed in which is outside the bounds of the array of options + // - A dijit.form.__SelectOption is passed in which is not a part of the select + + // NOTE: the compare for passing in a dijit.form.__SelectOption checks + // if the value property matches - NOT if the exact option exists + // NOTE: if passing in an array, null elements will be placed in the returned + // array when a value is not found. + var lookupValue = valueOrIdx, opts = this.options || [], l = opts.length; + + if(lookupValue === undefined){ + return opts; // dijit.form.__SelectOption[] + } + if(dojo.isArray(lookupValue)){ + return dojo.map(lookupValue, "return this.getOptions(item);", this); // dijit.form.__SelectOption[] + } + if(dojo.isObject(valueOrIdx)){ + // We were passed an option - so see if it's in our array (directly), + // and if it's not, try and find it by value. + if(!dojo.some(this.options, function(o, idx){ + if(o === lookupValue || + (o.value && o.value === lookupValue.value)){ + lookupValue = idx; + return true; + } + return false; + })){ + lookupValue = -1; + } + } + if(typeof lookupValue == "string"){ + for(var i=0; i<l; i++){ + if(opts[i].value === lookupValue){ + lookupValue = i; + break; + } + } + } + if(typeof lookupValue == "number" && lookupValue >= 0 && lookupValue < l){ + return this.options[lookupValue] // dijit.form.__SelectOption + } + return null; // null + }, + + addOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ option){ + // summary: + // Adds an option or options to the end of the select. If value + // of the option is empty or missing, a separator is created instead. + // Passing in an array of options will yield slightly better performance + // since the children are only loaded once. + if(!dojo.isArray(option)){ option = [option]; } + dojo.forEach(option, function(i){ + if(i && dojo.isObject(i)){ + this.options.push(i); + } + }, this); + this._loadChildren(); + }, + + removeOption: function(/*String|dijit.form.__SelectOption|Number|Array*/ valueOrIdx){ + // summary: + // Removes the given option or options. You can remove by string + // (in which case the value is removed), number (in which case the + // index in the options array is removed), or select option (in + // which case, the select option with a matching value is removed). + // You can also pass in an array of those values for a slightly + // better performance since the children are only loaded once. + if(!dojo.isArray(valueOrIdx)){ valueOrIdx = [valueOrIdx]; } + var oldOpts = this.getOptions(valueOrIdx); + dojo.forEach(oldOpts, function(i){ + // We can get null back in our array - if our option was not found. In + // that case, we don't want to blow up... + if(i){ + this.options = dojo.filter(this.options, function(node, idx){ + return (node.value !== i.value || node.label !== i.label); + }); + this._removeOptionItem(i); + } + }, this); + this._loadChildren(); + }, + + updateOption: function(/*dijit.form.__SelectOption|dijit.form.__SelectOption[]*/ newOption){ + // summary: + // Updates the values of the given option. The option to update + // is matched based on the value of the entered option. Passing + // in an array of new options will yeild better performance since + // the children will only be loaded once. + if(!dojo.isArray(newOption)){ newOption = [newOption]; } + dojo.forEach(newOption, function(i){ + var oldOpt = this.getOptions(i), k; + if(oldOpt){ + for(k in i){ oldOpt[k] = i[k]; } + } + }, this); + this._loadChildren(); + }, + + setStore: function(/*dojo.data.api.Identity*/ store, + /*anything?*/ selectedValue, + /*Object?*/ fetchArgs){ + // summary: + // Sets the store you would like to use with this select widget. + // The selected value is the value of the new store to set. This + // function returns the original store, in case you want to reuse + // it or something. + // store: dojo.data.api.Identity + // The store you would like to use - it MUST implement Identity, + // and MAY implement Notification. + // selectedValue: anything? + // The value that this widget should set itself to *after* the store + // has been loaded + // fetchArgs: Object? + // The arguments that will be passed to the store's fetch() function + var oStore = this.store; + fetchArgs = fetchArgs || {}; + if(oStore !== store){ + // Our store has changed, so update our notifications + dojo.forEach(this._notifyConnections || [], dojo.disconnect); + delete this._notifyConnections; + if(store && store.getFeatures()["dojo.data.api.Notification"]){ + this._notifyConnections = [ + dojo.connect(store, "onNew", this, "_onNewItem"), + dojo.connect(store, "onDelete", this, "_onDeleteItem"), + dojo.connect(store, "onSet", this, "_onSetItem") + ]; + } + this._set("store", store); + } + + // Turn off change notifications while we make all these changes + this._onChangeActive = false; + + // Remove existing options (if there are any) + if(this.options && this.options.length){ + this.removeOption(this.options); + } + + // Add our new options + if(store){ + this._loadingStore = true; + store.fetch(dojo.delegate(fetchArgs, { + onComplete: function(items, opts){ + if(this.sortByLabel && !fetchArgs.sort && items.length){ + items.sort(dojo.data.util.sorter.createSortFunction([{ + attribute: store.getLabelAttributes(items[0])[0] + }], store)); + } + + if(fetchArgs.onFetch){ + items = fetchArgs.onFetch.call(this, items, opts); + } + // TODO: Add these guys as a batch, instead of separately + dojo.forEach(items, function(i){ + this._addOptionForItem(i); + }, this); + + // Set our value (which might be undefined), and then tweak + // it to send a change event with the real value + this._loadingStore = false; + this.set("value", "_pendingValue" in this ? this._pendingValue : selectedValue); + delete this._pendingValue; + + if(!this.loadChildrenOnOpen){ + this._loadChildren(); + }else{ + this._pseudoLoadChildren(items); + } + this._fetchedWith = opts; + this._lastValueReported = this.multiple ? [] : null; + this._onChangeActive = true; + this.onSetStore(); + this._handleOnChange(this.value); + }, + scope: this + })); + }else{ + delete this._fetchedWith; + } + return oStore; // dojo.data.api.Identity + }, + + // TODO: implement set() and watch() for store and query, although not sure how to handle + // setting them individually rather than together (as in setStore() above) + + _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // set the value of the widget. + // If a string is passed, then we set our value from looking it up. + if(this._loadingStore){ + // Our store is loading - so save our value, and we'll set it when + // we're done + this._pendingValue = newValue; + return; + } + var opts = this.getOptions() || []; + if(!dojo.isArray(newValue)){ + newValue = [newValue]; + } + dojo.forEach(newValue, function(i, idx){ + if(!dojo.isObject(i)){ + i = i + ""; + } + if(typeof i === "string"){ + newValue[idx] = dojo.filter(opts, function(node){ + return node.value === i; + })[0] || {value: "", label: ""}; + } + }, this); + + // Make sure some sane default is set + newValue = dojo.filter(newValue, function(i){ return i && i.value; }); + if(!this.multiple && (!newValue[0] || !newValue[0].value) && opts.length){ + newValue[0] = opts[0]; + } + dojo.forEach(opts, function(i){ + i.selected = dojo.some(newValue, function(v){ return v.value === i.value; }); + }); + var val = dojo.map(newValue, function(i){ return i.value; }), + disp = dojo.map(newValue, function(i){ return i.label; }); + + this._set("value", this.multiple ? val : val[0]); + this._setDisplay(this.multiple ? disp : disp[0]); + this._updateSelection(); + this._handleOnChange(this.value, priorityChange); + }, + + _getDisplayedValueAttr: function(){ + // summary: + // returns the displayed value of the widget + var val = this.get("value"); + if(!dojo.isArray(val)){ + val = [val]; + } + var ret = dojo.map(this.getOptions(val), function(v){ + if(v && "label" in v){ + return v.label; + }else if(v){ + return v.value; + } + return null; + }, this); + return this.multiple ? ret : ret[0]; + }, + + _loadChildren: function(){ + // summary: + // Loads the children represented by this widget's options. + // reset the menu to make it populatable on the next click + if(this._loadingStore){ return; } + dojo.forEach(this._getChildren(), function(child){ + child.destroyRecursive(); + }); + // Add each menu item + dojo.forEach(this.options, this._addOptionItem, this); + + // Update states + this._updateSelection(); + }, + + _updateSelection: function(){ + // summary: + // Sets the "selected" class on the item for styling purposes + this._set("value", this._getValueFromOpts()); + var val = this.value; + if(!dojo.isArray(val)){ + val = [val]; + } + if(val && val[0]){ + dojo.forEach(this._getChildren(), function(child){ + var isSelected = dojo.some(val, function(v){ + return child.option && (v === child.option.value); + }); + dojo.toggleClass(child.domNode, this.baseClass + "SelectedOption", isSelected); + dijit.setWaiState(child.domNode, "selected", isSelected); + }, this); + } + }, + + _getValueFromOpts: function(){ + // summary: + // Returns the value of the widget by reading the options for + // the selected flag + var opts = this.getOptions() || []; + if(!this.multiple && opts.length){ + // Mirror what a select does - choose the first one + var opt = dojo.filter(opts, function(i){ + return i.selected; + })[0]; + if(opt && opt.value){ + return opt.value + }else{ + opts[0].selected = true; + return opts[0].value; + } + }else if(this.multiple){ + // Set value to be the sum of all selected + return dojo.map(dojo.filter(opts, function(i){ + return i.selected; + }), function(i){ + return i.value; + }) || []; + } + return ""; + }, + + // Internal functions to call when we have store notifications come in + _onNewItem: function(/*item*/ item, /*Object?*/ parentInfo){ + if(!parentInfo || !parentInfo.parent){ + // Only add it if we are top-level + this._addOptionForItem(item); + } + }, + _onDeleteItem: function(/*item*/ item){ + var store = this.store; + this.removeOption(store.getIdentity(item)); + }, + _onSetItem: function(/*item*/ item){ + this.updateOption(this._getOptionObjForItem(item)); + }, + + _getOptionObjForItem: function(item){ + // summary: + // Returns an option object based off the given item. The "value" + // of the option item will be the identity of the item, the "label" + // of the option will be the label of the item. If the item contains + // children, the children value of the item will be set + var store = this.store, label = store.getLabel(item), + value = (label ? store.getIdentity(item) : null); + return {value: value, label: label, item:item}; // dijit.form.__SelectOption + }, + + _addOptionForItem: function(/*item*/ item){ + // summary: + // Creates (and adds) the option for the given item + var store = this.store; + if(!store.isItemLoaded(item)){ + // We are not loaded - so let's load it and add later + store.loadItem({item: item, onComplete: function(i){ + this._addOptionForItem(item); + }, + scope: this}); + return; + } + var newOpt = this._getOptionObjForItem(item); + this.addOption(newOpt); + }, + + constructor: function(/*Object*/ keywordArgs){ + // summary: + // Saves off our value, if we have an initial one set so we + // can use it if we have a store as well (see startup()) + this._oValue = (keywordArgs || {}).value || null; + }, + + buildRendering: function(){ + this.inherited(arguments); + dojo.setSelectable(this.focusNode, false); + }, + + _fillContent: function(){ + // summary: + // Loads our options and sets up our dropdown correctly. We + // don't want any content, so we don't call any inherit chain + // function. + var opts = this.options; + if(!opts){ + opts = this.options = this.srcNodeRef ? dojo.query(">", + this.srcNodeRef).map(function(node){ + if(node.getAttribute("type") === "separator"){ + return { value: "", label: "", selected: false, disabled: false }; + } + return { + value: (node.getAttribute("data-" + dojo._scopeName + "-value") || node.getAttribute("value")), + label: String(node.innerHTML), + // FIXME: disabled and selected are not valid on complex markup children (which is why we're + // looking for data-dojo-value above. perhaps we should data-dojo-props="" this whole thing?) + // decide before 1.6 + selected: node.getAttribute("selected") || false, + disabled: node.getAttribute("disabled") || false + }; + }, this) : []; + } + if(!this.value){ + this._set("value", this._getValueFromOpts()); + }else if(this.multiple && typeof this.value == "string"){ + this_set("value", this.value.split(",")); + } + }, + + postCreate: function(){ + // summary: + // sets up our event handling that we need for functioning + // as a select + this.inherited(arguments); + + // Make our event connections for updating state + this.connect(this, "onChange", "_updateSelection"); + this.connect(this, "startup", "_loadChildren"); + + this._setValueAttr(this.value, null); + }, + + startup: function(){ + // summary: + // Connects in our store, if we have one defined + this.inherited(arguments); + var store = this.store, fetchArgs = {}; + dojo.forEach(["query", "queryOptions", "onFetch"], function(i){ + if(this[i]){ + fetchArgs[i] = this[i]; + } + delete this[i]; + }, this); + if(store && store.getFeatures()["dojo.data.api.Identity"]){ + // Temporarily set our store to null so that it will get set + // and connected appropriately + this.store = null; + this.setStore(store, this._oValue, fetchArgs); + } + }, + + destroy: function(){ + // summary: + // Clean up our connections + dojo.forEach(this._notifyConnections || [], dojo.disconnect); + this.inherited(arguments); + }, + + _addOptionItem: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // User-overridable function which, for the given option, adds an + // item to the select. If the option doesn't have a value, then a + // separator is added in that place. Make sure to store the option + // in the created option widget. + }, + + _removeOptionItem: function(/*dijit.form.__SelectOption*/ option){ + // summary: + // User-overridable function which, for the given option, removes + // its item from the select. + }, + + _setDisplay: function(/*String or String[]*/ newDisplay){ + // summary: + // Overridable function which will set the display for the + // widget. newDisplay is either a string (in the case of + // single selects) or array of strings (in the case of multi-selects) + }, + + _getChildren: function(){ + // summary: + // Overridable function to return the children that this widget contains. + return []; + }, + + _getSelectedOptionsAttr: function(){ + // summary: + // hooks into this.attr to provide a mechanism for getting the + // option items for the current value of the widget. + return this.getOptions(this.get("value")); + }, + + _pseudoLoadChildren: function(/*item[]*/ items){ + // summary: + // a function that will "fake" loading children, if needed, and + // if we have set to not load children until the widget opens. + // items: + // An array of items that will be loaded, when needed + }, + + onSetStore: function(){ + // summary: + // a function that can be connected to in order to receive a + // notification that the store has finished loading and all options + // from that store are available + } }); -dojo.toggleClass(_18.domNode,this.baseClass+"SelectedOption",_19); -dijit.setWaiState(_18.domNode,"selected",_19); -},this); -} -this._handleOnChange(this.value); -},_getValueFromOpts:function(){ -var _1a=this.getOptions()||[]; -if(!this.multiple&&_1a.length){ -var opt=dojo.filter(_1a,function(i){ -return i.selected; -})[0]; -if(opt&&opt.value){ -return opt.value; -}else{ -_1a[0].selected=true; -return _1a[0].value; -} -}else{ -if(this.multiple){ -return dojo.map(dojo.filter(_1a,function(i){ -return i.selected; -}),function(i){ -return i.value; -})||[]; -} -} -return ""; -},_onNewItem:function(_1b,_1c){ -if(!_1c||!_1c.parent){ -this._addOptionForItem(_1b); -} -},_onDeleteItem:function(_1d){ -var _1e=this.store; -this.removeOption(_1e.getIdentity(_1d)); -},_onSetItem:function(_1f){ -this.updateOption(this._getOptionObjForItem(_1f)); -},_getOptionObjForItem:function(_20){ -var _21=this.store,_22=_21.getLabel(_20),_23=(_22?_21.getIdentity(_20):null); -return {value:_23,label:_22,item:_20}; -},_addOptionForItem:function(_24){ -var _25=this.store; -if(!_25.isItemLoaded(_24)){ -_25.loadItem({item:_24,onComplete:function(i){ -this._addOptionForItem(_24); -},scope:this}); -return; -} -var _26=this._getOptionObjForItem(_24); -this.addOption(_26); -},constructor:function(_27){ -this._oValue=(_27||{}).value||null; -},_fillContent:function(){ -var _28=this.options; -if(!_28){ -_28=this.options=this.srcNodeRef?dojo.query(">",this.srcNodeRef).map(function(_29){ -if(_29.getAttribute("type")==="separator"){ -return {value:"",label:"",selected:false,disabled:false}; -} -return {value:_29.getAttribute("value"),label:String(_29.innerHTML),selected:_29.getAttribute("selected")||false,disabled:_29.getAttribute("disabled")||false}; -},this):[]; -} -if(!this.value){ -this.value=this._getValueFromOpts(); -}else{ -if(this.multiple&&typeof this.value=="string"){ -this.value=this.value.split(","); -} -} -},postCreate:function(){ -dojo.setSelectable(this.focusNode,false); -this.inherited(arguments); -this.connect(this,"onChange","_updateSelection"); -this.connect(this,"startup","_loadChildren"); -this._setValueAttr(this.value,null); -},startup:function(){ -this.inherited(arguments); -var _2a=this.store,_2b={}; -dojo.forEach(["query","queryOptions","onFetch"],function(i){ -if(this[i]){ -_2b[i]=this[i]; -} -delete this[i]; -},this); -if(_2a&&_2a.getFeatures()["dojo.data.api.Identity"]){ -this.store=null; -this.setStore(_2a,this._oValue,_2b); -} -},destroy:function(){ -dojo.forEach(this._notifyConnections||[],dojo.disconnect); -this.inherited(arguments); -},_addOptionItem:function(_2c){ -},_removeOptionItem:function(_2d){ -},_setDisplay:function(_2e){ -},_getChildren:function(){ -return []; -},_getSelectedOptionsAttr:function(){ -return this.getOptions(this.get("value")); -},_pseudoLoadChildren:function(_2f){ -},onSetStore:function(){ -}}); + } diff --git a/lib/dijit/form/_FormWidget.js b/lib/dijit/form/_FormWidget.js index eb80ca06b..ebd1cfb18 100644 --- a/lib/dijit/form/_FormWidget.js +++ b/lib/dijit/form/_FormWidget.js @@ -1,166 +1,377 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form._FormWidget"]){ -dojo._hasResource["dijit.form._FormWidget"]=true; +if(!dojo._hasResource["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._FormWidget"] = true; dojo.provide("dijit.form._FormWidget"); dojo.require("dojo.window"); dojo.require("dijit._Widget"); dojo.require("dijit._Templated"); dojo.require("dijit._CssStateMixin"); -dojo.declare("dijit.form._FormWidget",[dijit._Widget,dijit._Templated,dijit._CssStateMixin],{name:"",alt:"",value:"",type:"text",tabIndex:"0",disabled:false,intermediateChanges:false,scrollOnFocus:true,attributeMap:dojo.delegate(dijit._Widget.prototype.attributeMap,{value:"focusNode",id:"focusNode",tabIndex:"focusNode",alt:"focusNode",title:"focusNode"}),postMixInProperties:function(){ -this.nameAttrSetting=this.name?("name=\""+this.name.replace(/'/g,""")+"\""):""; -this.inherited(arguments); -},postCreate:function(){ -this.inherited(arguments); -this.connect(this.domNode,"onmousedown","_onMouseDown"); -},_setDisabledAttr:function(_1){ -this.disabled=_1; -dojo.attr(this.focusNode,"disabled",_1); -if(this.valueNode){ -dojo.attr(this.valueNode,"disabled",_1); -} -dijit.setWaiState(this.focusNode,"disabled",_1); -if(_1){ -this._hovering=false; -this._active=false; -var _2="tabIndex" in this.attributeMap?this.attributeMap.tabIndex:"focusNode"; -dojo.forEach(dojo.isArray(_2)?_2:[_2],function(_3){ -var _4=this[_3]; -if(dojo.isWebKit||dijit.hasDefaultTabStop(_4)){ -_4.setAttribute("tabIndex","-1"); -}else{ -_4.removeAttribute("tabIndex"); -} -},this); -}else{ -this.focusNode.setAttribute("tabIndex",this.tabIndex); -} -},setDisabled:function(_5){ -dojo.deprecated("setDisabled("+_5+") is deprecated. Use set('disabled',"+_5+") instead.","","2.0"); -this.set("disabled",_5); -},_onFocus:function(e){ -if(this.scrollOnFocus){ -dojo.window.scrollIntoView(this.domNode); -} -this.inherited(arguments); -},isFocusable:function(){ -return !this.disabled&&!this.readOnly&&this.focusNode&&(dojo.style(this.domNode,"display")!="none"); -},focus:function(){ -dijit.focus(this.focusNode); -},compare:function(_6,_7){ -if(typeof _6=="number"&&typeof _7=="number"){ -return (isNaN(_6)&&isNaN(_7))?0:_6-_7; -}else{ -if(_6>_7){ -return 1; -}else{ -if(_6<_7){ -return -1; -}else{ -return 0; -} -} -} -},onChange:function(_8){ -},_onChangeActive:false,_handleOnChange:function(_9,_a){ -this._lastValue=_9; -if(this._lastValueReported==undefined&&(_a===null||!this._onChangeActive)){ -this._resetValue=this._lastValueReported=_9; -} -if((this.intermediateChanges||_a||_a===undefined)&&((typeof _9!=typeof this._lastValueReported)||this.compare(_9,this._lastValueReported)!=0)){ -this._lastValueReported=_9; -if(this._onChangeActive){ -if(this._onChangeHandle){ -clearTimeout(this._onChangeHandle); -} -this._onChangeHandle=setTimeout(dojo.hitch(this,function(){ -this._onChangeHandle=null; -this.onChange(_9); -}),0); -} -} -},create:function(){ -this.inherited(arguments); -this._onChangeActive=true; -},destroy:function(){ -if(this._onChangeHandle){ -clearTimeout(this._onChangeHandle); -this.onChange(this._lastValueReported); -} -this.inherited(arguments); -},setValue:function(_b){ -dojo.deprecated("dijit.form._FormWidget:setValue("+_b+") is deprecated. Use set('value',"+_b+") instead.","","2.0"); -this.set("value",_b); -},getValue:function(){ -dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.","","2.0"); -return this.get("value"); -},_onMouseDown:function(e){ -if(!e.ctrlKey&&this.isFocusable()){ -var _c=this.connect(dojo.body(),"onmouseup",function(){ -if(this.isFocusable()){ -this.focus(); -} -this.disconnect(_c); + + +dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin], + { + // summary: + // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>, + // which can be children of a <form> node or a `dijit.form.Form` widget. + // + // description: + // Represents a single HTML element. + // All these widgets should have these attributes just like native HTML input elements. + // You can set them during widget construction or afterwards, via `dijit._Widget.attr`. + // + // They also share some common methods. + + // name: [const] String + // Name used when submitting form; same as "name" attribute or plain HTML elements + name: "", + + // alt: String + // Corresponds to the native HTML <input> element's attribute. + alt: "", + + // value: String + // Corresponds to the native HTML <input> element's attribute. + value: "", + + // type: String + // Corresponds to the native HTML <input> element's attribute. + type: "text", + + // tabIndex: Integer + // Order fields are traversed when user hits the tab key + tabIndex: "0", + + // disabled: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "disabled='disabled'", or just "disabled". + disabled: false, + + // intermediateChanges: Boolean + // Fires onChange for each value change or only on demand + intermediateChanges: false, + + // scrollOnFocus: Boolean + // On focus, should this widget scroll into view? + scrollOnFocus: true, + + // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are. + attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { + value: "focusNode", + id: "focusNode", + tabIndex: "focusNode", + alt: "focusNode", + title: "focusNode" + }), + + postMixInProperties: function(){ + // Setup name=foo string to be referenced from the template (but only if a name has been specified) + // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 + // Regarding escaping, see heading "Attribute values" in + // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 + this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, """) + '"') : ''; + this.inherited(arguments); + }, + + postCreate: function(){ + this.inherited(arguments); + this.connect(this.domNode, "onmousedown", "_onMouseDown"); + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + this._set("disabled", value); + dojo.attr(this.focusNode, 'disabled', value); + if(this.valueNode){ + dojo.attr(this.valueNode, 'disabled', value); + } + dijit.setWaiState(this.focusNode, "disabled", value); + + if(value){ + // reset these, because after the domNode is disabled, we can no longer receive + // mouse related events, see #4200 + this._set("hovering", false); + this._set("active", false); + + // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes) + var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode"; + dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){ + var node = this[attachPointName]; + // complex code because tabIndex=-1 on a <div> doesn't work on FF + if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug + node.setAttribute('tabIndex', "-1"); + }else{ + node.removeAttribute('tabIndex'); + } + }, this); + }else{ + if(this.tabIndex != ""){ + this.focusNode.setAttribute('tabIndex', this.tabIndex); + } + } + }, + + setDisabled: function(/*Boolean*/ disabled){ + // summary: + // Deprecated. Use set('disabled', ...) instead. + dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0"); + this.set('disabled', disabled); + }, + + _onFocus: function(e){ + if(this.scrollOnFocus){ + dojo.window.scrollIntoView(this.domNode); + } + this.inherited(arguments); + }, + + isFocusable: function(){ + // summary: + // Tells if this widget is focusable or not. Used internally by dijit. + // tags: + // protected + return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none"); + }, + + focus: function(){ + // summary: + // Put focus on this widget + if(!this.disabled){ + dijit.focus(this.focusNode); + } + }, + + compare: function(/*anything*/ val1, /*anything*/ val2){ + // summary: + // Compare 2 values (as returned by get('value') for this widget). + // tags: + // protected + if(typeof val1 == "number" && typeof val2 == "number"){ + return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2; + }else if(val1 > val2){ + return 1; + }else if(val1 < val2){ + return -1; + }else{ + return 0; + } + }, + + onChange: function(newValue){ + // summary: + // Callback when this widget's value is changed. + // tags: + // callback + }, + + // _onChangeActive: [private] Boolean + // Indicates that changes to the value should call onChange() callback. + // This is false during widget initialization, to avoid calling onChange() + // when the initial value is set. + _onChangeActive: false, + + _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // Called when the value of the widget is set. Calls onChange() if appropriate + // newValue: + // the new value + // priorityChange: + // For a slider, for example, dragging the slider is priorityChange==false, + // but on mouse up, it's priorityChange==true. If intermediateChanges==false, + // onChange is only called form priorityChange=true events. + // tags: + // private + if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){ + // this block executes not for a change, but during initialization, + // and is used to store away the original value (or for ToggleButton, the original checked state) + this._resetValue = this._lastValueReported = newValue; + } + this._pendingOnChange = this._pendingOnChange + || (typeof newValue != typeof this._lastValueReported) + || (this.compare(newValue, this._lastValueReported) != 0); + if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){ + this._lastValueReported = newValue; + this._pendingOnChange = false; + if(this._onChangeActive){ + if(this._onChangeHandle){ + clearTimeout(this._onChangeHandle); + } + // setTimout allows hidden value processing to run and + // also the onChange handler can safely adjust focus, etc + this._onChangeHandle = setTimeout(dojo.hitch(this, + function(){ + this._onChangeHandle = null; + this.onChange(newValue); + }), 0); // try to collapse multiple onChange's fired faster than can be processed + } + } + }, + + create: function(){ + // Overrides _Widget.create() + this.inherited(arguments); + this._onChangeActive = true; + }, + + destroy: function(){ + if(this._onChangeHandle){ // destroy called before last onChange has fired + clearTimeout(this._onChangeHandle); + this.onChange(this._lastValueReported); + } + this.inherited(arguments); + }, + + setValue: function(/*String*/ value){ + // summary: + // Deprecated. Use set('value', ...) instead. + dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0"); + this.set('value', value); + }, + + getValue: function(){ + // summary: + // Deprecated. Use get('value') instead. + dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0"); + return this.get('value'); + }, + + _onMouseDown: function(e){ + // If user clicks on the button, even if the mouse is released outside of it, + // this button should get focus (to mimics native browser buttons). + // This is also needed on chrome because otherwise buttons won't get focus at all, + // which leads to bizarre focus restore on Dialog close etc. + if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac + // Set a global event to handle mouseup, so it fires properly + // even if the cursor leaves this.domNode before the mouse up event. + var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){ + if (this.isFocusable()) { + this.focus(); + } + this.disconnect(mouseUpConnector); + }); + } + } }); -} -}}); -dojo.declare("dijit.form._FormValueWidget",dijit.form._FormWidget,{readOnly:false,attributeMap:dojo.delegate(dijit.form._FormWidget.prototype.attributeMap,{value:"",readOnly:"focusNode"}),_setReadOnlyAttr:function(_d){ -this.readOnly=_d; -dojo.attr(this.focusNode,"readOnly",_d); -dijit.setWaiState(this.focusNode,"readonly",_d); -},postCreate:function(){ -this.inherited(arguments); -if(dojo.isIE){ -this.connect(this.focusNode||this.domNode,"onkeydown",this._onKeyDown); -} -if(this._resetValue===undefined){ -this._resetValue=this.value; -} -},_setValueAttr:function(_e,_f){ -this.value=_e; -this._handleOnChange(_e,_f); -},_getValueAttr:function(){ -return this._lastValue; -},undo:function(){ -this._setValueAttr(this._lastValueReported,false); -},reset:function(){ -this._hasBeenBlurred=false; -this._setValueAttr(this._resetValue,true); -},_onKeyDown:function(e){ -if(e.keyCode==dojo.keys.ESCAPE&&!(e.ctrlKey||e.altKey||e.metaKey)){ -var te; -if(dojo.isIE){ -e.preventDefault(); -te=document.createEventObject(); -te.keyCode=dojo.keys.ESCAPE; -te.shiftKey=e.shiftKey; -e.srcElement.fireEvent("onkeypress",te); -} -} -},_layoutHackIE7:function(){ -if(dojo.isIE==7){ -var _10=this.domNode; -var _11=_10.parentNode; -var _12=_10.firstChild||_10; -var _13=_12.style.filter; -var _14=this; -while(_11&&_11.clientHeight==0){ -(function ping(){ -var _15=_14.connect(_11,"onscroll",function(e){ -_14.disconnect(_15); -_12.style.filter=(new Date()).getMilliseconds(); -setTimeout(function(){ -_12.style.filter=_13; -},0); + +dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget, +{ + // summary: + // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. + // description: + // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element, + // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?) + // works as expected. + + // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared + // directly in the template as read by the parser in order to function. IE is known to specifically + // require the 'name' attribute at element creation time. See #8484, #8660. + // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode, + // so maybe {value: ""} is so the value *doesn't* get copied to focusNode? + // Seems like we really want value removed from attributeMap altogether + // (although there's no easy way to do that now) + + // readOnly: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "readOnly". + // Similar to disabled except readOnly form values are submitted. + readOnly: false, + + attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { + value: "", + readOnly: "focusNode" + }), + + _setReadOnlyAttr: function(/*Boolean*/ value){ + dojo.attr(this.focusNode, 'readOnly', value); + dijit.setWaiState(this.focusNode, "readonly", value); + this._set("readOnly", value); + }, + + postCreate: function(){ + this.inherited(arguments); + + if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress + this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown); + } + // Update our reset value if it hasn't yet been set (because this.set() + // is only called when there *is* a value) + if(this._resetValue === undefined){ + this._lastValueReported = this._resetValue = this.value; + } + }, + + _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // Hook so set('value', value) works. + // description: + // Sets the value of the widget. + // If the value has changed, then fire onChange event, unless priorityChange + // is specified as null (or false?) + this._handleOnChange(newValue, priorityChange); + }, + + _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ + // summary: + // Called when the value of the widget has changed. Saves the new value in this.value, + // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details. + this._set("value", newValue); + this.inherited(arguments); + }, + + undo: function(){ + // summary: + // Restore the value to the last value passed to onChange + this._setValueAttr(this._lastValueReported, false); + }, + + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time + this._hasBeenBlurred = false; + this._setValueAttr(this._resetValue, true); + }, + + _onKeyDown: function(e){ + if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){ + var te; + if(dojo.isIE){ + e.preventDefault(); // default behavior needs to be stopped here since keypress is too late + te = document.createEventObject(); + te.keyCode = dojo.keys.ESCAPE; + te.shiftKey = e.shiftKey; + e.srcElement.fireEvent('onkeypress', te); + } + } + }, + + _layoutHackIE7: function(){ + // summary: + // Work around table sizing bugs on IE7 by forcing redraw + + if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight + var domNode = this.domNode; + var parent = domNode.parentNode; + var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter + var origFilter = pingNode.style.filter; // save custom filter, most likely nothing + var _this = this; + while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet + (function ping(){ + var disconnectHandle = _this.connect(parent, "onscroll", + function(e){ + _this.disconnect(disconnectHandle); // only call once + pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique + setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any + } + ); + })(); + parent = parent.parentNode; + } + } + } }); -})(); -_11=_11.parentNode; -} -} -}}); + } diff --git a/lib/dijit/form/_Spinner.js b/lib/dijit/form/_Spinner.js index 772f667dc..37750ffb7 100644 --- a/lib/dijit/form/_Spinner.js +++ b/lib/dijit/form/_Spinner.js @@ -1,57 +1,128 @@ /* - Copyright (c) 2004-2010, The Dojo Foundation All Rights Reserved. + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. Available via Academic Free License >= 2.1 OR the modified BSD license. see: http://dojotoolkit.org/license for details */ -if(!dojo._hasResource["dijit.form._Spinner"]){ -dojo._hasResource["dijit.form._Spinner"]=true; +if(!dojo._hasResource["dijit.form._Spinner"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dijit.form._Spinner"] = true; dojo.provide("dijit.form._Spinner"); dojo.require("dijit.form.ValidationTextBox"); -dojo.declare("dijit.form._Spinner",dijit.form.RangeBoundTextBox,{defaultTimeout:500,minimumTimeout:10,timeoutChangeRate:0.9,smallDelta:1,largeDelta:10,templateString:dojo.cache("dijit.form","templates/Spinner.html","<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" waiRole=\"presentation\"\n\t><div class=\"dijitReset dijitButtonNode dijitSpinnerButtonContainer\"\n\t\t><input class=\"dijitReset dijitInputField dijitSpinnerButtonInner\" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t/><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitUpArrowButton\"\n\t\t\tdojoAttachPoint=\"upArrowNode\"\n\t\t\t><div class=\"dijitArrowButtonInner\"\n\t\t\t\t><input class=\"dijitReset dijitInputField\" value=\"▲\" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t\t\t${_buttonInputDisabled}\n\t\t\t/></div\n\t\t></div\n\t\t><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\tdojoAttachPoint=\"downArrowNode\"\n\t\t\t><div class=\"dijitArrowButtonInner\"\n\t\t\t\t><input class=\"dijitReset dijitInputField\" value=\"▼\" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t\t\t\t\t${_buttonInputDisabled}\n\t\t\t/></div\n\t\t></div\n\t></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ \" type=\"text\" tabIndex=\"-1\" readOnly waiRole=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' dojoAttachPoint=\"textbox,focusNode\" type=\"${type}\" dojoAttachEvent=\"onkeypress:_onKeyPress\"\n\t\t\twaiRole=\"spinbutton\" autocomplete=\"off\" ${!nameAttrSetting}\n\t/></div\n></div>\n"),baseClass:"dijitTextBox dijitSpinner",cssStateNodes:{"upArrowNode":"dijitUpArrowButton","downArrowNode":"dijitDownArrowButton"},adjust:function(_1,_2){ -return _1; -},_arrowPressed:function(_3,_4,_5){ -if(this.disabled||this.readOnly){ -return; -} -this._setValueAttr(this.adjust(this.get("value"),_4*_5),false); -dijit.selectInputText(this.textbox,this.textbox.value.length); -},_arrowReleased:function(_6){ -this._wheelTimer=null; -if(this.disabled||this.readOnly){ -return; -} -},_typematicCallback:function(_7,_8,_9){ -var _a=this.smallDelta; -if(_8==this.textbox){ -var k=dojo.keys; -var _b=_9.charOrCode; -_a=(_b==k.PAGE_UP||_b==k.PAGE_DOWN)?this.largeDelta:this.smallDelta; -_8=(_b==k.UP_ARROW||_b==k.PAGE_UP)?this.upArrowNode:this.downArrowNode; -} -if(_7==-1){ -this._arrowReleased(_8); -}else{ -this._arrowPressed(_8,(_8==this.upArrowNode)?1:-1,_a); -} -},_wheelTimer:null,_mouseWheeled:function(_c){ -dojo.stopEvent(_c); -var _d=_c.detail?(_c.detail*-1):(_c.wheelDelta/120); -if(_d!==0){ -var _e=this[(_d>0?"upArrowNode":"downArrowNode")]; -this._arrowPressed(_e,_d,this.smallDelta); -if(!this._wheelTimer){ -clearTimeout(this._wheelTimer); -} -this._wheelTimer=setTimeout(dojo.hitch(this,"_arrowReleased",_e),50); -} -},postCreate:function(){ -this.inherited(arguments); -this.connect(this.domNode,!dojo.isMozilla?"onmousewheel":"DOMMouseScroll","_mouseWheeled"); -this._connects.push(dijit.typematic.addListener(this.upArrowNode,this.textbox,{charOrCode:dojo.keys.UP_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false},this,"_typematicCallback",this.timeoutChangeRate,this.defaultTimeout,this.minimumTimeout)); -this._connects.push(dijit.typematic.addListener(this.downArrowNode,this.textbox,{charOrCode:dojo.keys.DOWN_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false},this,"_typematicCallback",this.timeoutChangeRate,this.defaultTimeout,this.minimumTimeout)); -this._connects.push(dijit.typematic.addListener(this.upArrowNode,this.textbox,{charOrCode:dojo.keys.PAGE_UP,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false},this,"_typematicCallback",this.timeoutChangeRate,this.defaultTimeout,this.minimumTimeout)); -this._connects.push(dijit.typematic.addListener(this.downArrowNode,this.textbox,{charOrCode:dojo.keys.PAGE_DOWN,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false},this,"_typematicCallback",this.timeoutChangeRate,this.defaultTimeout,this.minimumTimeout)); -}}); + + +dojo.declare( + "dijit.form._Spinner", + dijit.form.RangeBoundTextBox, + { + // summary: + // Mixin for validation widgets with a spinner. + // description: + // This class basically (conceptually) extends `dijit.form.ValidationTextBox`. + // It modifies the template to have up/down arrows, and provides related handling code. + + // defaultTimeout: Number + // Number of milliseconds before a held arrow key or up/down button becomes typematic + defaultTimeout: 500, + + // minimumTimeout: Number + // minimum number of milliseconds that typematic event fires when held key or button is held + minimumTimeout: 10, + + // timeoutChangeRate: Number + // Fraction of time used to change the typematic timer between events. + // 1.0 means that each typematic event fires at defaultTimeout intervals. + // < 1.0 means that each typematic event fires at an increasing faster rate. + timeoutChangeRate: 0.90, + + // smallDelta: Number + // Adjust the value by this much when spinning using the arrow keys/buttons + smallDelta: 1, + + // largeDelta: Number + // Adjust the value by this much when spinning using the PgUp/Dn keys + largeDelta: 10, + + templateString: dojo.cache("dijit.form", "templates/Spinner.html", "<div class=\"dijit dijitReset dijitInlineTable dijitLeft\"\n\tid=\"widget_${id}\" role=\"presentation\"\n\t><div class=\"dijitReset dijitButtonNode dijitSpinnerButtonContainer\"\n\t\t><input class=\"dijitReset dijitInputField dijitSpinnerButtonInner\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t/><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitUpArrowButton\"\n\t\t\tdojoAttachPoint=\"upArrowNode\"\n\t\t\t><div class=\"dijitArrowButtonInner\"\n\t\t\t\t><input class=\"dijitReset dijitInputField\" value=\"▲\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t\t\t${_buttonInputDisabled}\n\t\t\t/></div\n\t\t></div\n\t\t><div class=\"dijitReset dijitLeft dijitButtonNode dijitArrowButton dijitDownArrowButton\"\n\t\t\tdojoAttachPoint=\"downArrowNode\"\n\t\t\t><div class=\"dijitArrowButtonInner\"\n\t\t\t\t><input class=\"dijitReset dijitInputField\" value=\"▼\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t\t\t\t\t${_buttonInputDisabled}\n\t\t\t/></div\n\t\t></div\n\t></div\n\t><div class='dijitReset dijitValidationContainer'\n\t\t><input class=\"dijitReset dijitInputField dijitValidationIcon dijitValidationInner\" value=\"Χ\" type=\"text\" tabIndex=\"-1\" readonly=\"readonly\" role=\"presentation\"\n\t/></div\n\t><div class=\"dijitReset dijitInputField dijitInputContainer\"\n\t\t><input class='dijitReset dijitInputInner' dojoAttachPoint=\"textbox,focusNode\" type=\"${type}\" dojoAttachEvent=\"onkeypress:_onKeyPress\"\n\t\t\trole=\"spinbutton\" autocomplete=\"off\" ${!nameAttrSetting}\n\t/></div\n></div>\n"), + + baseClass: "dijitTextBox dijitSpinner", + + // Set classes like dijitUpArrowButtonHover or dijitDownArrowButtonActive depending on + // mouse action over specified node + cssStateNodes: { + "upArrowNode": "dijitUpArrowButton", + "downArrowNode": "dijitDownArrowButton" + }, + + adjust: function(/*Object*/ val, /*Number*/ delta){ + // summary: + // Overridable function used to adjust a primitive value(Number/Date/...) by the delta amount specified. + // The val is adjusted in a way that makes sense to the object type. + // tags: + // protected extension + return val; + }, + + _arrowPressed: function(/*Node*/ nodePressed, /*Number*/ direction, /*Number*/ increment){ + // summary: + // Handler for arrow button or arrow key being pressed + if(this.disabled || this.readOnly){ return; } + this._setValueAttr(this.adjust(this.get('value'), direction*increment), false); + dijit.selectInputText(this.textbox, this.textbox.value.length); + }, + + _arrowReleased: function(/*Node*/ node){ + // summary: + // Handler for arrow button or arrow key being released + this._wheelTimer = null; + if(this.disabled || this.readOnly){ return; } + }, + + _typematicCallback: function(/*Number*/ count, /*DOMNode*/ node, /*Event*/ evt){ + var inc=this.smallDelta; + if(node == this.textbox){ + var k=dojo.keys; + var key = evt.charOrCode; + inc = (key == k.PAGE_UP || key == k.PAGE_DOWN) ? this.largeDelta : this.smallDelta; + node = (key == k.UP_ARROW || key == k.PAGE_UP) ? this.upArrowNode : this.downArrowNode; + } + if(count == -1){ this._arrowReleased(node); } + else{ this._arrowPressed(node, (node == this.upArrowNode) ? 1 : -1, inc); } + }, + + _wheelTimer: null, + _mouseWheeled: function(/*Event*/ evt){ + // summary: + // Mouse wheel listener where supported + + dojo.stopEvent(evt); + // FIXME: Safari bubbles + + // be nice to DOH and scroll as much as the event says to + var scrollAmount = evt.detail ? (evt.detail * -1) : (evt.wheelDelta / 120); + if(scrollAmount !== 0){ + var node = this[(scrollAmount > 0 ? "upArrowNode" : "downArrowNode" )]; + + this._arrowPressed(node, scrollAmount, this.smallDelta); + + if(!this._wheelTimer){ + clearTimeout(this._wheelTimer); + } + this._wheelTimer = setTimeout(dojo.hitch(this,"_arrowReleased",node), 50); + } + + }, + + postCreate: function(){ + this.inherited(arguments); + + // extra listeners + this.connect(this.domNode, !dojo.isMozilla ? "onmousewheel" : 'DOMMouseScroll', "_mouseWheeled"); + this._connects.push(dijit.typematic.addListener(this.upArrowNode, this.textbox, {charOrCode:dojo.keys.UP_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + this._connects.push(dijit.typematic.addListener(this.downArrowNode, this.textbox, {charOrCode:dojo.keys.DOWN_ARROW,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + this._connects.push(dijit.typematic.addListener(this.upArrowNode, this.textbox, {charOrCode:dojo.keys.PAGE_UP,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + this._connects.push(dijit.typematic.addListener(this.downArrowNode, this.textbox, {charOrCode:dojo.keys.PAGE_DOWN,ctrlKey:false,altKey:false,shiftKey:false,metaKey:false}, this, "_typematicCallback", this.timeoutChangeRate, this.defaultTimeout, this.minimumTimeout)); + } +}); + } diff --git a/lib/dijit/form/nls/da/validate.js b/lib/dijit/form/nls/da/validate.js index 2874dd890..b42a99d39 100644 --- a/lib/dijit/form/nls/da/validate.js +++ b/lib/dijit/form/nls/da/validate.js @@ -1 +1 @@ -({"rangeMessage":"Værdien er uden for intervallet.","invalidMessage":"Den angivne værdi er ugyldig.","missingMessage":"Værdien er påkrævet."})
\ No newline at end of file +({"rangeMessage":"Værdien er uden for intervallet.","invalidMessage":"Den angivne værdi er ikke gyldig.","missingMessage":"Værdien er påkrævet."})
\ No newline at end of file diff --git a/lib/dijit/form/nls/kk/ComboBox.js b/lib/dijit/form/nls/kk/ComboBox.js new file mode 100644 index 000000000..edb918a9c --- /dev/null +++ b/lib/dijit/form/nls/kk/ComboBox.js @@ -0,0 +1 @@ +({"previousMessage":"Алдыңғы нұсқалар","nextMessage":"Басқа нұсқалар"})
\ No newline at end of file diff --git a/lib/dijit/form/nls/kk/Textarea.js b/lib/dijit/form/nls/kk/Textarea.js new file mode 100644 index 000000000..617fcb91e --- /dev/null +++ b/lib/dijit/form/nls/kk/Textarea.js @@ -0,0 +1 @@ +({"iframeEditTitle":"өңдеу аумағы","iframeFocusTitle":"өңдеу аумағының жақтауы"})
\ No newline at end of file diff --git a/lib/dijit/form/nls/kk/validate.js b/lib/dijit/form/nls/kk/validate.js new file mode 100644 index 000000000..d67612119 --- /dev/null +++ b/lib/dijit/form/nls/kk/validate.js @@ -0,0 +1 @@ +({"rangeMessage":"Бұл мән ауқымнан тыс.","invalidMessage":"Енгізілген мән жарамды емес.","missingMessage":"Бұл мән міндетті."})
\ No newline at end of file diff --git a/lib/dijit/form/nls/pl/Textarea.js b/lib/dijit/form/nls/pl/Textarea.js index d918f59e0..33b050a1d 100644 --- a/lib/dijit/form/nls/pl/Textarea.js +++ b/lib/dijit/form/nls/pl/Textarea.js @@ -1 +1 @@ -({"iframeEditTitle":"Obszar edycji","iframeFocusTitle":"Ramka obszaru edycji"})
\ No newline at end of file +({"iframeEditTitle":"edycja obszaru","iframeFocusTitle":"edycja ramki obszaru"})
\ No newline at end of file diff --git a/lib/dijit/form/nls/sl/ComboBox.js b/lib/dijit/form/nls/sl/ComboBox.js index e9556880c..61d4469c6 100644 --- a/lib/dijit/form/nls/sl/ComboBox.js +++ b/lib/dijit/form/nls/sl/ComboBox.js @@ -1 +1 @@ -({"previousMessage":"Prejšnje možnosti","nextMessage":"Dodatne možnosti"})
\ No newline at end of file +({"previousMessage":"Prejšnje izbire","nextMessage":"Dodatne izbire"})
\ No newline at end of file diff --git a/lib/dijit/form/nls/sl/Textarea.js b/lib/dijit/form/nls/sl/Textarea.js index 912ee3962..0e0d51175 100644 --- a/lib/dijit/form/nls/sl/Textarea.js +++ b/lib/dijit/form/nls/sl/Textarea.js @@ -1 +1 @@ -({"iframeEditTitle":"urejanje področja","iframeFocusTitle":"urejanje področja okvirja"})
\ No newline at end of file +({"iframeEditTitle":"urejevalno področje","iframeFocusTitle":"okvir urejevalnega področja"})
\ No newline at end of file diff --git a/lib/dijit/form/nls/sl/validate.js b/lib/dijit/form/nls/sl/validate.js index 763572486..b84b35350 100644 --- a/lib/dijit/form/nls/sl/validate.js +++ b/lib/dijit/form/nls/sl/validate.js @@ -1 +1 @@ -({"rangeMessage":"Ta vrednost je zunaj obsega. ","invalidMessage":"Vnesena vrednost ni veljavna.","missingMessage":"Ta vrednost je zahtevana."})
\ No newline at end of file +({"rangeMessage":"Ta vrednost je izven območja.","invalidMessage":"Vnesena vrednost ni veljavna.","missingMessage":"Ta vrednost je zahtevana."})
\ No newline at end of file |