Unobtrusive, yet explicit

Posted by Jamis on March 01, 2010 @ 05:15 PM

A few weeks ago I started a new side project (a string-figure catalog, not yet ready for an audience, sadly), and I figured it would be a good opportunity to dabble in the new goodies in Rails 3. It’s been a fun experience, for the most part, but I’ll save my “wins and fails” for a separate post.

For now, I want to focus on one particular frustration: Unobtrusive Javascript (UJS). In any project of even moderate complexity, I’ve found that Javascript plays a role, and in Rails 2 the primary way to play that game was by inlining your Javascript. (This is where you put Javascript directly into your tags, for instance in “onchange” or “onclick” handlers.)

Apparently this is a Bad Thing, although the only arguments I’ve found against inline Javascript sound suspiciously like “purity for purity’s sake”. At any rate, Rails 3 is embracing UJS, and you’ll find that helper methods like “link_to_function” don’t even exist in Rails 3.

This raises the question: what do you do instead? Well, you have to use UJS. Only, UJS in Rails isn’t super mature yet; there’s a lot of manual labor involved simply trying to work around the absence of “link_to_function”.

So, I set to work. Initially, I tried to copy what rails.js was doing (for Ajax operations, etc.): I installed a handler, and examined the triggering element to see what operations match:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
document.observe("dom:loaded", function() {
  $(document.body).observe("click", function(event) {
    var element = event.findElement("a[data-toggle]");
    if(element) {
      var action = element.readAttribute("data-toggle");
      element.hide();
      element.next().show();
      event.stop();
    } else {
      var element = event.element();
      if(!element.readAttribute("data-tab")) element = element.up("a[data-tab]")
      if(element) {
        selectTab(element.readAttribute("data-tab"));
      }
    }
  });
});

I quickly realized that this does not scale, for two reasons. The first is that you quickly wind up with a massive branch statement inside each of your observer functions, with finicky conditions that (hopefully) map to actual elements in your views. The second is that the relationship between your markup and your Javascript is tenuous at best; even coming back to my code just a few days later, I found it was challenging to discover what code was executed when a link was clicked.

This is, I believe, one of the greatest strengths of inline Javascript: the relationship between markup and code is immediately obvious, and it requires very little hunting to follow the path of execution from the inception of an event.

So I went looking at what other options exist. Low Pro, an extension to Prototype for aiding with UJS, looked promising; I liked how each behavior was registered separately, which seemed like it would give a stronger relationship between markup and execution. However, Low Pro uses CSS selectors to identify which markup gets associated with which callbacks, and while this sounds like it ought to be a great idea, it falls down for one really big reason: CSS selectors depend on styling attributes (classes and ids), and trying to tie functionality to those means you are still left staring at markup and wondering where all the events go. Sometimes a class is stylistic, and sometimes it is logical, and there is not generally any clear way to determine which is which.

Now, you could resort to naming conventions: if a class name is prefixed with “behavior-” (or similar), then it refers to a behavior that is defined in Javascript. That’s closer to what I was looking for, so I played with that.

But what I soon discovered was that you wind up with a bunch of CSS classes that are not used for styling at all, because they specifically refer to dynamic behaviors. What I really wanted was an altogether different attribute for specifying behaviors, like what “onchange” and “onclick” gave me before. Only I had to beware upsetting the Manifold Avatars of UJS Purity by embedding actual Javascript.

What I finally ended up doing (I’ll say I “stumbled on it”, rather than “invented it”, since I’m positive it’s been done before) was defining a “data-behaviors” attribute on every element that needed one:


<%= link_to "Add an alias", "#", "data-behaviors" => "add-alias" %>

Then, in my Javascript driver, I registered callbacks for those named behaviors:

1
2
3
Behaviors.add("click", "add-alias", function(element) {
  // ...
});

The result is UJS that clearly reveals the relationship between the markup and the code; you can easily search for all elements in the views that behave like “add-alias”, for instance, and given a behavior name (like “add-alias”), you can quickly find the code that gets executed for it. Elements can have multiple behaviors, too: just give a space-delimited list of behavior names in the “data-behaviors” attribute.

It’s not perfect, though: the current implementation doesn’t deal well with elements that want to behave like X on “click”, but Y on “change”. That’s not a scenario I’ve needed to deal with yet, though, so I’m sure when (if?) it comes up, a solution can be found. In the meantime, I’m quite pleased with this. It “clicks”, whereas other UJS solutions just felt obscure and heavy-weight.

Below is the code for behaviors.js. Please feel free to fork the gist on Github and hack away; I’m sure it can be improved upon in lots of ways.

Enjoy!

Update: Josh Peek suggested some tips that resulted in a drastic simplification of behaviors.js. It’s simple enough now that there’s almost no point in providing it as a separate library!

Posted in Projects Tips & Tricks

Comments

Have something to add? Click here to leave a comment.

01 Mar 2010

1. Scott said...

I think you meant “UJS” instead of “RJS” in the third paragraph.

2. Jamis said...

Yup, you’re right Scott. Thanks for catching that.

3. Tom Brice said...

I am very interested in how this kind of thing (as well as RJS) is designed to work in a Rails3 world. I’m thankful to you for starting the discussion.

4. kangax said...

Prototype already extends `document` with `observe`, so you can replace `$(document.body).observe` with just `document.observe`. This will add event listener to root element — `document.documentElement` — instead of `document.body` (which is more or less identical).

5. Jamis said...

@kangax, thanks! This is what comes of cargo-culting. :) Your suggestion works great, and even lets me do Behaviors.add outside of a dom:loaded closure. Much cleaner.

6. Sam Hill said...

Much of the time when working on a group project I avoid id’s entirely in my css, and use them exclusively for js. I find it’s not restrictive nearly all of the time, and then one can still use document.getElementById(), which is good for performance…

7. Jamis said...

@Sam, ids only work if you can guarantee no more than one element on a page with a particular behavior. My fairly-simple project has already run into several instances where this isn’t true, so id-based selection wasn’t sufficient.

8. Steve St. Martin said...

the link_to_function as well as the previous 2.3.x behavior has not been removed, just is not the rails 3 way, the previous inline behavior exists in its own plugin now, take a look at http://github.com/rails/prototype_legacy_helper

9. Brian Morearty said...

Purity for purity’s sake. Thanks for saying it, Jamis.

My reaction when I first heard about UJS a couple years ago was that it was really cool. But I quickly became disillusioned when I realized UJS can hurt the user experience. I’ll take a better UX over pure code.

For an example go to http://www.dzone.com and click the login link in the top right corner of the page quickly, before the browser has had a chance to hook up the UJS. You will see a weird, ugly login page and be confused. (This really happened to me. I was weirded out.) This would not happen if the event had been hooked up inline.

Also it doesn’t matter that much if the generated code is impure. What matters most is that the code you have to maintain is clean. Worrying about impure generated JavaScript would be like worrying about the maintainability of the assembly language code that a C++ compiler generates.

10. Jamis said...

@Steve, “removed” means “not in Rails 3”. By moving it to its own plugin, link_to_function has indeed been “removed”.

I’ve talked with Josh Peek about this (who made that change in Rails 3), and his stance clarified things a bit for me, though. He is not opposed to small bits of inline Javascript, and in fact suggested that link_to(..., :onclick => ””) be used in place of link_to_function. The link_to_function helper was removed because it tended to encourage too much inline Javascript.

@Brian, amen. Sing it, brother!

11. Bryant Cutler said...

Actually, I think attaching the behavior via CSS-style selectors is fine. Ideally, your markup has class names and ids that denote the semantic function of the various HTML elements, rather than something specifically presentational. And of course, there’s nothing stopping you from using multiple space-separated classes on your HTML elements, some for styling and some for behavior. If you had to tie the Javascript functionality to an actual CSS selector, it’d be lame, but using a CSS-style selector to chose document nodes for behavior attachment is totally kosher.

02 Mar 2010

12. Radoslav Stankov said...

You can also use my Event.delegate / http://gist.github.com/66568 / and simplify even more:

document.delegate(“*[data-behaviors~=” + behavior + “]”, trigger, handler);

13. Jonathan del Strother said...

@Brian dzone.com are using window.onload to hook up their event handlers, which is a really bad idea since it has to wait until all the page resources have finished loading. If they were using DOMContentLoaded I’m pretty sure you wouldn’t run into that problem.

14. Morgan Roderick said...

“But what I soon discovered was that you wind up with a bunch of CSS classes that are not used for styling at all” ... how did that happen?

When I write CSS files, I only add stuff I need … just because an HTML element has a class attribute, doesn’t mean you have to mirror that in your CSS file.

Or is what you really mean, that you end up with a bunch of HTML class attributes, that you’re not using for styling at all?

That’s a perfectly valid situation, http://www.w3.org/TR/html401/struct/global.html#h-7.5.2


Finding dom nodes with CSS selectors, using modern JavaScript libraries and modern browsers, can only be faster than finding unqualified elements with HTML5 specific attributes using a regular expression.

There is even the querySelectorAll as native method built in modern browsers, that speeds things up considerably.

15. Stefano C. said...

Patching LowPro in order to be able to use it like this:

Event.addBehavior({‘add-alias’: AddAliasBehavior})

is extremely simple:

78c78,79 < var parts = sel.split(/:(?=[a-z]+$)/), css = parts0, event = parts1;

var parts = sel.split(/:(?=[a-z]+$)/), behavior_name = parts0, event = parts1; var css = ‘*[data-behavior~=’ + behavior_name + ‘]’

16. Stefano C. said...

Whoopsie, sorry for the formatting mess :-)

17. Svoop said...

Check out RightJS. It’s a slim JS lib which offers – amongst other things – behaviors (and soon native behaviors) as part of the core. It’s very performant, often pretty close to metal JS code, yet makes your UJS code way more readable. The author of RightJS has an affinity towards Ruby, so many helper methods look and feel like Ruby. (You can even enable underscore_method_names instead of the default camelCaseMethodName.)

http://www.rightjs.org

18. kostiantyn.kahanskyi@gmail.com said...

Sounds for me like the old “TDD is too expensive!” and “Rails doesn’t scale!” :-(

19. Jamis said...

@kostiantyn, really? But TDD is actually valuable, and Rails does scale. The difference here is that I have yet to see an argument in favor of UJS that doesn’t boil down to “you shouldn’t put Javascript in your views because…well…because!”

@everyone else: I tried to explain my stance on CSS selectors, but I’ll try one more time. When you’re looking at an element with a bunch of CSS classes, how do you know that any of them map to a behavior? And when you’re looking at a behavior definition in Javascript, how do you know which specific elements it applies to? I’m not being theoretical here: I’ve lived this. I’ve had to support apps where CSS styles are used to define behaviors, and it is hard, coming into the code for the first time, or even after some small time has passed, to find your way around.

And yes, using CSS classes would be “more performant”, but keep in mind that even for a worst-case scenario, my implementation is O(log(n)). We’re searching UP the tree, and not down it. Best (and most common) case: the element you clicked on has the data-behaviors attribute defined, in which case there is no searching at all, and we’re O(1). Worst case: someone clicks on a random location on the page, and the code has to search from that clicked element up to the root element. Not a scary proposition: still a cool O(log(n)), and often n will be small enough that it might as well be O(1) anyway.

At the end of the day: this is what works for me, and made UJS not hurt as much. You don’t like it? That’s fine, go implement something better and write a blog post about it. Try to convince me that your way is better. But please don’t touch “performance” unless you have actual numbers to back it up.

20. Bragi said...

How come no one have mentioned jQuery yet?

It’s trivial to add that kind of behaviour with this library plus it was partially written by Yehuda :)

21. Maximilian Schulz said...

@Bragi: I am wondering myself. I use those principles of unobtrusive JS in combination with jQuery for quite some time and it works perfectly. I still have to check the rails.js file for the jQuery lib, although I used it already in a small project. But I really do not the see the problem of moving the inline js.

And about the argument, I think we all strive to have the purist solution of all. Or at least I often do my refactorings on the basis of creating a cleaner solution. I would say it is the same argument why we all write the styling in a separate stylesheet and do not include it in the html code. It is a clear separation of concerns:

  • Everything related to styling belongs into a stylesheet
  • The pure data and its structure is within the html file
  • The client logic belongs into a javascript file

I like it that way and find it much easier to structure my code and keep it clean. Especially once you get a grip of the way to structure your javascript code.

22. Tim said...

Jamis,

I can make 3 valid arguments for unobtrusive js, maybe 4:

1) Abstracting out functionality. For instance, if I have 5 links that all need the same event handler.

2) TDD – though I don’t test drive my js now, I should & couldn’t if it were inline.

3) Maintenance/Organization – It’s much easier to find/modify code if it’s in one place.

4) Purity is not for purity’s sake. What happens to the poor designer that forgot to close a div and now has to dig through 5k+ lines of inline js/script tags to find the div?

I think the argument for purity’s sake is somewhat valid as well. Script really belongs outside of HTML. Though this is an after thought for most, as is me, because we aren’t as particular about our javascript as a Rubyist would be about their Ruby or instance. ;)

I’d be interested to hear a dedicated JS dev’s thoughts on your post & everyone’s comments.

23. Jamis said...

@Tim, thanks for chiming in with a sane response. I completely agree with your points 1-3, and even #4 to some extent.

However, I want to make it clear that when I vote for inline JS, I’m not voting for EVERYTHING INLINE. That way lies insanity. Of course you want the bulk of your code abstracted away. But what is so bad about something like the following?


<a href="#" onclick="Handlers.executeClick(this)">Click me!</a>

How is that worse than any of the other proposals that don’t involve any inline JS?

All I want, really, is a way to easily and explicitly associate an event on an element to an action in the code. Implicit declarations using CSS selectors do not cut it for me.

24. Harry said...

There are some performance reasons for putting all scripts in external files and making behavior unobtrusive.

  • http://www.stevesouders.com/blog/2009/05/06/positioning-inline-scripts/
  • http://developer.yahoo.com/performance/rules.html#js_bottom

25. Todd said...

Document.write and onclick FTW

26. S. M. Sohan said...

Your post has been linked at the www.DrinkRails.com blog

27. Gabe da Silveira said...

I understand where you’re coming from on this one Jamis, but I think you’re off the mark. I’m considering writing up a full response in my blog, but I want to point out quickly that using a custom attribute for behavior hooks is not a clear win by any means.

Like any decoupling or indirection, there is a mental overhead that UJS imposes. If all your behavior hooks are explicit via custom attributes, there is a certain comfort that can be taken from knowing that behavior will not exist unless it is right there in the HTML. However in exchange you now need to create behavior in two places. You’ve just shifted complexity around.

With UJS you select the elements you want to attach behavior to. This can all be done from a static JS file without any modifications to the HTML. Obviously sometimes you have to add more hooks, just the same as you would with CSS modifications, but if the markup is semantic and the underlying data schema is not changing then you can do quite a lot without modifying the HTML.

With inline hooks you have to remember to add them to every occurrence of the tag. Now when you create a div.widget, you need to remember to explicitly add all the behaviors you want right there in the HTML. Maybe that item is rendered in 20 different templates, and you have to remember to add it to all of them? So if you’re using Rails you create a helper method and then use that everywhere. Great! Until you want to dynamically construct one of those items on the client side via JS DOM methods. What’s that? You use AJAX to request an HTML fragment rendered by a partial? Aside from the increased latency and server load, that can work fairly elegantly… that is until some aspect of that element depends on the context around it. Now you are suddenly pulling all kinds of context, passing it to the server, and then potentially jumping through more hoops when it comes back.

Maybe this isn’t very convincing without a concrete example… If you’re interested I can give you a very specific example of where I was able to trade 300 lines of Ruby in for 100 lines of Javascript with dramatically fewer page reloads, numerous edge cases fixed and the addition of some very powerful UI enhancements that would have been simply impossible with a server-centric worldview. Using delegated event handlers in javascript made this possible and was a clear win across a dozen different metrics (memory consumption, performance, server load, code length, code clarity, encapsulation, etc, etc)

Fundamentally I think the problem you’re having is that the standards aren’t as clear for how to use UJS as they are for CSS. After all, when you change a class attribute, you already know you have to check the CSS for changes. You just to get into the habit of looking JS that is hooked onto that class as well. Maybe you use assert_select in your view tests in which case you need to update those as well—you wouldn’t create a new custom attribute just for testing would you?

To me it’s all about standardizing your approach. In my Rails app I claim one global namespace and then attach individual javascript objects encapsulating the behavior for each type of object. The individual static javascript files tend to mirror the names of the models and controllers. When I’m writing HTML templates I don’t need to futz around with complex helpers, I just create a div.widget as plain HTML, then go to standard.css to style it, and widget.js to add behaviour. It works very very well and it’s not purity for purity’s sake.

28. Jamis said...

@Gabe, thanks for sharing your thoughts. I do understand your point, but I don’t agree with it. :) I don’t buy the example you used, I guess. So I would have to add my custom attribute to each element…and? Wouldn’t you have to define the elements all those other times, anyway? And give them CSS classes to select on? I don’t see the problem.

I’m totally willing to admit that there may be cases where the custom attribute approach isn’t a good fit. I’m certainly not advocating this over all other techniques in every situation. But for the cases I’ve needed, this approach works fantastically, and is self-documenting, which you can’t say for the CSS selector approach. As I said, I’ve been in the boat where i had to decipher what an app was doing when all the behaviors were implicit, and that was not a fun experience.

29. Gabe da Silveira said...

Yes you do have to define the class name, but what I’m getting at is that’s all you should have to define. This gets to essence of the value of a semantic class name. If you just say, for example, here is a div.rating. Then you can go in CSS and define what a rating looks like, and you can go in JS and define the behavior of a rating, and you can write assert_select to say this page should be outputting X number of ratings, etc, etc.

Adding a custom attribute to define behavior does clarify intent, but that same case can be made for using tons of utility classes (or even inline styles!) for styling your page rather than using CSS selectors to style semantically named elements. Sometimes there is a reason for this approach (eg. grid systems for large sites), but often times you end up with a proliferation of classes that make the HTML harder to right and reuse (ie. the well-known class-itis phenomenon).

By specifying behavior inline by custom attributes, you do make it easier for new developers to understand that bit of HTML, but that is offset by the fact that if future template writers forget one custom attribute then one template of the site is potentially broken (whereas if you broke the behavior by modifying the class, it would break across the site and you would be less likely to miss it). It’s not a big deal you have one or two custom attributes, but the more you add the more error-prone it is, and if you break the convention in one or two places you can kiss all the self-documenting goodness goodbye. Therefore, if you have non-trivial javascript functionality, it’s better to just define your standard architectural conventions up front rather than relying on a technique which is not going to scale well.

30. Jason Cheow said...

I was first a JavaScript developer before I started on Rails and the reason why I took to Rails so easily was the clear separation between model, view and controller.

To chime in on the “purity for purity’s sake” thing, the reason why I prefer UJS is the same reason I like using Rails—clear separation between markup, styles and behaviour.

Moving inline JavaScript to external files is like the concept of skinny controller, fat model for me. Having a ton of code in the controller makes it explicit what each action in a controller does, but moving them to the model separates controller from business logic. For JavaScript then, moving inline code to an external file separates behaviour from markup. Both aids code maintainability.

As for the problem of not being able to see at first glance what behaviour a certain element on a page has, I use Gabe’s method of naming my JavaScript files the same way Rails names its controllers: application.js for global behaviours, [controller name].js for behaviours specific to a resource.

True, you won’t be able to see immediately what behaviours each element are hooked up to, but you can always match the resource you are examining to the correspondingly named JavaScript file, and within the file, if it’s well organised and documented, it shouldn’t be that difficult to locate the code you are looking for, especially if you are using CSS selectors to hook up behaviours to elements.

05 Mar 2010

31. Justin Blake said...

I think Jamis clears up his view nicely in comment 23. He’s not bashing UJS because he wants to throw a bunch of javascript into his HTML.

12 Mar 2010

32. Phil McClure said...

Surely you’re not suppose to touch the rail.js driver… I wrote a bit about UJS on my blog – http://therailworld.com/posts/26-Using-Prototype-and-JQuery-with-Rails3-UJS-

With this approach, the javascript is store in its own view. Very structured and very easy to find what relates to what.

14 Mar 2010

33. Tore Darell said...

I think the UJS evangelists have failed to really convince people why their (our) way is the Right Way. Most of the noise coming from the UJS cargo cult is moralising chants and lectures, but what people want to know is what’s in it for them. I’ve tried explaining exactly what makes it so much better than the alternative – I wouldn’t be doing all this work if there weren’t massive benefits to it.

That article is not a reply to this post, it’s more of a non-moralising description of the benefits of using JavaScript for what it really is. Summed up, the argument is this: People forget about basic programming best practises when dealing with JavaScript. JavaScript is a real programming language, and those of us who follow these practises use that fact to our advantage and reap the benefits from it.

Don’t listen to the vocal UJS cargo culters; most of them can’t tell a prototype from a constructor function.

17 Mar 2010

34. blanquer said...

Jamis,

You might want to check out http://github.com/rightscale/behaveJS (in case you don't know it)

It’s not just a test or a prototype, but currently used in production for the RigthScale management dashboard site (which is fairly rich and big Rails app). I believe you might find it implements/addresses many of your original points of unobtrusiveness, behavioral definitions, cleanliness of html, javascript page performance…

Cheers!
27 Mar 2010

35. Rod Knowlton said...

Jamis,

The argument for UJS that I’ve not seen mentioned here, and which to me is the primary argument, is accessibility.

UJS is an important part of progressive enhancement and graceful degradation. Using it preserves the core functionality of a site even for those without JavaScript enabled, such as screen-readers.

24 May 2010

36. chris said...

I agree with Rod. Accessibility is one of the most important things to take into account while developing a web presence nowadays. Why discard users while with a little effort it’s possible to make the page accessible to almost all users.