Fight id Proliferation and Update Any Element You Want

update: July 5, 2007: see ID Proliferation Eradication Technique #1… for an update.

The Prototype Updater constructor takes an Element (object) or element id (String) as the first parameter. You can see this in prototype.js in Abstract.Insertion.initialize and Ajax.Updater.updateContent . In both situations the first parameter sent to the constructor has $() applied before use. And as I’m sure you’re aware, the effect of that is that if the parameter is an Element, then the same element is returned. OTOH, if the element is a String, then that string is assumed to be an element id and the Element is found in the DOM and returned.

The big deal here is that the documentation lies or at a minimum fails to make this point clearly. Have a look at prototype-api.pdf. And this misunderstanding is carried forward in the Rails wrapper API functionality ActionView::Helpers::PrototypeHelper::JavaScriptGenerator::GeneratorMethods#insert_html, replace_html, remove, show, hide, and visual_effect. The result of this misunderstanding is that many JavaScript programmers think they need id’s all over the place — and Rails programmers actually do need id’s all over the place (unless and until the API is repaired…)

What is wanted in the Ruby wrapper, in order to expose the full capability of the underlying JavaScript libraries, is the ability to pass not only a String (containing an Element id) but optionally to pass an instance of JavaScriptGenerator to insert_html and replace_html and to have that generator rendered. Then we could do (borrowing from the Rails doc and extending…):
    1 update_page do |page|
    2   page.insert_html :bottom, page.select('p.welcome b').first, "<li>#{@item.name}</li>"
    3   page.visual_effect :highlight, 'list'
    4   page.hide 'status-indicator', 'cancel-link'
    5 end

Note that instead of the simple string literal ‘list’ (from the Rails doc) we’ve got a full-fledged expression there. And generate something like this:

    1 new Insertion.Bottom($$('p.welcome b').first, "<li>Some item</li>");
    2 new Effect.Highlight('list');
    3 ["status-indicator", "cancel-link"].each(Element.hide);

But really, the proposed change to the Ruby wrapper isn’t limited to CSS selectors of course. Once insert_html, replace_html and friends support a full-fledged generator parameter, you could put arbitrary JavaScript in there. The most obvious examples would be calling custom functions and DOM traversal functions.

About these ads
This entry was posted in AJAX, Ruby on Rails, script.aculo.us. Bookmark the permalink.

6 Responses to Fight id Proliferation and Update Any Element You Want

  1. topfunky says:

    What about just passing a string with Javascript in it? Does that work? Maybe that breaks the point of RJS…staying in Ruby-land instead of Javascript-land.

    page.insert_html :bottom, “$$(‘p.welcome b’).first()”, “Some item”

  2. Bill says:

    It seems like that ought to work topfunky. As you point out though it does break the whole point of RJS.

    While we’re talking about “the whole point of RJS”, the more I (try to) use RJS, the more I end up just writing JavaScript. If “the whole point of RJS” is to enable newbies to ape sample code quickly then it is on point. If “the whole point of RJS” is to provide a more capable, more understandable dev environment, or to speed learning (versus JS) then I don’t think it’s on point. RJS is insufficiently documented, insufficiently understood, and just well… insufficient. Any but the most casual user is inevitably sucked into debugging JS and reading Prototype and Scriptaculous doc and source code. To what beneficial end? By the time you do all that, the Ruby wrapper is just getting in your way isn’t it?

    I think that RJS, like Google’s GWT, is predicated on the asumption that JavaScript is a “bad” language that needs to be hidden – a necessary evil, but evil nonetheless. Even if you accept the premise, RJS hasn’t hidden any of the evil. Rather than hide evil, RJS replicates the JS language elements one-for-one. Except when it doesn’t (as in the case outlined above of the failure to support exprs in certain positions).

    If JS really was evil and we wanted to address that we’d almost certainly not replicate the language as a Ruby DSL. But as I said, I don’t think JS is evil anyway. It seems what’s actually needed is an _integration_ between Ruby and JS. I haven’t tried Dan Webb’s MinusMOR yet (.ejs templates) but I think that may be a better approach.

  3. Adam Keys says:

    Marcel Molina’s presentation at SXSW really illuminated this for me:

    http://fiveruns.com/downloads/sxswi_rails_and_ajax.pdf

    Besides clueing me into all sorts of hidden trickery, it reminded me that RJS is all about DRYing up verbose and frequently used idioms. Rather than passing all sorts of :complete and :loading parameters to form or link helpers, RJS extracts that intent and gives it a little language of its own.

  4. Bill says:

    I love that presentation Adam – partly because fiveruns is so dang cool and partly because I love orange and blue. One thing I notice both in the RJS language and in the Selenium tests is that everything works pretty well so long as you have id’s on all the elements of interest.

    So the original post here is at a surface-level about getting beyond the “element of interest must have an id element” limitation of the RJS DSL. There is a parallel problem in Selenium’s Ruby API, to wit, it works pretty well so long as you’ve got id’s on your elements — but if you try to use the (much touted) CSS or XPath selectors you’ll find they simply don’t work. It isn’t that Selenium doesn’t support ‘em — it does if you use the JavaScript lib. It’s just that the Ruby lib doesn’t.

    So maybe this issue is smaller than my comment above (http://www.meme-rocket.com/2007/04/10/fight-id-proliferation-and-update-any-element-you-want/#comment-6227) might indicate. Maybe we just need a couple tweaks here and there to bring the culture more fully in line w/ CSS selectors.

  5. Bill says:

    I just tried topfunky’s suggestion and it turns out it does not work. Rails generates JavaScript with the select expression quoted like this:

    new Insertion.Bottom(“$$(’p.welcome b’).first()”, “Some item”);

    And then of course, prototype’s Insertion object calls $() with that string and does not find any element with a matching id.

  6. Brad Wilson says:

    Bill,

    I’ve had some luck achieving the same result using the ActiveSupport::JSON::Variable class.
    Details are available at the link below.

    http://sentia.com.au/2007/11/5/using-javascript-code-for-rjs-instead-of-ids

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