]>
git.wh0rd.org - tt-rss.git/blob - lib/dijit/form/_FormMixin.js.uncompressed.js
1 define("dijit/form/_FormMixin", [
2 "dojo/_base/array", // array.every array.filter array.forEach array.indexOf array.map
3 "dojo/_base/declare", // declare
4 "dojo/_base/kernel", // kernel.deprecated
5 "dojo/_base/lang", // lang.hitch lang.isArray
7 "dojo/window" // winUtils.scrollIntoView
8 ], function(array
, declare
, kernel
, lang
, on
, winUtils
){
11 // dijit/form/_FormMixin
13 return declare("dijit.form._FormMixin", null, {
15 // Mixin for containers of form widgets (i.e. widgets that represent a single value
16 // and can be children of a `<form>` node or `dijit/form/Form` widget)
18 // Can extract all the form widgets
19 // values and combine them into a single javascript object, or alternately
20 // take such an object and set the values for all the contained
25 // Name/value hash for each child widget with a name and value.
26 // Child widgets without names are not part of the hash.
28 // If there are multiple child widgets w/the same name, value is an array,
29 // unless they are radio buttons in which case value is a scalar (since only
30 // one radio button can be checked at a time).
32 // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
35 // | { name: "John Smith", interests: ["sports", "movies"] }
38 // state: [readonly] String
39 // Will be "Error" if one or more of the child widgets has an invalid value,
40 // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "",
41 // which indicates that the form is ready to be submitted.
46 // * better handling for arrays. Often form elements have names with [] like
47 // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...])
50 _getDescendantFormWidgets: function(/*dijit/_WidgetBase[]?*/ children){
52 // Returns all form widget descendants, searching through non-form child widgets like BorderContainer
54 array.forEach(children || this.getChildren(), function(child){
58 res = res.concat(this._getDescendantFormWidgets(child.getChildren()));
65 array.forEach(this._getDescendantFormWidgets(), function(widget){
74 // returns if the form is valid - same as isValid - but
75 // provides a few additional (ui-specific) features:
77 // 1. it will highlight any sub-widgets that are not valid
78 // 2. it will call focus() on the first invalid sub-widget
80 return array.every(array.map(this._getDescendantFormWidgets(), function(widget){
81 // Need to set this so that "required" widgets get their
83 widget._hasBeenBlurred = true;
84 var valid = widget.disabled || !widget.validate || widget.validate();
85 if(!valid && !didFocus){
86 // Set focus of the first non-valid widget
87 winUtils.scrollIntoView(widget.containerNode || widget.domNode);
92 }), function(item){ return item; });
95 setValues: function(val){
96 kernel.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0");
97 return this.set('value', val);
99 _setValueAttr: function(/*Object*/ obj
){
101 // Fill in form values from according to an Object (in the format returned by get('value'))
103 // generate map from name --> [list of widgets with that name]
105 array
.forEach(this._getDescendantFormWidgets(), function(widget
){
106 if(!widget
.name
){ return; }
107 var entry
= map
[widget
.name
] || (map
[widget
.name
] = [] );
111 for(var name
in map
){
112 if(!map
.hasOwnProperty(name
)){
115 var widgets
= map
[name
], // array of widgets w/this name
116 values
= lang
.getObject(name
, false, obj
); // list of values for those widgets
118 if(values
=== undefined){
121 if(!lang
.isArray(values
)){
124 if(typeof widgets
[0].checked
== 'boolean'){
125 // for checkbox/radio, values is a list of which widgets should be checked
126 array
.forEach(widgets
, function(w
){
127 w
.set('value', array
.indexOf(values
, w
.value
) != -1);
129 }else if(widgets
[0].multiple
){
130 // it takes an array (e.g. multi-select)
131 widgets
[0].set('value', values
);
133 // otherwise, values is a list of values to be assigned sequentially to each widget
134 array
.forEach(widgets
, function(w
, i
){
135 w
.set('value', values
[i
]);
141 * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
143 array.forEach(this.containerNode.elements, function(element){
144 if(element.name == ''){return}; // like "continue"
145 var namePath = element.name.split(".");
147 var name=namePath[namePath.length-1];
148 for(var j=1,len2=namePath.length;j<len2;++j){
149 var p=namePath[j - 1];
150 // repeater support block
151 var nameA=p.split("[");
152 if(nameA.length > 1){
153 if(typeof(myObj[nameA[0]]) == "undefined"){
157 nameIndex=parseInt(nameA[1]);
158 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
159 myObj[nameA[0]][nameIndex] = { };
161 myObj=myObj[nameA[0]][nameIndex];
163 } // repeater support ends
165 if(typeof(myObj[p]) == "undefined"){
172 if(typeof(myObj) == "undefined"){
173 return; // like "continue"
175 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
176 return; // like "continue"
179 // TODO: widget values (just call set('value', ...) on the widget)
181 // TODO: maybe should call dojo.getNodeProp() instead
182 switch(element.type){
184 element.checked = (name in myObj) &&
185 array.some(myObj[name], function(val){ return val == element.value; });
188 element.checked = (name in myObj) && myObj[name] == element.value;
190 case "select-multiple":
191 element.selectedIndex=-1;
192 array.forEach(element.options, function(option){
193 option.selected = array.some(myObj[name], function(val){ return option.value == val; });
197 element.selectedIndex="0";
198 array.forEach(element.options, function(option){
199 option.selected = option.value == myObj[name];
206 element.value = myObj[name] || "";
212 // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
213 // which I am monitoring.
216 getValues: function(){
217 kernel
.deprecated(this.declaredClass
+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
218 return this.get('value');
220 _getValueAttr: function(){
222 // Returns Object representing form values. See description of `value` for details.
225 // The value is updated into this.value every time a child has an onChange event,
226 // so in the common case this function could just return this.value. However,
227 // that wouldn't work when:
229 // 1. User presses return key to submit a form. That doesn't fire an onchange event,
230 // and even if it did it would come too late due to the defer(...) in _handleOnChange()
232 // 2. app for some reason calls this.get("value") while the user is typing into a
233 // form field. Not sure if that case needs to be supported or not.
237 array
.forEach(this._getDescendantFormWidgets(), function(widget
){
238 var name
= widget
.name
;
239 if(!name
|| widget
.disabled
){ return; }
241 // Single value widget (checkbox, radio, or plain <input> type widget)
242 var value
= widget
.get('value');
244 // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
245 if(typeof widget
.checked
== 'boolean'){
246 if(/Radio/.test(widget
.declaredClass
)){
249 lang
.setObject(name
, value
, obj
);
251 // give radio widgets a default of null
252 value
= lang
.getObject(name
, false, obj
);
253 if(value
=== undefined){
254 lang
.setObject(name
, null, obj
);
258 // checkbox/toggle button
259 var ary
=lang
.getObject(name
, false, obj
);
262 lang
.setObject(name
, ary
, obj
);
269 var prev
=lang
.getObject(name
, false, obj
);
270 if(typeof prev
!= "undefined"){
271 if(lang
.isArray(prev
)){
274 lang
.setObject(name
, [prev
, value
], obj
);
278 lang
.setObject(name
, value
, obj
);
284 * code for plain input boxes (see also domForm.formToObject, can we use that instead of this code?
285 * but it doesn't understand [] notation, presumably)
287 array.forEach(this.containerNode.elements, function(elm){
289 return; // like "continue"
291 var namePath = elm.name.split(".");
293 var name=namePath[namePath.length-1];
294 for(var j=1,len2=namePath.length;j<len2;++j){
295 var nameIndex = null;
296 var p=namePath[j - 1];
297 var nameA=p.split("[");
298 if(nameA.length > 1){
299 if(typeof(myObj[nameA[0]]) == "undefined"){
302 nameIndex=parseInt(nameA[1]);
303 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
304 myObj[nameA[0]][nameIndex] = { };
306 }else if(typeof(myObj[nameA[0]]) == "undefined"){
307 myObj[nameA[0]] = { }
310 if(nameA.length == 1){
311 myObj=myObj[nameA[0]];
313 myObj=myObj[nameA[0]][nameIndex];
317 if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){
318 if(name == name.split("[")[0]){
319 myObj[name]=elm.value;
321 // can not set value when there is no name
323 }else if(elm.type == "checkbox" && elm.checked){
324 if(typeof(myObj[name]) == 'undefined'){
327 myObj[name].push(elm.value);
328 }else if(elm.type == "select-multiple"){
329 if(typeof(myObj[name]) == 'undefined'){
332 for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){
333 if(elm.options[jdx].selected){
334 myObj[name].push(elm.options[jdx].value);
346 // Returns true if all of the widgets are valid.
347 // Deprecated, will be removed in 2.0. Use get("state") instead.
349 return this.state
== "";
352 onValidStateChange: function(/*Boolean*/ /*===== isValid =====*/){
354 // Stub function to connect to if you want to do something
355 // (like disable/enable a submit button) when the valid
356 // state changes on the form as a whole.
358 // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead.
361 _getState: function(){
363 // Compute what this.state should be based on state of children
364 var states
= array
.map(this._descendants
, function(w
){
365 return w
.get("state") || "";
368 return array
.indexOf(states
, "Error") >= 0 ? "Error" :
369 array
.indexOf(states
, "Incomplete") >= 0 ? "Incomplete" : "";
372 disconnectChildren: function(){
374 // Deprecated method. Applications no longer need to call this. Remove for 2.0.
377 connectChildren: function(/*Boolean*/ inStartup
){
379 // You can call this function directly, ex. in the event that you
380 // programmatically add a widget to the form *after* the form has been
383 // TODO: rename for 2.0
385 this._descendants
= this._getDescendantFormWidgets();
387 // To get notifications from children they need to be started. Children didn't used to need to be started,
388 // so for back-compat, start them here
389 array
.forEach(this._descendants
, function(child
){
390 if(!child
._started
){ child
.startup(); }
394 this._onChildChange();
398 _onChildChange: function(/*String*/ attr
){
400 // Called when child's value or disabled state changes
402 // The unit tests expect state update to be synchronous, so update it immediately.
403 if(!attr
|| attr
== "state" || attr
== "disabled"){
404 this._set("state", this._getState());
407 // Use defer() to collapse value changes in multiple children into a single
408 // update to my value. Multiple updates will occur on:
411 // 3. user selecting a radio button (which will de-select another radio button,
412 // causing two onChange events)
413 if(!attr
|| attr
== "value" || attr
== "disabled" || attr
== "checked"){
414 if(this._onChangeDelayTimer
){
415 this._onChangeDelayTimer
.remove();
417 this._onChangeDelayTimer
= this.defer(function(){
418 delete this._onChangeDelayTimer
;
419 this._set("value", this.get("value"));
425 this.inherited(arguments
);
427 // Set initial this.value and this.state. Don't emit watch() notifications.
428 this._descendants
= this._getDescendantFormWidgets();
429 this.value
= this.get("value");
430 this.state
= this._getState();
432 // Initialize value and valid/invalid state tracking.
437 "attrmodified-state, attrmodified-disabled, attrmodified-value, attrmodified-checked",
439 if(evt
.target
== self
.domNode
){
440 return; // ignore events that I fire on myself because my children changed
442 self
._onChildChange(evt
.type
.replace("attrmodified-", ""));
447 // Make state change call onValidStateChange(), will be removed in 2.0
448 this.watch("state", function(attr
, oldVal
, newVal
){ this.onValidStateChange(newVal
== ""); });
452 this.inherited(arguments
);