with Imagination: by Dustin Diaz

./with Imagination

A JavaScript, CSS, XHTML web log focusing on usability and accessibility by Dustin Diaz

The “add a class name” JavaScript widget philosophy with “Fantasticulator”

Thursday, August 24th, 2006

There’s a disturbing trend going around in JavaScript widget-making land that even I myself have been sentenced to being guilty as charged, and that is the view of “adding a class name” to invoke the behavior of some JavaScript widget. Of course this may not sound like that big of a deal and in retrospect it was a very cool idea which made it very simple to implement widgets blindly without having to write any real JavaScript yourself. The fact of the matter is, there can be some heavy side-effects that most intermediate developers don’t want to pay attention to.

Examples of “adding the class name”

The implementation of this idea is simple. Let’s say we want to make a new widget called Fantasticulator which is a new hip web 2.0 widget you can embed directly onto your webpage simply by adding the JavaScript library into the head of your document like so:

Embedding the Fantasticulator Engine

<script src='lib/js/fantasticulator.js'></script>

Oh and let’s not forget to add our dependency files to really make this thing Fantastic!

Fantasticulator dependency files

<script src='lib/js/prototype.js'></script>
<script src='lib/js/scriptaculous.js'></script>
<script src='lib/js/jquery.js'></script>
<script src='lib/js/yahoo.js'></script>
<script src='lib/js/dom.js'></script>

Then to give your document ammunition to trigger the Fantasticulator all you have to do is add the class name of “fantacy” to any element in your page. See below for a demonstration.

Embedding your fantacies


<p><a href="#" class="fantacy">JavaScript Foo</a></p>

Why this is bad

Not to dismiss its conveniency, but it fails in performance, it’s demanding, and it’s a high risk for conflicts.

Performance

Without looking under the hood of any library that requires you to add a class name to your document to receive the super-cool functionality, it’s fairly obvious what needs to get done. Like any other getElementsByClassName function, you need to supply a class name, a tag name, and a root node. And to generalize things to a base level that gives the implementer the most amount of flexibility, you must start at the document root then crawl all the way to the bottom checking each and every node for a class name. To say the least… that’s bad. Client-side performance of the “add a class name” philosophy is reason-nuf (reason enough) to avoid implementing this style of widget-making.

Demanding

Generally, you want to avoid contracts that force the implementer to muck with their HTML. But like I said, albeit its downright convenient method of adding behavior for developers, you shouldn’t make this assumption that access to snippets of HTML is going to be possible. But rather it should be a best practice to target the elements yourself which gives you the freedom of optimization, and then pass those elements to the widget machine.

Risky Business

In any case, it gets a bit risky when libraries are dependent on a class name existing in the markup. Especially in the era of web 2.0 JavaScript Foo, you may run into issues of class names getting overridden by replacement or removal functions. If Developer Joe runs Utils.nukeClasses() before Developer Schmoe runs Fantasticulator.init(), you’ve got a conflict.

Anyway, here’s what you do

By far the simplest solution to put into your next Fantasticulator widget is to accept an argument where the developer is allowed to pass in elements. Yes, it requires more work for the developer, but in the end it’s faster, less demanding, and safer.

Pass the buck


<script>
...
var parent = document.getElementById('foo');
var elements = parent.getElementsByTagName('a');
var Fantacy = new Fantasticulator(elements);
Fantacy.init();
...
</script>

28 Responses to “The “add a class name” JavaScript widget philosophy with “Fantasticulator””

  1. Jim

    I have to say that at the scale most of us are working in, it won’t make any real difference. There is little risk of me clashing with another element by the same class name, and the speed difference certainly won’t be noticeable on anything I produce.

    However, I appreciate the knowledge as I am sure that when working on projects of much larger scale (like you do at Yahoo! and like I would someday like to get to) I am sure it really does make a difference.

    Jim

  2. Joseph Scott

    I wrote a Flickr like edit in place chunk of code and had requests to implement the “add a class name” technique. Although I didn’t spell it out as well, I resisted such requests because it just seemed wrong to require specific class names for features to work.

    The performance issue is another very strong argument against this approach.

  3. Ross

    I agree in theory, but I have to admin that I’ve been guilty of using this approach on more that one occasion. I guess it all comes down to the expected audience of your widget: if you are targeting people designing large-scale, high-performance sites, then they can handle the extra Javascript and will appreciate the performace benefits. But if you’re targeting novices, it’s hard to beat the convenience of the class approach since they don’t have to touch the JS code.

    It would be interesting to see the actual performance benefits gained on an average site from passing elements directly to a widget, over parsing the document to grab elements with a given class.

  4. Andrew Dupont

    Can’t you do both? Can’t you have an effect that’s smart enough to apply itself to any element with a particular class name, but allow that effect to be applied “manually” instead?

    I’m not unsympathetic about the performance issue of getElementsByClassName, but compared to things like $$/getElementsBySelector it’s the freaking Road Runner. As long as you limit a getElementsByClassName search to the narrowest scope you need, the performance is really not that bad. Plus Firefox, Opera 9, and the WebKit nightlies can do this with lightning-fast DOM Level 2 XPath.

    Also, you say “it’s a high risk for conflicts,” but I’m not sure what you mean. Any number of class names can be assigned to a given element — that’s one of the reasons why this technique is so widespread.

  5. Mark Wubben

    If you use a different script which messes with your class names like that, it can’t really be called unobtrusive – so why would you use it? :)

    I think, as Andrew noted, it’s fine to do this as long as the scope is narrow enough. The FantasticTree script will work rather well if it’s limited to ul and ol elements.

  6. Dustin Diaz

    Andrew, the risk was stated as an example in the article. Eg: Developer Joe runs Utils.nukeClasses() directly before Developer Schmoe runs Fantasticulator.init(). Or better yet, a designer can also come in and not even know what’s going on and just switches out the class name manually.

    Keep in mind, none of this is to say it’s all that bad. I believe it’s a wide-spread technique because it’s a lazy man’s way of implementing a widget. As I previously mentioned - it is in fact, down right convenient. This article is aimed more at high level widgets and if you want to “play nice with the other JavaScript kids” then this technique is something you’d want to avoid.

  7. Andrew Dupont

    I’m sorry, I guess I misinterpreted the “Why this is bad” heading. :-)

    And, yeah, I missed that part at the end. But I echo what Mark said — what sort of script would nuke class names? For any reason? I’m not saying it’d never happen, but by that logic I shouldn’t rely upon anything being in my DOM tree, since a previous script might have removed all nodes from the document.

    I definitely get the point you’re making, but I think I’d rephrase it: any script that requires you to change your markup in any way can’t be called unobtrusive. But all the scripts that operate this way (that I can think of) simply use it as a shortcut. You can gather the DOM nodes any way you want and feed them into a script manually.

  8. Stuart Langridge

    First: if you’re going to worry about someone running nukeClasses() first, then all bets are off. If someone runs nukeIds() first then your code example won’t work either.

    Second: we’re trying to encourage people to put script in external files, right? So they’ll need a little startUpAllMyShit.js file which “initialises” everything. That’s a pain.

    Third: what’s to stop the designer removing or changing an ID, which will also break the code?

    Fourthly, if you’re doing it right, using classes is semantic. You’re not saying “apply the class fantastico to your to provide a hook for the JavaScript”, you’re saying “this is a ‘fantastico’-type list”. The fact that it’s *made* into a fantastico-type list by some JS running is beside the point here.

    I entirely concede the point about collides, though; take my sorttable thing, for example. It’s quite likely that some other sorting script might want to trigger on the class “sortable”, and that could be a problem. Explicit initiation is so *inelegant*, though!

  9. Dustin Diaz

    So maybe nukeClasses was a bad example. But mucking with class names is still very common.

    I think the reasons for the “add a class name approach” are very well taken and undoubtly have their benefits. The point is to proceed with caution - not to mention the performance issues alone as I mentioned were “reason enough” to avoid it. Walking the entire DOM (just to parse class names) on small or big sites is never a good practice. When using getElementsByClassName functions you should most definitely take the steps of adding a tag name and parent node to make your queries run faster.

    Of course if browsers added native xPath support which ran super fast, all would be well in the land of performance.

    Re: Stuart: “Fourthly, if you’re doing it right, using classes is semantic”

    – so when you’re doing it wrong, are they still semantic? Or is it by nature that doing it right means they’re semantic? Or is making your class names semantic what makes it right?

    Also I’m not sure how “explicit initiation” removes the elegance. I disagree with this.

    In a case for some drag ‘n drop library, I don’t find it inelegant to do the following:

    var module = new DragDrop('my-id');
    // or rather
    var modParent = document.getElementById('foo');
    var modules = modParent.getElementsByTagName('li');
    new DragDrop(modules);
    

    That’s just normal implementation in my own view. Not to mention I don’t think we’ll be finding the name “draggable” in the microformat list any time soon (yes, another bad example).

    Overall, I think that most advanced developers can agree that allowing both methods to invoke your script is a good idea. As Andrew mentioned in comment #4, making both methods avail in your widget API will allow maximum flexibility.

    Don’t get me wrong on this whole idea - I’m just bringing up something I don’t think has been talked about before. The last thing I wanted to do was start up another “considered harmful” post.

  10. Andrew Dupont

    “Of course if browsers added native xPath support which ran super fast, all would be well in the land of performance.”

    I’ve submitted a patch to Prototype that’d do this for getElementsByClassName and $$ (i.e. getElementsBySelector). Wouldn’t cover IE, but it’s a start.

    Dean Edwards has something like that in the works for his own library — and his solution accommodates IE as well. Knowing Dean, it’ll totally kick all other scripts’ asses.

  11. Jonathan Snook

    We have somewhat two issues at play here. 1) Should your super fantastico script do the grunt work of locating elements? I say no. Finding elements, and performing functionality on those can and probably should be separated out. In doing so, you offer the flexibility that your script can actually be used with multiple libraries (assuming no additional dependencies beyond using something like $ or $$). 2) even if we left it up to the end developer to pass in elements, what’s to stop them from using some generic getClassName function that isn’t optimized, either.

    Unfortunately, there will always be people who take the easy way out without considering the consequences of doing so.

  12. Már

    Dustin, I went throught the exact same thought process you describe, but my conclusion was to use an approach somewhere inbetween the two extremes you describe.

    All the widgets I write require the developer to manually initialize the widget (i.e. fantacy.init();). But instead of requiring the developers (”deployers”, really) to prepare the appropriate elements themselves, I set up a way for them to configure a CSS-like selector pattern describing the elements they want to target, and have the widget do the (not so) hard work.

    A decent $$/.getElementsBySelector implementation allows scoping and is furthermore is optimized so that “#myID” and “#myID a” selectors are almost just as fast as doing it manually with .getElementById and .getElementsByTagName…

    Although the approach you describe is fairly “elegant”, it still requires more bytes of code and a fair bit of Javascript knowledge from the people deploying and initializing the scripts, than simply copy-and-pasting-and-editing a few configuration lines with a simple CSS selector in them…

  13. Dustin Diaz

    @M?r: I actually like your approach as it seems to take in the best of both worlds and takes into consideration many of the things discussed in the comments.

  14. Joel Hughes

    Hi,
    I know where your coming from but I kinda see it from the other side. I.e. I like the idea of simply adding a script to a page, altering the markup and hey presto (no JS modifications - obviously this only suites a certain type of function).

    I agree that trawling through the DOM to find a class name is poor performance and poor use of class names.

    I would prefer to use a set of custom XHTML attribute and create your own custom DTD if you want to make sure everything still validates.

    I suppose it depends if the ‘widget’ has been created with developers or designers in mind.

    Joel

  15. Tobie

    Great post, lots of great remarks in the comments too.

    @Stuart Langridge: I don’t agree with your fourth point at all. (using class is semantic).

    Imagine the following: a grocery store has a website which includes tables of products and one of these tables contains all the different types of apples currently for sale. Being semantic here would, for example, imply adding a class of “fruits” to this table.

    If you wished for your table to be sortable, and needed to add a class name of “sortable” to do so, you would actually be loosing semantic value rather than gaining it. Apples are not sortable, apples are fruits!

    And even if one could argue that the above mentioned table is actually “a table of sortable fruits”, this would only be true for JavaScript enabled web-browsers. So, again, the semantic value of this added class name would be null in any other context (screen-reader, news-feed, etc…).

    A much better approach to this issue is what M?r describes above: being able to pass a series of DOM nodes to the library using CSS selectors or the like. I’ll gladly post an example using _cough…_ Prototype _cough…_ if Dustin allows me to do so… (I’m unfortunately not familiar with the YUI syntax at all) ;-).

  16. Dustin Diaz

    Tobie: As long as it’s JavaScript (and not like, Assembly), then this place is for open discussion. But as M?r pointed out, a decent selector init method could be the best of both worlds. Eg:

    var foo = new Fantasticulator('#foo li');
    foo.go();
  17. Paul

    Something that works like a charm but sadly won’t get your page validated:

    using custom tags like:

    This way you just use getElementsByTagName(’fantacy’) to retrieve all. Create a loop and run the javascript routines and replace the fantacy node with the newly created node.

  18. Tobie Langel

    Don’t worry Dustin, I’m as familiar with assembly as with YUI ;-)

    There is a speed issue with Már’s solution though, if more than one library/widget is to be applied to the same elements:

    var foo = new Fantasticulator('#foo li');
    foo.go();
    var bar = new web2ator('#foo li');
    bar.go();
    

    That’s one of the reasons I’d favor a slightly lower-level approach, more inline with what Jonathan suggested above:

    $$('#foo li').each(function(element){
      var foo = new Fantasticulator(element);
      foo.go();
      var bar = new web2ator(element);
      bar.go();
    });

    This solution also pushes stricter decoupling between what a library does and what it is applied to, which is generally a better coding practice.

  19. Antonio Collins

    I agree wholeheartedly with DD. Considering how passionate folks are about seperating presentation from content, I find it surprising that folks are overloading class (which is purely a presentation cue) to serve a behavioral purpose.

    I’ve resorted to namespacing with good results, but terrible feedback. So, in DD’s original example, my alternative would be:


    JavaScript Foo

    With namespacing, you don’t have to co-op any existing attributes and you gain the ability to attach parameters directly to the object in question. Designers working on the html more easily understand what scripted behavior will be applied to the object. And most page startup can be handled by a single routine that simply finds objects with certain attributes and instantiates any referenced handlers.

    As I said, I’ve received terrible feedback on this tactic. So far, I don’t think any of the feedback is discouraging. Most of it focuses on the level of acceptance of xhtml, but since most recent browsers preserve the namespaced attributes, I don’t think its any more of a concern than depending on xmlhttp.

  20. Antonio Collins

    first post here, so i didn’t expect the html to be interpreted. here’s the example i reference:

    <!DOCTYPE html …/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
    <html xmlns=”http://www.w3.org/1999/xhtml” xmlns:my=”urn:my-stuff”>…
    <p><a href=”#” class=”fantacy”>JavaScript Foo</a></p>

  21. Antonio Collins

    geez, forgot to make my changes!

    &ltp><a href=”#” my:handler=”Fantasticulator” my:effect=”pizzaz” my:auto-refresh=”false”>JavaScript Foo&lt/a></p>

  22. Joel Hughes

    Antonio,
    will that validate? Do you not need to supply a custom DTD as well?

    Looks great though I think, the custom namespace can really help differentiate (well, maybe if something more meaningful than ‘my’ is used ;-)

    Previously I have used http://alistapart.com/articles/customdtd/ to help hack the DTD (to make everything validates).

    Joel

  23. Mar

    @Tobie (comment 18 above) wrote:

    “There is a speed issue with Mar’s solution though, if more than one library/widget is to be applied to the same elements”

    Good point Tobie.

    In fact one of the design changes I have in store for my widget libraries is to make them able also accept an array of elementRefs in addition to the current CSS-style selector string.

    This is always a tradeoff between machine-performance vs. ease-of-deployment (developer-porformance), and I guess we’re all trying to hit the sweet-spot in between the two.

  24. Antonio Collins

    Joel:
    No this won’t validate in most xhtml tools because DTDs are brain-dead when it comes to namespaces. However, it will validate in most tools that support schemas and are namespace-aware.

    Failed validation is another reason for some of the negative feedback I got. But just because some tools doesn’t how to deal with X doesn’t mean that X is wrong.

  25. Már

    Antonio, my main objection to the “custom-attributes” approach has very little to do with validation, and more to do with what seems to be lack of seperation between (A) content/structure, (B) presentation and (C) behaviour.

    Your example…

    <p><a href=”#” my:handler=”Fantasticulator” my:effect=”pizzaz” my:auto-refresh=”false”>JavaScript Foo</a></p>

    …seems to mix A and C together quite a bit, and makes configuring and tweaking the behaviour of widgets the HTML programmer’s job.

    Again, I’m not trying to rain on your parade, I know you’re trying to hit the mythical “sweet-spot” like the rest of us. This is just my opinion, and one of the reasons I don’t like the Dojo-toolkit.

  26. Már

    @Dustin, neither your HTML nor your web-server explicitly states that your pages are encoded as ISO-8859-1, so Firefox (1.5, WinXP) seems to default to assuming it’s UTF-8 and thus makes a royal mess of most non-ASCII characters.

    (Sorry, I just hate to see my name displayed as “M?r” :-)

  27. Oliver Clevont

    Why not just name your class names based on their semantics rather than their behavior. Then you won’t have this philosophical conundrum. So instead of giving it a class name of “sortable”, call it “ordered-list” and have your widget system infer that ordered-lists should have a sortable behavior.

  28. Zac McCormick

    @M�r

    “…seems to mix A and C together quite a bit, and makes configuring and tweaking the behaviour of widgets the HTML programmer’s job.”

    All of the examples on here mix A and C together! The custom attribute method just does it in a declarative manner (attriutes) rather than a procedural (script tag). As for the few extra options, the equivalent would be:

    var foo = new Fantasticulator(’#foo li’, ‘pizzas’, false);

    Any non-trivial widget is going to have customizations, and the HTML programmer is going most likely going to need to be familiar with this stuff. Either the options are passed inline like I showed above, or they are in an external script using a literal object. Using the custom attributes method you could also use the external file + literal object approach to achieve the desired decoupling of behavioral definition from structure. Something like:

    <a href="#" my:options="MyFantasticulatorOptionsObject">JavaScript Foo</a>

    Where MyFantasticulatorOptionsObject defines (in an external file) any custom properties for that instance. It accomplishes what you are after, but it still is not ideal in all situations. There are many times where context sensitive data is only easily available while the page is rendering, and needs to be output somewhere in the document. For instance, a menu widget gets its menu items from a database, and having a separate server script to generate that options object not only adds complexity, but sometimes it is impossible due to the fact that some options could be dependent any other things on the page that are no longer available because we are now in a completely separate http request context. Trying to put dynamic widget initialization in separate scripts would be tricky, especially if you wanted to be able to put “all the widgets for this page” in a single external initialization script. External scripts are OK… but I’ve found that there’s always some state data that just needs to be rendered to the page, and trickery to get it to the external script doesn’t always make sense, especially since this is not a show-stopping issue, it works perfect, it’s just not 100% decoupling of the components.

    Excellent discussion, I enjoyed reading everyone’s posts.

Leave a Reply

Phone Number:

If you're about to post code in your comment, please wrap your code with the tag-combo <pre><code>. Also please escape your html entities - otherwise they will be stripped out. I recommend using postable.

Get "JavaScript Design Patterns"

"As a web developer, you'll already know that JavaScript™ is a powerful language, allowing you to add an impressive array of dynamic functionality to otherwise static web sites. But there is more power waiting to be unlocked--JavaScript is capable of full object-oriented capabilities, and by applying OOP principles, best practices, and design patterns to your code, you can make it more powerful, more efficient, and easier to work with alone or as part of a team."

Buy JS Design Patterns from Amazon.com Buy JS Design Patterns from Apress

Flickr

Submit a Prototype

All content copyright © 2003 - 2007 under the Creative Commons License. Wanna know something? Just ask.

About | Archives | Blog Search

[x] close

Loading...

Submit a prototype

By checking this prototype I agree that I am not submitting false credentials, pornography, or a hate crime website. I also understand that by submitting my entry I may or may not be accepted, and if accepted, my entry may be taken down at any given time if I violate these terms.