]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dijit/form/_FormWidgetMixin", [ |
2 | "dojo/_base/array", // array.forEach | |
3 | "dojo/_base/declare", // declare | |
4 | "dojo/dom-attr", // domAttr.set | |
5 | "dojo/dom-style", // domStyle.get | |
6 | "dojo/_base/lang", // lang.hitch lang.isArray | |
7 | "dojo/mouse", // mouse.isLeft | |
8 | "dojo/sniff", // has("webkit") | |
9 | "dojo/window", // winUtils.scrollIntoView | |
10 | "../a11y" // a11y.hasDefaultTabStop | |
11 | ], function(array, declare, domAttr, domStyle, lang, mouse, has, winUtils, a11y){ | |
12 | ||
13 | // module: | |
14 | // dijit/form/_FormWidgetMixin | |
15 | ||
16 | return declare("dijit.form._FormWidgetMixin", null, { | |
17 | // summary: | |
18 | // Mixin for widgets corresponding to native HTML elements such as `<checkbox>` or `<button>`, | |
19 | // which can be children of a `<form>` node or a `dijit/form/Form` widget. | |
20 | // | |
21 | // description: | |
22 | // Represents a single HTML element. | |
23 | // All these widgets should have these attributes just like native HTML input elements. | |
24 | // You can set them during widget construction or afterwards, via `dijit/_WidgetBase.set()`. | |
25 | // | |
26 | // They also share some common methods. | |
27 | ||
28 | // name: [const] String | |
29 | // Name used when submitting form; same as "name" attribute or plain HTML elements | |
30 | name: "", | |
31 | ||
32 | // alt: String | |
33 | // Corresponds to the native HTML `<input>` element's attribute. | |
34 | alt: "", | |
35 | ||
36 | // value: String | |
37 | // Corresponds to the native HTML `<input>` element's attribute. | |
38 | value: "", | |
39 | ||
40 | // type: [const] String | |
41 | // Corresponds to the native HTML `<input>` element's attribute. | |
42 | type: "text", | |
43 | ||
44 | // type: String | |
45 | // Apply aria-label in markup to the widget's focusNode | |
46 | "aria-label": "focusNode", | |
47 | ||
48 | // tabIndex: String | |
49 | // Order fields are traversed when user hits the tab key | |
50 | tabIndex: "0", | |
51 | _setTabIndexAttr: "focusNode", // force copy even when tabIndex default value, needed since Button is <span> | |
52 | ||
53 | // disabled: Boolean | |
54 | // Should this widget respond to user input? | |
55 | // In markup, this is specified as "disabled='disabled'", or just "disabled". | |
56 | disabled: false, | |
57 | ||
58 | // intermediateChanges: Boolean | |
59 | // Fires onChange for each value change or only on demand | |
60 | intermediateChanges: false, | |
61 | ||
62 | // scrollOnFocus: Boolean | |
63 | // On focus, should this widget scroll into view? | |
64 | scrollOnFocus: true, | |
65 | ||
66 | // Override _WidgetBase mapping id to this.domNode, needs to be on focusNode so <label> etc. | |
67 | // works with screen reader | |
68 | _setIdAttr: "focusNode", | |
69 | ||
70 | _setDisabledAttr: function(/*Boolean*/ value){ | |
71 | this._set("disabled", value); | |
72 | domAttr.set(this.focusNode, 'disabled', value); | |
73 | if(this.valueNode){ | |
74 | domAttr.set(this.valueNode, 'disabled', value); | |
75 | } | |
76 | this.focusNode.setAttribute("aria-disabled", value ? "true" : "false"); | |
77 | ||
78 | if(value){ | |
79 | // reset these, because after the domNode is disabled, we can no longer receive | |
80 | // mouse related events, see #4200 | |
81 | this._set("hovering", false); | |
82 | this._set("active", false); | |
83 | ||
84 | // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes) | |
85 | var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : | |
86 | ("_setTabIndexAttr" in this) ? this._setTabIndexAttr : "focusNode"; | |
87 | array.forEach(lang.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){ | |
88 | var node = this[attachPointName]; | |
89 | // complex code because tabIndex=-1 on a <div> doesn't work on FF | |
90 | if(has("webkit") || a11y.hasDefaultTabStop(node)){ // see #11064 about webkit bug | |
91 | node.setAttribute('tabIndex', "-1"); | |
92 | }else{ | |
93 | node.removeAttribute('tabIndex'); | |
94 | } | |
95 | }, this); | |
96 | }else{ | |
97 | if(this.tabIndex != ""){ | |
98 | this.set('tabIndex', this.tabIndex); | |
99 | } | |
100 | } | |
101 | }, | |
102 | ||
103 | _onFocus: function(/*String*/ by){ | |
104 | // If user clicks on the widget, even if the mouse is released outside of it, | |
105 | // this widget's focusNode should get focus (to mimic native browser hehavior). | |
106 | // Browsers often need help to make sure the focus via mouse actually gets to the focusNode. | |
107 | if(by == "mouse" && this.isFocusable()){ | |
108 | // IE exhibits strange scrolling behavior when refocusing a node so only do it when !focused. | |
109 | var focusConnector = this.connect(this.focusNode, "onfocus", function(){ | |
110 | this.disconnect(mouseUpConnector); | |
111 | this.disconnect(focusConnector); | |
112 | }); | |
113 | // Set a global event to handle mouseup, so it fires properly | |
114 | // even if the cursor leaves this.domNode before the mouse up event. | |
115 | var mouseUpConnector = this.connect(this.ownerDocumentBody, "onmouseup", function(){ | |
116 | this.disconnect(mouseUpConnector); | |
117 | this.disconnect(focusConnector); | |
118 | // if here, then the mousedown did not focus the focusNode as the default action | |
119 | if(this.focused){ | |
120 | this.focus(); | |
121 | } | |
122 | }); | |
123 | } | |
124 | if(this.scrollOnFocus){ | |
125 | this.defer(function(){ winUtils.scrollIntoView(this.domNode); }); // without defer, the input caret position can change on mouse click | |
126 | } | |
127 | this.inherited(arguments); | |
128 | }, | |
129 | ||
130 | isFocusable: function(){ | |
131 | // summary: | |
132 | // Tells if this widget is focusable or not. Used internally by dijit. | |
133 | // tags: | |
134 | // protected | |
135 | return !this.disabled && this.focusNode && (domStyle.get(this.domNode, "display") != "none"); | |
136 | }, | |
137 | ||
138 | focus: function(){ | |
139 | // summary: | |
140 | // Put focus on this widget | |
141 | if(!this.disabled && this.focusNode.focus){ | |
142 | try{ this.focusNode.focus(); }catch(e){}/*squelch errors from hidden nodes*/ | |
143 | } | |
144 | }, | |
145 | ||
146 | compare: function(/*anything*/ val1, /*anything*/ val2){ | |
147 | // summary: | |
148 | // Compare 2 values (as returned by get('value') for this widget). | |
149 | // tags: | |
150 | // protected | |
151 | if(typeof val1 == "number" && typeof val2 == "number"){ | |
152 | return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2; | |
153 | }else if(val1 > val2){ | |
154 | return 1; | |
155 | }else if(val1 < val2){ | |
156 | return -1; | |
157 | }else{ | |
158 | return 0; | |
159 | } | |
160 | }, | |
161 | ||
162 | onChange: function(/*===== newValue =====*/){ | |
163 | // summary: | |
164 | // Callback when this widget's value is changed. | |
165 | // tags: | |
166 | // callback | |
167 | }, | |
168 | ||
169 | // _onChangeActive: [private] Boolean | |
170 | // Indicates that changes to the value should call onChange() callback. | |
171 | // This is false during widget initialization, to avoid calling onChange() | |
172 | // when the initial value is set. | |
173 | _onChangeActive: false, | |
174 | ||
175 | _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ | |
176 | // summary: | |
177 | // Called when the value of the widget is set. Calls onChange() if appropriate | |
178 | // newValue: | |
179 | // the new value | |
180 | // priorityChange: | |
181 | // For a slider, for example, dragging the slider is priorityChange==false, | |
182 | // but on mouse up, it's priorityChange==true. If intermediateChanges==false, | |
183 | // onChange is only called form priorityChange=true events. | |
184 | // tags: | |
185 | // private | |
186 | if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){ | |
187 | // this block executes not for a change, but during initialization, | |
188 | // and is used to store away the original value (or for ToggleButton, the original checked state) | |
189 | this._resetValue = this._lastValueReported = newValue; | |
190 | } | |
191 | this._pendingOnChange = this._pendingOnChange | |
192 | || (typeof newValue != typeof this._lastValueReported) | |
193 | || (this.compare(newValue, this._lastValueReported) != 0); | |
194 | if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){ | |
195 | this._lastValueReported = newValue; | |
196 | this._pendingOnChange = false; | |
197 | if(this._onChangeActive){ | |
198 | if(this._onChangeHandle){ | |
199 | this._onChangeHandle.remove(); | |
200 | } | |
201 | // defer allows hidden value processing to run and | |
202 | // also the onChange handler can safely adjust focus, etc | |
203 | this._onChangeHandle = this.defer( | |
204 | function(){ | |
205 | this._onChangeHandle = null; | |
206 | this.onChange(newValue); | |
207 | }); // try to collapse multiple onChange's fired faster than can be processed | |
208 | } | |
209 | } | |
210 | }, | |
211 | ||
212 | create: function(){ | |
213 | // Overrides _Widget.create() | |
214 | this.inherited(arguments); | |
215 | this._onChangeActive = true; | |
216 | }, | |
217 | ||
218 | destroy: function(){ | |
219 | if(this._onChangeHandle){ // destroy called before last onChange has fired | |
220 | this._onChangeHandle.remove(); | |
221 | this.onChange(this._lastValueReported); | |
222 | } | |
223 | this.inherited(arguments); | |
224 | } | |
225 | }); | |
226 | ||
227 | }); |