Blogger.create { :name =>'Matt Aimonetti',
:location => 'San Diego, Ca',
:email => mattaimonetti AT gmail.com,
:linkedin => Matt's Linkedin page,
:recommend_me => HERE,
:contractor => true}

Ajax Pagination in less than 5 minutes

Written by matt on September 26th, 2007

updated Nov 26 to reflect the recent low pro changes. (please use low pro 0.5 and Prototype 1.6)

Recently one of my client asked me to add 'ajax' pagination to his application. His site already had a very nice pagination using the excellent will_paginate from Mislav and the guys(PJ & Chris) from err the blog but since my client had a special need where he had to have Ajax.

It took me virtually no time to convert the standard pagination into an Ajax navigation while still degrading gracefully.(it works even without Javascript)

I really enjoy using will_paginate, it's very well written and the authors keep up with the bugs and new features.

Start by installing will_paginate:

ruby script/plugin install svn://errtheblog.com/svn/plugins/will_paginate

Then go watch the Railcast screencast about will paginate.

Once you have your pagination working, we will do some 'progressive enhancement'.

What we want is to add a behavior to the pagination link. The behavior would make the same call than the normal link but via an ajax(Javascript) call.

Add lowpro

To do that, you simply need to add the excellent 'lowpro' Prototype extension from Dan Webb

You can get the files directly from the lowpro's repository.

Add lowpro.js to your public/javascript folder.

Don't forget to include the javascript in your page. (

1
2

  <%= javascript_include_tag 'lowpro' %>

Create a behavior

Now open your application.js file (or whichever Javascript file you're using) and add the following:

1
2
3
4
5

  Event.addBehavior.reassignAfterAjax = true;
  Event.addBehavior({
    'div.pagination a' : Remote.Link
  })

Refresh your cache, reload your page, and test the link. It will probably look like it doesn't do anything but if you are using firebug or if you are checking your logs, you'll notice something happened. The problem is that we didn't tell our action to send an Ajax response so we get the html full page all over again.

Setup a response for javascript requests

Got to your action handling the pagination and let's setup a response for Javascript:

1
2
3
4
5
6
7
8
9
10
11
12

  def index
  @photos = Photo.paginate(:all, :conditions => ["photos.user_id = ?", current_user.id], :page => params[:page])
    respond_to do |format|
      format.html # index.html.erb
      format.js do
        render :update do |page|
          page.replace_html 'photos', :partial => "photos"
        end
      end
    end
  end

Perfect! Now when a visitor clicks on my pagination links, only the photos are paginated, the rest of the page stays the same. Note that my navigation bar is inside the partial so it gets 'updated' after a visitor clicks on any pagination link.

Read more and convert to UJS (unobtrusive javascript)

Read more about UJS and think about replacing all your nasty inline javascript snippets by pretty behaviors :) (Think about stopping using the obtrusive rails helpers)



Comments

  • Jacob Radford on 26 Sep 22:30

    The partial that is returned has an updated pagination area also, if I read what you wrote correctly. Does low-pro pick up those links also, so that they will be remote ajax calls?

  • Matt Aimonetti on 27 Sep 06:24

    @Jacob, my pagination links are only in the partial itself. At the bottom of the partial I'm calling

    <%= will_paginate @photos %>
    

    Lowpro picks up these links too, even after the visitor clicked on a pagination link and reloaded the pictures.

  • Felipe Giotto on 01 Oct 16:18

    This is the first Ajax Pagination post I've ever seen! Really simple and functional! Congratulations!

    Felipe ;-)

  • Matt Buck on 04 Oct 23:40

    I just thought I'd point this out, in case anybody runs into an error. If you are using javascriptincludetags :defaults, you will actually need to break it out into individual calls to each file. This is to ensure that you load the files in the proper order:

    1. prototype.js
    2. lowpro.js
    3. remote.js
    4. application.js

    Loading the javascript files in this order will satisfy the dependencies correctly.

    Great post, Matt!

  • Lee Enricoso on 12 Oct 12:36

    Encountered a weird problem. May be you guys could point me in the right direction. Tried following the instruction above. Setup the necessary javascript call (using lowpro) in my application.js. I'm pretty sure that when I click the pagination button an AJAX call is being thrown. Traced it using firebug. In the controller where the pagination is being handled the handling of the javascript is not being process. The "format.js do..." block is not being executed. Any chance some of you guys could shed some light on this. Thanks.

  • Matt Aimonetti on 12 Oct 13:35

    What's the Ajax response? Firebug should give you a response. Is it the full html page? make sure your respond_to block works properly. I'm running my apps on rails 2.0 so I can't guarantee it works with 1.2.4.

    -Matt

  • koloa on 16 Oct 07:32

    hi. i was wondering how i can use this in an action that calls a form? paginate will recall that form action.

    i have this

    def action initialize my form data render my custom template ajax pagination code.... end

    i am basically trying to create a blog post form, but also list photos so the user can have the photos addresses handy. id like to be able to paginate these photos on the form page. kind of in a slump.

    thanks!

  • Matt Aimonetti on 16 Oct 10:45

    Mislav is working on that. I personally patched the source until Mislav is done, check his progress there: http://err.lighthouseapp.com/projects/466-plugins/tickets?q=tagged%3Awill_paginate

    -Matt

  • Robert on 16 Oct 16:27

    I've tried this with Rails 2.0, and it doesn't seem to be doing anything. The result still refreshes the page and I'm using the newest versions of all of the libraries.

  • Matt Aimonetti on 17 Oct 00:58

    Robert, if the whole page is refreshed that means that the remote call didn't work. Use Firebug to see if an Ajax call is made. If it isn't that probably means that your Javascript Behavior isn't set properly or that your js file isn't loaded properly or that the remote file is missing.

    Good luck.

    -Matt

  • Robert on 17 Oct 13:54

    My application.js has: Event.addBehavior({ 'div.pagination a' : Remote.Link });

    My view rendered looks like: ... <head> <title>Locations</title> <link href="/stylesheets/main.css?1191535166" />

    </head> ... some more stuff ...

    ...

    Reponds statement in the index method respond_to do |format| format.html # index.html.erb format.js do render :update do |page| page.replace_html 'table', :partial => "table" end end end

    I'm using Rails 2.0, with prototype 1.6.0rc1 and the newest willpaginate from the repository.

    This seems to be exactly what the article says. Where could I be going wrong?

    Thanks.

    Rob

  • Robert on 17 Oct 13:55

    My application.js has: Event.addBehavior({ 'div.pagination a' : Remote.Link });

    My view rendered looks like: ... <head> <title>Locations</title> <link href="/stylesheets/main.css?1191535166" />

    </head> ... some more stuff ...

    ...

    Reponds statement in the index method respond_to do |format| format.html # index.html.erb format.js do render :update do |page| page.replace_html 'table', :partial => "table" end end end

    I'm using Rails 2.0, with prototype 1.6.0rc1 and the newest willpaginate from the repository.

    This seems to be exactly what the article says. Where could I be going wrong?

    Thanks.

    Rob

  • Robert on 17 Oct 14:00

    Another thing to mention is I tried using Firebug to set a breakpoint in application.js on the addBehavior line, as well as other places in remote and lowpro, and nothing hit. There is some very obscure bug.

  • Malko on 25 Nov 00:36

    Matt,

    This works like a charm with trunk lowpro and new 1.6 prototype. I got everything working perfectly except for one small detail: After you "update" the first time, the lowpro behavior does not seem to be attached, thus falling back into a non-ajax call. Is this due to the trunk lowpro / 1.6 prototype combo, or do you have any idea why that may be happening?

    Best

  • Matt Aimonetti on 25 Nov 17:14

    @ Malko

    Low pro 0.5 changed a bit and you now need to set reassignafterAjax to true.

    Event.addBehavior.reassignAfterAjax = true;

    Thanks for the comment, I forgot to post about this recent change.

  • bob on 25 Nov 18:03

    hi, nice article. unfortunately the link to 'remote.js' is not working for me at the moment, getting a 404: http://svn.danwebb.net/external/lowpro/trunk/behaviours/remote.js

  • Malko on 25 Nov 20:28

    @bob

    remote.js has been merged into the lowpro.js base. This is why you can't find it. If you just download the new lowpro (which works with prototype 1.6), you should be fine. Just make you see Matt's comment above.

    Best

  • juju on 26 Nov 02:50

    Hi,

    This seems like a fantastic way to paginate, I already had the "will_paginate" plugin installed, so I just wanted to implement that lowpro feature. I have the latest prototype & lowpro included in my page, checked that the files were in the correct path and readable.

    but when I reload the page, all I see in firebug is an error: Remote is not defined [Break on this error] 'div.pagination a' : Remote.Link

    been through the lowpro.js file, and found this function in line 275 "Remote.Link = Behavior.create(Remote.Base, { ..."

    so I'm stucked... :( anyone has a clue of what I did wrong ?

    thanks

    julien

  • juju on 26 Nov 04:17

    oooops, a bit of logic and I found it....

    using javascriptincludetag :default the application.js was loaded before lowpro.js, it's why I had the error.

    I replaced the default tag to include the javascripts in correct order : prototype, lowpro, application and now it works :)

  • Matt Aimonetti on 26 Nov 15:51

    Just updated the article to reflect the latest low pro changes.

    A quick trick, if you don't want to use Event.addBehavior.reassignAfterAjax = true; because it might slow down your application, you can always use

    Event.addBehavior.reload();

    to manually reload the behavior rules.

  • Edward Ocampo-Gooding on 30 Nov 20:07

    Hi Matt,

    I just tried your update, and using low pro 0.5 and Prototype 1.6.0, things seem to work properly during the first ajax click, but subsequent ones cause two events to fire. Do you know what's up with that?

  • Francesco on 06 Dec 09:25

    hi, i'm trying to use this example, i think i've done all right but my partial is update with this code: try { Element.update("components", "\u003Cdiv id=\"components\"\u003E\n\n \n\u003Ch2\u003EDimmer\u003C\/h2\u003E\n\n\u003Ca href=\"\/components\/new_component\/5\"\u003ENew 'Dimmer' Component\u003C\/a\u003E\u003Cbr \/\u003E\u003Cbr \/\u003E\n\n\u003Ctable cellpadding=\"0\" cellspacing=\"0\" class=\"border\"\u003E\n \u003Cthead\u003E\n \u003Ctr\u003E\n \n \u003Cth\u003EName\u003C\/th\u003

    instead of the correctly formatted html. What am i doing wrong?

  • rai on 08 Dec 08:47

    hello,

    I'm having the same problem as juju:

    Firefox says Remote is not defined 'div.pagination a' : Remote.Link.

    I'm sure that I don't have any call to javascriptincludetag : default

    And in my application.rhtml I have:

    I don't see in the debuger any error for loading this files.

    And in my application.js I have:

    Event.addBehavior.reassignAfterAjax = true; Event.addBehavior({ 'div.pagination a' : Remote.Link })

    Any clue ?

    thanks,

    rai

  • Matt on 10 Dec 09:08

    @rai make sure you are using the latest version of lowpro and that lowpro is loaded before application.js

    I'm sorry I can't help more :(

  • Tom on 13 Dec 08:29

    This works well when you only have one paginator per action. But will_paginate builds the links from the template params so I can't figure out how to have multiple paginators on a single page. Does anyone have some pointers to get that to work? I was thinking along the lines of using different class names for each pagination div and adding different Behaviors but I'm not sure how to modify Remote.Link to call out different actions than the default.

  • skoppy on 13 Dec 15:22

    I'm running into a weird error where the ajax call seems to be working however instead of replacing the partial it is putting in a new copy on top of it shifting it down and to the right. I can still see the top border of the previous page, and like I said earlier all the new pages show up shifted slightly, down and to the right?

    Any ideas on what may be causing this and how I may be able to resolve the issue?

  • jake on 18 Dec 12:32

    @francesco

    You're probably using an :update=>'elemname' in your remotefunction which causes the element to be replaced with the jscript returned by the render :update {...}

  • skoppy on 18 Dec 17:31

    @jake

    I'm having the same issue has francesco, but it only occurs after you flip through the pagination, click on a new page and then hit the back button to return to the page with the pagination on it.

    I'm kind of new at this so where might I find that remote function that you mentioned? I took my code straight from the example above. And while this error might be something some people may never see it would be bad for the people that do run across it.

    Any help would be appreciated. Thanks.

  • Eric on 04 Jan 07:15

    rai and others - I just had a similar problem and it is due to prototype 1.5.0. Upgrade you're prototype.js and you'll be much better.

  • Sandy on 17 Jan 19:47

    @Tom

    I got multiple element ajax pagination working fairly easily.

    1. in your controller make sure you have unique page params defined when using paginate :page => params[:page_assets] :page => params[:page_comments] etc...
    2. Also in the controller check what page param has been submitted and act accordingly

    if params[:page_assets] respond_to do |format| format.html format.js do render :update do |page| page.replacehtml 'assetlist', :partial => "assets/show_assets", :locals => {:assets => @assets} end end end end

    Probably could move this into a view helper to minimize duplication when checking multiple conditions but i couldn't be bothered at the moment.

  • skoppy on 04 Feb 13:40

    Using this method how could I add an effect to this? Maybe one of the script.aculo.us effects.

  • Grayson Pierce on 13 Feb 02:28

    Matt

    Thanks for figuring out the issue with :defaults. Pulling my hair out, until I read your comment.

    Cheers,

    GP

  • Grayson Pierce on 13 Feb 14:30

    Hmm, I'm having a really simple problem; pagination is working, the ajax calls are updating the content however the page numbers aren't posting pack so that the correct page is "highlighted" as being the active.

    If I do the URL

    items?page=4

    that obviously shows the correct page number as being selected.

  • Grayson Pierce on 13 Feb 21:50

    Ok, I got it, didn't see the note regarding putting the pagination tags inside the partial, wow... it finally worked!!!!

  • Ken on 16 Feb 23:15

    Nice pagination enhancement. Got it working mostly except it seems every other page submit executes a browser refresh with URL update to my browser path bar. So to be a little more clear click from page 1 -> 2 goes fine, 2 to 3 causes a refresh, from 3 back to 1 causes no refresh. Seems it's every other pagination click that causes a refresh no matter how many pages or what order I click the pages in. Anyone have any clue on this? I'm Using rails 2.0.2 and latest prototype.

  • Eric Givens on 21 Feb 06:53

    Let me just say: when the title says '5 minutes' it means 5 minutes!!! if that!! :)

    Great tutorial. Worked perfectly, first try. And it helped me wrap my brain around UJS/LowPro a little more. Beautiful!

  • Brodie on 10 Mar 09:17

    So, here is an issue. This all works well, but let's say I'm using it in a scenario where I have multiple tabs that are also loaded via ajax. Each tab displays different data of the same model. For instance let's say the model is Post, and I have tab links to posts by category, author, rating and user favorites. I used a different action in the controller for each of these tabs.

    What happens with the pagination is that it works fine for the default /posts view. However, when I get to the highest rated tab, the records load and the paginating links show up but when I click the link, it doesn't go to the next page. I can see the request in the console, it's requesting /posts/highest_rated?page=2 but that action isn't returning to the view like the default posts is.

    Any ideas?

  • Clemens Kofler on 30 Apr 15:18

    Brodie: Maybe you're using different containers for the different tabs? In this case, you'd have to customize some of Matt's stuff. If Firebug executes the AJAX request correctly, it's usually a sign that there's something wrong with response evaluation.

  • Roberto on 13 May 14:06

    Hi Matt, i really appreciate your hack to will_paginate, it was excactly what o was looking for. I have Prototype 1.6.0.2 and Low Pro 0.5. Everything seems to work fine and it did, but with firebug i get this annoying javascript error and googling didn't help:

    Event.addBehavior has no properties application.js?1210710865()application.js?12... (line 5) Event.addBehavior.reassignAfterAjax = true;

    where my application.js is exactly like your and in my view i included the javascripts like this:

    <%= javascript_include_tag 'prototype' %>
    <%= javascript_include_tag 'lowpro' %>
    <%= javascript_include_tag 'remote' %>
    <%= javascript_include_tag 'application' %>
    <%= javascript_include_tag 'effects' %>
    <%= javascript_include_tag 'controls' %>
    

    I even tried to substitute "Event.addBehavior.reassignAfterAjax = true;" with this "Event.addBehavior.reload();" but with the same result.

    Any ideas?

    Thanks in advance.

  • Scott on 26 Jun 00:26

    Thanks for the excellent tutorial. Note, my will_paginate links were not updating correctly so I added the following to the code:

    format.js do render :update do |page| page.replace_html 'images', :partial => "/admin/images/image", :collection => @images page.replacehtml 'willpaginate', "#{will_paginate @images}" end end

  • Post a comment