Recently, Matt Berseth blogged about his adventures of taking some of the jQuery goodness and flexability and re-creating it for use with ASP.Net AJAX.  Now I've been reading Matt's blog for some time now (and if you haven't been, then you should!) and I've been a fan of his depth of knowledge when it comes to AJAX and the AJAXControlToolkit.  More to the point, I think that we're on the same side of the fence when it comes to the thought that the user experience is paramount to an accepted software solution.

What REALLY caught my eye about that post, was the way he figured out to use a "master" checkbox to uncheck and check a group of checkboxes.  Ye 'ole "Check/Uncheck All".  Having had a need to do that very same thing.. and not wanting to write a page and a half of DOM driven javascript to make it happen...  I downloaded his sample and set forth on putting the magic into my app. 

It didn't work.

So I sat there with a perplexed look on my face pondering why the code worked so well in the demo application and not my application.  I had done everything "the same" as it were.  Then, upon closer inspection of the demo app, it hit me like a ton of bricks.  Matt isn't use ASP:CheckBoxes in the demo.. but plain 'Ole HTML checkboxes.

   1: <tr><th><input type="checkbox" class="checkall" /></th><th>Description</th><th>Comments</th><th>More Comments</th></tr>
   2: <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   3: <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   4: <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   5: <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   6: <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>
   7: <tr><td><input class="groupclass" name="group" type="checkbox"/></td><td>Some Text</td><td>Some Text</td><td>Some Text</td></tr>

That of course, just wouldn't do.  So I went ahead and created a trimmed down version of his sample page just to work with the ASP.Net checkboxes

   1: <asp:CheckBox class="checkall" ID="chkMasterCheck" runat="server" Text="Master Checkbox (Toggle check/uncheck all)" /><br /><br />
   2: <asp:CheckBox class="groupclass" ID="chkChild1" runat="server" Text="Child CheckBox 1" /><br />
   3: <asp:CheckBox class="groupclass" ID="chkChild2" runat="server" Text="Child CheckBox 2" /><br />
   4: <asp:CheckBox class="groupclass" ID="chkChild3" runat="server" Text="Child CheckBox 3" /><br />
   5: <asp:CheckBox class="groupclass" ID="chkChild4" runat="server" Text="Child CheckBox 4" /><br />

After looking at the code that Matt provided, I realized the issue was with the getElementsByClassName function in majax.js... specifically, where it's looping through all of the elements and comparing each element's className to the search pattern:

   1: for(var l=0, ll=elements.length; l<ll; l+=1){
   2:     current = elements[l];
   3:     match = false;
   4:     for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
   5:         match = classesToCheck[m].test(current.className);
   6:         if (!match) {
   7:             break;
   8:         }
   9:     }
  10:     if (match) {
  11:         returnElements.push(current);
  12:     }
  13: }

Now, in all fairness to Matt of course, the problem really isn't with the way he's looping through the elements, but more specifically with how ASP.Net renders certain input controls.. like checkbox, radiobutton, etc.  When looking at the rendered DOM from the ASP.Net checkbox sample page, it becomes painfully clear where the problem lies:

   1: <span class="checkall"><input id="chkMasterCheck" type="checkbox" name="chkMasterCheck" /><label for="chkMasterCheck">Master Checkbox (Toggle check/uncheck all)</label></span><br /><br />
   2: <span class="groupclass"><input id="chkChild1" type="checkbox" name="chkChild1" /><label for="chkChild1">Child CheckBox 1</label></span><br />
   3: <span class="groupclass"><input id="chkChild2" type="checkbox" name="chkChild2" /><label for="chkChild2">Child CheckBox 2</label></span><br />
   4: <span class="groupclass"><input id="chkChild3" type="checkbox" name="chkChild3" /><label for="chkChild3">Child CheckBox 3</label></span><br />
   5: <span class="groupclass"><input id="chkChild4" type="checkbox" name="chkChild4" /><label for="chkChild4">Child CheckBox 4</label></span><br />

As you can see, when ASP.Net renders it's checkbox control, it helpfully splits it into an input and label html control, and conveniently wraps it all in a nice span tag for us.  How quaint.  So when Matt's getElementsByClassName method runs, it does indeed find an element to add to the group.  It's just that it finds a SPAN instead of the INPUT that we want it to find.

Knowing this information, we can make a quick change to the javascript routine to check that if it does find an element with the className we're after, and that element is in fact a SPAN, then we really should be looking to see if the first child element is and INPUT and use that to add to the group instead:

   1: if (match) {
   2:     if (current.tagName == "SPAN")
   3:         if (current.firstChild)
   4:             if (current.firstChild.tagName == "INPUT")
   5:                 current = current.firstChild;
   6:     returnElements.push(current);
   7: }

Now of course, I've not done any cross browser compatibility checks or anything like that.  I'll leave that as an exercise for the reader :)

You can download Matt's original sample project from his blog post, you can download my modifications here.