You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
392 lines
12 KiB
JavaScript
392 lines
12 KiB
JavaScript
dojo.provide("dijit.form._FormMixin");
|
|
|
|
dojo.declare("dijit.form._FormMixin", null,
|
|
{
|
|
// summary:
|
|
// Mixin for containers of form widgets (i.e. widgets that represent a single value
|
|
// and can be children of a <form> node or dijit.form.Form widget)
|
|
// description:
|
|
// Can extract all the form widgets
|
|
// values and combine them into a single javascript object, or alternately
|
|
// take such an object and set the values for all the contained
|
|
// form widgets
|
|
|
|
/*=====
|
|
// value: Object
|
|
// Name/value hash for each form element.
|
|
// If there are multiple elements w/the same name, value is an array,
|
|
// unless they are radio buttons in which case value is a scalar since only
|
|
// one can be checked at a time.
|
|
//
|
|
// If the name is a dot separated list (like a.b.c.d), it's a nested structure.
|
|
// Only works on widget form elements.
|
|
// example:
|
|
// | { name: "John Smith", interests: ["sports", "movies"] }
|
|
=====*/
|
|
|
|
// TODO:
|
|
// * Repeater
|
|
// * better handling for arrays. Often form elements have names with [] like
|
|
// * people[3].sex (for a list of people [{name: Bill, sex: M}, ...])
|
|
//
|
|
//
|
|
|
|
reset: function(){
|
|
dojo.forEach(this.getDescendants(), function(widget){
|
|
if(widget.reset){
|
|
widget.reset();
|
|
}
|
|
});
|
|
},
|
|
|
|
validate: function(){
|
|
// summary: returns if the form is valid - same as isValid - but
|
|
// provides a few additional (ui-specific) features.
|
|
// 1 - it will highlight any sub-widgets that are not
|
|
// valid
|
|
// 2 - it will call focus() on the first invalid
|
|
// sub-widget
|
|
var didFocus = false;
|
|
return dojo.every(dojo.map(this.getDescendants(), function(widget){
|
|
// Need to set this so that "required" widgets get their
|
|
// state set.
|
|
widget._hasBeenBlurred = true;
|
|
var valid = widget.disabled || !widget.validate || widget.validate();
|
|
if (!valid && !didFocus) {
|
|
// Set focus of the first non-valid widget
|
|
dijit.scrollIntoView(widget.containerNode||widget.domNode);
|
|
widget.focus();
|
|
didFocus = true;
|
|
}
|
|
return valid;
|
|
}), function(item) { return item; });
|
|
},
|
|
|
|
setValues: function(val){
|
|
dojo.deprecated(this.declaredClass+"::setValues() is deprecated. Use attr('value', val) instead.", "", "2.0");
|
|
return this.attr('value', val);
|
|
},
|
|
_setValueAttr: function(/*object*/obj){
|
|
// summary: Fill in form values from according to an Object (in the format returned by attr('value'))
|
|
|
|
// generate map from name --> [list of widgets with that name]
|
|
var map = { };
|
|
dojo.forEach(this.getDescendants(), function(widget){
|
|
if(!widget.name){ return; }
|
|
var entry = map[widget.name] || (map[widget.name] = [] );
|
|
entry.push(widget);
|
|
});
|
|
|
|
for(var name in map){
|
|
if(!map.hasOwnProperty(name)){
|
|
continue;
|
|
}
|
|
var widgets = map[name], // array of widgets w/this name
|
|
values = dojo.getObject(name, false, obj); // list of values for those widgets
|
|
|
|
if(values===undefined){
|
|
continue;
|
|
}
|
|
if(!dojo.isArray(values)){
|
|
values = [ values ];
|
|
}
|
|
if(typeof widgets[0].checked == 'boolean'){
|
|
// for checkbox/radio, values is a list of which widgets should be checked
|
|
dojo.forEach(widgets, function(w, i){
|
|
w.attr('value', dojo.indexOf(values, w.value) != -1);
|
|
});
|
|
}else if(widgets[0]._multiValue){
|
|
// it takes an array (e.g. multi-select)
|
|
widgets[0].attr('value', values);
|
|
}else{
|
|
// otherwise, values is a list of values to be assigned sequentially to each widget
|
|
dojo.forEach(widgets, function(w, i){
|
|
w.attr('value', values[i]);
|
|
});
|
|
}
|
|
}
|
|
|
|
/***
|
|
* TODO: code for plain input boxes (this shouldn't run for inputs that are part of widgets)
|
|
|
|
dojo.forEach(this.containerNode.elements, function(element){
|
|
if (element.name == ''){return}; // like "continue"
|
|
var namePath = element.name.split(".");
|
|
var myObj=obj;
|
|
var name=namePath[namePath.length-1];
|
|
for(var j=1,len2=namePath.length;j<len2;++j){
|
|
var p=namePath[j - 1];
|
|
// repeater support block
|
|
var nameA=p.split("[");
|
|
if (nameA.length > 1){
|
|
if(typeof(myObj[nameA[0]]) == "undefined"){
|
|
myObj[nameA[0]]=[ ];
|
|
} // if
|
|
|
|
nameIndex=parseInt(nameA[1]);
|
|
if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
|
|
myObj[nameA[0]][nameIndex] = { };
|
|
}
|
|
myObj=myObj[nameA[0]][nameIndex];
|
|
continue;
|
|
} // repeater support ends
|
|
|
|
if(typeof(myObj[p]) == "undefined"){
|
|
myObj=undefined;
|
|
break;
|
|
};
|
|
myObj=myObj[p];
|
|
}
|
|
|
|
if (typeof(myObj) == "undefined"){
|
|
return; // like "continue"
|
|
}
|
|
if (typeof(myObj[name]) == "undefined" && this.ignoreNullValues){
|
|
return; // like "continue"
|
|
}
|
|
|
|
// TODO: widget values (just call attr('value', ...) on the widget)
|
|
|
|
switch(element.type){
|
|
case "checkbox":
|
|
element.checked = (name in myObj) &&
|
|
dojo.some(myObj[name], function(val){ return val==element.value; });
|
|
break;
|
|
case "radio":
|
|
element.checked = (name in myObj) && myObj[name]==element.value;
|
|
break;
|
|
case "select-multiple":
|
|
element.selectedIndex=-1;
|
|
dojo.forEach(element.options, function(option){
|
|
option.selected = dojo.some(myObj[name], function(val){ return option.value == val; });
|
|
});
|
|
break;
|
|
case "select-one":
|
|
element.selectedIndex="0";
|
|
dojo.forEach(element.options, function(option){
|
|
option.selected = option.value == myObj[name];
|
|
});
|
|
break;
|
|
case "hidden":
|
|
case "text":
|
|
case "textarea":
|
|
case "password":
|
|
element.value = myObj[name] || "";
|
|
break;
|
|
}
|
|
});
|
|
*/
|
|
},
|
|
|
|
getValues: function(){
|
|
dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use attr('value') instead.", "", "2.0");
|
|
return this.attr('value');
|
|
},
|
|
_getValueAttr: function(){
|
|
// summary:
|
|
// Returns Object representing form values.
|
|
// description:
|
|
// Returns name/value hash for each form element.
|
|
// If there are multiple elements w/the same name, value is an array,
|
|
// unless they are radio buttons in which case value is a scalar since only
|
|
// one can be checked at a time.
|
|
//
|
|
// If the name is a dot separated list (like a.b.c.d), creates a nested structure.
|
|
// Only works on widget form elements.
|
|
// example:
|
|
// | { name: "John Smith", interests: ["sports", "movies"] }
|
|
|
|
// get widget values
|
|
var obj = { };
|
|
dojo.forEach(this.getDescendants(), function(widget){
|
|
var name = widget.name;
|
|
if(!name||widget.disabled){ return; }
|
|
|
|
// Single value widget (checkbox, radio, or plain <input> type widget
|
|
var value = widget.attr('value');
|
|
|
|
// Store widget's value(s) as a scalar, except for checkboxes which are automatically arrays
|
|
if(typeof widget.checked == 'boolean'){
|
|
if(/Radio/.test(widget.declaredClass)){
|
|
// radio button
|
|
if(value !== false){
|
|
dojo.setObject(name, value, obj);
|
|
}else{
|
|
// give radio widgets a default of null
|
|
value = dojo.getObject(name, false, obj);
|
|
if(value === undefined){
|
|
dojo.setObject(name, null, obj);
|
|
}
|
|
}
|
|
}else{
|
|
// checkbox/toggle button
|
|
var ary=dojo.getObject(name, false, obj);
|
|
if(!ary){
|
|
ary=[];
|
|
dojo.setObject(name, ary, obj);
|
|
}
|
|
if(value !== false){
|
|
ary.push(value);
|
|
}
|
|
}
|
|
}else{
|
|
// plain input
|
|
dojo.setObject(name, value, obj);
|
|
}
|
|
});
|
|
|
|
/***
|
|
* code for plain input boxes (see also dojo.formToObject, can we use that instead of this code?
|
|
* but it doesn't understand [] notation, presumably)
|
|
var obj = { };
|
|
dojo.forEach(this.containerNode.elements, function(elm){
|
|
if (!elm.name) {
|
|
return; // like "continue"
|
|
}
|
|
var namePath = elm.name.split(".");
|
|
var myObj=obj;
|
|
var name=namePath[namePath.length-1];
|
|
for(var j=1,len2=namePath.length;j<len2;++j){
|
|
var nameIndex = null;
|
|
var p=namePath[j - 1];
|
|
var nameA=p.split("[");
|
|
if (nameA.length > 1){
|
|
if(typeof(myObj[nameA[0]]) == "undefined"){
|
|
myObj[nameA[0]]=[ ];
|
|
} // if
|
|
nameIndex=parseInt(nameA[1]);
|
|
if(typeof(myObj[nameA[0]][nameIndex]) == "undefined"){
|
|
myObj[nameA[0]][nameIndex] = { };
|
|
}
|
|
} else if(typeof(myObj[nameA[0]]) == "undefined"){
|
|
myObj[nameA[0]] = { }
|
|
} // if
|
|
|
|
if (nameA.length == 1){
|
|
myObj=myObj[nameA[0]];
|
|
} else{
|
|
myObj=myObj[nameA[0]][nameIndex];
|
|
} // if
|
|
} // for
|
|
|
|
if ((elm.type != "select-multiple" && elm.type != "checkbox" && elm.type != "radio") || (elm.type=="radio" && elm.checked)){
|
|
if(name == name.split("[")[0]){
|
|
myObj[name]=elm.value;
|
|
} else{
|
|
// can not set value when there is no name
|
|
}
|
|
} else if (elm.type == "checkbox" && elm.checked){
|
|
if(typeof(myObj[name]) == 'undefined'){
|
|
myObj[name]=[ ];
|
|
}
|
|
myObj[name].push(elm.value);
|
|
} else if (elm.type == "select-multiple"){
|
|
if(typeof(myObj[name]) == 'undefined'){
|
|
myObj[name]=[ ];
|
|
}
|
|
for (var jdx=0,len3=elm.options.length; jdx<len3; ++jdx){
|
|
if (elm.options[jdx].selected){
|
|
myObj[name].push(elm.options[jdx].value);
|
|
}
|
|
}
|
|
} // if
|
|
name=undefined;
|
|
}); // forEach
|
|
***/
|
|
return obj;
|
|
},
|
|
|
|
// TODO: ComboBox might need time to process a recently input value. This should be async?
|
|
isValid: function(){
|
|
// summary:
|
|
// Returns true if all of the widgets are valid
|
|
|
|
// This also populate this._invalidWidgets[] array with list of invalid widgets...
|
|
// TODO: put that into separate function? It's confusing to have that as a side effect
|
|
// of a method named isValid().
|
|
|
|
this._invalidWidgets = dojo.filter(this.getDescendants(), function(widget){
|
|
return !widget.disabled && widget.isValid && !widget.isValid();
|
|
});
|
|
return !this._invalidWidgets.length;
|
|
},
|
|
|
|
|
|
onValidStateChange: function(isValid){
|
|
// summary:
|
|
// Stub function to connect to if you want to do something
|
|
// (like disable/enable a submit button) when the valid
|
|
// state changes on the form as a whole.
|
|
},
|
|
|
|
_widgetChange: function(widget){
|
|
// summary:
|
|
// Connected to a widget's onChange function - update our
|
|
// valid state, if needed.
|
|
var isValid = this._lastValidState;
|
|
if(!widget || this._lastValidState===undefined){
|
|
// We have passed a null widget, or we haven't been validated
|
|
// yet - let's re-check all our children
|
|
// This happens when we connect (or reconnect) our children
|
|
isValid = this.isValid();
|
|
if(this._lastValidState===undefined){
|
|
// Set this so that we don't fire an onValidStateChange
|
|
// the first time
|
|
this._lastValidState = isValid;
|
|
}
|
|
}else if(widget.isValid){
|
|
this._invalidWidgets = dojo.filter(this._invalidWidgets||[], function(w){
|
|
return (w != widget);
|
|
}, this);
|
|
if(!widget.isValid() && !widget.attr("disabled")){
|
|
this._invalidWidgets.push(widget);
|
|
}
|
|
isValid = (this._invalidWidgets.length === 0);
|
|
}
|
|
if (isValid !== this._lastValidState){
|
|
this._lastValidState = isValid;
|
|
this.onValidStateChange(isValid);
|
|
}
|
|
},
|
|
|
|
connectChildren: function(){
|
|
// summary:
|
|
// Connects to the onChange function of all children to
|
|
// track valid state changes. You can call this function
|
|
// directly, ex. in the event that you programmatically
|
|
// add a widget to the form *after* the form has been
|
|
// initialized.
|
|
dojo.forEach(this._changeConnections, dojo.hitch(this, "disconnect"));
|
|
var _this = this;
|
|
|
|
// we connect to validate - so that it better reflects the states
|
|
// of the widgets - also, we only connect if it has a validate
|
|
// function (to avoid too many unneeded connections)
|
|
var conns = this._changeConnections = [];
|
|
dojo.forEach(dojo.filter(this.getDescendants(),
|
|
function(item){ return item.validate; }
|
|
),
|
|
function(widget){
|
|
// We are interested in whenever the widget is validated - or
|
|
// whenever the disabled attribute on that widget is changed
|
|
conns.push(_this.connect(widget, "validate",
|
|
dojo.hitch(_this, "_widgetChange", widget)));
|
|
conns.push(_this.connect(widget, "_setDisabledAttr",
|
|
dojo.hitch(_this, "_widgetChange", widget)));
|
|
});
|
|
|
|
// Call the widget change function to update the valid state, in
|
|
// case something is different now.
|
|
this._widgetChange(null);
|
|
},
|
|
|
|
startup: function(){
|
|
this.inherited(arguments);
|
|
// Initialize our valid state tracking. Needs to be done in startup
|
|
// because it's not guaranteed that our children are initialized
|
|
// yet.
|
|
this._changeConnections = [];
|
|
this.connectChildren();
|
|
}
|
|
});
|