update 3/29/2009 8:34 AM: In November, 2007 Brad Wilson pointed out an even cleaner fix. Rather than relying on page.select you can just use page.literal to inject (literal) JavaScript code right where you want it. So recasting my example using page.literal it would look something like this:
1 page.insert_html :bottom, page.literal( "$$( '#contact_ .email-category ul').first" ), :partial => 'show', :locals => {:show => @email_address}
In Fight id Proliferation I highlighted the prevalent misunderstanding of the prototype API’s insert_html, replace_html and friends that is evident both in the Prototype documentation and in Rails. Due to that misunderstanding, many of us are polluting our HTML with many more id attributes than would otherwise be necessary. In the original post I proposed a fix to Rails to remedy the situation and allow us to use CSS selectors to identify a target HTML element for the various calls. In this post I provide a workaround you can use now, while waiting for a cleaner fix.
Previously, I pointed out that we’d really like to use page.select and more generally, any expression that produces an element, as e.g. the second parameter to page.insert_html. So it would be really nice if this worked:
1 page.insert_html :bottom, page.select( "#contact_ .email-category ul").first, :partial => 'show', :locals => {:show => @email_address}
If that worked, we wouldn’t need an id on each element representing the email address for a contact. Instead we’d just put an id on the contact (at the outer level) and we’d tag the parts of the contact (email address, phone number, etc) with appropriate classes. Much more microformatty.
Well, as you know, you can’t do that just now in Rails. But there is a workaround. It’s a bit ugly but it works. And the ugliness is in the RJS, not in the HTML. So your markup is smaller and cleaner, even if you’re generating an extra line of JavaScript.
The workaround is to use the each method on the result of calling page.select. This lets you iterate (in the browser) over the elements returned from evaluating the CSS selector. Within that iteration, you have available the element of interest. Now that you’ve got an element, you can call the various page methods that require an element or id such as page.insert position, element. You can also call methods directly on the element itself such as element.reset. Here’s the previous example recast in this way:
1 page.select( "#contact_ .email-category ul").each do |element| 2 page.insert_html :bottom, element, :partial => 'show', :locals => {:show => @email_address} 3 end
This works great in Rails 1.2 even if it is a little bit ugly. By ugly I mean that the code doesn’t exactly express what we want. Notice that the original code was selecting the first element matched by the CSS selector whereas the workaround code is iterating over all elements matched. So long as you’re careful to construct CSS selectors that match exactly the number of elements you’re really after though, you should be ok.
Here’s a similar snippet but this one calls the Prototype reset method on the element:
1 page.select( "#contact_ .email-category form").each do |element| 2 element.reset 3 end
By tagging only top-level elements with id’s and using classes to tag internal components you end up with cleaner HTML. This is valuable anywhere you present a list of complex, editable objects. For example, here is one contact of many displayed in a list:

Each contact has a set of email addresses, blog URL’s and phone numbers. We don’t want to have to assign id’s to every single phone number, blog URL and email address on the page. Instead we assign id’s only to the top-level elements. Here is a contact list with contact_19 expanded. Under contact-details we’ve got email addresses under an element with classes contact-category and email-category. That element contains both the unordered list of email addresses and a form to add new ones:
1 2 3 4 5 support@thought-propulsion.com 6 7 8 Email Addresses 9 10 support@thought-propulsion.com 11 12 13 14 15 16 17 18 19 20 21
So if you want to use Simply Helpful to generate your top-level id’s have at it, but you needn’t use it to generate id’s for all the internal parts just because of a little misunderstanding :)
Pingback: Bill Burcham's memeRocket :: UJS; RJS versus POJS; Prototype Stack versus JQuery Stack