The Code
This user script was specifically tested on Hotmail, Yahoo! Mail, and Google Personalized Home Page. By default, it will run on all pages except Gmail, where it is known to cause problems. If you find that it interferes with other sites you use, you should add them to the "Excluded pages" list in the Manage User Scripts dialog.
The basic functionality is fairly straightforward. I do want to draw attention to one specific function: NSResolver. This function is passed as a parameter to the document.evaluate function to execute an XPath query. Firefox's XPath engine uses the NSResolver function to evaluate namespace prefixes in the XPath expression. In XHTML 1.0 and 1.1 pages served with a "Contenttype: application/xhtml+xml" HTTP header, all the elements on the page will be in the XHTML namespace, http://www.w3.org/1999/xhtml. The script checks for this condition by testing whether document.documentElement.namespaceURI is defined. If the namespace is defined, the script constructs an XPath expression with an xhtml: prefix, to find elements in the XHTML namespace. When Firefox's XPath engine evaluates the expression, it calls the NSResolver function to resolve the xhtml: prefix, and then searches for the requested elements in that namespace.
Since we know that XHTML is the only namespace we'll ever use, we cheat a little bit and always return the XHTML namespace from the NSResolver function. But if a page used multiple namespaces (for example, an XHTML document with embedded MathML or SVG data), we could check the prefix parameter in the NSResolver function and return the appropriate namespace for XHTML, MathML, SVG, or any other XML vocabulary we wanted to include in our XPath query.
Save the following user script as checkrange.user.js:
// ==UserScript==
// @name Check Range
// @namespace http://squarefree.com/userscripts
// @description Multi-select a range of checkboxes
// @include *
// @exclude http*://mail.google.com/*
// ==/UserScript==
// based on code by Jesse Ruderman
// and included here with his gracious permission
var elmCurrentCheckbox = null;
function NSResolver(prefix) {
return 'http://www.w3.org/1999/xhtml';
}
function selectCheckboxRange(elmStart, elmEnd) {
var sQuery, elmLast;
if (document.documentElement.namespaceURI) {
sQuery = "//xhtml:input[@type='checkbox']";
} else {
sQuery = "//input[@type='checkbox']";
}
var snapCheckboxes = document.evaluate(sQuery, document, NSResolver,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
var i;
for (i = 0; i < snapCheckboxes.snapshotLength; i++) {
var elmCheckbox = snapCheckboxes.snapshotItem(i);
if (elmCheckbox == elmEnd) {
elmLast = elmStart;
break;
}
if (elmCheckbox == elmStart) {
elmLast = elmEnd;
break;
}
}
// note: intentionally re-using counter variable i
for (; (elmCheckbox = snapCheckboxes.snapshotItem(i)); ++i) {
if (elmCheckbox != elmStart &&
elmCheckbox != elmEnd &&
elmCheckbox.checked != elmStart.checked) {
// Fire are onclick event instead of modifying the checkbox's
// value directly, fire an onclick event. Yahoo! Mail and
// Google Personalize have onclick handlers on their
// checkboxes. This will also trigger an onchange event,
// which some sites rely on.
var event = document.createEvent("MouseEvents");
event.initEvent("click", true, false);
elmCheckbox.dispatchEvent(event);
}
if (elmCheckbox == elmLast) { break; }
}
}
function handleChange(event) {
var elmTarget = event.target;
if (isCheckbox(elmTarget) &&
(event.button == 0 || event.keyCode == 32)) {
if (event.shiftKey && elmCurrentCheckbox) {
selectCheckboxRange(elmCurrentCheckbox, elmTarget);
}
elmCurrentCheckbox = elmTarget;
}
}
function isCheckbox(elm) {
return (elm.tagName.toUpperCase()=="INPUT" && elm.type=="checkbox");
}
document.documentElement.addEventListener("keyup", handleChange, true);
document.documentElement.addEventListener("click", handleChange, true);