Unobtrusive JS 0.2.2 - "the two in one day!" release

I’m sure many people are aware of the risks in running pre-1.0 software/plugins and that there are likely to be many bugs discovered until that 1.0 release finally hits (and beyond).

Unfortunately, it takes really working with a Rails plugin to find problems and I have been doing so today. I encountered a strange error where the plugin was generating IDs for elements that shouldn’t have any javascript events attached.

Update 21/08/2006: The latest version of this plugin is 0.3 – please see this post and the official UJS website for more information.


def tag_options(opts)
  unless opts[:inline]
    JAVASCRIPT_EVENTS.each do |event|
      if opts.include?("on#{event}") # this was the culprit!
        opts['id'] = generate_html_id unless opts['id']
        register_js_behaviour("##{opts['id']}:#{event}",
                  opts["on#{event}"]) unless opts["on#{event}"].nil?
        opts.delete("on#{event}")
      end
    end
  end
  rails_tag_options(opts)
end

What it turned out that was happening was that some Rails helpers were adding empty onclick handlers to some tags so they were being picked up by the above code. All that was required was a simple fix:


# get rid of this
# if opts.include?("on#{event}") 
# and use this:
unless opts["on#{event}"].blank?

So grab Unobtrusive Javascript for Rails 0.2.2 while its hot!

Return to home page | Check out my tumblelog

8 Comments on this article

1. Comment by PJ Kelly on 11 Aug 2006 at 17:08

Great work on the updates to the plugin. I’ve found it extremely useful in my work.

Just wanted to let you know (if you weren’t already aware) that the plugin (0.2.2) doesn’t seem to be working with the newest security update for Rails (1.1.6). It works fine with 1.1.5 though.

I get the following error when trying to run it in 1.1.6:

ActionController::RoutingError (Recognition failed for ”/unobtrusivejavascript/generate”): /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/actioncontroller/routing.rb:522:in recognition_failed' /usr/local/lib/ruby/gems/1.8/gems/actionpack-1.12.5/lib/action_controller/routing.rb:512:inrecognize!’ /usr/local/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/dispatcher.rb:38:in dispatch' /usr/local/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/fcgi_handler.rb:150:inprocessrequest’ /usr/local/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/fcgihandler.rb:54:in process!' /usr/local/lib/ruby/site_ruby/1.8/fcgi.rb:600:ineachcgi’ /usr/local/lib/ruby/siteruby/1.8/fcgi.rb:597:in each_cgi' /usr/local/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/fcgi_handler.rb:53:inprocess!’ /usr/local/lib/ruby/gems/1.8/gems/rails-1.1.6/lib/fcgi_handler.rb:23:in `process!’ /Users/pjkelly/Development/boomerang/public/dispatch.fcgi:24

2. Comment by Scott Rutherford on 15 Aug 2006 at 10:08

I have had the same problem with this and another plugin of mine breaking after upgrading to 1.1.6.

The reason is that the only paths now allowed are apps, components and lib, so the controller generated by Unobstrusive JS can no longer be found.

I have fixed the FCKeditor plugin (temporarily) by using this:

module ActionController module Routing

  class ControllerComponent
    class << self
    protected
    def safe_load_paths #:nodoc:
      if defined?(RAILS_ROOT)
        $LOAD_PATH.select do |base|
          base = File.expand_path(base)
          extended_root = File.expand_path(RAILS_ROOT)
          # Exclude all paths that are not nested within app, lib, or components, or the fckeditor plugin
          base.match(/\A#{Regexp.escape(extended_root)}\/*(app|lib|components|vendor\/plugins\/fckeditor\/app)\/[a-z]/) || base =~ %r{rails-[\d.]+/builtin}
        end
      else
        $LOAD_PATH
      end
    end
    end
  end

end end

But this is obviously not going to play well with other plugins. Seems that Engines may be the answer for this, or copy the controller into the normal place. Any other ideas?

3. Comment by Luke Redpath on 15 Aug 2006 at 13:08

According to Dan, everything is working fine with 1.1.6 but that might be because he is on the plugin trunk.

We are aiming to have 0.3 out by the end of the week which definately works with 1.1.6 (though not on edge past revision 4727 due to a problem with the new dependencies system), includes some vital bug fixes, some new cool helpers for apply_behaviour and most importantly caching.

4. Comment by Benjamin on 16 Aug 2006 at 20:08

I think we’re lucky to have so many willing “testers” in the community to try out new things. Sure, a lot of projects never get off the ground, but some do, and it’s largely due to developers and their test reports. – ben @ http://rubyonrailsblog.com/

5. Comment by Scott Rutherford on 17 Aug 2006 at 10:08

Scratch my post, all is well.

6. Comment by Devin Ben-Hur on 17 Aug 2006 at 21:08

Firstly, thanks for this plugin, I like the approach and am glad to be able to move inline js out of the way and the methods to generate it using the rjs style in my views.

I’m having a problem with edge rails (r4779) and just saw your comment from 15.08 about issues with the new dependancies. Any idea when you’ll be addressing these dependancies?

Also, when I ran the plugin’s tests they failed testresultshouldnotcontainanyinlinejavascriptevents and testshouldhavejavascripteventsregisteredas_unobtrusive

I tracked this down to JAVASCRIPTEVENTS in taghelper_patches.rb missing ‘load’ while your tests are probing for it.

On a more general front, I’m concerned that the method you’re using to save behaviors in the session and then generate them on a seperate request is subject to race conditions.

It’s not uncommon for browser clients to open several pages of a site more or less in parrallel (firefox has plugins which do this on demand or automatically, but users can do rapid-fire open-in-new-tab middle clicks to get the same effect manually).

When a bunch of page requests come in in parrallel, there’s no gaurentee that the clients request for the generated javascript for page A is going to be handled before the another request for page B comes in. By storing the behaviors in thge session, then discarding them after generation, you’re assuming a normal order of requests:

pgA, js(A), pgB, js(B)

but the vagaries of network traffic and client timings could easily result in the server seeing the requests come in as:

pgA, pgB, js(A), js(B) or pgA, pgB, js(B), js(A)

In these two cases, it appears to me that the current approach will render a null generated javascript response for the second js request; further the first one is going to render B’s javascript to the request satisfing A.

The solution i see would be to assign unique identifiers to stored behavior groups and including the identifier in the link tag for that behavior group.

This is going to open further questions when you start addressing caching. You may discover that the session is the wrong place to store these as you’ll want a method to identify identical generated javascripts and serve them to multiple users.

(Or perhaps I’m just totally missing something and worrying about nothing.)

7. Comment by Luke Redpath on 17 Aug 2006 at 21:08

Devin – I can’t say for certain but I don’t think you have anything to worry about. In the new release of UJS (0.3) which is due in a few days, the URLs for the behaviour files are unique to each page; furthermore the caching issue has been addressed in two ways – one using ETags and one using a mechanism similar to Rails page caching.

Keep an eye out for the new release and the new UJS website!

8. Comment by Devin Ben-Hur on 17 Aug 2006 at 21:08

Sorry about all the italics above. I forgot to think about escaping codeish identifiers to prevent the comment markup from mangling them. Hopefully it’s still intelligible.

Return to home page | Check out my tumblelog

Have your say

A name and email address (will not be displayed) is required. You may use Textile in your comments.