]> git.wh0rd.org - tt-rss.git/blame - lib/dijit/form/_FormMixin.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dijit / form / _FormMixin.js
CommitLineData
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
8if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dijit.form._FormMixin"] = true;
2f01fe57
AD
10dojo.provide("dijit.form._FormMixin");
11dojo.require("dojo.window");
81bea17a
AD
12
13
14dojo.declare("dijit.form._FormMixin", null, {
15 // summary:
16 // Mixin for containers of form widgets (i.e. widgets that represent a single value
17 // and can be children of a <form> node or dijit.form.Form widget)
18 // description:
19 // Can extract all the form widgets
20 // values and combine them into a single javascript object, or alternately
21 // take such an object and set the values for all the contained
22 // form widgets
23
24/*=====
25 // value: Object
26 // Name/value hash for each child widget with a name and value.
27 // Child widgets without names are not part of the hash.
28 //
29 // If there are multiple child widgets w/the same name, value is an array,
30 // unless they are radio buttons in which case value is a scalar (since only
31 // one radio button can be checked at a time).
32 //
33 // If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
34 //
35 // Example:
36 // | { name: "John Smith", interests: ["sports", "movies"] }
37=====*/
38
39 // state: [readonly] String
40 // Will be "Error" if one or more of the child widgets has an invalid value,
41 // "Incomplete" if not all of the required child widgets are filled in. Otherwise, "",
42 // which indicates that the form is ready to be submitted.
43 state: "",
44
45 // TODO:
46 // * Repeater
47 // * better handling for arrays. Often form elements have names with [] like
48 // * people[3].sex (for a list of people [{name: Bill, sex: M}, ...])
49 //
50 //
51
52 reset: function(){
53 dojo.forEach(this.getDescendants(), function(widget){
54 if(widget.reset){
55 widget.reset();
56 }
57 });
58 },
59
60 validate: function(){
61 // summary:
62 // returns if the form is valid - same as isValid - but
63 // provides a few additional (ui-specific) features.
64 // 1 - it will highlight any sub-widgets that are not
65 // valid
66 // 2 - it will call focus() on the first invalid
67 // sub-widget
68 var didFocus = false;
69 return dojo.every(dojo.map(this.getDescendants(), function(widget){
70 // Need to set this so that "required" widgets get their
71 // state set.
72 widget._hasBeenBlurred = true;
73 var valid = widget.disabled || !widget.validate || widget.validate();
74 if(!valid && !didFocus){
75 // Set focus of the first non-valid widget
76 dojo.window.scrollIntoView(widget.containerNode || widget.domNode);
77 widget.focus();
78 didFocus = true;
79 }
80 return valid;
81 }), function(item){ return item; });
82 },
83
84 setValues: function(val){
85 dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use set('value', val) instead.", "", "2.0");
86 return this.set('value', val);
87 },
88 _setValueAttr: function(/*Object*/ obj){
89 // summary:
90 // Fill in form values from according to an Object (in the format returned by get('value'))
91
92 // generate map from name --> [list of widgets with that name]
93 var map = { };
94 dojo.forEach(this.getDescendants(), function(widget){
95 if(!widget.name){ return; }
96 var entry = map[widget.name] || (map[widget.name] = [] );
97 entry.push(widget);
98 });
99
100 for(var name in map){
101 if(!map.hasOwnProperty(name)){
102 continue;
103 }
104 var widgets = map[name], // array of widgets w/this name
105 values = dojo.getObject(name, false, obj); // list of values for those widgets
106
107 if(values === undefined){
108 continue;
109 }
110 if(!dojo.isArray(values)){
111 values = [ values ];
112 }
113 if(typeof widgets[0].checked == 'boolean'){
114 // for checkbox/radio, values is a list of which widgets should be checked
115 dojo.forEach(widgets, function(w, i){
116 w.set('value', dojo.indexOf(values, w.value) != -1);
117 });
118 }else if(widgets[0].multiple){
119 // it takes an array (e.g. multi-select)
120 widgets[0].set('value', values);
121 }else{
122 // otherwise, values is a list of values to be assigned sequentially to each widget
123 dojo.forEach(widgets, function(w, i){
124 w.set('value', values[i]);
125 });
126 }
127 }
128
129 /***
130 * TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
131
132 dojo.forEach(this.containerNode.elements, function(element){
133 if(element.name == ''){return}; // like "continue"
134 var namePath = element.name.split(".");
135 var myObj=obj;
136 var name=namePath[namePath.length-1];
137 for(var j=1,len2=namePath.length;j<len2;++j){
138 var p=namePath[j - 1];
139 // repeater support block
140 var nameA=p.split("[");
141 if(nameA.length > 1){
142 if(typeof(myObj[nameA[0]]) == "undefined"){
143 myObj[nameA[0]]=[ ];
144 } // if
145
146 nameIndex=parseInt(nameA[1]);
147 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
148 myObj[nameA[0]][nameIndex] = { };
149 }
150 myObj=myObj[nameA[0]][nameIndex];
151 continue;
152 } // repeater support ends
153
154 if(typeof(myObj[p]) == "undefined"){
155 myObj=undefined;
156 break;
157 };
158 myObj=myObj[p];
159 }
160
161 if(typeof(myObj) == "undefined"){
162 return; // like "continue"
163 }
164 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
165 return; // like "continue"
166 }
167
168 // TODO: widget values (just call set('value', ...) on the widget)
169
170 // TODO: maybe should call dojo.getNodeProp() instead
171 switch(element.type){
172 case "checkbox":
173 element.checked = (name in myObj) &&
174 dojo.some(myObj[name], function(val){ return val == element.value; });
175 break;
176 case "radio":
177 element.checked = (name in myObj) && myObj[name] == element.value;
178 break;
179 case "select-multiple":
180 element.selectedIndex=-1;
181 dojo.forEach(element.options, function(option){
182 option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
183 });
184 break;
185 case "select-one":
186 element.selectedIndex="0";
187 dojo.forEach(element.options, function(option){
188 option.selected = option.value == myObj[name];
189 });
190 break;
191 case "hidden":
192 case "text":
193 case "textarea":
194 case "password":
195 element.value = myObj[name] || "";
196 break;
197 }
198 });
199 */
200
201 // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
202 // which I am monitoring.
203 },
204
205 getValues: function(){
206 dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
207 return this.get('value');
208 },
209 _getValueAttr: function(){
210 // summary:
211 // Returns Object representing form values. See description of `value` for details.
212 // description:
213
214 // The value is updated into this.value every time a child has an onChange event,
215 // so in the common case this function could just return this.value. However,
216 // that wouldn't work when:
217 //
218 // 1. User presses return key to submit a form. That doesn't fire an onchange event,
219 // and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange()
220 //
221 // 2. app for some reason calls this.get("value") while the user is typing into a
222 // form field. Not sure if that case needs to be supported or not.
223
224 // get widget values
225 var obj = { };
226 dojo.forEach(this.getDescendants(), function(widget){
227 var name = widget.name;
228 if(!name || widget.disabled){ return; }
229
230 // Single value widget (checkbox, radio, or plain <input> type widget)
231 var value = widget.get('value');
232
233 // Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
234 if(typeof widget.checked == 'boolean'){
235 if(/Radio/.test(widget.declaredClass)){
236 // radio button
237 if(value !== false){
238 dojo.setObject(name, value, obj);
239 }else{
240 // give radio widgets a default of null
241 value = dojo.getObject(name, false, obj);
242 if(value === undefined){
243 dojo.setObject(name, null, obj);
244 }
245 }
246 }else{
247 // checkbox/toggle button
248 var ary=dojo.getObject(name, false, obj);
249 if(!ary){
250 ary=[];
251 dojo.setObject(name, ary, obj);
252 }
253 if(value !== false){
254 ary.push(value);
255 }
256 }
257 }else{
258 var prev=dojo.getObject(name, false, obj);
259 if(typeof prev != "undefined"){
260 if(dojo.isArray(prev)){
261 prev.push(value);
262 }else{
263 dojo.setObject(name, [prev, value], obj);
264 }
265 }else{
266 // unique name
267 dojo.setObject(name, value, obj);
268 }
269 }
270 });
271
272 /***
273 * code for plain input boxes (see also dojo.formToObject, can we use that instead of this code?
274 * but it doesn't understand [] notation, presumably)
275 var obj = { };
276 dojo.forEach(this.containerNode.elements, function(elm){
277 if(!elm.name) {
278 return; // like "continue"
279 }
280 var namePath = elm.name.split(".");
281 var myObj=obj;
282 var name=namePath[namePath.length-1];
283 for(var j=1,len2=namePath.length;j<len2;++j){
284 var nameIndex = null;
285 var p=namePath[j - 1];
286 var nameA=p.split("[");
287 if(nameA.length > 1){
288 if(typeof(myObj[nameA[0]]) == "undefined"){
289 myObj[nameA[0]]=[ ];
290 } // if
291 nameIndex=parseInt(nameA[1]);
292 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
293 myObj[nameA[0]][nameIndex] = { };
294 }
295 } else if(typeof(myObj[nameA[0]]) == "undefined"){
296 myObj[nameA[0]] = { }
297 } // if
298
299 if(nameA.length == 1){
300 myObj=myObj[nameA[0]];
301 } else{
302 myObj=myObj[nameA[0]][nameIndex];
303 } // if
304 } // for
305
306 if((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type == "radio" && elm.checked)){
307 if(name == name.split("[")[0]){
308 myObj[name]=elm.value;
309 } else{
310 // can not set value when there is no name
311 }
312 } else if(elm.type == "checkbox" && elm.checked){
313 if(typeof(myObj[name]) == 'undefined'){
314 myObj[name]=[ ];
315 }
316 myObj[name].push(elm.value);
317 } else if(elm.type == "select-multiple"){
318 if(typeof(myObj[name]) == 'undefined'){
319 myObj[name]=[ ];
320 }
321 for(var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){
322 if(elm.options[jdx].selected){
323 myObj[name].push(elm.options[jdx].value);
324 }
325 }
326 } // if
327 name=undefined;
328 }); // forEach
329 ***/
330 return obj;
331 },
332
333 isValid: function(){
334 // summary:
335 // Returns true if all of the widgets are valid.
336 // Deprecated, will be removed in 2.0. Use get("state") instead.
337
338 return this.state == "";
339 },
340
341 onValidStateChange: function(isValid){
342 // summary:
343 // Stub function to connect to if you want to do something
344 // (like disable/enable a submit button) when the valid
345 // state changes on the form as a whole.
346 //
347 // Deprecated. Will be removed in 2.0. Use watch("state", ...) instead.
348 },
349
350 _getState: function(){
351 // summary:
352 // Compute what this.state should be based on state of children
353 var states = dojo.map(this._descendants, function(w){
354 return w.get("state") || "";
355 });
356
357 return dojo.indexOf(states, "Error") >= 0 ? "Error" :
358 dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : "";
359 },
360
361 disconnectChildren: function(){
362 // summary:
363 // Remove connections to monitor changes to children's value, error state, and disabled state,
364 // in order to update Form.value and Form.state.
365 dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect"));
366 dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); });
367 },
368
369 connectChildren: function(/*Boolean*/ inStartup){
370 // summary:
371 // Setup connections to monitor changes to children's value, error state, and disabled state,
372 // in order to update Form.value and Form.state.
373 //
374 // You can call this function directly, ex. in the event that you
375 // programmatically add a widget to the form *after* the form has been
376 // initialized.
377
378 var _this = this;
379
380 // Remove old connections, if any
381 this.disconnectChildren();
382
383 this._descendants = this.getDescendants();
384
385 // (Re)set this.value and this.state. Send watch() notifications but not on startup.
386 var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set");
387 set("value", this.get("value"));
388 set("state", this._getState());
389
390 // Monitor changes to error state and disabled state in order to update
391 // Form.state
392 var conns = (this._childConnections = []),
393 watches = (this._childWatches = []);
394 dojo.forEach(dojo.filter(this._descendants,
395 function(item){ return item.validate; }
396 ),
397 function(widget){
398 // We are interested in whenever the widget changes validity state - or
399 // whenever the disabled attribute on that widget is changed.
400 dojo.forEach(["state", "disabled"], function(attr){
401 watches.push(widget.watch(attr, function(attr, oldVal, newVal){
402 _this.set("state", _this._getState());
403 }));
404 });
405 });
406
407 // And monitor calls to child.onChange so we can update this.value
408 var onChange = function(){
409 // summary:
410 // Called when child's value or disabled state changes
411
412 // Use setTimeout() to collapse value changes in multiple children into a single
413 // update to my value. Multiple updates will occur on:
414 // 1. Form.set()
415 // 2. Form.reset()
416 // 3. user selecting a radio button (which will de-select another radio button,
417 // causing two onChange events)
418 if(_this._onChangeDelayTimer){
419 clearTimeout(_this._onChangeDelayTimer);
420 }
421 _this._onChangeDelayTimer = setTimeout(function(){
422 delete _this._onChangeDelayTimer;
423 _this._set("value", _this.get("value"));
424 }, 10);
425 };
426 dojo.forEach(
427 dojo.filter(this._descendants, function(item){ return item.onChange; } ),
428 function(widget){
429 // When a child widget's value changes,
430 // the efficient thing to do is to just update that one attribute in this.value,
431 // but that gets a little complicated when a checkbox is checked/unchecked
432 // since this.value["checkboxName"] contains an array of all the checkboxes w/the same name.
433 // Doing simple thing for now.
434 conns.push(_this.connect(widget, "onChange", onChange));
435
436 // Disabling/enabling a child widget should remove it's value from this.value.
437 // Again, this code could be more efficient, doing simple thing for now.
438 watches.push(widget.watch("disabled", onChange));
439 }
440 );
441 },
442
443 startup: function(){
444 this.inherited(arguments);
445
446 // Initialize value and valid/invalid state tracking. Needs to be done in startup()
447 // so that children are initialized.
448 this.connectChildren(true);
449
450 // Make state change call onValidStateChange(), will be removed in 2.0
451 this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
452 },
453
454 destroy: function(){
455 this.disconnectChildren();
456 this.inherited(arguments);
457 }
458
459 });
460
2f01fe57 461}