MetaSkills.net

Unobtrusive JS In Rails 3 With Prototype

Posted On: January 29th, 2010 by kencollins

Are you bleeding on the edge of rails 3 and need to shim up some unobtrusive JavaScript to work with your link_to code that uses a destructive :method option? I did today and here is what I did to solve it. If you are unfamiliar with the problem, and what has been happening in rails 3 with UJS, check out Piotr Solnica's blog for a good run down. Or you can check out the simple code block below.

1
2
3
4
5
6

# This Ruby
link_to 'Logout', session_path, :method => :delete

# Will out put this HTML in Rails 3
# <a href="/session" data-method="delete" data-url "/session" rel="nofollow">Logoout</a>

So no more tag soup. Yea! There was much rejoicing, but I could not find any illustrated examples of what type of JavaScript to use to back this up. There are two problems at play here. First, simple link <a> tags are not forms and hence methods like post/put/delete are not going to be solved with simple query params. Second, because link tags are not forms, there is no authenticity_token hidden in the generated form, like a button_to would generate. Let's ignore the second problem for a bit and see what I came up with for using with Prototype.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

var MyJsObject = {
  
  linkToDelete: function(event) {
    event.stop();
    var e = event.element();
    var a = e.readAttribute('href') ? e : e.up('a');
    var form = FORM({action:a.href, method:'post'},[
      INPUT({type:'hidden',name:'_method',value:'delete'}),
      INPUT({type:'hidden',name:'authenticity_token',value:authParams.get('authenticity_token')})]
    );
    form.submit();
    return false;
  }
  
};

document.observe('dom:loaded', function(){
  $$('a[data-method=delete]').each(function(a){ a.observe('click', MyJsObject.linkToDelete); });
});

In this example I am only covering the DELETE action. This code could be abstracted to take a "method" argument and use it. As you can see, it's just a simple iteration over the links with custom "data-method" attributes and attaching a function to it. For brevity, I am using the Builder.js methods for building DOM objects. See anything odd up there? The authentication token? You got it, destructive actions like POST/DELETE/PUT will need that token. I wrote up a great article a year or so ago that still works today titled RESTful AJAX with Forgery Protection that gives me that nice global var. Check it out for the last piece to this puzzle and have fun working on the edge!

Stephen Celis

  HOMEPAGE  | January 29th, 2010 at 06:56 PM
Stephen Celis When edge Rails made the switch, it started generating "prototype.rails.js" and "jquery.rails.js" files which take care of this for you, just make sure to javascript_include_tag them.

Ken Collins

  HOMEPAGE  | January 29th, 2010 at 08:00 PM
Ken Collins

Awesome! Let me check that out

Ken Collins

  HOMEPAGE  | January 29th, 2010 at 08:43 PM
Ken Collins

Yup, I had to generate a rails project from edge to find this. Seems the generator is remote and searching source did not yield a hit, which is why I did not find it earlier. The prototype code written is really nice. Very very informative reading it too. Thanks

Steve St. Martin

  HOMEPAGE  | January 30th, 2010 at 05:14 PM
Steve St. Martin The UJS functionality is currently going under some restructuring, most existing functionality will be moved into a legacy plugin: http://github.com/rails/prototype_legacy_helper and the javascript libraries have been rewritten removing backwards compat (as the legacy plugin will maintain 2.3 support): http://github.com/rails/jquery-ujs http://github.com/rails/prototype-ujs these are being changed in favor of a cleaner 3.0 api such as link_to @user, :remote => true, :method => :delete and form_for @user, :remote => true as opposed to the link_to_remote and remote_form_for

Radoslav Stankov

  HOMEPAGE  | February 4th, 2010 at 02:38 AM
Radoslav Stankov You can try my Event.delegate prototype plugin ( http://gist.github.com/66568 ). And just do this: document.observe('dom:loaded', function(){ document.delegate('a[data-method=delete]', click', MyJsObject.linkToDelete); }); this will use event delegation, and won't populate the page with event handlers. ( as a bonus it will handle dynamic adding / removing of a[date-method=delete] elements )

Leave a Comment

Name (required)
Email (will not be published)
Website
Comment