Infinite Scrolling trong Rails

Ngày nay, nhiều trang web sử dụng một kỹ thuật gọi là infinite scrolling (hoặc endless page). Về cơ bản, đó là cách khi người dùng cuộn xuống trang, nhiều bản ghi được tải không đồng bộ bằng AJAX. Theo cách này, di chuyển trang sẽ tự nhiên hơn và dễ dàng hơn cho người dùng thay vì phải liên tục nhấp vào link 'Next page'. Bài này sẽ giải thích làm thế nào để thực hiện infinite scrolling thay cho cách phân trang cổ điển.

Khởi tạo Project

Ta sẽ tạo một blog demo đơn giản với chức năng chỉ hiển thị các bài đăng

$ rails new infinite_scrolling`

Thêm một số gem vào Gemfile :

gem "will_paginate"
gem "faker"
gem "bootstrap-sass"
gem "bootstrap-will_paginate"

Ta sẽ sử dụng gem will_paginate để thực hiện việc phân trang bản ghi, gem faker giúp ta tạo ra các mẫu dữ liệu demo lưu ở bản ghi. Trong project này ta sẽ thêm gem bootstrap-sass, và bootstrap-willpaginate chứa style Bootstrap cho pagination. Và chạy lệnh

$ bundle install

Bây giờ thêm

//= require bootstrap

vào file application.js , sửa file application.css thành application.scss và thêm

@import "bootstrap";

Model

Ta chị tạo một bảng là Post chứa các cột là:

  • id (integer, primary key)
  • title (string)
  • body (text)
  • created_at (datetime)
  • updated_at (datetime)

Chạy lệnh tạo model:

$ rails g model Post title:string body:text
$ rails db:migrate

Tiếp theo ta tạo một số dữ liệu test ở trong seed.rb :

50.times do |n|
  title = "Post #{n}" 
  body = Faker::Lorem.sentence 5
  Post.create! title: title, body: body
end

Lệnh này sẽ tạo ra 50 posts có body được sinh ngẫu nhiêu bởi Faker có số câu là 5. Chạy lệnh:

$ rails db:seed

Cuối cùng ta tạo một PostController có method là index và view tương ứng là index.html.erb. Trong routes.rb ta thêm:

root to: "posts#index"
resources :posts, only: :index

Controller

Vì ta sử dụng gem will_paginate để phân trang nên trong controller posts_controller.rb ta sử dụng:

def index
  @posts = Post.select(:id, :title, :body).order(created_at: :desc)
    .paginate page: params[:page], per_page: 10
end

Method paginate nhận một tùy chọn page là tham số để lấy số trang yêu cầu. Tùy chọn per_page xác định số lượng bản ghi được hiển thị trên mỗi trang.

View

index.html.erb

<div class="page-header">
  <h1>My posts</h1>
</div>

<div id="my-posts">
  <%= render @posts %>
</div>

<div id="infinite-scrolling">
  <%= will_paginate %>
</div>

Trong khối div #my-posts chứa những post đã được phân trang. Câu lệnh render @posts sẽ hiển thị mỗi post từ mảng bằng cách dùng partial _post.html.erb

posts/post.html.erb

<div>
  <h2><%= link_to post.title, post_path(post) %></h2>
  <p><%= post.body %></p>
</div>

Infinite Scrolling

Bây giờ ta sẽ chuyển phân trang thành infinite scrolling bằng JQuery. Tạo một file tên là pagination.js trong thư mục javascripts

$(document).on('turbolinks:load', function () {
  $(window).on('scroll', function(){
    more_posts_url = $('.pagination .next_page a').attr('href');
    if (more_posts_url && $(window).scrollTop() > $(document).height() - $(window).height() - 100) {
      $('.pagination').html('<img src="/assets/ajax-loader.gif" alt="Loading..." title="Loading..." />');
      $.getScript(more_posts_url);
    }
  });
});

Ở đây ta đang dùng sự kiện scroll window thì khi người dùng cuộn thì lấy liên kết đến trang tiếp theo và kiểm tra có tồn tại url đó hay không. Người dùng cuộn xuống gần cuối trang cách 60px thì đây là lúc load thêm post hơn. Giá trị 100px này là tùy ý, có thể thay đổi tùy vào từng trường hợp. Nếu điều kiện thỏa mãn lúc đó thay thanh phân trang thành một ảnh GIF(bạn có thể download tại ajaxload.info). Cuối cùng để thực hiện yêu cầu không đồng bộ bằng URL đã lấy ta sử dụng $.getScript.

Trong index của posts_controller.rb ta sử dụng respond_to để phản hồi cả 2 dạng html và javascript:

def index
  @posts = Post.select(:id, :title, :body).order(created_at: :desc)
    .paginate page: params[:page], per_page: 10
  respond_to do |format|
    format.html
    format.js
  end
end

Cuối cùng tạo một view được render tương ứng với kiểu js

post/index.js.erb

$('#my-posts').append('<%= j render @posts %>');
<% if @posts.next_page %>
  $('.pagination').replaceWith('<%= j will_paginate @posts %>');
<% else %>
  $(window).off('scroll');
  $('.pagination').remove();
<% end %>

Ta hiển thị các post khác bằng cách append vào block #my-post . Sau đó, kiểm tra xem có còn trang hay không. Nếu có, thay thế thanh phân trang hiện tại với một trang mới. Ngược lại, ta remove thanh phân trang và unbind các sự kiện scroll window.

Kết

Trên đây những chia sẻ của mình về infinite scrolling trong Rails. Hi vọng bài viết sẽ hữu ích, cảm ơn các bạn đã đón đọc.

Tham khảo: https://www.sitepoint.com/infinite-scrolling-rails-basics/


All Rights Reserved