ttrss/lib/dijit/form/_FormMixin.js

462 lines
15 KiB
JavaScript

/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dijit.form._FormMixin"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit.form._FormMixin"] = true;
dojo.provide("dijit.form._FormMixin");
dojo.require("dojo.window");
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 child widget with a name and value.
// Child widgets without names are not part of the hash.
//
// If there are multiple child widgets w/the same name, value is an array,
// unless they are radio buttons in which case value is a scalar (since only
// one radio button can be checked at a time).
//
// If a child widget's name is a dot separated list (like a.b.c.d), it's a nested structure.
//
// Example:
// | { name: "John Smith", interests: ["sports", "movies"] }
=====*/
// state: [readonly] String
// Will be "Error" if one or more of the child widgets has an invalid value,
// "Incomplete" if not all of the required child widgets are filled in. Otherwise, "",
// which indicates that the form is ready to be submitted.
state: "",
// 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
dojo.window.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 set('value', val) instead.", "", "2.0");
return this.set('value', val);
},
_setValueAttr: function(/*Object*/ obj){
// summary:
// Fill in form values from according to an Object (in the format returned by get('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.set('value', dojo.indexOf(values, w.value) != -1);
});
}else if(widgets[0].multiple){
// it takes an array (e.g. multi-select)
widgets[0].set('value', values);
}else{
// otherwise, values is a list of values to be assigned sequentially to each widget
dojo.forEach(widgets, function(w, i){
w.set('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 set('value', ...) on the widget)
// TODO: maybe should call dojo.getNodeProp() instead
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;
}
});
*/
// Note: no need to call this._set("value", ...) as the child updates will trigger onChange events
// which I am monitoring.
},
getValues: function(){
dojo.deprecated(this.declaredClass+"::getValues() is deprecated. Use get('value') instead.", "", "2.0");
return this.get('value');
},
_getValueAttr: function(){
// summary:
// Returns Object representing form values. See description of `value` for details.
// description:
// The value is updated into this.value every time a child has an onChange event,
// so in the common case this function could just return this.value. However,
// that wouldn't work when:
//
// 1. User presses return key to submit a form. That doesn't fire an onchange event,
// and even if it did it would come too late due to the setTimout(..., 0) in _handleOnChange()
//
// 2. app for some reason calls this.get("value") while the user is typing into a
// form field. Not sure if that case needs to be supported or not.
// 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.get('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{
var prev=dojo.getObject(name, false, obj);
if(typeof prev != "undefined"){
if(dojo.isArray(prev)){
prev.push(value);
}else{
dojo.setObject(name, [prev, value], obj);
}
}else{
// unique name
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;
},
isValid: function(){
// summary:
// Returns true if all of the widgets are valid.
// Deprecated, will be removed in 2.0. Use get("state") instead.
return this.state == "";
},
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.
//
// Deprecated. Will be removed in 2.0. Use watch("state", ...) instead.
},
_getState: function(){
// summary:
// Compute what this.state should be based on state of children
var states = dojo.map(this._descendants, function(w){
return w.get("state") || "";
});
return dojo.indexOf(states, "Error") >= 0 ? "Error" :
dojo.indexOf(states, "Incomplete") >= 0 ? "Incomplete" : "";
},
disconnectChildren: function(){
// summary:
// Remove connections to monitor changes to children's value, error state, and disabled state,
// in order to update Form.value and Form.state.
dojo.forEach(this._childConnections || [], dojo.hitch(this, "disconnect"));
dojo.forEach(this._childWatches || [], function(w){ w.unwatch(); });
},
connectChildren: function(/*Boolean*/ inStartup){
// summary:
// Setup connections to monitor changes to children's value, error state, and disabled state,
// in order to update Form.value and Form.state.
//
// 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.
var _this = this;
// Remove old connections, if any
this.disconnectChildren();
this._descendants = this.getDescendants();
// (Re)set this.value and this.state. Send watch() notifications but not on startup.
var set = inStartup ? function(name, val){ _this[name] = val; } : dojo.hitch(this, "_set");
set("value", this.get("value"));
set("state", this._getState());
// Monitor changes to error state and disabled state in order to update
// Form.state
var conns = (this._childConnections = []),
watches = (this._childWatches = []);
dojo.forEach(dojo.filter(this._descendants,
function(item){ return item.validate; }
),
function(widget){
// We are interested in whenever the widget changes validity state - or
// whenever the disabled attribute on that widget is changed.
dojo.forEach(["state", "disabled"], function(attr){
watches.push(widget.watch(attr, function(attr, oldVal, newVal){
_this.set("state", _this._getState());
}));
});
});
// And monitor calls to child.onChange so we can update this.value
var onChange = function(){
// summary:
// Called when child's value or disabled state changes
// Use setTimeout() to collapse value changes in multiple children into a single
// update to my value. Multiple updates will occur on:
// 1. Form.set()
// 2. Form.reset()
// 3. user selecting a radio button (which will de-select another radio button,
// causing two onChange events)
if(_this._onChangeDelayTimer){
clearTimeout(_this._onChangeDelayTimer);
}
_this._onChangeDelayTimer = setTimeout(function(){
delete _this._onChangeDelayTimer;
_this._set("value", _this.get("value"));
}, 10);
};
dojo.forEach(
dojo.filter(this._descendants, function(item){ return item.onChange; } ),
function(widget){
// When a child widget's value changes,
// the efficient thing to do is to just update that one attribute in this.value,
// but that gets a little complicated when a checkbox is checked/unchecked
// since this.value["checkboxName"] contains an array of all the checkboxes w/the same name.
// Doing simple thing for now.
conns.push(_this.connect(widget, "onChange", onChange));
// Disabling/enabling a child widget should remove it's value from this.value.
// Again, this code could be more efficient, doing simple thing for now.
watches.push(widget.watch("disabled", onChange));
}
);
},
startup: function(){
this.inherited(arguments);
// Initialize value and valid/invalid state tracking. Needs to be done in startup()
// so that children are initialized.
this.connectChildren(true);
// Make state change call onValidStateChange(), will be removed in 2.0
this.watch("state", function(attr, oldVal, newVal){ this.onValidStateChange(newVal == ""); });
},
destroy: function(){
this.disconnectChildren();
this.inherited(arguments);
}
});
}