Introduction
The .NET Framework provides for both client-side and server-side validation. Client-side validation can avoid the time involved in sending a form to the server, only to have it rejected because of a validation error. It can notify the user of errors very quickly. Client-side cross-validation of multiple checkbox
es is problematic because of when the .NET Framework does client-side validation. Unless the programmer gives particular attention to checkbox
validation, the user may see messages that appear to respond to exactly the opposite of what he is doing.
A Solution to the Problem
The Framework does client-side validation when the Change
event occurs. This occurs after the user has finished making any changes to the value of the control and moves focus elsewhere. The problem occurs when "elsewhere" is to another checkbox
in the set of checkbox
es being validated. Consider the following scenario: The validation rule is that at least one of two adjacent checkbox
es must be checked. The script implementing this rule is set up to run for both checkbox
es. Assume that the second checkbox
starts out unchecked and that the user has just unchecked the first one and has clicked the second one to check it. The user will immediately see a check appear in the second box. Since clicking the second box causes the first box to lose focus, the validation script for the first box will run. Since the user has just unchecked the first box, the validation script will find it unchecked. The script is running in response to the first box's Change
event, triggered by the focus switching to the second checkbox
. At the moment of the focus change, the validation script finds the second box unchecked. Validation fails, and the user gets an error message. But from the user's point of view, this is wrong, since what he sees is that he has just checked the second box. The message says that neither is checked, but the user sees the check mark in the second box. Had the first box lost focus by the user putting it elsewhere, the result of the validation would make a lot more sense to the user.
It's important, then, to make sure that focus on one checkbox
isn't lost as a result of the user checking a different box. The approach that I present here is to cause validation on a Mouseout
event, causing the validation to occur before the user has a chance to click the other checkbox
. Checkbox
es may have associated labels, clicks on which also cause the checkbox
status to toggle. The code presented here also handles these labels.
The technique used to force validation should not change focus to another control, as this would confuse the tab order. The technique presented here raises a Blur
event on the checkbox
control, causing the validation script to run, and then focuses once again on the checkbox
itself.
Using the Code
Here's the code that you'll need to add to your page to implement this validator. This sample verifies that at least one of three checkbox
es is checked. The names of the controls appear in both the client-side and server-side validation functions. You will have to modify these functions to use the names of your checkbox
controls.
Put this script at the top of your page after the <meta>
tags. If you are validating within a user control, put it at the top of the control page. The first function is the client-side validator. The second causes a focus change when the mouse leaves the checkbox
that is to be validated.
<script type="text/javascript" language="javascript">
function AtLeastOneCheckbox_ClientValidate(source, args)
{
args.IsValid =
document.getElementById("<%=cbxInProgress.ClientID %>").checked
|| document.getElementById("<%=cbxComplete.ClientID %>").checked
|| document.getElementById("<%=cbxCancelled.ClientID %>").checked;
}
function BlurThenRefocus()
{
if(document.activeElement == window.event.srcElement
|| window.event.srcElement.nodeName == 'LABEL'
&& document.activeElement.id == window.event.srcElement.htmlFor)
{
document.activeElement.blur();
if(window.event.srcElement.nodeName == 'LABEL')
{
document.getElementById
(window.event.srcElement.htmlFor).focus();
}
else
{
window.event.srcElement.focus();
}
}
}
</script>
In your checkbox
definitions, specify BlurThenRefocus()
as the onMouseOut
event handler. Add the custom validator where you would like the error message to be displayed. Here's an example of three validated checkbox
es followed by a CustomValidator
for the boxes. The ClientValidatorFunction
refers to the AtLeastOneCheckbox_ClientValidate
function defined above. The OnServerValidate
function will be defined in the server-side code below.
<asp:checkbox id="cbxInProgress" Runat="server"
Text="In Progress" tabIndex="1" onmouseout="BlurThenRefocus()">
</asp:checkbox>
<asp:checkbox id="cbxComplete" Runat="server" Text="Complete"
tabIndex="2" onmouseout="BlurThenRefocus()">
</asp:checkbox>
<asp:checkbox id="cbxCancelled" Runat="server" Text="Cancelled"
tabIndex="3" onmouseout="BlurThenRefocus()">
</asp:checkbox>
<asp:CustomValidator id="AtLeastOneCheckbox" runat="server"
ErrorMessage="Check at least one status"
Display="Dynamic"
OnServerValidate="AtLeastOneCheckbox_ServerValidate"
ClientValidationFunction="AtLeastOneCheckbox_ClientValidate" />
Next we cause this CustomValidator
to be executed when the value of any of the three checkbox
es is changed. Add the code below just before the </form>
tag at the bottom of the page or at the very bottom of the user-control page. It associates the CustomValidator
with each of the checkbox
es. This is done instead of specifying a ControlToValidate
in the CustomValidator
definition above, since it will validate three different controls.
<script type="text/javascript">
ValidatorHookupControlID("<%= cbxInProgress.ClientID %>",
document.getElementById("<%= AtLeastOneCheckbox.ClientID %>"));
ValidatorHookupControlID("<%= cbxComplete.ClientID %>",
document.getElementById("<%= AtLeastOneCheckbox.ClientID %>"));
ValidatorHookupControlID("<%= cbxCancelled.ClientID %>",
document.getElementById("<%= AtLeastOneCheckbox.ClientID %>"));
</script>
All that's left now is to define the server-side validation function. It returns its result in its second argument. Put this code where you define your server-side methods.
public void AtLeastOneCheckbox_ServerValidate
(object source, ServerValidateEventArgs args)
{
args.IsValid = cbxInProgress.Checked || cbxComplete.Checked
|| cbxCancelled.Checked;
}
Running the Example Project
The example contains three checkbox
es, a Submit button, and a Validation Summary box. When you first run the program, you will notice that as you pass the cursor over the checkbox
es, no messages appear. This is because validation is performed only when the status of a box changes. However, if you click the Submit button, the server-side validation code will run, displaying the validation error. Once you have changed the status of a checkbox
, however, the status messages that you will see (or the removal thereof) are the results of client-side validation. The example's Validation Summary box exists only to help you tell whether the client-side or the server-side validation is presenting or clearing the message. Only the server-side validation function writes into the summary box. If client-side validation fails, clicking the Submit button will not result in a page post-back to the server.
You may wish to experiment with using the Tab key and the space bar on your keyboard to navigate the page and change the status of the checkbox
es.
Sources
The technique that I use for applying common validation to several controls comes from the article CustomValidator dependent on multiple controls by DanielHac.
History
- 11/27/2007: Original article