Monday, April 28, 2008

.Net 3.5 Validator Extensibility - Part Three

Today I would like to show you how to do some extra something that you would like to before and after the evaluationfuncton of a validator is fired. The key to make this work is about swapping the default evaluationfunction of a validator with your own function. The following are the steps to achieve this purpose.



Create a control derived from ScriptControl

First of all, we create a control derived from ScriptControl and override GetScriptDescriptors and GetScriptReferences in order to create the script descriptors and references associated with this control.



Second, this script control has four additional properties: TargetFormID, InvalidColor, ValidColor and ValidationGroup. These properties will be passed to its descriptor's properties.



Third, remember to specify the javascript file to the assembly with [assembly:WebResource(.....)].



Create data object and collection that collects information to associate a label with validators

Now, we need to collect information about relating a specific label to validators. You may want to ask why not the controls validators validate? That information can be obtained from the validator element in javascript throught one of its property named "controltovalidate". This data object contains to public accessors which are the LabelID and ValidatorIDs along with two friendly constructors. one takes labelId and validatorIds of array as parameters and the other take labelId of string and validators of BaseValidator as parameters.



Next, we need a collection object to retain these data objects and also give it the ability to convert data into json string. So, we create a collection that derived from List<[data object]> which takes data object as template. Then we create a method that does the transformation by using DataContractJsonSerializeer object. In order to make this object to understand the data object we created previously, we need to mark the data object with DataContract attribute for class definition, and DataMember for property definition.



Now, we are ready to create the key component that makes the whole thing works.



Create a Javascript file

So, the last step is to create a javascript file and write scripts according to ASP.NET AJAX convension. The following is the sample showing you how to create a ASP.NET AJAX client control:




Type.registerNamespace("Demo");

// Constructor
Demo.ValidationExtension= function(element) {
Demo.ValidationExtension.initializeBase(this, [element]);
}

Demo.ValidationExtension.prototype = {
// Release resources before control is disposed.
dispose: function() {
Demo.HoverButton.callBaseMethod(this, 'dispose');
},

//initialize resources
initialize: function() {
Demo.HoverButton.callBaseMethod(this, 'initialize');
}
}

Demo.ValidationExtension.registerClass('Demo.ValidationExtension, Sys.UI.Control);



Next, we need to define some properties for this client control. As we have mentioned in creating server side script control, there are several properties will be translated into client control's properties and they are "InvalidColor", "ValidColor", "ValidationGroup" and "Fields" which is the json converted from the data object describing the relationship between labels and validators.



After defining these properties, we need to swap the original evaluationfunction with our own and keep the original for later use. First, we loop through Fields properties to do the swapping and save a record of labelid, elementids and validatorid and its orginal evaluationfunction. Remember, this property is an array and it contains objects with two properties: LabelID and ValidatorIDs. This must be done in initialize function.



The last step is to define our own validation function. Because we swap the original evaluationfunction with our own, when validation is triggered, our validation function will be invoked. Here is where we can do something before and after the validator's evaluationfunction is fired and in this case, it will change the color of the label and of controls associated with this validator according to the result of validation.



Below is a sample of the programming:




[assembly:WebResource("Demo.FieldValidationExtension.js", "text/javascript")]

namespace Demo
{
public class ValidatorExtension : ScriptControl
{
private FieldValidationExtenderCollection _extenders;
private Color _validColor;
private Color _invalidColor;
private string _formId;
private string _validationGroup;

public string TargetFormID
{
get { return _formId; }
set { _formId = value; }
}

public string ValidationGroup
{
get { return _validationGroup; }
set { _validationGroup = value; }
}

public FieldValidationExtenderCollection Extenders
{
get { return _extenders; }
}

public Color ValidColor
{
get { return _validColor; }
set { _validColor = value; }
}

public Color InvalidColor
{
get { return _invalidColor; }
set { _invalidColor = value; }
}

public ValidatorExtension()
{
_extenders = new FieldValidationExtenderCollection();
_validColor = Color.Black;
_invalidColor = Color.Red;
}

public void InvalidControl(Label label, WebControl control)
{
label.ForeColor = _invalidColor;
control.ForeColor = _invalidColor;
}

public void ValidControl(Label label, WebControl control)
{
label.ForeColor = _validColor;
control.ForeColor = _validColor;
}

protected override IEnumerable GetScriptDescriptors()
{
ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Demo.FieldValidationExtension", this.Page.FindControl(this.TargetFormID).ClientID);
descriptor.AddScriptProperty("Fields", _extenders.ToJSON());
descriptor.AddProperty("InvalidColor", _invalidColor.Name);
descriptor.AddProperty("ValidColor", _validColor.Name);
descriptor.AddProperty("ValidationGroup", _validationGroup);
yield return descriptor;
}

protected override IEnumerable GetScriptReferences()
{
yield return new ScriptReference("Demo.FieldValidationExtension.js", "Patmos.Web");
}
}
}




namespace Demo
{
[DataContract]
public class FieldValidationExtender
{
private string _labelId;
private List _validatorIds;

[DataMember]
public string LabelControlID
{
get { return _labelId; }
set { _labelId = value; }
}

[DataMember]
public string[] Validators
{
get { return ValidatorIds.ToArray(); }
set { ValidatorIds = new List(value); }
}

public List ValidatorIds
{
get { return _validatorIds; }
set { _validatorIds = value; }
}

public FieldValidationExtender()
{
_labelId = "";
_validatorIds = new List();
}

public FieldValidationExtender(string labelId)
{
_labelId = labelId;
_validatorIds = new List();
}

public FieldValidationExtender(string labelId, params string[] validatorIds)
{
_labelId = labelId;
_validatorIds = new List(validatorIds);
}

public FieldValidationExtender(string labelId, params BaseValidator[] validators)
{
_labelId = labelId;
_validatorIds = new List();
foreach (BaseValidator v in validators)
{
_validatorIds.Add(v.ClientID);
}
}
}
}

namespace Demo
{
public class FieldValidationExtenderCollection : List
{
public string ToJSON()
{
DataContractJsonSerializer s = new DataContractJsonSerializer(this.GetType());
MemoryStream mem = new MemoryStream();
s.WriteObject(mem, this);
return Encoding.Default.GetString(mem.ToArray());
}
}
}




Type.registerNamespace("Demo");

Demo.FieldValidationExtension = function(element) {
Demo.FieldValidationExtension.initializeBase(this, [element]);

this._fields = null;
this._invalidColor = 'red';
this._validColor = 'black';
this._validationGroup = "";
this._validatorExtenders = new Array;
this._validationMethodOverride = Function.createDelegate(this, this._onvalidate);
}

Demo.FieldValidationExtension.prototype = {
initialize: function() {
Demo.FieldValidationExtension.callBaseMethod(this, 'initialize');

for(var i = 0; i < this._fields.length; i++)
{
for(var j = 0; j < this._fields[i].Validators.length; j ++)
{
var elm = $get(this._fields[i].Validators[j]);
var elements = new Array();

if (elm.controltovalidate !== "undefined" && elm.controltovalidate.length > 0)
{
var e = $get(elm.controltovalidate);
if (e.tagName.toLowerCase() == 'div' || e.tagName.toLowerCase() == 'span')
{
var controlIds = elm.controlstovalidate.split(',');
for(var k = 0; k < controlIds.length; k ++)
{
Array.add(elements, $get(controlIds[k]));
}
}
else
Array.add(elements, e);
}
else
{
var controlIds = elm.controlstovalidate.split(',');
for(var k = 0; k < controlIds.length; k ++)
{
Array.add(elements, $get(controlIds[k]));
}
}

Array.add(this._validatorExtenders, {Label: $get(this._fields[i].LabelControlID), ValidatorId: this._fields[i].Validators[j], ElementsToValidate: elements, OriginalEvlFunc: Function.createDelegate(elm, elm.evaluationfunction)});
elm.evaluationfunction = this._validationMethodOverride;
}
}
if (typeof(Page_ClientValidate) == "function") {
Page_ClientValidate(this._validationGroup);
}
},

dispose: function() {
//Add custom dispose actions here
Demo.FieldValidationExtension.callBaseMethod(this, 'dispose');
},

_onvalidate : function(val) {
var idx = this._findValIndex(val);
if (idx == null)
Error.argumentUndefined(val.id, 'validator id can\'t be found.');
else
{
var indexes = this._findAllValByLabel(this._validatorExtenders[idx].Label.id);
var isValid = true;
for(var i = 0; i < indexes.length; i++)
{
var v = $get(this._validatorExtenders[indexes[i]].ValidatorId);
var isValValid = this._validatorExtenders[indexes[i]].OriginalEvlFunc(v);
if (!isValValid)
v.style.display = 'inline';
else
v.style.display = 'none';

isValid = isValid && isValValid;
}
this._setAssociatedControlsStatus(this._validatorExtenders[idx], isValid);
/*if (!isValid)
val.style.display = 'inline';
else
val.style.display = 'none';*/
return isValid;
}
},

_findValIndex : function(val) {
var index = null;

for(var i = 0; i < this._validatorExtenders.length; i ++)
{
if (this._validatorExtenders[i].ValidatorId == val.id)
index = i;
}

return index;
},

_findAllValByLabel : function(labelId) {
var valIdexes = new Array();
for(var i = 0; i < this._validatorExtenders.length; i ++)
{
if (this._validatorExtenders[i].Label.id == labelId)
Array.add(valIdexes, i);
}
return valIdexes;
},

_setAssociatedControlsStatus: function(valext, isvalid) {
if (!isvalid)
{
valext.Label.style.color = this._invalidColor;
this._setElementsToValidateStatus(valext.ElementsToValidate, this._invalidColor);
}
else
{
valext.Label.style.color = this._validColor;
this._setElementsToValidateStatus(valext.ElementsToValidate, this._validColor);
}
},

_setElementsToValidateStatus: function(elements, color) {
for(var i = 0; i < elements.length; i ++)
{
elements[i].style.color = color;
}
},

get_InvalidColor: function() {
return this._invalidColor;
},

set_InvalidColor: function(value) {
if (this._invalidColor != value) {
this._invalidColor = value;
this.raisePropertyChanged("InvalidColor");
}
},

get_ValidColor: function() {
return this._validColor;
},

set_ValidColor: function(value) {
if (this._validColor != value) {
this._validColor = value;
this.raisePropertyChanged("ValidColor");
}
},

get_ValidationGroup: function() {
return this._validationGroup;
},

set_ValidationGroup: function(value) {
if (this._validationGroup != value) {
this._validationGroup = value;
this.raisePropertyChanged("ValidationGroup");
}
},

get_Fields: function() {
return this._fields;
},

set_Fields: function(value) {
if (this._fields != value) {
this._fields = value;
this.raisePropertyChanged("Fields");
}
}
}
Demo.FieldValidationExtension.registerClass(Demo.FieldValidationExtension', Sys.UI.Control);



I wish these articles that I put out related to ASP.NET validation extensibility are helpful to some degrees for those who read. Next time, I am planning to put out an article of how to call a webservice to validate inputs at browser. Thanks!

No comments: