【Rails】form_withで非同期通信して表示を動的に変更(data-remote="true")
概要
リストを表示するときに追加読み込み、並び替え、絞り込みなどで表示を動的に変更したいことがしばしばある。
そんな時に、:remote => true、.js.erbなどを利用して対応する。
また、そのままだと:remote=>trueをしてもJSリクエストとして処理されず、.js.erbではなく.html.erbが描画されてしまったのでrails-ujsを使うようにする。
環境
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a)
Rails 7.0.3.1
Bundler version 2.3.7
完成物
https://github.com/di-kotobuki/hatena/tree/ajax
・動作の様子
data-remote="true"
フォームやリンクのhtml要素にdata-remote="true"の属性が付くと通信がJSリクエストとなって非同期通信になる。
これの何がいいかと言うと、画面がリロードされずに画面の一部の要素を再描画できたりすることだと思う。
VueやReactを使うなら不要になる。
やり方
link_toであればremote: trueを、form_withであればlocal: falseを設定すればいい。
Rails で JavaScript を利用する - Railsガイド
route
適当なgetとpostのルートを作成。
Rails.application.routes.draw do get "/articles", to: "articles#index" post "/articles", to: "articles#create" end
controller
上記で参照しているarticlesコントローラーとそのメソッドを作成。
コマンド一つでcontrollerやviewを作ってくれるのでそれを編集していく。
$ rails generate controller Articles index --skip-routes
class ArticlesController < ApplicationController def index @articles = ["aaa", "bbb", "ccc"] end def create @article = params[:title] respond_to do |format| format.html { redirect_to articles_path } format.js end end end
view
indexでレンダリングされるindex.html.erbは先のコマンド実行で作成されているので適当に加筆。
<Articles#index> <Find me in app/views/articles/index.html.erb> <div> <%= form_with url: articles_path, local: false do |f| %> <%= f.text_field :title %> <%= f.submit %> <% end %> <%= render "list", articles: @articles %> </div>
リスト部分をテンプレートに分割する(_list.html.erb)。
<ul id="articles"> <% articles.each do |article| %> <li><%= article %></li> <% end %> </ul>
createでレンダリングされるのはcontroller側で特に指定していなければcreate.js.erbがレンダリングされる。これは自分でファイルを作成する。
内容は、formで入力された値でhtml要素を追加するというもの。
(() => { document.getElementById("title").value = ""; const target = document.getElementById("articles"); const el = document.createElement("li"); el.textContent = "<%= @article %>"; target.appendChild(el); })();
rails-ujs
ここまでの状態でrails -sで起動して確認してみると、submitしても何もページが再読み込みされるだけで何も起こらない。
本来formt.jsでcreate.js.erbが読み込まれるはずが、format.htmlでindexへのリダイレクトになってしまっている。
この理由は、submit時のリクエストのContent-TypeでMIMEタイプがtext/htmlになっているからで、これはrails-ujsを適用すれば解決するらしい。
この辺りの通信に関する部分はRailsのバージョンで異なっていたりするらしいので、調べるときはRailsのバージョンをちゃんと見た方がいい。
下記を参考にする。
javascript - Rails UJS not firing with Rails 7 - Stack Overflow
プロジェクトルートで実行。
$ ./bin/importmap pin @rails/ujs
app/javascript/application.jsに追記。
import Rails from '@rails/ujs'; Rails.start();
おしまい
これで完成物の出来上がり。