Graceful Degradation with Prototype, Scriptaculous and Ruby on Rails - Part 2: The Tools Of The Trade

—Saturday, November 12 2005

NOTE: This is part two of a three part series entitled “Graceful Degredation with Prototype, Scriptaculous and Ruby on Rails”, part one can be found here.

The problem is also the solution

In the first part of this series I showed a couple of examples of problems that we’ll be facing along the way. Just as we have powerful libraries such as Prototype and to help us easily implement Javascript based effects and Ajax, we also need a tool that helps us degrade this functionality. As strange as it may seem, Javascript is both the problem and the solution. One script that was released no to long ago called Behaviour was a great first step in providing an answer.

The promise of Behaviour.js

When Ben Nolan first released Behaviour.js to the community, it looked promising and garnered some well deserved attention. To my knowledge it was one of the first attempts to provide an abstract enough solution to a huge problem.

Behaviour was designed to address the problem with event handlers being placed into html tags, and 95% of the examples show how to handle onclick events. It’s power goes beyond that, but it’s still not designed to handle more complex examples that almost any Ajax based application will face.

Matching against CSS selectors is an extremely useful approach and the learning curve for Behaviour.js is as about a straight line as you can get it if your familiar with CSS. However, we need something with more power, or as a frightened Java developer might put it “It doesn’t scale”. While I’m certainly not knocking Ben’s efforts with Behaviour and I commend him for the work he’s done on it, but for complex examples I don’t see it being as attractive.

Behaviour passes the element that triggered the event to the properties you define. This works fine when that’s the node you want to deal with, but it starts getting tedious when that node can be any place in the DOM at any given time. Every element in a document is unique and can contain a unique identifier by means of it’s id attribute. The id attribute of a DOM node should be thought of as a primary key if we were talking about a database. In this case, the document is our database and the elements within that document are our records with their unique id.

Using ID’s as instructions for our behavior layer

In a valid XHTML document, there is only one thing we can (or hope we can) rely on to be unique and that is an elements id attribute. We can think of an id in the dom like a primary key in the database, it should always be unique. In our case, it also needs to describe what our behavior layer should do. Here is an example of what this might look like:

<ul id="blurbs">
<li id="blurb229">
    <h3>A lovely title</h3>
    <p>You should probably delete me from the database.</p>
    <div class="actions"><a href="/blurbs/destroy/229" id="remove-blurb229">delete</a></div>

This could be considered a pretty standard example of what you could be facing. Pay close attention to the descriptive naming conventions in the id’s. The ul has an id of blurbs, it’s children which are what we are really working with here have a singular name mixed with the object id that would be stored in a database for this object–blurbs229. 229 is the magic number here, the blurb prefix is merely a namespace for the object we are working with, and also, valid ids can not start with a number. Finally, the a element which fired the event has our instruction which tells our behavior layer what to do, in this case remove-blurb229. Perhaps a chart will paint a clearer picture.


Now that we’ve laid this out in html, we need a viable tool to help us tie it all together and make it work.

The Degrader tool: Degrader.js

Download Degrader.js

I’ve tried to comment the code in Degrader.js as much as possible, and hopefully that will get you up to speed on what it’s doing and how to use is. It’s still very much in the first stages of development so if you have any suggestions on improvements or questions then please fire away with a comment and I’ll try to answer any questions. If this leads to simpler and more flexible tools then I couldn’t be happier. Now with that said, lets get a brief run down of how it works.

At the heart of Degrader.js are Regular Expressions, although not the easiest things to learn they are by far the most powerful and flexible to work with and since we need the ability to describe almost anything, Regular Expressions were a prime candidate.

Degrader.js works by matching rules you define against a dom elements id. If a match is made, Degrader.js will trigger the callback onMatch and pass the complete rule and the element belonging to the id that was matched. Ok, that was a mouth full, lets look at some code.

var rules = $A([
   element: 'a',
   pattern: /^toggle-([\-a-z0-9]+)/i,
   onMatch: function(element, rule) {
     var target = Degrade.X(element, rule.pattern, 1);
     Event.observe(element, 'click', function(){Element.toggle(target)});

new Degrade.Degrader(rules);

In the above code snippets, I have 1 rule which is used to toggle an elements visibility. Degreader.js will parse the DOM and would detect all a elements whose pattern matched what we specified in our code.

<!-- Matches -->
<a href="#" id="toggle-item23">Show Item 23</a>
<a href="#" id="toggle-magicitem43">I'm Magic</a>

<!-- Not matched -->
<a href="#" id="toggle3232">I won't be matched</a>
<span id="toggle-item42">I also will not be matched</span>

So the first two elements would trigger onMatch and execute the function you define for it, in this case it would be the code below.

function(element, rule) {
     var target = Degrade.X(element, rule.pattern, 1);
     Event.observe(element, 'click', function(){Element.toggle(target)});

As you can see, the function you provide should accept the element which triggered the match and the complete rule equipped with the pattern or any other properties you wish to define. Simple enough right?

I’m going to leave it here for now, but in part 3 we’ll build a sample Ruby on Rails application that will tie everything together and apply what I’ve been talking about in a sudo-real-world application.