Validating CheckBoxLists when using ClientIDMode=Predictable
A common question on the www.asp.net forums asks how to validate a CheckBoxList control. There are two cases:
- Require at least one checkbox
- Require a specific number of checkboxes
In the past, I’ve answered this question in a way that now breaks when the new ClientIDMode property is set to Predictable. (For more, see “The impact of ClientIDMode=Predictable”.)
The old way
Look at the HTML generated by the CheckBoxList control. It has a specific pattern where every <input type="checkbox"> tag has an ID based on the ClientID of the control.
<span id="CheckBoxList1">
<input type="checkbox" id="CheckBoxList1_0" >
<input type="checkbox" id="CheckBoxList1_1">
<input type="checkbox" id="CheckBoxList1_2">
<input type="checkbox" id="CheckBoxList1_3">
</span>
I recommended using a CustomValidator. The server side code is easy.
protected void RequiredCBL(object source, ServerValidateEventArgs args)
{
int count = 0;
CustomValidator val = (CustomValidator) source;
CheckBoxList cbl = (CheckBoxList) val.FindControl(val.ControlToValidate);
foreach (ListItem item in cbl.Items)
if (item.Selected)
count++;
args.IsValid = (count > 0);
}
The client-side code I used to recommend looked like this:
<script type='text/javascript'>
function RequiredCBL(sender, args)
{
var count = 0;
for (var i = 0; true; i++)
{
var listitem = document.getElementById(sender.controltovalidate + "_" + i.toString());
if (!listitem)
break; //!done
if (listitem.checked)
count++;
}
args.IsValid = count > 0;
}
</script>
ClientIDMode=Predictable can change the id= attributes of the <input type='checkbox'> elements by appending “_row#” when the control is inside ListView and FormView.
<span id="CheckBoxList1">
<input type="checkbox" id="CheckBoxList1_0_0" >
<input type="checkbox" id="CheckBoxList1_1_0">
<input type="checkbox" id="CheckBoxList1_2_0">
<input type="checkbox" id="CheckBoxList1_3_0">
</span>
Unfortunately, you cannot use the previous client-side script as this breaks:
var listitem = document.getElementById(sender.controltovalidate + "_" + i.toString());
and you cannot just do this:
var listitem = document.getElementById(sender.controltovalidate + "_" + i.toString() + "_0");
because the additional component depends on the row number.
The new way
Microsoft’s web validation code has to handle this for RadioButtonLists. It does so by searching the child HTML tree of the CheckBoxList. Let’s use that technique, but my implementation creates an array of DHTML input elements once to be reused.
<script type='text/javascript'>
function RequiredCBL(sender, args)
{
var count = 0;
var checkboxes = GetBtnList(document.getElementById(sender.controltovalidate));
for (var i = 0; i < checkboxes.length; i++)
{
if (checkboxes[i].checked)
count++;
}
args.IsValid = count > 0;
}
// Returns a list of input elements contained within a CheckBoxList or RadioButtonList control's HTML
// The first call creates that list through a search of the child node tree.
// The result is an array of input elements in the order they were found.
function GetBtnList(pFld)
{
function FindChildren(array, parent)
{
for (var vI = 0; vI < parent.childNodes.length; vI++)
{
var vC = parent.childNodes[vI];
if (vC.tagName == "INPUT")
array[array.length] = vC;
else
FindChildren(array, vC); // RECURSIVE
} // for
}
var vCB = pFld.childbuttons;
if (!vCB)
{
vCB = new Array();
FindChildren(vCB, pFld);
pFld.childbuttons = vCB;
}
return vCB;
} // GetBtnList
</script>