Ruby on Rails
How to Paginate With Ajax

Juba If you are interested, I have written a tutorial on how to paginate, sort and live search a table with Ajax and Rails. It is available at :

http://dev.nozav.org/rails_ajax_table.html


Disclaimer: This might be the worst way ever to solve this requirement, but I wrote this in 10 minutes and I’ve never looked back!

My plan here was to have a list of ‘items’ in my admin interface that the user can paginate through and select to edit.. using the joys of rails 0.11.x, pagination, ajax, and liberal helpings of vaseline.

The Controller

The only 2 things of note here are the use of render_partial in this action, and the paginate call to set up my list of ‘items’ for the user to browse through. Read the Rails API docs on these two to get savvy, you savvy?

  model :item

  ...

  # in the 'catalogue', when you click on a section, you list all the items for it. 
  # this is the paginated ajax snippet
  def item_list_with_ajax
    unless @params['id'].nil?
      @item_pages, @items = paginate :items, :conditions => ["section_id = ?", @params['id']], :order_by => "name DESC", :per_page => 20
    end
    render_partial 'admin/blocks/item_nav' 
  end

The View

For fun, I’ve included my entire partial, showing you the love. The only thing unusual is this pagination_links_with_ajax method. Where did that come from, I wonder? Life’s full of mystery, I love it. I love gravy, too.

Item Browser
<div id="item-sidebar">
<% unless @items.nil? or @items.empty? %>
  <% for item in @items %>
  <div class="little-black-text">· 
  <%= link_to_remote item.name, :update => 'content-body', :url => { :action => 'item_edit_with_ajax', :id => item.id } %>
  </div>
  <% end %>
  <hr/>
  <div class="tipster"><%= link_to_remote 'Refresh', :update => 'item-sidebar', :url => { :action => 'item_list_with_ajax', :page => @params['page'] } %> 
  · <%= pagination_links_with_ajax @item_pages, { :element_name => 'item-sidebar', :action =>  'item_list_with_ajax' } %></div>
<% else %>
   <div class="little-grey-text">no items</div>
<% end %>
</div>

The Helper

Aha! The final piece of the puzzle – it’s like finding the holy grail to aid your dying father (who was shot in the stomach and perhaps the spine by a villainous antiquarian with dreams of glory and everlasting life):

module AdminHelper

  # ripped straight out of docs, modified for ajax
  def pagination_links_with_ajax(paginator, options={})
   options.merge!(ActionView::Helpers::PaginationHelper::DEFAULT_OPTIONS) {|key, old, new| old}

   window_pages = paginator.current.window(options[:window_size]).pages

   return if window_pages.length <= 1 unless
     options[:link_to_current_page]

   first, last = paginator.first, paginator.last

   returning html = '' do
     if options[:always_show_anchors] and not window_pages[0].first?
       #html << link_to(first.number, options[:name] => first)
       html << link_to_remote(first.number, :update => options[:element_name], :url => { :action => options[:action], :page => first })

       html << ' ... ' if window_pages[0].number - first.number > 1
       html << ' '
     end

     window_pages.each do |page|
       if paginator.current == page && !options[:link_to_current_page]
         html << page.number.to_s
       else
         #html << link_to(page.number, options[:name] => page)
         html << link_to_remote(page.number, :update => options[:element_name], :url => { :action => options[:action], :page => page })
       end
       html << ' '
     end

     if options[:always_show_anchors] && !window_pages.last.last?
       html << ' ... ' if last.number - window_pages[-1].number > 1
       #html << link_to(paginator.last.number, options[:name] => last)
       html << link_to_remote(last.number, :update => options[:element_name], :url => { :action => options[:action], :page => last })
     end
   end
  end

end

You can even see where I commented out the original link_to method calls and snuck in link_to_remote. Aren’t I devious? Almost like picking the wrong cup of a carpenter.

So, there you have it, folks, ajaxified lists for you heal the wounded web-paradigm with everlasting, asynchronous pagination!

But don’t cross the seal.

—ScottMaclure


Or another way that is probably more robust to internal changes in paginate_links

Add to application_helper.rb

  # Identical to +pagination_links+ except uses +link_to_remote+ instead of
  # +link_to+.  Additional options are:
  # +:update+::   same as in +link_to_remote+
  def pagination_links_remote(paginator, options={})
    update = options.delete(:update)

    str = pagination_links(paginator, options)
    if str != nil
      str.gsub(/href="([^"]*)"/) do
        url = $1
        "href=\"#\" onclick=\"new Ajax.Updater('" + update + "', '" + url +
        "', {asynchronous:true, evalScripts:true}); return false;\"" 
      end
    end
  end

Without Ajax:

pagination_links(@item_pages, :window_size => 5)

With Ajax:

pagination_links_remote(@item_pages, :window_size => 5, :update => "item-sidebar")



Here’s another refinement which allows you to use the Ajax options :update, :complete, :before, :success, etc.

Add to application_helper.rb

module ActionView
  module Helpers
    module PaginationHelper
      def pagination_links_remote(paginator, page_options={}, ajax_options={}, html_options={})
        name = page_options[:name] || DEFAULT_OPTIONS[:name]
        params = (page_options[:params] || DEFAULT_OPTIONS[:params]).clone

        pagination_links_each(paginator, page_options) do |n|
          params[name] = n
          ajax_options[:url] = params
          link_to_remote(n.to_s, ajax_options, html_options)
        end
      end
    end # PaginationHelper
  end # Helpers
end # ActionView

With Ajax:

  <%= pagination_links_remote @user_pages, {:window_size => 2},
      {:update => 'UserList',
      :complete => visual_effect(:blind_down, 'UserList'),
      :before   => %(Element.show('spinner')),
      :success  => %(Element.hide('spinner'))} %>

—Mike Gaffney


Another way to do this is to use the ’’pagination_links_each” method. For example, the following could be added to your application_helper.rb:
  def pagination_links_remote(paginator, page_options={}, ajax_options={}, html_options={})
    pagination_links_each(paginator, page_options) {|page|
      ajax_options[:url].merge!({:page => page})
      link_to_remote(page, ajax_options, html_options)}
  end

You can then use this in your views:
            pagination_links_remote(@user_pages, {:window_size => 3},
            :url => {:action => :list})

—Dan DeLeo