Now I know the title sounds presumptuous, but there’s a certain methodology I’ve settled into that seems to work really well for encouraging Javascript that’s legible and safe. I thought I’d share it with anyone that doesn’t consider themselves a JS playa, in case it’s of some use to you too.
Most Javascript libraries these days are written in a similar way, so it seems to be de facto recognised best practice, but it’s worth showing the anatomy of the simple case so you can build on it rather than having to work out what’s going on from an enormous, somewhat crufty sprawl.
/*
@description Javascript template
@createdBy JPS
@createdOn 2006-10-03
@notes Standard template
*/
MyObjectWithHandyName = {
// Properties
p: {
// HTML IDs
i: { },
// HTML classes
c: { },
// Something else we might need to reference
sthg: {}
// Be wary of accidental trailing commas here, as it’s the end of
// the array and IE doesn’t like a comma at that position
},
// Methods here - use hierarchy if large object
DOM: {},
eyeCandy: { dropdowns: {}, errors: {} },
httpReq: {},
// Window onload method - instantiates everything
go: function() {
alert(’OK!’);
}
};
// Now add onload handler to do anything your object needs to do when page loads
// Prototype
// Event.observe(window, ‘onload’, MyObjectWithHandyName.go, false);
// Mochikit & others
// addLoadEvent(MyObjectWithHandyName.go);
// No library?
window.onload = MyObjectWithHandyName.go;
What’re the advantages of the above? Well, first of all, it just formalizes what you’ve already decided to do: that is, to encapsulate all the functionality to do with a certain something in one file. This just puts it all in one object, which you could call DHTML
, or iFoo
, or GoogleHack
, or MyApp
. It prevents collisions with standard Javascript functions, library functions you might include etc. Also, if in future you want to know if a function has been defined on a page, but from a different Javascript file, it’s sufficient for a smallish project to check the top-level object exists.
Secondly, the system is very extensible, and tidy with it. If configuration variables go in the hierarchical p
(roperties) block at the top of the file, then you can re-use your code by, say, including a second Javascript file on certain pages, that rewrites this configuration. You can even change methods like this, if you know where they’re going to be, in a safe, extensible way. The hierarchy of the whole object means you can nest methods as far down as you want: then, if you find yourself repeating much of the hierarchy, you can use the with(object)
control structure to tidy your code:
foo: { bar: { quux: { a: function() {…}, b: function() {…} } } },
blort: {
with(foo.bar.quux) {
a();
b();
a(b(a()));
}
foo.bar.quux.a();
}
Thirdly, it’s easy to maintain. Encapsulation and a certain predictability, and the encouragement to make methods small and put them somewhere that makes sense rather than build e.g. sprawling validation methods that, oh, do a bit of browser sniffing as well, and a bit of alert()
calling… this definitely forces me to be careful in what I write, and that puts me in a good position to fix things later on.
I can’t say I’ve done any serious testing, but this way of building functionality seems far more robust, and exits more gracefully (on most decent browsers), than other paradigms for Javascript design. It’s possible this is how the whole Javascript community is now coding and I’m teaching my grannies to suck eggs: certainly it’s not how you’d code given ten minutes on Google, so it probably bears repeating.
A few caveats, of course, because there is no silver bullet:
- All function definitions go in
MyObjectWithHandyName
: nothing outside that apart from the onload to do any actual function calls. - Any text used more than once (URLs, HTML classes, alert text, repalcement text etc.) goes in
p
at the top of the script. - Any event handlers should be wary of what they get as their first parameter, and what the
this
object refers to: depending on how they get called, that might change from event to element to their hierarchical container inMyObjectWithHandyName
. - Trailing commas: at the end of an associative array, don’t leave a trailing comma as Firefox will quietly ignore it but IE will give one of its typically opaque syntax errors.
- Similarly, don’t omit commas between elements—
{ a: {} b: {} }
is wrong—as all browsers will die. Easily done, if you’re writing a new method and you forget thego()
already exists.
Anyway, give it a go and see what you think.