最近剛好在實作捲軸下拉時,動態顯示出剩下來分頁的內容,所以參考了RailsCast Endless Page的影片,以下是實作後的筆記。
先做出簡單的頁面
假設我們需要在index page實作列出所有portfolio的效果
我們最一開始的view和controller應該會長這樣
# home/index.html.erb
<div id="products" class="row">
<% @portfolios.each do |portfolio| %>
<div class="col-lg-2 col-sm-6 portfolio-item product">
<div class="card h-100">
<a href="#">
<%= image_tag portfolio.image.url, class: "card-img-top"%>
</a>
</div>
</div>
<% end %>
</div>
# home_controller.rb
def index
@portfolios = Portfolio.all
end
為了等下的實作方便,讓我們來改寫一下目前的頁面,我們要把程式碼移到partial中,然後用render來渲染。
# home/index.html
<div id="products" class="row">
<%= render @portfolios %>
</div>
# portfolios/_potfolio.html.erb
<div class="col-lg-2 col-sm-6 portfolio-item product">
<div class="card h-100">
<a href="#">
<%= image_tag portfolio.image.url, class: "card-img-top"%>
</a>
</div>
</div>
註:render collection的用法,可以參考龍哥的這一篇Layout, Render 與 View Helper的內容。
實作分頁功能
接下來我們要實作分頁功能,因為我們的無限捲軸功能也是透過javascript去產生原本頁面上的分頁來完成的。
首先我們需要使用kaminari
這個gem,在Gemfile裡寫入
# Gemfile
gem 'kaminari'
執行bundle安裝gem
bundle install
接下來我們在home_controller
中,加上以下的程式碼
def index
@portfolios = Portfolio.all.page(params[:page]).per(6)
end
讓我們看一下我們都新增了哪些東西
- page(params[:page])
這段程式碼可以params判別目前的頁數是第幾頁。比如說當我們點擊第四頁的時候,我們就可以透過params[:page]
得到目前的頁數。
http://localhost:3000/?page=4
- per(6)
這代表你每幾個objects一頁。
接下來在view中加入<%= paginate @portfolios %>
,這段程式碼可以幫我們產生分頁所需的link。
<div id="products" class="row">
<%= render @portfolios %>
</div>
<%= paginate @portfolios %>
到這邊我們就已經產生了分頁的效果。
處理滾動捲軸的內容
接下來我們要實作的內容是,當向下滾動到某個區域的時候,網頁會自動顯示接下來的分頁。
- 卷軸滾動到特定位置時觸發
- 即時渲染下一個分頁的內容
卷軸滾動到特定位時觸發
首先先在home.coffee
中加入以下的程式碼,我們會一步一步來解釋
jQuery ->
$(window).scroll ->
if $(window).scrollTop() > $(document).height() - $(window).height() - 60
alert "Near Bottom"
- $(window).scroll()
代表當我們滾動視窗時,就會觸發內部的程式碼
- $(window).scrollTop()
scrollTop()
可以讓我們設定該元素(這裡是window)跟頂部的位移。當你尚未開始滾動時,位移是零。
- $(window).height() 和 $(document).height()
這可以讓我們得到document跟目前視窗的高度
所以這段程式碼的邏輯就是,當頁面向下滾動時,如果頁面的偏移量,大於整個document的高度 - 視窗的高度 - 60時,觸發alert。
即時渲染下一個分頁的內容
接下來讓我們改寫並加入新的程式碼到home.coffee
和home/index.js.erb
兩個檔案中。
home.coffee程式碼
# home.coffee
jQuery ->
$(window).scroll ->
url = $('.pagination .next a').attr('href')
if url && $(window).scrollTop() > $(document).height() - $(window).height() - 60
$('.pagination').text("載入更多圖片")
$.getScript(url)
- url = $('.pagination .next a').attr('href')
這段程式碼會得到頁面中,Next的anchor的href值。這段程式碼的重要之處在於,我們接下來可以透過url還有query string,幫我們取得「下一頁」應該要渲染出的object,這樣我們就可以透過滾動到底部時取得下一頁的內容。
我們可以看一下以下的截圖。
而我們的url就會是/?page=2
,這邊對應的path就是root_path
,並加上一段query string(page=2
)。
- $('.pagination').text("載入更多圖片")
設置classj為paginate的element中的text。
- $.getScipt(url)
getScript其實是以下程式碼的簡寫
$.ajax({
url: url,
dataType: "script",
success: success
});
目的是用來送出ajax 的GET request。所以我們可以知道getScript會對我們剛剛得到的/?page=2
送出一個ajax的GET request。
index.js.erb
接續剛剛上一段的$.getScipt(url)
,其實就是對url送出get請求。
由於我們的root_path
(root 'home#index'
)對應到的是home_controller
下的index method,所以我們如果可以在app/view/home
的資料夾下,新增一個index.js.erb
檔來處理request format為js的情況。
註:在*.js.erb這類的檔案中,我們可以混用ruby與js,然後編譯成js code後再傳回去給client端。
# home/index.js.erb
$('#products').append('<%= j render(@portfolios) %>');
<% if @portfolios.next_page %>
$('.pagination').replaceWith('<%= j paginate(@portfolios) %>');
<% else %>
$('.pagination').remove();
<% end %>
<% sleep 1 %>
- $('#products').append('<%= j render(@portfolios) %>');
這段程式碼會在id為products的element後方,插入render(@portfolios)
渲染出的html。
這邊另外比較值得注意的是j()
的用法。
j()
其實是escape_javascript()
的縮寫,目的是跳脫雙引號帶來的束縛,不會因為在內部出現多個雙引號導致javascript壞掉。更多內容可以參考這篇Rails為何要使用escape_javascript?。
如果你還記得的話,我們的home_controller
中的index方法是
def index
@portfolios = Portfolio.all.page(params[:page]).per(6)
end
當我們對home/index送出get request時,我們還額外送出了一個query string?page=2
。所以我們等於是透過送出ajax request還有query string,得到了「下一頁(第二頁)」中的object。
- <% if @portfolios.next_page %>
$('.pagination').replaceWith('<%= j paginate(@portfolios) %>');
<% else %>
$('.pagination').remove();
<% end %>
這段程式碼代表的意思是,當目前的頁面的下一頁還有object時,將目前的分頁link用下一頁的分頁link取代掉。也就是假設目前是第一頁,當我們往下滾到定點時,用第二頁的分頁link取代第一頁。
這樣的話我們就可以一直不斷地抓取下一頁的內容。
- <% sleep 1 %>
讓程式暫停1秒再繼續執行。這邊可以自行決定要不要使用,如果不希望分頁滾動時渲染太快,可以考慮使用。
參考資料
RailsCast #114 Endless Page
Rails為何要使用escape_javascript?
Why escape_javascript before rendering a partial?
Layout, Render 與 View Helper
註:圖片取自Pixabay 創用CC並自行修改
@linjiahung, 这是小可可我在steemit最好的邂逅,好喜欢你的贴(^∀^)哇~~~