RailsとStimulusjsで非同期に一覧のページネーションする
昔のコードでは、$('#paginator').on 'ajax:success',...
みたいにcallbackをフックに書き換えたり、
js.erbのレスポンスでHTMLを書き換えるような$('#pagenate').html("<%= escape_javascript(render 'index_page') %>");
をやっていました。
Stimulusjsを使った場合は、どう実装するか、ということを書きます。
ページネーションのライブラリにはkaminariを使います。
以下のテンプレートのページネーションをajaxで行うように修正していきます。
今回は、ソースコードを公開していないので、謎の断片が混ざるのは一般化することがめんどくさくなっているだけで、仕様です。
<h1>index ページです</h1>
<%= paginate @video_chats, remote: true %>
<% @video_chats.each do |video_chat| %>
<div>
<%= video_chat.text %>
</div>
<% end %>
--
処理の全体の流れは、Stimulusjsのcontrollerから、非同期リクエストの呼び出しとHTMLの書き換えを行います。
Stimulusjsのcontrollerは、以下の通りです。
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="video-chats-loader"
export default class extends Controller {
static values = { url: String, keyword: String };
connect() {
this.load();
}
load(page) {
this.element.innerHTML = 'Loading...';
let url = this.urlValue;
if (page) {
url += `${url.includes("?") ? "&" : "?"}page=${page}`;
}
if (this.keywordValue) {
url += `${url.includes("?") ? "&" : "?"}keyword=${encodeURIComponent(
this.keywordValue
)}`;
}
fetch(url)
.then((response) => response.text())
.then((html) => (this.element.innerHTML = html));
}
}
htmlの修正は、<% @video_chats.each do |video_chat| %>...
の部分をパーシャルに切り出して、data-controller
の宣言をしていきます。
<h1>index ページです</h1>
<div
data-controller="video-chats-loader"
data-video-chats-loader-keyword-value="<%= @keyword %>"
data-video-chats-loader-url-value="<%= video_chats_video_path(@video, format: :html) %>"></div>
こっちはパーシャルです。 params: { format: :js }
がミソです。formatを上書きすることでページネーションのリンクをクリックするとjsテンプレートを要求します。
<%= paginate video_chats, remote: true, params: { format: :js } %>
<% video_chats.each do |video_chat| %>
<div>
<%= video_chat.text %>
</div>
<% end %>
ページネーションのレスポンスを返すcontrollerは以下の通りです。
def video_chats
respond_to do |format|
format.js
format.html do
keyword = params[:keyword]
video = Video.find(params[:id])
video_chats = video.video_chats.order('timestamp').page(params[:page]).per(60)
video_chats = (video_chats.where('message LIKE ?', "%#{keyword}%") if keyword.present?)
render partial: 'videos/video_chats', locals: { video_chats: }
end
end
end
format.js
が返すテンプレートは次の通りです。Stimulusjsのcontrollerを呼び出しています。
var page = <%= params[:page] || 1 %>
var element = document.querySelector("[data-controller='video-chats-loader']")
var controller = window.Stimulus.getControllerForElementAndIdentifier(element, "video-chats-loader")
controller.load(page)
これらを繋げることで、 1 2 3 last
と書かれたハイパーリンクをクリックすると、この部分のみを非同期に書き換えるようになります。
所感
冒頭で挙げた昔の実装よりは、DOMの書き換えをcontrollerに閉じ込めることができているので、マシではありますが、Stimulusjsのcontrollerを呼び出すためにformat.js
を経由してしまっているのが微妙だなと思います。
それと、コードを追いにくい。Stimulusjsコンポーネントから間接的に、パーシャルがレンダリングされると普通は読めない。
素直にturboを使いましょう!!!!!!
参考にした実装
- https://stimulus.hotwired.dev/handbook/working-with-external-resources
- https://www.stimulus-components.com/docs/stimulus-content-loader
以上。
Discussion