Introduction
Validation is one of the necessary tasks that every web systems developer will face at one point or another. Validation can be as simple as ensuring that data is input in fields (checking for empty fields), and as complex as ensuring that input conforms to a certain format (matching with Regular Expressions). ASP.NET (1.1) offers a collection of validation controls that (supposedly) help in validating web forms. These controls on the other hand, are not sufficient for the every day needs of most developers; they fail at many points I illustrate below. My code tries to fill some of the gaps, and some advice will point in the right direction of solving issues my code cannot solve.
Scope
My focus will be on providing client side control over validation. Thus, my code will be JavaScript in its core. C# code will be used to facilitate using the JavaScript code. Code to extend or recreate the existing validation controls is not within the scope of this article.
ASP.NET Validation Fails
The controls provided by ASP.NET fail in the following points:
- No ability to specify when validators work with respect to the clicked buttons.
- No ability to group validators together so that they can be activated or deactivated according to a certain client-side event.
- When hiding a certain control (on the client-side) by setting its style to 'display: none;', the validators become hidden but still active.
- A very important drawback is that these validators don't work on non-IE browsers.
Validation in Firefox
Firefox is a widely used web browser. Its market has grown to the extent where it competes with Internet Explorer. When the ASP.NET validators were made, non-IE browsers were not taken into consideration. That is evident in the fact that their client-side validation does not work on Firefox (I only tested Firefox). Some say that the emitted JavaScript is IE specific, but I noticed recently that there is not even any emitted JavaScript when the browser is Firefox. How does ASP.NET emit JavaScript for IE and none for Firefox? That is called Adaptive Rendering, where the browser is determined, and according to the determined browser, the controls are rendered differently.
So, my code works in IE only because validation works in IE only. What do we do in the case of non-IE browsers? We have to look at third party validation controls that generate compliant JavaScript. I found that the free DOMValidators are very good. My code works with DOMValidators.
The Solution
It is all in the JavaScript code. The script file has a collection of functions that will give developers the power to enable/disable validation on the client side. The script provides the following:
- Ability to specify when validators are active and when they are inactive. This can help in binding validators to certain buttons. For example, if we have (as illustrated in the picture) two input sections. The first is for Members, and the second is for New Users that wish to become Members. When a Member clicks the Login button, validation for the New Users section should not be active. So, if users want to login, they should only provide a Username and a Password, and not the fields of the New User section.
- Ability to group validation controls together under Groups. This way, we can disable/enable them according to group name.
- Ability to enable/disable the validators inside a certain container.
- Ability to enable/disable the validators that validate a certain control.
- Ability to hide a certain container with all the validators inside it (disable them, not just hide them).
- Give all this control without having to build new controls or even extend the existing ones.
The JavaScript
The JavaScript functions fall into three groups:
- Controlling validators that exist inside a certain container. If the container is not specified, then all validators in the page are affected (
enableValidators
, disableValidators
). - Controlling validators that validate a certain control X and are inside a control Y. If Y is not specified, then all controls in the page that validate X are affected (
enableCTVValidators
, disableCTVValidators
). - Controlling validators that belong to a specific group (
enableGroupValidators
, disableGroupValidators
).
There is another group that enables showing/hiding a certain section of the page, disabling and hiding all the contained validators in the process (show
, hide
, show/hide
). This group uses the functions in the three groups above.
Using Them
The above functions can be used by calling them using JavaScript. Such calls are usually inside the client-side event handlers of the action controls in our pages. For example, we can call enableValidators()
inside the onclick
event of the Login button.
btnLogin.Attributes["onclick"] =
"enableValidators('" + tblMember.ClientID + "');"+
" disableValidators('" + tblNew.ClientID + "');";
The above code will make the btnLogin
button enable all validators inside the table tblMember
and disable all the validators inside the table tblNew
.
We can also group some validators together by giving them a groupID
. This should be done in the HTML view of our pages.
<asp:requiredfieldvalidator id="rfvLoginName"
ErrorMessage="Username Required" Display="Dynamic"
ControlToValidate="txtLoginName" groupID="Member" runat="server">
!</asp:requiredfieldvalidator>
and here is what happens in the code-behind:
btnLogin.Attributes["onclick"] =
"enableGroupValidators('Member'); disableGroupValidators('NewMember');";
The validators belonging to the group called Member
are enabled, and those belonging to the group called NewMember
are disabled, when the btnLogin
button is clicked (clicked on the client side). The opposite behavior happens when clicking the btnSubmit
button in this code:
btnSubmit.Attributes["onclick"] = "enableGroupValidators('NewMember');" +
" disableGroupValidators('Member');";
The validation for the whole page is re-enabled when any of the buttons loses focus. This is a suitable solution since the onblur
event happens after our buttons are clicked.
For more examples of using these functions and the rest of the functions, please check the demo project that accompanies this article.
Server Side Validation
Although it is not the focus of this article, it is crucial that our page gets validated on the server side as well. That is due to the fact that client-side validation can be easily bypassed.
Since we are enabling/disabling validators on the client-side, and since on the server side there is no perception of the changes we make on the client side (validators still function on the server side even when we disable them on the client side), checking the Page.IsValid
property will not do it. That is why we use this method:
private bool checkIsValid(Control c)
{
bool result = true;
foreach( Control child in c.Controls)
{
if(child is BaseValidator && !((IValidator)child).IsValid)
{
return false;
}
result = result & checkIsValid(child);
}
return result;
}
The above method checks all validators inside a given control. If all the validators are valid, then the provided control is valid; otherwise, the whole control is not valid.
The JavaScript Detailed
This is not a required reading for those interested in using the code. This section is for those interested in knowing the JavaScript inner workings of the code.
There are two versions for every type of function, one that enables validation and one that disables it. Let us start from the top. In the Contained Validators part, there is the main traversal function that finds the intended validation controls and sets their status.
function traverseTree(status, control)
{
if(control == null)
{
for(var i = 0; i < Page_Validators.length; i++)
{
Page_Validators[i].enabled = status;
Page_Validators[i].style.display = status ? 'inline' : 'none';
}
}
else
{
if(control.controltovalidate != null)
{
control.enabled = status;
control.style.display = status ? 'inline' : 'none';
}
for( var i=0; i < control.childNodes.length; i++)
{
traverseTree(status, control.childNodes[i]);
}
}
}
What this function does is the following: If the control is null
(in other words if a container control is provided), the easiest way to set the status of all the validators would be to loop through the Page_Validators
array. This array is generated whenever validation is used in a page. To set the status of a validator, we set its enabled
property to status (which is either true
or false
), and we hide/show the validator by setting its style.display
to none
or inline
. Now, if the container is provided (control != null
), then we traverse the control tree recursively, setting the status of all validators on our way. How do we know that a certain control is a validation control? By checking its evaluationfunction
attribute.
Now, this is how we enable validators inside a container:
function enableValidators(containerID)
{
var control;
if(containerID == null)
{
control = null;
}
else
{
control = document.getElementById(containerID);
}
if( containerID == null || control != null )
traverseTree(true, control);
}
This function checks if a containerID
is provided; if it is, then the traversTree
function is called with the corresponding control found by using getElementById
. The disableValidators
function is similar.
Now, here we can see the traverse function for the ControlToValidate
functions:
function traverseTreeCTV(status, controlToValidateID, control)
{
if(control == null)
{
for(var i = 0; i < Page_Validators.length; i++)
{
if(Page_Validators[i].controltovalidate != null &&
Page_Validators[i].controltovalidate == controlToValidateID)
{
Page_Validators[i].enabled = status;
Page_Validators[i].style.display = status ? 'inline' : 'none';
}
}
}
else
{
if(control.controltovalidate != null &&
control.controltovalidate == controlToValidateID)
{
control.enabled = status;
control.style.display = status ? 'inline' : 'none';
}
for( var i=0; i < control.childNodes.length; i++)
{
traverseTreeCTV(status, controlToValidateID, control.childNodes[i]);
}
}
}
If a container control is not provided, then the status of all the controls in the page that validate the control of ID controlToValidateID
is set. Like before, we loop through the Page_Validators
array. If the container is provided, then we traverse the control tree recursively, setting the status of all controls that have a controltovalidate
of controlToValidateID
.
Nothing new about enableCTVValidators
and disableCTVValidators
, they just call the above function.
Now, for the Group Validators functions:
function setGroupValidatorsStatus(groupID, status)
{
for(var i = 0; i < Page_Validators.length; i++)
{
if(Page_Validators[i].attributes['groupID'].value == groupID)
{
Page_Validators[i].enabled = status;
Page_Validators[i].style.display = status ? 'inline' : 'none';
}
}
}
All the previous function does is look for all the validators that have an attribute called groupID
of value groupID
. When such controls are found, their status is set accordingly.
Again, enableGroupValidators
and disableGroupValidators
merely call the above function.
I will leave the Show/Hide functions for you to investigate.
About the Demo Project
The demo provides examples for using most of the provided functions. Inside the project, there is a Web Form called 'WebForm1
' and it imports the JavaScript file 'ValidationDefeater.js'.
Inside 'WebForm1
', there is a collection of TextBox
es with their corresponding Validators and the buttons to submit the form. These are divided into two sections: one section for Members and the other section for New Users. Logically, the two sections are separate, but functionally, their validators are not, at least without our method. By using the techniques described, they become really two separate sections.
There is a dropdown called ddlMode
and it is used to select the mode in which our separation is to be enforced. The modes are: Group
mode, Container
mode, ControlToValidate
mode, and Show
/Hide
mode.
When the ControlToValidate
mode is selected, two new dropdowns appear. Each belonging to one of the two sections, and each holding the names of all the TextBox
es in the corresponding section. The point is to select the name of the control that we wish to disable validation on.
The two btnLogin
and btnSubmit
buttons each belonging to the corresponding section are prepared in the code-behind. The client side onclick
event for these buttons is set to the appropriate function calls according to the selected mode.
When the Show
/Hide
mode is selected, two RadioButton
s appear. Selecting one of these two will show a section (disabling all the validation inside it) and show the other (enabling all the validation inside it).
That wraps it all up.
Finally
I hope what I provided helps most of the readers. Please feel free to comment and provide suggestions or alternatives that will help improve. If you have appreciation for this article, show it by voting. Thank you.
Updates
- 17 May 2006:
- Made some modifications to the demo code.
evaluationfunction
is now used instead of controltovalidate
to check for validators.- Now the validation is re-enabled after the buttons are clicked (
onblur
). - Added a section "Server Side Validation".