Monday, July 25, 2005

Check All/Uncheck All JavaScript buttons: Take 2

The trouble with JavaScript widgets is that they aren't self-aware, and they're typically designed by someone who has minimal assumptions about the page they're on (or the browsers they're compatible with). DHTML magnifiers assume the content they're meant to magnify is safely at the top of the page, and break when it isn't. Check All/Uncheck All button scripts don't account for the possibility of subranges. And so on and so forth.

One of our upcoming pages on the site redo is a form with several checkboxes all within the same name group, but subdivided into academic disciplines. The CGI is still expecting the same form data it did before these C/U buttons were added.

The basic script for checking all boxes goes as follows: an onclick handler passes the entire Input collection to a script, which loops over it and sets the input.checked property to true or false, based on a flag which is flipped at the end of the routine, right before toggling the button text. The trouble here is subranges.

Fortunately, the subdivisions are separate tables which can be given IDs: the onclick handler sends instead the ID, and walking the DOM tree, the script collects just the inputs which are children of that ID.
function check(ctainer) {
var field = document.getElementById(ctainer).getElementsByTagName("input");
for (i = 0; i < field.length; i++) {
if (field[i].type.toLowerCase()=="checkbox") {field[i].checked = !checkflag;} 
}
checkflag = !checkflag;
return (checkflag) ? "Uncheck All":"Check All";
}

Here comes the complexity. The Check All/Uncheck All buttons are useless and confusing to scriptless UAs. Being associated with black magic, they should be inserted dynamically using black magic. In The Good Old Days we would merrily employ document.write or innerHTML to insert the content.

Well, when you serve pages as XHTML, those methods disappear. Poof. And the reason is simple. XHTML is supposed to be valid XML. If a script can insert any text string into a page, it can break the XML. So, instead we create the C/U button by constructing an input element, feeding it the necessary attributes, and finding an appropriate node to insert it into. 'tdid' is the ID for the table, and 'garth' is the ID of the TH element whose contents the button will be appended to.
function makeButt(tdid,garth) { 
var l=document.createElement('input');
l.setAttribute('type','button');
l.setAttribute('value','Check All');
l.onclick = function() { this.value=check(garth); } 
document.getElementById(tdid).appendChild(l);
}

The theory is that all elements will be manipulated through setAttribute and removeAttribute, and tag specific methods will be deprecated. The reality is that IE 5/6 has a somewhat incomplete model for which attributes this works. People discovered that in IE, setAttribute didn't like events. So, we're attaching the check function directly to the object's onclick method.

If you were paying attention above, you'll notice the check function could have used setAttribute to check the boxes and removeAttribute to uncheck them, instead of a tag specific method. The reason it doesn't is because IE can't remove the checked attribute. It just can't. It can, however, set the attribute value to "" which unchecks the box... except in every other browser, which follow W3C spec on arguments and formerly collapsed attributes. If any value, even a blank one, is attributed to a collapsed attribute in HTML code, it's considered to be the same as the collapsed attribute in HTML 3. FF and Opera treat setting the value="" as checking the box.

The only way to do it in JavaScript is to pack both methods into an if/then subroutine, with the IE method first and the DOM method second. Forked code sucks.

Oh, and why check the input elements for whether they're checkboxes? The C/U buttons are themselves input elements and should be excluded. Currently, no browser complains when you manipulate the checked property of an input element which isn't supposed to have one (even in XHTML mode), but it isn't inconceivable this too will someday break.