Describing the problem
In a project I am working in, I was faced with a problem. The client asked to change the default presentation of the errors that appeared. He wanted to highlight a background of the field with a custom color when a user entered wrong data on a form. The ordinary technique to show that the validation process failed is to show some red text in a certain position, or just an asterisk. In addition to this, we can also place a ValidationSummary
control to show the summarized output. I was searching for some built-in features in .NET to run a custom client function before the postback, but without success.
In this article, I am going to show how to add a special Cascading Style Sheet class name to an attached control in an error situation, on the server and the client sides, or even call a custom client function. In addition, I am going to show how to customize validation summary results.
You can download the sources (10.9 Kb), and easily add the functionalities to your form. There is a class library called HighlightValidators
. It contains all types of enhanced validators like RequiredValidator
, CompareValidator
, RangeValidator
, RegularExpressionValidator
, and CustomValidator
. In the demo app (6.52 Kb), you can also find an example form.
How to use the solution
To use it, you need to go through these steps:
- Copy UserControls.HighlightValidators.dll from the HighlightValidators/bin/Release folder to the bin folder of your web-application.
- Add the following block into the web.config file:
...
<pages>
<controls>
<add tagPrefix="uc" assembly="UserControls.HighlightValidators"
namespace="UserControls.HighlightValidators" />
</controls>
</pages>
...
It will register the Highlight controls from the UserControls.HighlightValidators assembly for the whole web-application.
- After that, you can declare the
HighlightRequiredValidator
, for instance, as follows:
<uc:HighlightRequiredValidator id=HighlightRequiredValidator1
ErrorCssClass="on_error" ErrorMessage="First Name is required"
ControlToValidate="TextBox1" runat="Server" />
I set ErrorCssClass
to "on_error
". Now, when validation fails, the control TextBox1
will be customized according to the on_error
class.
- All Highlight validators also have the
ErrorClientFunction
property. You can use it to call custom client functions in an error situation:
<uc:HighlightCompareValidator ID="HighlightCompareValidator1" runat="server"
ControlToValidate="TextBox1" ErrorMessage="Number of books must be numeric"
ErrorCssClass="on_error" ErrorClientFunction="onError"
Operator="DataTypeCheck" Type="Integer" />
Now, you need to declare the function onError()
on the page:
function onError(val)
{
alert("Number of books must be numeric");
}
Please note that this function takes a parameter, val
. This object describes the validator in which the validation has failed. For more information, read the section below.
Diving in to the code
To enhance the standard validators, I used them as inherited classes, and created a new branch of validators with the “Highlight” prefix, because I just wanted to add some desired functionality and maintain compatibility so developers can easily switch between the standard ASP.NET validators and the enhanced Highlight validators.
At first, let’s take a look at how changing the class name works. There are two parts of the code – server and client sides. With the server side, everything is easy: I just added a check whether the validator is valid or if the validation process failed. If it isn’t valid, I add an error class name to the attached control:
protected override void OnPreRender(EventArgs e)
{
WebControl control = (WebControl)Parent.FindControl(ControlToValidate);
ClassNameHelper.Remove(control, this.errorCssClass);
if (!IsValid)
{
ClassNameHelper.Add(control, this.errorCssClass);
}
base.OnPreRender(e);
}
The ClassNameHelper.Remove()
method deletes the error class name in any case, because the previous postback could have failed validation and the class name was added. Now, we need to remove it first, and then check again. If IsValid
equals false
again, then we need to add the error class name again using the ClassNameHelper.Add()
method.
Nothing interesting so far. But let’s figure out how to change the class name of the attached control on the client side before the user postbacks the form. As I said in the introduction section, if we set EnableClientScript
to true
(its default value), then in case the user enters wrong data, the validation client scripts do not postback the form to the server and add some red text error description or show a validation summary. To investigate this process, I went to the aspnet_client folder and looked into the WebUIValidation.js script file.
I found there, the ValidatorUpdateDisplay
function:
function ValidatorUpdateDisplay(val) {
if (typeof(val.display) == "string") {
if (val.display == "None") {
return;
}
if (val.display == "Dynamic") {
val.style.display = val.isvalid ? "none" : "inline";
return;
}
}
val.style.visibility = val.isvalid ? "hidden" : "visible";
}
That’s it! Client scripts use it to show/hide the red error message of the validator. So if we can override it, we can add some additional code, and that will help to implement the enhanced functionalities. To achieve this, I used a little trick – a hijacking. For example, if we have a function test
in some included JavaScript file, and we need to override it in the some HTML file, we can do this:
var oldTest;
function newTest()
{
oldTest();
alert("we've overrided it");
}
window.onload = function()
{
oldTest = test;
test = newTest;
}
When the page loads (window.onload
event), we save the test()
function pointer in the oldTest
variable, and replace the current pointer of test()
with the newTest()
pointer. So now, if we call the test()
function, actually we are calling the oldTest()
function. Please note that if you use the name of a JavaScript function without parentheses, it’s only a pointer, but if you need to call the function, you need to use “()” at the end.
Let’s go back to the ValidatorUpdateDisplay()
function. Now we know how to override it. We also have the val
object as a parameter. This object describes the validator which is in the validation process and which changes state after some user action. Some of the important properties of the validator object are described in the following table:
ID | Equals the ClientID property of the validator |
controltovalidate | Contains the ClientID of the control which is attached to the validator |
isvalid | Indicates whether the validation process failed or not |
errormessage | Contains the ErrorMessage property |
Now, I am going to override the ValidatorUpdateDisplay()
function. I need to go through the list of all the Highlight validators placed on the current form and add custom error class names to the controls, in case the validation fails (isvalid == false
). I use the Page_HighlightValidators
array to save all necessary information about a Highlight validator (validator ClientID
, ErrorCssName
, and others) to be accessible for client scripts. This happens during the OnLoad
event of the Highlight validator:
private string errorCssClass;
public string ErrorCssClass
{
get { return this.errorCssClass; }
set { this.errorCssClass = value; }
}
private string errorClientFunction;
public string ErrorClientFunction
{
get { return this.errorClientFunction; }
set { this.errorClientFunction = value; }
}
protected override void OnLoad(EventArgs e)
{
ClientScriptManager cs = Page.ClientScript;
...
Control control = Parent.FindControl(this.ControlToValidate);
if (control != null)
{
if (!cs.IsClientScriptBlockRegistered(this.GetType(),
String.Format("HighlightValidation_{0}", this.ClientID)))
{
cs.RegisterClientScriptBlock(this.GetType(),
String.Format("HighlightValidation_{0}", this.ClientID),
String.Format("Page_HighlightValidators.push(
new Array('{0}', '{1}', '{2}', '{3}'));",
this.ClientID, control.ClientID, this.errorCssClass,
this.errorClientFunction), true);
}
}
...
}
I use the push()
method of the Array
object to add a new item to the Page_HighlightValidators
array because I actually do not know the order of the placed Highlight validators on the form. ErrorCssClass
and ErrorClientFunction
are new properties of the Highlight validators. We can use them to set custom error class names and client function names that we need to run in case the validation fails.
Eventually, I can use a loop in an already overridden ValidatorUpdateDisplay()
function, and add custom error class names if the validation fails:
function newValidatorUpdateDisplay(val)
{
oldValidatorUpdateDisplay(val);
for (i = 0; i < Page_HighlightValidators.length; i++)
{
if (val.id == Page_HighlightValidators[i][0] &&
val.controltovalidate == Page_HighlightValidators[i][1])
{
var control = document.getElementById(Page_HighlightValidators[i][1]);
var errorClassName = Page_HighlightValidators[i][2];
var errorCallFunction = Page_HighlightValidators[i][3];
if (errorClassName != "")
{
RemoveClassName(control, errorClassName);
if (!val.isvalid)
{
AddClassName(control, errorClassName);
control.title = val.attributes.title.value;
}
}
if (errorCallFunction != "" &&
eval("typeof("+errorCallFunction+")") == "function")
{
if (!val.isvalid)
{
eval(errorCallFunction+"(val)");
}
}
}
}
}
Instead of just replacing the class name with the error class name, I use the RemoveClassName
and AddClassName
functions which have the ability to add and remove class names. They have the ability to attach a few class names, separating them by spaces, like this:
<div class="class_one class_two">...</div>
Please note also that the properties from the class_two
class override the properties from class_one
. So, we just change the properties which indicate the error, and do not destroy others.
In the code above, I also added calling the custom error client function, and pass the val
object to it as the parameter.
Custom validation summary on the client side
Now, we've enhanced the validators, and can indicate to the customer who uses our form the kind of errors that happen, in any way we like, using custom error class names and custom client functions. But let's take a look at the ValidationSummary
control. How can its default functionalities be made flexible for us?
For example, I want to place a ValidationSummary
control as a div
in the center of the browser's window and customize its appearance. For now, let's forget about the asp:ValidationSummary
control. And let's go back to the aspnet_client folder again, and open the WebUIValidation.js file. There is a ValidationSummaryOnSubmit()
function which the ValidationSummary
control calls when it's needed to display the errors list. It's easy to find out that this function uses a global array, Page_Validators
, which contains the collective information about all the validators on the current form. OK, that’s what we need. We can use this array to go through all the validators and check their state.
Next, I am going to create a div
for my custom Validation Summary control:
<div id="SummaryValidation">
<span>
<p style="font-weight:bold">The following errors were occured:</p>
<ul id="ValidationProblems"></ul>
</span>
</div>
I already added a header and a list tag, where I am planning to display all the errors. To center the div
in the browser window, I am using a little trick. For more details about this, read this perfect article: Horizontally Centered Absolute Positioning.
In the next step, we need to attach the JavaScript function to the Submit event of the form, where we will check all the validators and create a list of errors if they are present; in the other case, we just let the form be posted to the server:
protected void Page_Load(object sender, EventArgs e)
{
ClientScriptManager cs = Page.ClientScript;
if (!cs.IsOnSubmitStatementRegistered(this.GetType(),
"PrepareValidationSummary"))
{
cs.RegisterOnSubmitStatement(this.GetType(),
"PrepareValidationSummary", "CheckForm()");
}
}
Now, I declare the CheckForm()
function as follows:
function CheckForm()
{
if (!Page_IsValid)
{
var s = "";
for (i=0; i<Page_Validators.length; i++) {
if (!Page_Validators[i].isvalid &&
typeof(Page_Validators[i].errormessage) == "string") {
s += "<li>" + Page_Validators[i].errormessage + "</li>";
}
}
if (s != "")
{
document.getElementById('ValidationProblems').innerHTML = s;
document.getElementById('SummaryValidation').style.display = 'inline';
}
}
}
In CheckForm()
, I check all the validators for validity and collect error information. Then, if at least one error is present, I display the validation summary.
To hide the validation summary, create the HideValidationSummary()
function, and attach it to the document.onlick
event so it will disappear after the customer clicks somewhere on the browser’s window.
I hope it is helpful to you, and will appreciate if you leave your opinions, corrections, or any other thoughts. Thanks for your time.
Change history
- 7/7/2006 - Refactoring: added the
EventHelper
class to share some recurring blocks of code which are present in each highlight validator declaration.