ID Proliferation Eradication Technique #1: Leverage page.select with page.insert

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_#{@contact.id} .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_#{@contact.id} .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_#{@contact.id} .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_#{@contact.id} .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:

Contact Email Address

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 <ul class="contacts-list editable">
    2     <li id="contact_25" class="contact-summary">
    3     </li>
    4     <li id="contact_19" class="contact-summary">
    5         <div class="name">support@thought-propulsion.com</div>
    6         <div class="contact-details" style="">
    7             <div class="contact-category email-category">
    8                 <h2>Email Addresses</h2>
    9                 <ul>
   10                     <li>support@thought-propulsion.com</li>
   11                 </ul>
   12             </div>
   13             <div class="contact-category blog-category">
   14             </div>
   15             <div class="contact-category phone-category">
   16             </div>
   17         </div>
   18     </li>
   19     <li id="contact_22" class="contact-summary">
   20     </li>
   21 </ul>

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 :)

About these ads
This entry was posted in AJAX, CSS, RJS templates, Ruby on Rails. Bookmark the permalink.

One Response to ID Proliferation Eradication Technique #1: Leverage page.select with page.insert

  1. Pingback: Bill Burcham's memeRocket :: UJS; RJS versus POJS; Prototype Stack versus JQuery Stack

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s