Sunday, 27 November 2016

Infinite or Endless Scrolling pagination in Rails with Kaminari

In this blog we will implement infinite scrolling with kaminari gem. Infinite scrolling appends more content to the end of the page before the viewer gets to it, so they can just keep scrolling unabated and the application doesn't need to initially load a bunch of records only a small percent of viewers will ever scroll down to.

Add 'kaminari' to your Gemfile -


gem 'kaminari'

Run the jQuery generator to download the necessary files and setup your Rails app to use jQuery.

$ rails generate jquery:install

In Controller -

def index
  @projects = Project.order(:created_at).page(params[:page])
end


In index.html.erb

<h1>Listing projects</h1>

<table id="projects">
  <thead>
    <tr>
      <th>Title</th>
    </tr>
  </thead>

  <tbody class="project-snippet">
    <%= render @projects %>
  </tbody>
</table>

<br>

<%= paginate @projects %>

<script>

paginator = new ScrollPaginator({
       item: $('.project-snippet'),
       items_source_url: '<%= projects_path %>',
       total_pages: <%= @projects.count/(Project::PAGINATION_SIZE) %>
   }).enable();

</script>


In assets/javascripts/scrollpaginator.js -

ScrollPaginator.VISIBILITIES = ['TOP', 'COMPLETE'];

function ScrollPaginator(options) {
   this.options = {
       object_visibility: 'TOP',
       total_pages: 0
   };
   $.extend(this.options, options);

   this.sample_element = $('<div id="sample_object" class="sample_object"></div>');
   $(this.sample_element)
       .height(this.options.item.height()).width(this.options.item.width())
       .attr('class', $(this.sample_element).attr('class') + this.options.item.attr('class'));

   this.last_loaded_page = 1;
   this.request_next = true;
}

ScrollPaginator.prototype = {
   provision: function () {
       this.sample_element.attr('class', this.options.item.attr('class'));
       this.options.item.parent().append(this.sample_element)
   },

   validVisibility: function () {
       $.inArray(this.options.object_visibility, ScrollPaginator.VISIBILITIES)
   },

   increment_page: function () {
       this.last_loaded_page += 1;
       if (this.last_loaded_page >= this.options.total_pages) {
           this.last_loaded_page = this.options.total_pages;
       }
   },

   decrement_page: function () {
       this.last_loaded_page -= 1;
       if (this.last_loaded_page <= 1) {
           this.last_loaded_page = 1;
       }
   },

   activateOnScrollPagination: function () {
       var _this = this;
       $(window).scroll(function () {
           _this.loadItems();
       });
   },

   isSampleVisible: function () {
       var docViewTop = $(window).scrollTop();
       var docViewBottom = docViewTop + $(window).height();
       var elemTop = $(this.sample_element).offset().top;
       var elemBottom = elemTop + $(this.sample_element).height();
       switch (this.options.object_visibility) {
           case 'TOP':
               return ((elemTop <= docViewBottom) && (elemTop >= docViewTop));
               break;
           case 'COMPLETE':
               return ((elemBottom <= docViewBottom) && (elemTop >= docViewTop));
               break;
           default:
               console.log('Visibility option is not valid.');
               return false;
       }
   },

   loadItems: function () {
       var _this = this;
       if (this.isSampleVisible()) {
           if (this.request_next) {
               if (this.last_loaded_page < this.options.total_pages) {
                   this.increment_page();

                   /*--- For preventing multiple requests ---*/
                   this.request_next = false;

                   $.ajax({
                       method: 'GET',
                       dataType: 'script',
                       url: this.options.items_source_url + '?page=' + _this.last_loaded_page
                   }).success(function () {
                       _this.request_next = true;
                       _this.loadItems();
                   }).fail(function () {
                       _this.decrement_page();
                   });
               } else {
                   this.request_next = false;
               }
           }
       }
   },

   enable: function () {
       this.provision();
       this.activateOnScrollPagination();
       this.loadItems();
   }
};

1 comment: