define("dijit/form/_FormWidgetMixin", [ "dojo/_base/array", // array.forEach "dojo/_base/declare", // declare "dojo/dom-attr", // domAttr.set "dojo/dom-style", // domStyle.get "dojo/_base/lang", // lang.hitch lang.isArray "dojo/mouse", // mouse.isLeft "dojo/sniff", // has("webkit") "dojo/window", // winUtils.scrollIntoView "../a11y" // a11y.hasDefaultTabStop ], function(array, declare, domAttr, domStyle, lang, mouse, has, winUtils, a11y){ // module: // dijit/form/_FormWidgetMixin return declare("dijit.form._FormWidgetMixin", null, { // summary: // Mixin 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/_WidgetBase.set()`. // // 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: [const] String // Corresponds to the native HTML `<input>` element's attribute. type: "text", // type: String // Apply aria-label in markup to the widget's focusNode "aria-label": "focusNode", // tabIndex: String // Order fields are traversed when user hits the tab key tabIndex: "0", _setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span> // 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, // Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc. // works with screen reader _setIdAttr: "focusNode", _setDisabledAttr: function(/*Boolean*/ value){ this._set("disabled", value); domAttr.set(this.focusNode, 'disabled', value); if(this.valueNode){ domAttr.set(this.valueNode, 'disabled', value); } this.focusNode.setAttribute("aria-disabled", value ? "true" : "false"); 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 : ("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode"; array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){ var node = this[attachPointName]; // complex code because tabIndex=-1 on a <div> doesn't work on FF if(has("webkit") || a11y.hasDefaultTabStop(node)){ // see #11064 about webkit bug node.setAttribute('tabIndex', "-1"); }else{ node.removeAttribute('tabIndex'); } }, this); }else{ if(this.tabIndex != ""){ this.set('tabIndex', this.tabIndex); } } }, _onFocus: function(/*String*/ by){ // If user clicks on the widget, even if the mouse is released outside of it, // this widget's focusNode should get focus (to mimic native browser hehavior). // Browsers often need help to make sure the focus via mouse actually gets to the focusNode. if(by == "mouse" && this.isFocusable()){ // IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused. var focusConnector = this.connect(this.focusNode, "onfocus", function(){ this.disconnect(mouseUpConnector); this.disconnect(focusConnector); }); // 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(this.ownerDocumentBody, "onmouseup", function(){ this.disconnect(mouseUpConnector); this.disconnect(focusConnector); // if here, then the mousedown did not focus the focusNode as the default action if(this.focused){ this.focus(); } }); } if(this.scrollOnFocus){ this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click } 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 && (domStyle.get(this.domNode, "display") != "none"); }, focus: function(){ // summary: // Put focus on this widget if(!this.disabled && this.focusNode.focus){ try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/ } }, 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){ this._onChangeHandle.remove(); } // defer allows hidden value processing to run and // also the onChange handler can safely adjust focus, etc this._onChangeHandle = this.defer( function(){ this._onChangeHandle = null; this.onChange(newValue); }); // 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 this._onChangeHandle.remove(); this.onChange(this._lastValueReported); } this.inherited(arguments); } }); });