Before:
var xulScriptlet;
var blueprint = document.getElementById('blueprints').firstChild;
while(blueprint) {
if(blueprint.getAttribute('class') == 'scriptlet')
break;
blueprint = blueprint.nextSibling;
}
var xulScriptlet = blueprint.cloneNode(true);
xulScriptlet.getElementsByAttribute('class', 'name')[0].value =
scriptlet.info.name;
xulScriptlet.getElementsByAttribute('class', 'version')[0].value =
scriptlet.info.version;
document.getElementById('scriptlets').appendChild(xulScriptlet);
After…
var xulScriptlet = $('#blueprints > .scriptlet)._.cloneNode(true);
$(xulScriptlet).$('.name')._.value = scriptlet.info.name;
$(xulScriptlet).$('.name')._.version = scriptlet.info.version;
$('#scriptlets')._.appendChild(xulScriptlet);
I got tired of writing DOM code, and envious of how the excellent jQuery uses CSS selectors. However, I needed a couple of things more than jQuery offered and a couple of hundreds less, I needed them in XUL rather than in HTML, and since Joel turned my XHTML templating system into an XML-to-Erlang compiler, I have been dying to write something with a “compile()” function in it. A few hours of healthy hacking later, the result was in the SamePlace repository.
(It was also an excercise in what I call API transparency, about which I’ll hopefully write soon.)
Here’s a quick rundown on how to use it. The ”$” function opens a “query environment”, where you can write using (a subset of) CSS selector syntax. Examples:
$('#foo')
$('.bar')
$('#foo > label')
$('#foo [hidden="true"])
You can chain query environments, so these two lines are equivalent:
$('#foo > label')
$('#foo').$('> label')
To escape the query environment and get to the DOM world, use ”_” for the first matched element and “_a” for an array of all matched elements:
$('#foo > label')._.value = 'hello, world!';
$('#foo > label')._a.forEach(function(label) { label.value = 'hello, world!'; });
If you already have a DOM element to start searching from, pass that to ”$()”; it will set the context for subsequent queries:
var xulFoo = $('#foo');
$(xulFoo).$('> .name')._.value = 'Ben';
$(xulFoo).$('> .surname')._.value = 'Bitdiddle';
As I said, I needed a couple of extra things: parent and ancestor selectors, respectively mapped to ”<” and ”^”. For example, to iterate over all boxes which are parent of hidden elements:
$('[hidden="true"] < box')._a.forEach(function(box) { doSomething(box); });
Limitations: search by class maps to getElementsByAttribute()
, so $(”.bar”) will only find elements with class="bar"
and not elements with class="bar baz"
. I didn’t want to compile down to XPath (which isn’t available in Thunderbird), and using JavaScript to walk an element’s subtree and test each descendant’s attribute for a match was a terrible performance hit. Also, I’m not happy at all with the “_a” property, can someone think of a less ugly name?
Anyway, enjoy, and let me know if it proves useful.
Trivia: it is said of obscure programmers that “they can’t understand they’re own code the day after they’ve written it”. I don’t usually consider myself “obscure”, but I believe I found a new low in the above criteria:
return function(context) {
if(!('length' in context))
context = [context];
return fold(
function(finder, candidates) {
return fold(
function(candidate, newCandidates) {
return newCandidates.concat(
Array.slice(finder(candidate)));
}, [], candidates);
},
context, finders);
}
Which, I confess, I wasn’t understanding while I was writing it.