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!

Sunday, April 6, 2008

.Net 3.5 Validator Extensibility - Part Two

Starting from this week, I will be showing some examples of how to create your own .net validation control starting with Phone validator.

Issue

Often we as developer receive requests like handling phone number in three textboxes instead of one. and the required number of validators grows from two (the required field validator and regular expression validator) to six (the two previously mentioned validators for each textbox). Although it is not a complicated task, a ui developer still need to spend time to drag and drop textboxes and respective validators accordingly. It could be considerd as boring.

Solution

Therefore, a solution is advised and plan is set to create two controls; one PhoneNumber TextBox control and PhoneNumber Validation control.

First Step - Create PhoneNumber Control

Obviously, a PhoneNumber control needs to be able to render three TextBoxes. These TextBoxes will be placing as PhoneNumber control's child controls. But there is a catch in terms of its renderation. In order to hook up the ControlToValidate property of PhoneNumber validator, which will be created in the later part of this article, with PhoneNumber TextBox control, these three textboxes need to be placed under a div or a span block. The best way to do this is to create this PhoneNumber control inherited from WebCotrol and override some of its properties and key methods to make it render the result we want. There is also another feature that I would like to add to this control which will allow a jump to next textbox when input is valid. We can do so by attaching a javascript library to this control. It can be done in several ways; but here, I would like to implement this control with IScriptControl interface.(Details about how to implement a ScriptControl class, please consult the online tutorials on www.asp.net/ajax.)

Here are its programming and javascript library.:


[DefaultProperty("PhoneNumber")]

[ToolboxData("<{0}:PhoneTextBox runat=server>")]

public class PhoneTextBox : WebControl, IScriptControl

{

private TextBox _tbxAreaCode;

private TextBox _tbxPrefix;

private TextBox _tbxSuffix;

private ScriptManager _sm;



[Bindable(true)]

[Category("General")]

[DefaultValue("")]

[Localizable(true)]

public string PhoneNumber

{

get

{



String s = (String)ViewState["PhoneNumber"];

return ((s == null) ? string.Empty : s);

}

set

{

if (value.Length == 0)

ViewState["PhoneNumber"] = "";

else

{

Regex regex = new Regex("^\\d{10}$");

string tmp = value.Replace("(", "").Replace(")", "").Replace("-", "").Replace(" ", "");

if (!regex.IsMatch(tmp) && tmp.Length > 0)

throw new ArgumentException("Phone number must be 10 digits of number", "PhoneNumber");



ViewState["PhoneNumber"] = tmp;

if (value.Length == 10)

{

_tbxAreaCode.Text = tmp.Substring(0, 3);

_tbxPrefix.Text = tmp.Substring(3, 3);

_tbxSuffix.Text = tmp.Substring(6, 4);

}

}

}

}

[Bindable(true)]

[Category("General")]

[DefaultValue("")]

[Localizable(true)]

public string ControlToJumpTo

{

get

{



String s = (String)ViewState["ControlToJumpTo"];

return ((s == null) ? string.Empty : s);

}

set

{

ViewState["ControlToJumpTo"] = value;

}

}



[Bindable(true)]

[Category("Validation")]

[DefaultValue("")]

[Localizable(true)]

public string ValidationGroup

{

get

{

String s = (String)ViewState["ValidationGroup"];

return ((s == null) ? string.Empty : s);

}



set

{

ViewState["ValidationGroup"] = value;

}

}



protected override HtmlTextWriterTag TagKey

{

get

{

return HtmlTextWriterTag.Span;

}

}



protected override string TagName

{

get

{

return "span";

}

}



protected override void OnInit(EventArgs e)

{

EnsureChildControls();

base.OnInit(e);

}



protected override void CreateChildControls()

{

base.CreateChildControls();

_tbxAreaCode = new TextBox();

_tbxAreaCode.ID = "_tbxAreaCode";

_tbxAreaCode.MaxLength = 3;

_tbxAreaCode.Width = new Unit("30px");

Controls.Add(_tbxAreaCode);

_tbxPrefix = new TextBox();

_tbxPrefix.ID = "_tbxPrefix";

_tbxPrefix.MaxLength = 3;

_tbxPrefix.Width = new Unit("30px");

Controls.Add(_tbxPrefix);

_tbxSuffix = new TextBox();

_tbxSuffix.ID = "_tbxSuffix";

_tbxSuffix.MaxLength = 4;

_tbxSuffix.Width = new Unit("40px");

Controls.Add(_tbxSuffix);

_v = new PhoneValidator();

_v.ID = ID + "_v";

_v.ControlToValidate = ID;

_v.IsPhoneRequired = IsRequired;

_v.ValidationGroup = ValidationGroup;

_v.ErrorMessageForInvalidPhoneNumber = PhoneInvalidErrorMessage;

_v.ErrorMessageForMissingPhoneNumber = PhoneMissingErrorMessage;

_v.Text = "*";

Controls.Add(_v);

}



protected override void OnLoad(EventArgs e)

{

base.OnLoad(e);

if (Page.IsPostBack Page.IsCallback)

{

PhoneNumber = _tbxAreaCode.Text + _tbxPrefix.Text + _tbxSuffix.Text;

}

else

{

if (!string.IsNullOrEmpty(PhoneNumber))

{

_tbxAreaCode.Text = PhoneNumber.Substring(0, 3);

_tbxPrefix.Text = PhoneNumber.Substring(3, 3);

_tbxSuffix.Text = PhoneNumber.Substring(6, 4);

}

}

}



protected override void OnPreRender(EventArgs e)

{

base.OnPreRender(e);

if (!this.DesignMode)

{

// Test for ScriptManager and register if it exists

_sm = ScriptManager.GetCurrent(Page);



if (_sm == null)

throw new HttpException("A ScriptManager control must exist on the current page.");



_sm.RegisterScriptControl(this);

}

}



protected override void Render(HtmlTextWriter writer)

{

if (!this.DesignMode)

_sm.RegisterScriptDescriptors(this);

base.Render(writer);

}



protected override void RenderContents(HtmlTextWriter writer)

{

_tbxAreaCode.RenderControl(writer);

writer.Write(" - ");

_tbxPrefix.RenderControl(writer);

writer.Write(" - ");

_tbxSuffix.RenderControl(writer);

_v.RenderControl(writer);

}



#region IScriptControl Members



public IEnumerable GetScriptDescriptors()

{

ScriptControlDescriptor descriptor = new ScriptControlDescriptor("Patmos.Web.UI.WebControls.PhoneTextBox", this.ClientID);

descriptor.AddElementProperty("Areacode", _tbxAreaCode.ClientID);

descriptor.AddElementProperty("Prefix", _tbxPrefix.ClientID);

descriptor.AddElementProperty("Suffix", _tbxSuffix.ClientID);

descriptor.AddElementProperty("ControlToJumpTo", Page.FindControl(ControlToJumpTo).ClientID);

yield return descriptor;

}



public IEnumerable GetScriptReferences()

{

yield return new ScriptReference("PathTo.PhoneTextBoxFunction.js", this.GetType().Assembly.FullName);

}

}



Type.registerNamespace("Patmos.Web.UI.WebControls");
Patmos.Web.UI.WebControls.PhoneTextBox = function(element) {

Patmos.Web.UI.WebControls.PhoneTextBox.initializeBase(this, [element]);
this._areacode = null;
this._prefix = null;
this._suffix = null;
this._jumpTo = null;
this._keyUpHandler = Function.createDelegate(this, this._onKeyUp);

}

Patmos.Web.UI.WebControls.PhoneTextBox.prototype = {

initialize: function() {
Patmos.Web.UI.WebControls.PhoneTextBox.callBaseMethod(this, 'initialize');
$addHandler(this._areacode, 'keyup', this._keyUpHandler);
$addHandler(this._prefix, 'keyup', this._keyUpHandler);
$addHandler(this._suffix, 'keyup', this._keyUpHandler);

},

dispose: function() {
//Add custom dispose actions here
Patmos.Web.UI.WebControls.PhoneTextBox.callBaseMethod(this, 'dispose');
},

get_Areacode: function() {
return this._areacode;
},

set_Areacode: function(value) {
this._areacode = value;
this.raisePropertyChanged("Areacode");
},

get_Prefix: function() {
return this._prefix;
},

set_Prefix: function(value) {
this._prefix = value;
this.raisePropertyChanged("Prefix");
},

get_Suffix: function() {
return this._suffix;
},

set_Suffix: function(value) {
this._suffix = value;
this.raisePropertyChanged("Suffix");
},

get_ControlToJumpTo: function() {
return this._jumpTo;
},

set_ControlToJumpTo: function(value) {
this._jumpTo = value;
this.raisePropertyChanged("ControlToJumpTo");
},

_onKeyUp: function(e) {
if (e.target.id == this._areacode.id)
{
if (this._areacode.value.length == 3)
{
this._prefix.select();
}
}
else if (e.target.id == this._prefix.id)
{
if (this._prefix.value.length == 3)
{
this._suffix.select();
}
}
else
{
if (this._suffix.value.length == 4)
this._jumpTo.focus();
}
}
}

Patmos.Web.UI.WebControls.PhoneTextBox.registerClass('Patmos.Web.UI.WebControls.PhoneTextBox', Sys.UI.Control);




Second Step - Create PhoneNumber Validator

First of all, I create a class derived from BaseValidator and reprogram some of its properties and methods to make it work in our favor. Because I also want this validator to be able to do same things as RequiredField and RegularExpression validators, I added three properties to handle it and there are MissingPhoneNumberErrorMessage, InvalidPhoneNumberErrorMessage and IsPhoneRequired. Second, I implement EvaluateIsValid method (It is required because it's a abstract method in BaseValidator). This is actually a server side validation method and it is triggered when Page.Validate is called. I override ControlPropertiesValid to make sure that the control to validate is of the type of PhoneNumber TextBox control. Finally, I override AddAttributesToRender and OnPreRender methods. In OnPreRender event, I register its javascript library using client script manager. In AddAttributesToRender, I register its associated properties that will eventually be used in its client validation function. Without registering these attributes, the client validation will not engage.

Here are its programming and javascript library:


public class PhoneValidator : BaseValidator
{
private string _error;

public new string ErrorMessage
{
get { return _error; }
set { throw new NotSupportedException("ErrorMessage is not supported. Please use ErrorMessageForMissingPhoneNumber if phone number is required, or ErrorMessageForInvalidPhoneNumber if phone number contains illegal characters."); }
}

[Browsable(true)]
[Category("Behavior")]
[Themeable(false)]
[DefaultValue(true)]
[Description("Is Phone is required?")]
public bool IsPhoneRequired
{
get
{
return (bool)(ViewState["IsPhoneRequired"] ?? true);
}
set
{
ViewState["IsPhoneRequired"] = value;
}
}

[Browsable(true)]
[Category("Behavior")]
[Themeable(false)]
[DefaultValue("Phone number is required.")]
[Description("Error message for missing areacode.")]
public string ErrorMessageForMissingPhoneNumber
{
get
{
return (string)(ViewState["ErrorMessageForMissingPhoneNumber"] ?? string.Empty);
}
set
{
ViewState["ErrorMessageForMissingPhoneNumber"] = value;
}
}

[Browsable(true)]
[Category("Behavior")]
[Themeable(false)]
[DefaultValue("Phone number is invalid.")]
[Description("Error message for invalid phone number.")]
public string ErrorMessageForInvalidPhoneNumber
{
get
{
return (string)(ViewState["ErrorMessageForInvalidPhoneNumber"] ?? string.Empty);
}
set
{
ViewState["ErrorMessageForInvalidPhoneNumber"] = value;
}
}

protected override void AddAttributesToRender(System.Web.UI.HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);

if (this.RenderUplevel)
{
string clientID = this.ClientID;
PhoneTextBox c = (PhoneTextBox)Page.FindControl(ControlToValidate);
Page.ClientScript.RegisterExpandoAttribute(clientID, "evaluationfunction", "PhoneValidatorEvaluateIsValid");
Page.ClientScript.RegisterExpandoAttribute(clientID, "controlstovalidate", string.Format("{0},{1},{2}", c.AreacodeClientID, c.PrefixClientID, c.SuffixClientID));
Page.ClientScript.RegisterExpandoAttribute(clientID, "areacodecontroltovalidate", c.AreacodeClientID);
Page.ClientScript.RegisterExpandoAttribute(clientID, "prefixcontroltovalidate", c.PrefixClientID);
Page.ClientScript.RegisterExpandoAttribute(clientID, "suffixcontroltovalidate", c.SuffixClientID);
Page.ClientScript.RegisterExpandoAttribute(clientID, "setfocusonerror", this.SetFocusOnError.ToString());
Page.ClientScript.RegisterExpandoAttribute(clientID, "isphonerequired", this.IsPhoneRequired.ToString());
Page.ClientScript.RegisterExpandoAttribute(clientID, "missingphonenumber", this.ErrorMessageForMissingPhoneNumber);
Page.ClientScript.RegisterExpandoAttribute(clientID, "invalidphonenumber", this.ErrorMessageForInvalidPhoneNumber);
Page.ClientScript.RegisterExpandoAttribute(clientID, "validationgroup", this.ValidationGroup);
Page.ClientScript.RegisterExpandoAttribute(clientID, "errormessage", "");
}
}

protected override bool ControlPropertiesValid()
{
if (!(Page.FindControl(ControlToValidate) is PhoneTextBox))
throw new NotSupportedException("ControlToValidate property must point to PhoneTextBox.");
return true;
}

protected override bool EvaluateIsValid()
{
string value = GetPhoneNumber();
if (IsPhoneRequired)
{
if (string.IsNullOrEmpty(value))
{
_error = ErrorMessageForMissingPhoneNumber;
return false;
}
}

if (!string.IsNullOrEmpty(value))
{
Regex regex1 = new Regex("^\\d{10}$");
if (!regex1.IsMatch(value))
{
_error = ErrorMessageForInvalidPhoneNumber;
return false;
}
}
return true;
}

private string GetPhoneNumber()
{
PhoneTextBox c = (PhoneTextBox)Page.FindControl(ControlToValidate);
return c.PhoneNumber;
}

protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);

if (base.RenderUplevel)
{
this.Page.ClientScript.RegisterClientScriptResource(typeof(PhoneValidator), "PathTo.PhoneValidationFunction.js");
}
}
}



function PhoneValidatorEvaluateIsValid(val) {
var isRequired = val.isphonerequired;
if (isRequired.toLowerCase() == 'true')
{
if ((ValidatorTrim(ValidatorGetValue(val.areacodecontroltovalidate)) == '') || (ValidatorTrim(ValidatorGetValue(val.prefixcontroltovalidate)) == '') || (ValidatorTrim(ValidatorGetValue(val.suffixcontroltovalidate)) == ''))
val.errormessage = val.missingphonenumber;
return false;
}

var pattern1 = /^\d{10}$/;

var value = ValidatorTrim(ValidatorGetValue(val.areacodecontroltovalidate)) + ValidatorTrim(ValidatorGetValue(val.prefixcontroltovalidate)) + ValidatorTrim(ValidatorGetValue(val.suffixcontroltovalidate));

if (value.length > 0)
{
if (!pattern1.test(value))
{
val.errormessage = val.invalidphonenumber;
return false;
}
}
return true;
}

Stay tuned for next week's article. I will be showing an example of how to extend client validation functions (You will find out what it means next week!).