]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/form/_FormMixin.js.uncompressed.js
upgrade dojo to 1.8.3 (refs #570)
[tt-rss.git] / 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
6         "dojo/on",
7         "dojo/window" // winUtils.scrollIntoView
8 ], function(array, declare, kernel, lang, on, winUtils){
9
10         // module:
11         //              dijit/form/_FormMixin
12
13         return declare("dijit.form._FormMixin", null, {
14                 // summary:
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)
17                 // description:
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
21                 //              form widgets
22
23         /*=====
24                 // value: Object
25                 //              Name/value hash for each child widget with a name and value.
26                 //              Child widgets without names are not part of the hash.
27                 //
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).
31                 //
32                 //              If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
33                 //
34                 //              Example:
35                 //      |       { name: "John Smith", interests: ["sports", "movies"] }
36         =====*/
37
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.
42                 state: "",
43
44                 // TODO:
45                 //      * Repeater
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}, ...])
48
49
50                 _getDescendantFormWidgets: function(/*dijit/_WidgetBase[]?*/ children){
51                         // summary:
52                         //              Returns all form widget descendants, searching through non-form child widgets like BorderContainer
53                         var res = [];
54                         array.forEach(children || this.getChildren(), function(child){
55                                 if("value" in child){
56                                         res.push(child);
57                                 }else{
58                                         res = res.concat(this._getDescendantFormWidgets(child.getChildren()));
59                                 }
60                         }, this);
61                         return res;
62                 },
63
64                 reset: function(){
65                         array.forEach(this._getDescendantFormWidgets(), function(widget){
66                                 if(widget.reset){
67                                         widget.reset();
68                                 }
69                         });
70                 },
71
72                 validate: function(){
73                         // summary:
74                         //              returns if the form is valid - same as isValid - but
75                         //              provides a few additional (ui-specific) features:
76                         //
77                         //              1. it will highlight any sub-widgets that are not valid
78                         //              2. it will call focus() on the first invalid sub-widget
79                         var didFocus = false;
80                         return array.every(array.map(this._getDescendantFormWidgets(), function(widget){
81                                 // Need to set this so that "required" widgets get their
82                                 // state set.
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);
88                                         widget.focus();
89                                         didFocus = true;
90                                 }
91                                 return valid;
92                         }), function(item){ return item; });
93                 },
94
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);
98                 },
99                 _setValueAttr: function(/*Object*/ obj){
100                         // summary:
101                         //              Fill in form values from according to an Object (in the format returned by get('value'))
102
103                         // generate map from name --> [list of widgets with that name]
104                         var map = { };
105                         array.forEach(this._getDescendantFormWidgets(), function(widget){
106                                 if(!widget.name){ return; }
107                                 var entry = map[widget.name] || (map[widget.name] = [] );
108                                 entry.push(widget);
109                         });
110
111                         for(var name in map){
112                                 if(!map.hasOwnProperty(name)){
113                                         continue;
114                                 }
115                                 var widgets = map[name],                                                // array of widgets w/this name
116                                         values = lang.getObject(name, false, obj);      // list of values for those widgets
117
118                                 if(values === undefined){
119                                         continue;
120                                 }
121                                 if(!lang.isArray(values)){
122                                         values = [ values ];
123                                 }
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);
128                                         });
129                                 }else if(widgets[0].multiple){
130                                         // it takes an array (e.g. multi-select)
131                                         widgets[0].set('value', values);
132                                 }else{
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]);
136                                         });
137                                 }
138                         }
139
140                         /***
141                          *      TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
142
143                         array.forEach(this.containerNode.elements, function(element){
144                                 if(element.name == ''){return}; // like "continue"
145                                 var namePath = element.name.split(".");
146                                 var myObj=obj;
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"){
154                                                         myObj[nameA[0]]=[ ];
155                                                 } // if
156
157                                                 nameIndex=parseInt(nameA[1]);
158                                                 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
159                                                         myObj[nameA[0]][nameIndex] = { };
160                                                 }
161                                                 myObj=myObj[nameA[0]][nameIndex];
162                                                 continue;
163                                         } // repeater support ends
164
165                                         if(typeof(myObj[p]) == "undefined"){
166                                                 myObj=undefined;
167                                                 break;
168                                         };
169                                         myObj=myObj[p];
170                                 }
171
172                                 if(typeof(myObj) == "undefined"){
173                                         return;         // like "continue"
174                                 }
175                                 if(typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
176                                         return;         // like "continue"
177                                 }
178
179                                 // TODO: widget values (just call set('value', ...) on the widget)
180
181                                 // TODO: maybe should call dojo.getNodeProp() instead
182                                 switch(element.type){
183                                         case "checkbox":
184                                                 element.checked = (name in myObj) &&
185                                                         array.some(myObj[name], function(val){ return val == element.value; });
186                                                 break;
187                                         case "radio":
188                                                 element.checked = (name in myObj) && myObj[name] == element.value;
189                                                 break;
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; });
194                                                 });
195                                                 break;
196                                         case "select-one":
197                                                 element.selectedIndex="0";
198                                                 array.forEach(element.options, function(option){
199                                                         option.selected = option.value == myObj[name];
200                                                 });
201                                                 break;
202                                         case "hidden":
203                                         case "text":
204                                         case "textarea":
205                                         case "password":
206                                                 element.value = myObj[name] || "";
207                                                 break;
208                                 }
209                         });
210                         */
211
212                         // Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
213                         // which I am monitoring.
214                 },
215
216                 getValues: function(){
217                         kernel.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
218                         return this.get('value');
219                 },
220                 _getValueAttr: function(){
221                         // summary:
222                         //              Returns Object representing form values.   See description of `value` for details.
223                         // description:
224
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:
228                         //
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()
231                         //
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.
234
235                         // get widget values
236                         var obj = { };
237                         array.forEach(this._getDescendantFormWidgets(), function(widget){
238                                 var name = widget.name;
239                                 if(!name || widget.disabled){ return; }
240
241                                 // Single value widget (checkbox, radio, or plain <input> type widget)
242                                 var value = widget.get('value');
243
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)){
247                                                 // radio button
248                                                 if(value !== false){
249                                                         lang.setObject(name, value, obj);
250                                                 }else{
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);
255                                                         }
256                                                 }
257                                         }else{
258                                                 // checkbox/toggle button
259                                                 var ary=lang.getObject(name, false, obj);
260                                                 if(!ary){
261                                                         ary=[];
262                                                         lang.setObject(name, ary, obj);
263                                                 }
264                                                 if(value !== false){
265                                                         ary.push(value);
266                                                 }
267                                         }
268                                 }else{
269                                         var prev=lang.getObject(name, false, obj);
270                                         if(typeof prev != "undefined"){
271                                                 if(lang.isArray(prev)){
272                                                         prev.push(value);
273                                                 }else{
274                                                         lang.setObject(name, [prev, value], obj);
275                                                 }
276                                         }else{
277                                                 // unique name
278                                                 lang.setObject(name, value, obj);
279                                         }
280                                 }
281                         });
282
283                         /***
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)
286                         var obj = { };
287                         array.forEach(this.containerNode.elements, function(elm){
288                                 if(!elm.name)   {
289                                         return;         // like "continue"
290                                 }
291                                 var namePath = elm.name.split(".");
292                                 var myObj=obj;
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"){
300                                                         myObj[nameA[0]]=[ ];
301                                                 } // if
302                                                 nameIndex=parseInt(nameA[1]);
303                                                 if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
304                                                         myObj[nameA[0]][nameIndex] = { };
305                                                 }
306                                         }else if(typeof(myObj[nameA[0]]) == "undefined"){
307                                                 myObj[nameA[0]] = { }
308                                         } // if
309
310                                         if(nameA.length == 1){
311                                                 myObj=myObj[nameA[0]];
312                                         }else{
313                                                 myObj=myObj[nameA[0]][nameIndex];
314                                         } // if
315                                 } // for
316
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;
320                                         }else{
321                                                 // can not set value when there is no name
322                                         }
323                                 }else if(elm.type == "checkbox" && elm.checked){
324                                         if(typeof(myObj[name]) == 'undefined'){
325                                                 myObj[name]=[ ];
326                                         }
327                                         myObj[name].push(elm.value);
328                                 }else if(elm.type == "select-multiple"){
329                                         if(typeof(myObj[name]) == 'undefined'){
330                                                 myObj[name]=[ ];
331                                         }
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);
335                                                 }
336                                         }
337                                 } // if
338                                 name=undefined;
339                         }); // forEach
340                         ***/
341                         return obj;
342                 },
343
344                 isValid: function(){
345                         // summary:
346                         //              Returns true if all of the widgets are valid.
347                         //              Deprecated, will be removed in 2.0.  Use get("state") instead.
348
349                         return this.state == "";
350                 },
351
352                 onValidStateChange: function(/*Boolean*/ /*===== isValid =====*/){
353                         // summary:
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.
357                         //
358                         //              Deprecated.  Will be removed in 2.0.  Use watch("state", ...) instead.
359                 },
360
361                 _getState: function(){
362                         // summary:
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") || "";
366                         });
367
368                         return array.indexOf(states, "Error") >= 0 ? "Error" :
369                                 array.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : "";
370                 },
371
372                 disconnectChildren: function(){
373                         // summary:
374                         //              Deprecated method.   Applications no longer need to call this.   Remove for 2.0.
375                 },
376
377                 connectChildren: function(/*Boolean*/ inStartup){
378                         // summary:
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
381                         //              initialized.
382
383                         // TODO: rename for 2.0
384
385                         this._descendants = this._getDescendantFormWidgets();
386
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(); }
391                         });
392
393                         if(!inStartup){
394                                 this._onChildChange();
395                         }
396                 },
397
398                 _onChildChange: function(/*String*/ attr){
399                         // summary:
400                         //              Called when child's value or disabled state changes
401
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());
405                         }
406
407                         // Use defer() to collapse value changes in multiple children into a single
408                         // update to my value.   Multiple updates will occur on:
409                         //      1. Form.set()
410                         //      2. Form.reset()
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();
416                                 }
417                                 this._onChangeDelayTimer = this.defer(function(){
418                                         delete this._onChangeDelayTimer;
419                                         this._set("value", this.get("value"));
420                                 }, 10);
421                         }
422                 },
423
424                 startup: function(){
425                         this.inherited(arguments);
426
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();
431
432                         // Initialize value and valid/invalid state tracking.
433                         var self = this;
434                         this.own(
435                                 on(
436                                         this.containerNode,
437                                         "attrmodified-state, attrmodified-disabled, attrmodified-value, attrmodified-checked",
438                                         function(evt){
439                                                 if(evt.target == self.domNode){
440                                                         return; // ignore events that I fire on myself because my children changed
441                                                 }
442                                                 self._onChildChange(evt.type.replace("attrmodified-", ""));
443                                         }
444                                 )
445                         );
446
447                         // Make state change call onValidStateChange(), will be removed in 2.0
448                         this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
449                 },
450
451                 destroy: function(){
452                         this.inherited(arguments);
453                 }
454
455         });
456 });