Ruby On Rails 3, form_tag remote on responding to Ajax request

31,220

Solution 1

I posted this answer on Hacker News, but figured the Stack Overflow community might benefit as well :-)

In Rails 3, the javascript drivers are very hands-off (i.e. unobtrusive). The problem you're having is that your app is returning to the browser a string of javascript, but there is nothing in the page that is then executing that javascript in the context of the page.

The rails.js ujs driver binds to forms and links with data-remote=true, which is what the :remote => true is doing, to make them submit their requests remotely, but that is where the Rails magic stops.

The good news is that the remote requests fires off some events you can bind to, which give you access to the data returned by the server (which fire off in the following order):

  ajax:before
  ajax:loading
  ajax:success
  ajax:complete
  ajax:failure
  ajax:after

You need to bind an event to the ajax:success event of your form. So, if your form had the id "myform", you'd want something like this on your page:

  $('#myform').bind('ajax:success', function(evt, data, status, xhr){
    eval(xhr.responseText);
  });

xhr.responseText is what your server returns, so this simply executes it as javascript.

Of course, it's proper to also bind to the failure event with some error handling as well.

I usually don't even use the action.js.erb method of returning javascript, I just have my controller render the HTML partial, and then I have a binding like this in the page:

  $('#myform').bind('ajax:success', function(evt, data, status, xhr){
    $('#target-div').html(xhr.responseText);
  });

I'm actually in the middle of writing a full article on this, but hopefully this is enough to get you going.

EDIT: I finished that article, fully explaining remote links and forms in Rails 3. Maybe it will help:

Rails 3 Remote Links and Forms: A Definitive Guide

Solution 2

If you look at the rendered source of your page, you should notice an error in the fields attributes.

The correct way to do it is as follows:

<%= form_tag({:action => 'list'}, :remote => true %>

Notice the curly brackets, very important! Alternatively you could have done:

<%= form_tag('list', :remote => true %>

Solution 3

Do you have 2 partials named '_list'? Maybe that's causing problems and you should just a little more specific:

$("#my_list").html("<%= escape_javascript(render(:partial => "list.html.erb"))%>");


I'm not sure if this helps, but are if you using in IE be aware that IE sends some headers that screw with how your controller responds. So you may be sending an Ajax request with IE, but your Rails app thinks its just a plain html request.

I've had to setup jQuery to first erase the current headers and then add just the javascript header:

$.ajaxSetup({
'beforeSend': function(xhr) {xhr.setRequestHeader("Accept",'');xhr.setRequestHeader("Accept", "text/javascript")}

})

Solution 4

Using list as your function name in the controller may be the problem. That name is used internally by Rails.

Share:
31,220
Kunal
Author by

Kunal

Engineer. Mostly Java, and the occasional Python.

Updated on April 28, 2020

Comments

  • Kunal
    Kunal about 4 years

    thanks for reading this post. I've been stuck on an issue with RoR for the past few days. I have a form under index.html.erb as:

    <head>
        <title>Ajax List Demo</title>
        <%= javascript_include_tag :defaults %>  
        <%= csrf_meta_tag %>  
    </head>
    <body>
      <h3>Add to list using Ajax</h3>
    
      <% form_tag :action => :list , :method=>:get, :remote=>true  do %>
        Enter the public url:<%= text_field_tag 'url' ,'', :size => 80 %>
        <%= submit_tag "Find" %>
      <% end %>
    
      <div id="my_list">
      </div>
    </body>
    

    In the controller I have:

     def list
        puts "here!!!!"
        reader = Reader.new
        @profiles =  reader.processURL(params[:url]) #profileList = 
        respond_to do |format|
          #format.html { render :partial=>true, :locals => { :profiles => @profiles}}#{          render :partial=>'profiles/list',:layout => false, :locals => { :profiles => @profiles}} 
    
    format.js {render :content_type => 'text/javascript', :locals => { :profiles => @profiles}}
    
    # index.html.erb
          #   format.rss     render :partial=>'profiles/list',:layout => false, :locals => { :profiles => @profiles}
        end
    

    And a js file for remote UJS as list.js.erb

    $("#my_list").html("<%= escape_javascript(render(:partial => "list"))%>");
    

    The issue is I cannot get the results to render the partial _list.html.erb, in the div tag my_list. I get a blank page, 406 error. If I un-comment the render html code in the controller I get the partial back rendered in the browser. I am kind of stuck, I want to submit the form and the results to pop in the my_list div. I'm new to rails so if I'm missing something obvious don't hesitate to point it out to me....I'm definitely willing to try.


    Changed it to this:

    <html>
        <head>
            <title>Ajax List Demo</title>
            <h1>Listing posts</h1>
               <%= javascript_include_tag 'jquery.js' %>  
        <%= csrf_meta_tag %>  
        </head>
        <body>
            <h3>Add to list using Ajax</h3>
    
            <% form_tag :action => :doit , :method=>:get, :remote=>true  do %>
            Enter the public url:<%= text_field_tag 'url' ,'', :size => 80 %>
            <%= submit_tag "Find" %>
            <% end %>
    
            <div id="my_list">
                       </div>
    

    Controller:

    def doit
        puts "here!!!!"
        reader = Reader.new
        @profiles =  reader.processURL(params[:url])
        respond_to do |format|
     #     format.html {render :partial=>true, :locals => { :profiles => @profiles}}#{    render :partial=>'profiles/list',:layout => false, :locals => { :profiles => @profiles}} 
          format.js #{render :content_type => 'text/javascript', :locals => { :profiles => @profiles}}
          # index.html.erb
          #   format.rss     render :partial=>'profiles/list',:layout => false, :locals => { :profiles => @profiles}
        end
    

    JS _doit.js.erb $("#my_list").html("<%= escape_javascript(render(:partial => "doit"))%>");

    And finally a partial:

    _doit.html.erb.

    However I am still getting the 406 error, I dont have a duplicate _doit js or erb. Does anything standout as incorrect from this? Thanks again!


    Another update:

    I think the form is not rendered correctly:

    This rendered:

    <% form_tag :action => :doit , :remote=>true, :id => 'myform' do %>
            Enter the public url:<%= text_field_tag 'url' ,'', :size => 80 %>
            <%= submit_tag "Find" %>
            <% end %>
    

    This:

    <form accept-charset="UTF-8" action="/home/doit?id=myform&amp;remote=true" method="post">
    <div style="margin:0;padding:0;display:inline">
    <input name="utf8" type="hidden" value="&#x2713;" />
    <input name="authenticity_token" type="hidden" value="MLuau4hvfdGO6FrYCzE0c0JzwHhHKZqjmV49U673sK8=" />
    </div>        Enter the public url:
    <input id="url" name="url" size="80" type="text" value="" />
    
        <input name="commit" type="submit" value="Find" />
    
    
    
            <input name="commit" type="submit" value="Find" />
    

    Its adding my remote tag and id to the query string, isnt this wrong?

    Ok finally got a clue forms need to be bracketed:

     <%= form_tag( { :action => 'doit' }, :multipart => true, :remote=>true, :id => 'myform' ) do %>
    

    Ok last update tonight: Now I get in the logs:

    Started POST "/home/doit" for 127.0.0.1 at Wed Oct 27 22:40:55 -0400 2010 here!!!! Processing by HomeController#doit as JS Parameters: {"commit"=>"Find", "url"=>"http://www.facebook.com/people/James-Stewart/653161299", "authenticity_token"=>"MLuau4hvf dGO6FrYCzE0c0JzwHhHKZqjmV49U673sK8=", "utf8"=>"Γ£ô"} Rendered home/_doit.html.erb (4.0ms) Rendered home/doit.js.erb (9.0ms) Completed 200 OK in 807ms (Views: 40.0ms | ActiveRecord: 0.0ms)

    I see as JS and it says it renders my js/partial. However I am getting nothing on my_list div. My JS file:

    $("#my_list").html("<%= escape_javascript(render(:partial => "doit"))%>");
    

    My html.erb form file has now:

    <script$('#myform').bind('ajax:success', function(evt, data, status, xhr){
        xhr.responseText;
      });></script>
    

    Its like the form does nothing, which is a good sign, no more 406 error. I know this is close, if anyone can point what I need to do in the js that would be great otherwise I'll take a break and try tmrw.


    Ok I think its getting a response back just not rendering as you pointed out would be the issue yesterday Steve.

    Debugging the JS on Firebug I see the html I want rendered in the div, for this:

    http://localhost:3000/javascripts/prototype.js?1285674435/event/seq/1
    

    Which means I think I am getting the JS response back now.

    I have this on the form page:

    <script>$('#myform').bind('ajax:success', function(evt, data, status, xhr){
        $('#my_list').html(eval(xhr.responseText));
      });</script>
    

    Inspections say it doesnt know what myform is, but I put :id => 'myform' in the Rails code.


    Again all thanks, I got a ton of help here and I want to share how I finally got it working back to the community.

    The, js file for the method doit(def. need a better controller action name) is doit.js

    The code was ultimately:

    $("my_list").update("<%= escape_javascript(render(:partial => "doit"))%>");
    

    For some reason leaving it as #my_list wouldn't be found in firefox, I had to use firebug to finally figure this out.

    Obviously this is different from the way suggested below, and I am going to place the js script back into the form and remove the .js.erb file and see how that works works. I suppose I just render the partial in the format.js response? Also where does everyone find info on writing the UJS files? I know nothing about the syntax for anything starting with $.

    Again thanks for the help, finally feel like I am making progress on learning rails.

  • Kunal
    Kunal over 13 years
    Hey all, I posted a follow changing it from list to doit to make sure its not because of an internal method. Still not returning anything for the .js rendering, I get the 406 error.
  • Mark Essel
    Mark Essel over 13 years
    Thanks for the answer on HN and here. As an .erb user with javascript I counted on $(document).ready(function ()... with sinatra and rails to kick off events
  • Kunal
    Kunal over 13 years
    Steve thanks for posting this, its helping me understand Ajax, and Rails alot. I posted an update, because I think the html rendered from rails does not seem to match up to what I would have expected. Anyone else getting this?
  • jangosteve
    jangosteve over 13 years
    Hmm, as I take another look at it, xhr.responseText is just a string, meaning putting that in your function wouldn't actually execute it. Try changing "xhr.responseText" to "eval(xhr.responseText)". I've updated my example with it.
  • Kunal
    Kunal over 13 years
    Close to figuring this out. I have the firebug debugger showing I get the js back. I will re-read your advice and see if I can understand correctly where my js scripts need to be placed.
  • jangosteve
    jangosteve over 13 years
    I finally finished that article I had mentioned, which may help. Added it to the post (at the bottom).
  • Kunal
    Kunal over 13 years
    Steve thanks, I got it working finally, and will post an update. For the js files, and scripts, where are people reading up on it? Saw your articles and I am going to start reading them today.
  • jangosteve
    jangosteve over 13 years
    I have a list of additional resources at the end of the posted article, and there are also a couple railscasts (I haven't watched them yet, but railscasts are usually great). I couldn't find any articles that explained any of this completely, which is why I wrote that article myself ;-)
  • jangosteve
    jangosteve over 13 years
    I'm not sure I quite understand your updated question, but all of the stuff that starts with $ is jQuery, which is the javascript framework you're using along with Rails, which is the Ruby framework. So, you probably want to read up more on jQuery and how it works. The form's ID is probably not being rendered right, because it should actually be :html => {:id => "my_form"}, instead of omitting the :html part.