PDA

View Full Version : Cross-validation of Form Fields



Animal
4 May 2007, 5:22 AM
Certain Field types, which are validated using a minimum and maximum value will very often have to be validated as a pair using the "other" Field's value, rather than anything decided by the JSP author at authoring time, or at page generation time.

The two cases currently are NumberField and DateField

To support this, a Field needs to be able to find a sibling Field by name. That should be a fairly core piece of functionality. It's supported by straight HTML input elements. Each element has a reference to its form element.

To support this, Ext.form.BasicForm.add(Field...) has to set the form in each of the Fields that it is adding.

Once this is supported, cross-validation can be done.

Please can we add this? I think it is a very basic and commonly used pattern.

Here's my override code, and it works very nicely with two related NumberFields:



Ext.override(Ext.form.BasicForm, {
/**
* Add Ext.form components to this form
* @param {Field} field1
* @param {Field} field2 (optional)
* @param {Field} etc (optional)
*/
add : function(){
for (var i = 0; i < arguments.length; i++) {
arguments[i].setForm(this);
this.items.add(arguments[i]);
}
}
});

Ext.override(Ext.form.Field, {
// private
setForm: function(f){
this.form = f;
},
// private
getForm: function(f){
return this.form;
},

/**
* Get a sibling field by name.
* @param {String} n The name of the sibling field to find.
* @return {Object} The Ext.form.Field instance in the form with the specified name.
*/
getSibling: function(n) {
if (n instanceof Ext.form.Field) {
return n;
}
var f = this.getForm();
if (f) {
return f.findField(n);
}
},

// private
onBlur : function(){
this.el.removeClass(this.focusClass);
this.hasFocus = false;
if (this.forceUpperCase) { // The only addition here!
this.el.dom.value = String(this.getValue()).toUpperCase();
}
if(this.validationEvent !== false && this.validateOnBlur && this.validationEvent != "blur"){
this.validate();
}
var v = this.getValue();
if(v != this.startValue){
this.fireEvent('change', this, v, this.startValue);
}
this.fireEvent("blur", this);
},

applyTo : function(target){
this.target = target;
this.el = Ext.get(target);
this.render(this.el.dom.parentNode);
Ext.get(this.el.dom.parentNode).addClass("x-form-element"); // Was missing
return this;
}
});

Ext.override(Ext.form.NumberField, {
// private
validateValue : function(value){
if(!Ext.form.NumberField.superclass.validateValue.call(this, value)){
return false;
}
if(value.length < 1){ // if it's blank and textfield didn't flag it then it's valid
return true;
}
value = String(value).replace(this.decimalSeparator, ".");
if(isNaN(value)){
this.markInvalid(String.format(this.nanText, value));
return false;
}
var num = this.parseValue(value);
var min = this.getValidationValue(this.minValue);
var max = this.getValidationValue(this.maxValue);
if(num < min.value){
this.markInvalid(String.format(this.minText, min.value));
this.crossValidate(min, max);
return false;
}
if(num > max.value){
this.markInvalid(String.format(this.maxText, max.value));
this.crossValidate(min, max);
return false;
}
this.crossValidate(min, max);
return true;
},

crossValidate: function(min, max) {
// If two fields depend upon each other's values, cross-validate.
if (!this.isValidating) {
this.isValidating = true;
if (min.otherField) {
min.otherField.validate();
}
if (max.otherField) {
max.otherField.validate();
}
this.isValidating = false;
}
},

getValidationValue: function(v) {
// If it's a string, use it as a reference to a sibling
if(isNaN(v)) {
var o = this.getSibling(v);
if (o) return {otherField: o, value: this.parseValue(o.getValue())};
} else {
return {otherField: false, value: v};
}
return {value: Number.NaN};
}
});

jack.slocum
4 May 2007, 8:19 AM
Stuff like this was why I introduced VTypes - adding custom reusable validation. You can add in validations like this quite easily. You may need the latest SVN code for this to work right, as I recently added the passing of the field to the vtype function.


Ext.form.VTypes.lessThan = function(v, field){
var relatedField = Ext.getCmp(field.relatedField);
return field.getValue() < relatedField.getValue();
});

Ext.form.VTypes.greaterThan = function(v, field){
var relatedField = Ext.getCmp(field.relatedField);
return field.getValue() > relatedField.getValue();
});

Then when you create a field:


var field = new Ext.form.NumberField({
....
relatedField: 'some-other-field',
vtype: 'lessThan',
vtypeText : 'Field foo must be less than that other field'
...
});

You could also use a "validator" function.

Animal
4 May 2007, 9:15 AM
OK, I should just be able to plug that function I posted into a TextField as a validator.

Subclasses of TextField override validateValue, and so won't get the vtype and validator behaviour that TextField has in that function.

Public holiday on Monday, so I'll post progress in Tuesday...

Thanks!:D

jack.slocum
4 May 2007, 9:35 AM
Subclasses of TextField override validateValue, and so won't get the vtype and validator behaviour that TextField has in that function.

They all (I believe) have a call like:


if(!Ext.form.DateField.superclass.validateValue.call(this, value)){
return false;
}

So they must pass TextField validation first.

Animal
4 May 2007, 9:59 AM
They certainly do! Once more, Ext is ahead of the game!