Updating Multiple DOM Elements (Unobtrusively) with AJAX in Rails

Okay, so you’re like me and you have a complex app that requires AJAX calls to update numerous areas on the page with content from partials. For instance, a date range changes, gets submitted, and that updates a calendar, the list of items below it, and so on and so forth. And, like me, you’re choosing to use sexy, unobtrusive javascript to do so. (I’ll be using jQuery as it’s my flavor of choice, but feel free to apply the same principles to your JS library). The purpose of this article is not to explain AJAX or Rails, I will assume if you’re reading, you have some education on both subjects.

First, we have a template that looks a little something like this:

<div id="events-calendar">
  <%= render :partial => "events/calendar" %>
</div>
<% form_tag events_path, :id => "update_dates", :name => "update_dates" %>
  <%= label_tag :start_date %>
  <%= text_field_tag  :start_date %>
  <%= label_tag :end_date %>
  <%= text_field_tag  :end_date %>
  <%= submit_tag "Go!" %>
<% end %>
<div id="events-list">
  <%= render :partial => "events/list" %>
</div>

Now, in the past using RJS, this was a very easy thing to do, you just did something to the effect of:

render :update do |page|
  page.replace "events-calendar", :partial => "events/calendar"
  page.replace "events-list", :partial => "events/list"
end


Also, note that I could put all of this in a partial of it’s own and re-render the whole thing, but for all intents and purposes of the app I’m working on, that doesn’t cut it since I’d have about 6 partials that look very similar to this one (for example, I have a view that just has the date inputs and a list that should update, a view that has the calendar and a form for editing an event, etc etc).

In this example, we want when the dates are updated, the calendar and the list of events should refresh in a single AJAX call, without the use of RJS. There are a plethora of reasons to break your RJS habit sooner, rather than later… I tend to dislike it for the sheer fact that it’s tied to one JS library or another, whereas using external JS means if I decide I want to use YUI instead of jQuery, the only thing I have to change is my JS function to do a YUI-style AJAX call and DOM update. No Rails plugins, messing with controllers or RJS templates, etc. etc. Plus, it looks so much cleaner when your controllers don’t have a bunch of render :updates in them.

On to the good stuff… In my application.js (or wherever you fancy), I have the following:

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

$(document).ready(function() {
  $('form#update_dates').submit(function() {
    $.get($(this).attr('action'), $(this).serialize(), function(data, status) {
      $('#events-calendar').html(data.calendar);
      $('#events-list').html(data.list);
    }, 'json');
    return false;
  });
});

I’m setting up the beforeSend AJAX option because jQuery doesn’t communicate AJAX calls to Rails as being of JS format, so if you don’t have this, you’ll likely end up in your format.html . You’ll need to know the appropriate AJAX calls for your library, the reason I’m using $.get instead of $.ajax with a dataType: ‘json’ is that I only care about having one callback. Also, we’re asking for a JSON result, but I’m not using $.getJSON  because I’ve had some issues in the past in Firefox with the getJSON method. If you needed to handle errors as well as successes, you would need to use $.ajax with the error and success callbacks.

Okay, so now let’s look at the data that Rails is going to give us before we write the callback. We know we want to send back 2 partials, and we don’t want to tie any javascript or DOM knowledge into this, so here’s the approach. We are using the Rails helper method <%= events_path %> to direct us to our index method, so we’ll use respond_to to detect the javascript call.

class EventsController < ApplicationController

  def index
    ## do your logic here, for example:
    if params[:start_date].present? and params[:end_date].present?
      @events = Event.all.between(params[:start_date], params[:end_date]) ## between would just be a named scope that adds date conditions
    else
      @events = Event.all
    end

    respond_to do |format|
      format.html # render default index.erb
      format.js {
        render :json => { :list => render_to_string(:partial => "events/list"), :calendar => render_to_string(:partial => "events/calendar") }.to_json
      }
    end
  end

end

So there are a few things this controller method is doing that are outside the scope of this article, such as using named_scopes, the respond_to block, and render_to_string — all of which can be found documented in the Rails API reference. The main thing we’re concerned with here is rendering json so that our keys are usable within our javascript. They are not the same as the id of the div since your controller shouldn’t care what your DOM looks like. And voila, it really is that easy, simple and clean to build tons of AJAX into your app without making it overly messy.

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

This entry was posted in AJAX, Development, jQuery, Ruby on Rails. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>