How to Implement ajax pagination with will_paginate gem
Asked Answered
T

6

23

I am using will_paginate gem in my ROR project to show the records in pages.

I want the next page to be loaded without reloading the whole page using ajax.

I found some examples on the net but they don't work for me.

How to do this?

Tussis answered 29/11, 2012 at 10:42 Comment(0)
L
43

Create a new helper (ex. app/helpers/will_paginate_helper.rb) with the following content:

module WillPaginateHelper
  class WillPaginateJSLinkRenderer < WillPaginate::ActionView::LinkRenderer
    def prepare(collection, options, template)
      options[:params] ||= {}
      options[:params]['_'] = nil
      super(collection, options, template)
    end

    protected
    def link(text, target, attributes = {})
      if target.is_a? Fixnum
        attributes[:rel] = rel_value(target)
        target = url(target)
      end

      @template.link_to(target, attributes.merge(remote: true)) do
        text.to_s.html_safe
      end
    end
  end

  def js_will_paginate(collection, options = {})
    will_paginate(collection, options.merge(:renderer => WillPaginateHelper::WillPaginateJSLinkRenderer))
  end
end

Then in your view use this tag for ajax pagination:

<%= js_will_paginate @recipes %>

Remember that the pagination links will include existing params of the url, you can exclude these as shown below. This is standard will paginate functionality:

<%= js_will_paginate @recipes, :params => { :my_excluded_param => nil } %>

Hope that solves your problem.

Update 1: Added a explanation of how this works to the original question where I posted this solution.

Update 2: I've made it Rails 4 compatible with remote: true links and renamed the helper method to js_will_paginate.

Lothar answered 20/12, 2012 at 15:25 Comment(7)
I've seen your answer a few places - and after implementing your code the controller is definitely hit and results are pulled from the database. But the page isn't changing at all. What am I missing? The server log says " Rendered jobs/_jobstable.html.erb (80.7ms) " but the actual page on my screen is not rendering.Encampment
The browser is making a jquery ajax call (of type script) which is expecting a JS response that will update the view. One way to respond is with JS <%= "$('body').html('#{escape_javascript(render 'some_partial')}');".html_safe %> But you can modify the code slightly to do whatever you want.Lothar
This hack works like a charm on Rails 3.x but is not longer available for Rails 4.2. link_to_function is deprecated. Could you please put the update? Thank in advance.Hellenic
hey I am trying this method for ajax pagination and in the console it seems like everything is processed but the most important thing doesn't happen: the paginated items don't change! it just doesn't change the page, so when you click the pagination link nothing happens...Korey
Your controller should respond to the JS request and re-render the section on the page. As an example your JS view file can respond with jQuery: $('#items').html('<%= j(render partial: 'items/index') %>');Lothar
How would I implement url history along with this ajax_pagination helper?Gaudet
Hi I know its very late but I am getting "uninitialized constant WillPaginate::ActionView" this error after using this fix. can anyone tell me how to fix this error? Its not allowing to render my page.Borlow
R
17

Hey if you want to paginate products then try this:

app/controllers/products_controller.rb

def index
  @products = Product.paginate(page: params[:page], per_page: 10)
end

views/products/index.html.erb

<div class = "sort_paginate_ajax"><%= render 'products' %></div>

views/products/_products.html.erb

<% @products.each do |product| %>
# your code
<% end %>
<%= will_paginate @products %>

views/products/index.js.erb

$('.sort_paginate_ajax').html("<%= escape_javascript(render("products"))%>")

assets/javascripts/application.js

$(function() {
  $(".sort_paginate_ajax th a, .sort_paginate_ajax .pagination a").on("click", function(){
    $.getScript(this.href);
    return false;
  });
});

So first we are calling paginate method on Product for all products, here an offset will be set according to params[:page] and limit will be set by per_page options. Also we are rendering products partial which will be rendered every time when other page is opened.

In partial call on @products that you want, and then will_paginate method is applied on @products, it also creates params[:page] that is used in controller.

Now in response to ajax request render content of div having class sort_paginate_ajax with the partial that we created.

Also to make request ajax request, on document load capture script of all a tags in div.sort_paginate_ajax, and return false.

Now pages will be called with ajax request

I hope this would help you.

Robinette answered 30/11, 2012 at 5:16 Comment(3)
nice, the issue I have found is that the first click on a page worked well, but after moving to a next page the ajax call was not applying to the links and it was called as html request, means that when the first ajax call is made and the partial is replaced there is no more live event appended to the pagination links, so I added the proper js to the js.erb file to bind the live event to the links when a new partial will be rendered and it worked great. !Note, live method is deprecated starting from jQuery 1.7, so you have to replace it with .on() instead of .live()Thomasinathomasine
@Thomasinathomasine when you say you added the proper js to the js.erb file could you provide an example please, im having an issue where the first next link works great but then the second request generates a link in the url of <code>/?_=1399023289230&page=3</code> for example. I have changed .live() to .on() aswell, thank youAscus
@Ascus youtube.com/watch?v=UbtmSsjKn38 I've used this one a few times in my projects.. Can't remember really what was the proper js I was talking about. Might be related to .on() and .live() events.Thomasinathomasine
M
6

Additional to previous answer.

string of code:

$(".sort_paginate_ajax th a, .sort_paginate_ajax .pagination a").on("click", function(){

replace with string:

$(".sort_paginate_ajax").on("click", ".pagination a", function(){

The onclick event applied only to the elements that already exist. So, for new appears links, need delegate click event from parent ".sort_paginate_ajax" to childs ".pagination a".

Md answered 22/5, 2015 at 11:1 Comment(0)
M
5

You could just use JQuery:

Controller:

def index
  @posts = Post.paginate(:page => params[:page])      

  respond_to do |format|
    format.html
    format.js
  end
end

Then in index.html.erb:

<div id="post-content", posts: @posts >
  <%= j render 'post_content' %>
</div>

In partial '_post_content.html.erb':

<%= will_paginate @posts %>
<% @posts.each do |post| %>
  <p><%= post.content %></p>
<% end %>

pagination.js:

$(document).ready(function() {
  $('.pagination > a').attr('data-remote', 'true');
});

index.js.erb:

$('#post-content').html("<%= j render 'post_content' %>")

Make sure to add the

$('.pagination > a').attr('data-remote', 'true');

again in your JS template (index.js.erb), so it sets the links data-remote again when the Ajax runs.

Miliary answered 14/6, 2016 at 0:24 Comment(0)
E
1

I want to expand on Pierre's answer.

If you are using materialize sass with will_paginate_materialize then you will have an initializer to style the pagination links.

gem 'will_paginate', '~> 3.1.0'

gem 'will_paginate-materialize', git: 'https://github.com/mldoscar/will_paginate-materialize', branch: 'master'

So, in order to get the remote true to work on the links rendered by the will_paginate_materialize initializer I did this:

will_paginate_helper.rb

module WillPaginateHelper
  class WillPaginateJSLinkRenderer < WillPaginate::ActionView::LinkRenderer
  end
  def js_will_paginate(collection, options = {})
    will_paginate(collection, options.merge(:renderer =>WillPaginateHelper::WillPaginateJSLinkRenderer))
  end
end

I changed the will paginate materialize initializer to look like this:

will_paginate_materialize.rb

MaterializePagination::MaterializeRenderer.module_eval do
  def page_number(page)
    classes = ['waves-effect', ('active' if page == current_page)].join(' ')
    tag :li, link(page, page, :rel => rel_value(page)), class: classes
  end

  def prepare(collection, options, template)
    options[:params] ||= {}
    options[:params]['_'] = nil
    super(collection, options, template)
  end

  protected
  def link(text, target, attributes = {})
    if target.is_a? Fixnum
      attributes[:rel] = rel_value(target)
      target = url(target)
    end

    @template.link_to(target, attributes.merge(remote: true)) do
      text.to_s.html_safe
    end
  end
end

WillPaginate::ViewHelpers::LinkRenderer.class_eval do
  def symbolized_update(target, other, blacklist = nil)
    other.each_pair do |key, value|
      key = key.to_sym
      existing = target[key]
      next if blacklist && blacklist.include?(key)

      if value.respond_to?(:each_pair) and (existing.is_a?(Hash) or existing.nil?)
        symbolized_update(existing || (target[key] = {}), value)
      else
        if value.instance_variable_defined?(:@parameters)
          value = value.instance_variable_get(:@parameters)
        end
        target[key] = value
      end
    end
  end
end

In my view I left the renderer in the will paginate link the same:

= will_paginate results, renderer: MaterializePagination::Rails

Effie answered 21/6, 2019 at 16:52 Comment(0)
F
0

To follow on from @Pierre's excellent answer re creating a helper, I've seen some follow up questions regarding updating the views (a problem I initially had). @Pierre follows up with that problem by stating that you need to create a js response. Here's what I did after creating the helper:

in my show.html.erb

<div id="see-comments">
    <%= render @comments %>
</div>
<div id="pagination-comments">
    <%= js_will_paginate @comments %>
</div>

I created another file called show.js.erb (so two formats for show)

// to update comments    
$("#see-comments").html("<%= j render @comments %>");
// to update the pagination links
$("#pagination-comments").html('<%= js_will_paginate @comments %>');
Fingerstall answered 7/11, 2016 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.