]>
Commit | Line | Data |
---|---|---|
2f01fe57 | 1 | /* |
81bea17a | 2 | Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. |
2f01fe57 AD |
3 | Available via Academic Free License >= 2.1 OR the modified BSD license. |
4 | see: http://dojotoolkit.org/license for details | |
5 | */ | |
6 | ||
7 | ||
81bea17a AD |
8 | if(!dojo._hasResource["dijit.form._FormWidget"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
9 | dojo._hasResource["dijit.form._FormWidget"] = true; | |
2f01fe57 AD |
10 | dojo.provide("dijit.form._FormWidget"); |
11 | dojo.require("dojo.window"); | |
12 | dojo.require("dijit._Widget"); | |
13 | dojo.require("dijit._Templated"); | |
14 | dojo.require("dijit._CssStateMixin"); | |
81bea17a AD |
15 | |
16 | ||
17 | dojo.declare("dijit.form._FormWidget", [dijit._Widget, dijit._Templated, dijit._CssStateMixin], | |
18 | { | |
19 | // summary: | |
20 | // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>, | |
21 | // which can be children of a <form> node or a `dijit.form.Form` widget. | |
22 | // | |
23 | // description: | |
24 | // Represents a single HTML element. | |
25 | // All these widgets should have these attributes just like native HTML input elements. | |
26 | // You can set them during widget construction or afterwards, via `dijit._Widget.attr`. | |
27 | // | |
28 | // They also share some common methods. | |
29 | ||
30 | // name: [const] String | |
31 | // Name used when submitting form; same as "name" attribute or plain HTML elements | |
32 | name: "", | |
33 | ||
34 | // alt: String | |
35 | // Corresponds to the native HTML <input> element's attribute. | |
36 | alt: "", | |
37 | ||
38 | // value: String | |
39 | // Corresponds to the native HTML <input> element's attribute. | |
40 | value: "", | |
41 | ||
42 | // type: String | |
43 | // Corresponds to the native HTML <input> element's attribute. | |
44 | type: "text", | |
45 | ||
46 | // tabIndex: Integer | |
47 | // Order fields are traversed when user hits the tab key | |
48 | tabIndex: "0", | |
49 | ||
50 | // disabled: Boolean | |
51 | // Should this widget respond to user input? | |
52 | // In markup, this is specified as "disabled='disabled'", or just "disabled". | |
53 | disabled: false, | |
54 | ||
55 | // intermediateChanges: Boolean | |
56 | // Fires onChange for each value change or only on demand | |
57 | intermediateChanges: false, | |
58 | ||
59 | // scrollOnFocus: Boolean | |
60 | // On focus, should this widget scroll into view? | |
61 | scrollOnFocus: true, | |
62 | ||
63 | // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are. | |
64 | attributeMap: dojo.delegate(dijit._Widget.prototype.attributeMap, { | |
65 | value: "focusNode", | |
66 | id: "focusNode", | |
67 | tabIndex: "focusNode", | |
68 | alt: "focusNode", | |
69 | title: "focusNode" | |
70 | }), | |
71 | ||
72 | postMixInProperties: function(){ | |
73 | // Setup name=foo string to be referenced from the template (but only if a name has been specified) | |
74 | // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 | |
75 | // Regarding escaping, see heading "Attribute values" in | |
76 | // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 | |
77 | this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, """) + '"') : ''; | |
78 | this.inherited(arguments); | |
79 | }, | |
80 | ||
81 | postCreate: function(){ | |
82 | this.inherited(arguments); | |
83 | this.connect(this.domNode, "onmousedown", "_onMouseDown"); | |
84 | }, | |
85 | ||
86 | _setDisabledAttr: function(/*Boolean*/ value){ | |
87 | this._set("disabled", value); | |
88 | dojo.attr(this.focusNode, 'disabled', value); | |
89 | if(this.valueNode){ | |
90 | dojo.attr(this.valueNode, 'disabled', value); | |
91 | } | |
92 | dijit.setWaiState(this.focusNode, "disabled", value); | |
93 | ||
94 | if(value){ | |
95 | // reset these, because after the domNode is disabled, we can no longer receive | |
96 | // mouse related events, see #4200 | |
97 | this._set("hovering", false); | |
98 | this._set("active", false); | |
99 | ||
100 | // clear tab stop(s) on this widget's focusable node(s) (ComboBox has two focusable nodes) | |
101 | var attachPointNames = "tabIndex" in this.attributeMap ? this.attributeMap.tabIndex : "focusNode"; | |
102 | dojo.forEach(dojo.isArray(attachPointNames) ? attachPointNames : [attachPointNames], function(attachPointName){ | |
103 | var node = this[attachPointName]; | |
104 | // complex code because tabIndex=-1 on a <div> doesn't work on FF | |
105 | if(dojo.isWebKit || dijit.hasDefaultTabStop(node)){ // see #11064 about webkit bug | |
106 | node.setAttribute('tabIndex', "-1"); | |
107 | }else{ | |
108 | node.removeAttribute('tabIndex'); | |
109 | } | |
110 | }, this); | |
111 | }else{ | |
112 | if(this.tabIndex != ""){ | |
113 | this.focusNode.setAttribute('tabIndex', this.tabIndex); | |
114 | } | |
115 | } | |
116 | }, | |
117 | ||
118 | setDisabled: function(/*Boolean*/ disabled){ | |
119 | // summary: | |
120 | // Deprecated. Use set('disabled', ...) instead. | |
121 | dojo.deprecated("setDisabled("+disabled+") is deprecated. Use set('disabled',"+disabled+") instead.", "", "2.0"); | |
122 | this.set('disabled', disabled); | |
123 | }, | |
124 | ||
125 | _onFocus: function(e){ | |
126 | if(this.scrollOnFocus){ | |
127 | dojo.window.scrollIntoView(this.domNode); | |
128 | } | |
129 | this.inherited(arguments); | |
130 | }, | |
131 | ||
132 | isFocusable: function(){ | |
133 | // summary: | |
134 | // Tells if this widget is focusable or not. Used internally by dijit. | |
135 | // tags: | |
136 | // protected | |
137 | return !this.disabled && this.focusNode && (dojo.style(this.domNode, "display") != "none"); | |
138 | }, | |
139 | ||
140 | focus: function(){ | |
141 | // summary: | |
142 | // Put focus on this widget | |
143 | if(!this.disabled){ | |
144 | dijit.focus(this.focusNode); | |
145 | } | |
146 | }, | |
147 | ||
148 | compare: function(/*anything*/ val1, /*anything*/ val2){ | |
149 | // summary: | |
150 | // Compare 2 values (as returned by get('value') for this widget). | |
151 | // tags: | |
152 | // protected | |
153 | if(typeof val1 == "number" && typeof val2 == "number"){ | |
154 | return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2; | |
155 | }else if(val1 > val2){ | |
156 | return 1; | |
157 | }else if(val1 < val2){ | |
158 | return -1; | |
159 | }else{ | |
160 | return 0; | |
161 | } | |
162 | }, | |
163 | ||
164 | onChange: function(newValue){ | |
165 | // summary: | |
166 | // Callback when this widget's value is changed. | |
167 | // tags: | |
168 | // callback | |
169 | }, | |
170 | ||
171 | // _onChangeActive: [private] Boolean | |
172 | // Indicates that changes to the value should call onChange() callback. | |
173 | // This is false during widget initialization, to avoid calling onChange() | |
174 | // when the initial value is set. | |
175 | _onChangeActive: false, | |
176 | ||
177 | _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ | |
178 | // summary: | |
179 | // Called when the value of the widget is set. Calls onChange() if appropriate | |
180 | // newValue: | |
181 | // the new value | |
182 | // priorityChange: | |
183 | // For a slider, for example, dragging the slider is priorityChange==false, | |
184 | // but on mouse up, it's priorityChange==true. If intermediateChanges==false, | |
185 | // onChange is only called form priorityChange=true events. | |
186 | // tags: | |
187 | // private | |
188 | if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){ | |
189 | // this block executes not for a change, but during initialization, | |
190 | // and is used to store away the original value (or for ToggleButton, the original checked state) | |
191 | this._resetValue = this._lastValueReported = newValue; | |
192 | } | |
193 | this._pendingOnChange = this._pendingOnChange | |
194 | || (typeof newValue != typeof this._lastValueReported) | |
195 | || (this.compare(newValue, this._lastValueReported) != 0); | |
196 | if((this.intermediateChanges || priorityChange || priorityChange === undefined) && this._pendingOnChange){ | |
197 | this._lastValueReported = newValue; | |
198 | this._pendingOnChange = false; | |
199 | if(this._onChangeActive){ | |
200 | if(this._onChangeHandle){ | |
201 | clearTimeout(this._onChangeHandle); | |
202 | } | |
203 | // setTimout allows hidden value processing to run and | |
204 | // also the onChange handler can safely adjust focus, etc | |
205 | this._onChangeHandle = setTimeout(dojo.hitch(this, | |
206 | function(){ | |
207 | this._onChangeHandle = null; | |
208 | this.onChange(newValue); | |
209 | }), 0); // try to collapse multiple onChange's fired faster than can be processed | |
210 | } | |
211 | } | |
212 | }, | |
213 | ||
214 | create: function(){ | |
215 | // Overrides _Widget.create() | |
216 | this.inherited(arguments); | |
217 | this._onChangeActive = true; | |
218 | }, | |
219 | ||
220 | destroy: function(){ | |
221 | if(this._onChangeHandle){ // destroy called before last onChange has fired | |
222 | clearTimeout(this._onChangeHandle); | |
223 | this.onChange(this._lastValueReported); | |
224 | } | |
225 | this.inherited(arguments); | |
226 | }, | |
227 | ||
228 | setValue: function(/*String*/ value){ | |
229 | // summary: | |
230 | // Deprecated. Use set('value', ...) instead. | |
231 | dojo.deprecated("dijit.form._FormWidget:setValue("+value+") is deprecated. Use set('value',"+value+") instead.", "", "2.0"); | |
232 | this.set('value', value); | |
233 | }, | |
234 | ||
235 | getValue: function(){ | |
236 | // summary: | |
237 | // Deprecated. Use get('value') instead. | |
238 | dojo.deprecated(this.declaredClass+"::getValue() is deprecated. Use get('value') instead.", "", "2.0"); | |
239 | return this.get('value'); | |
240 | }, | |
241 | ||
242 | _onMouseDown: function(e){ | |
243 | // If user clicks on the button, even if the mouse is released outside of it, | |
244 | // this button should get focus (to mimics native browser buttons). | |
245 | // This is also needed on chrome because otherwise buttons won't get focus at all, | |
246 | // which leads to bizarre focus restore on Dialog close etc. | |
247 | if(!e.ctrlKey && dojo.mouseButtons.isLeft(e) && this.isFocusable()){ // !e.ctrlKey to ignore right-click on mac | |
248 | // Set a global event to handle mouseup, so it fires properly | |
249 | // even if the cursor leaves this.domNode before the mouse up event. | |
250 | var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){ | |
251 | if (this.isFocusable()) { | |
252 | this.focus(); | |
253 | } | |
254 | this.disconnect(mouseUpConnector); | |
255 | }); | |
256 | } | |
257 | } | |
2f01fe57 | 258 | }); |
81bea17a AD |
259 | |
260 | dojo.declare("dijit.form._FormValueWidget", dijit.form._FormWidget, | |
261 | { | |
262 | // summary: | |
263 | // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. | |
264 | // description: | |
265 | // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element, | |
266 | // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?) | |
267 | // works as expected. | |
268 | ||
269 | // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared | |
270 | // directly in the template as read by the parser in order to function. IE is known to specifically | |
271 | // require the 'name' attribute at element creation time. See #8484, #8660. | |
272 | // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode, | |
273 | // so maybe {value: ""} is so the value *doesn't* get copied to focusNode? | |
274 | // Seems like we really want value removed from attributeMap altogether | |
275 | // (although there's no easy way to do that now) | |
276 | ||
277 | // readOnly: Boolean | |
278 | // Should this widget respond to user input? | |
279 | // In markup, this is specified as "readOnly". | |
280 | // Similar to disabled except readOnly form values are submitted. | |
281 | readOnly: false, | |
282 | ||
283 | attributeMap: dojo.delegate(dijit.form._FormWidget.prototype.attributeMap, { | |
284 | value: "", | |
285 | readOnly: "focusNode" | |
286 | }), | |
287 | ||
288 | _setReadOnlyAttr: function(/*Boolean*/ value){ | |
289 | dojo.attr(this.focusNode, 'readOnly', value); | |
290 | dijit.setWaiState(this.focusNode, "readonly", value); | |
291 | this._set("readOnly", value); | |
292 | }, | |
293 | ||
294 | postCreate: function(){ | |
295 | this.inherited(arguments); | |
296 | ||
297 | if(dojo.isIE < 9 || (dojo.isIE && dojo.isQuirks)){ // IE won't stop the event with keypress | |
298 | this.connect(this.focusNode || this.domNode, "onkeydown", this._onKeyDown); | |
299 | } | |
300 | // Update our reset value if it hasn't yet been set (because this.set() | |
301 | // is only called when there *is* a value) | |
302 | if(this._resetValue === undefined){ | |
303 | this._lastValueReported = this._resetValue = this.value; | |
304 | } | |
305 | }, | |
306 | ||
307 | _setValueAttr: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ | |
308 | // summary: | |
309 | // Hook so set('value', value) works. | |
310 | // description: | |
311 | // Sets the value of the widget. | |
312 | // If the value has changed, then fire onChange event, unless priorityChange | |
313 | // is specified as null (or false?) | |
314 | this._handleOnChange(newValue, priorityChange); | |
315 | }, | |
316 | ||
317 | _handleOnChange: function(/*anything*/ newValue, /*Boolean?*/ priorityChange){ | |
318 | // summary: | |
319 | // Called when the value of the widget has changed. Saves the new value in this.value, | |
320 | // and calls onChange() if appropriate. See _FormWidget._handleOnChange() for details. | |
321 | this._set("value", newValue); | |
322 | this.inherited(arguments); | |
323 | }, | |
324 | ||
325 | undo: function(){ | |
326 | // summary: | |
327 | // Restore the value to the last value passed to onChange | |
328 | this._setValueAttr(this._lastValueReported, false); | |
329 | }, | |
330 | ||
331 | reset: function(){ | |
332 | // summary: | |
333 | // Reset the widget's value to what it was at initialization time | |
334 | this._hasBeenBlurred = false; | |
335 | this._setValueAttr(this._resetValue, true); | |
336 | }, | |
337 | ||
338 | _onKeyDown: function(e){ | |
339 | if(e.keyCode == dojo.keys.ESCAPE && !(e.ctrlKey || e.altKey || e.metaKey)){ | |
340 | var te; | |
341 | if(dojo.isIE){ | |
342 | e.preventDefault(); // default behavior needs to be stopped here since keypress is too late | |
343 | te = document.createEventObject(); | |
344 | te.keyCode = dojo.keys.ESCAPE; | |
345 | te.shiftKey = e.shiftKey; | |
346 | e.srcElement.fireEvent('onkeypress', te); | |
347 | } | |
348 | } | |
349 | }, | |
350 | ||
351 | _layoutHackIE7: function(){ | |
352 | // summary: | |
353 | // Work around table sizing bugs on IE7 by forcing redraw | |
354 | ||
355 | if(dojo.isIE == 7){ // fix IE7 layout bug when the widget is scrolled out of sight | |
356 | var domNode = this.domNode; | |
357 | var parent = domNode.parentNode; | |
358 | var pingNode = domNode.firstChild || domNode; // target node most unlikely to have a custom filter | |
359 | var origFilter = pingNode.style.filter; // save custom filter, most likely nothing | |
360 | var _this = this; | |
361 | while(parent && parent.clientHeight == 0){ // search for parents that haven't rendered yet | |
362 | (function ping(){ | |
363 | var disconnectHandle = _this.connect(parent, "onscroll", | |
364 | function(e){ | |
365 | _this.disconnect(disconnectHandle); // only call once | |
366 | pingNode.style.filter = (new Date()).getMilliseconds(); // set to anything that's unique | |
367 | setTimeout(function(){ pingNode.style.filter = origFilter }, 0); // restore custom filter, if any | |
368 | } | |
369 | ); | |
370 | })(); | |
371 | parent = parent.parentNode; | |
372 | } | |
373 | } | |
374 | } | |
2f01fe57 | 375 | }); |
81bea17a | 376 | |
2f01fe57 | 377 | } |