ここで作成するUIは下記のものです。
様々な技術で実装し、Hotwireの特徴を見ていただくのが目的です。
text/html): Turbo Frames版、Turbo Streams版、jQuery版application/json): React版text/javascript): Server Generated JavaScript Response版... <turbo-frame class="border rounded shadow h-96 p-4" id="user-profile"> </turbo-frame> ...
<turbo-frame>タグを使う必要があります。 ...
<%= link_to user_user_profile_path(user), data: {turbo_frame: "user-profile"} do %>
<span class="absolute inset-0"></span>
<%= user.user_profile.name %>
<% end %>
...
data: {turbo_frame: "user-profile"} (data-turbo-frame="user-profile")により、リンクをクリックした際のレスポンスが<turbo-frame id="user-profile">に挿入されるようにクライアント側から指示をしています。... <turbo-frame id="user-profile"> ... [サイドパネルの表示内容] ... </turbo-frame> ...
<turbo-frame id="user-profile">だけが切り出され、画面の部分更新に使用されます。... <div class="border rounded shadow h-96 p-4" id="user-profile"> </div> ...
...
<%= link_to user_user_profile_path(user), data: {turbo_stream: true} do %>
<span class="absolute inset-0"></span>
<%= user.user_profile.name %>
<% end %>
...
data: {turbo_stream: true} (data-turbo-stream="true")により、リンクをクリックした際のリクエストヘッダーにAccept: text/vnd.turbo-stream.html ...がつきます。
respond_toを使用した際にturbo_streamフォーマットを識別できるようになります。 <%= turbo_stream.update 'user-profile' do %> ... [サイドパネルの表示内容] ... <% end %> <!-- 下記のレスポンスになります <turbo-stream action="update" target="user-profile"><template> ... [サイドパネルの表示内容] ... </template></turbo-stream> -->
function UsersIndex() { const [users, setUsers] = useState(null) const [selectedUser, setSelectedUser] = useState(null) useEffect(() => { fetch("/users", { headers: {Accept: "application/json"}, }).then(res => res.json()) .then(data => setUsers(data)) }, []) return (<> ... <tbody className="divide-y divide-gray-200"> {users.map(user => <tr key={`user-${user.id}`} className={`cursor-pointer ${selectedUser?.id === user.id ? "bg-yellow-200" : ""}`} // setSelectedUser(user)がトリガー // これでステートを変更して、サイドパネルに表示するユーザを指定し、 // 再レンダーを起こす onClick={() => setSelectedUser(user)}> ... </tr>)} </tbody> ... { // サイドパネルの枠 } <div className="border rounded shadow h-96 p-4" id="user-profile"> {selectedUser && <UserProfile userId={selectedUser.id}/>} </div> </div> </>) } function UserProfile({userId}) { const [userProfile, setUserProfile] = useState(null) useEffect(() => { fetch(` /users/${userId}/user_profile`, { headers: {Accept: "application/json"}, }).then(res => res.json()) .then(data => setUserProfile(data)) }, [userId]) return (<> {userProfile ? <div> ... [サイドパネルの表示内容] ... </div> : <div>Loading...</div> } </> ) }
下記のServer Generated JavaScript Response版とともに、Hotwireが公開される前の2020年ごろまで、Ruby on Railsフロントエンドでよく使用されていた方法です。
... <div class="border rounded shadow h-96 p-4" id="user-profile"> </div> ...
... <tr id="<%= dom_id user %>" data-js="click-user-row" data-href="<%= url_for user_user_profile_path(user) %>" class="cursor-pointer user-row"> <td><%= user.user_profile.name %></td> <td><%= user.email %></td> </tr> ...
data-js="click-user-row"により、下記のjQueryと接続しています。... const components = $("[data-js='click-user-row']") const frame = $("#user-profile") const highlightClass = "bg-yellow-200" components.each(function (index, el) { init(el) }) function init(el: HTMLElement) { const component = $(el) component.on("click", function (event) { event.preventDefault(); select(component) frame.load(component.data('href')) }) }
load()関数が呼ばれます。
load()によりサーバにリクエストが投げられ、HTMLレスポンスを受け取ります。frame(サイドパネルの枠)の中に挿入されます。RailsのUJS (Unobtrusive JavaScript)(jQuery版)を使用する方法です。Server-generated JavaScript ResponsesのポストでDHHが詳しく解説しています。
上記のjQuery版とともに、Hotwireが公開される前の2020年ごろまで、Ruby on Railsフロントエンドでよく使用されていた方法です。
... <div class="border rounded shadow h-96 p-4" id="user-profile"> </div> ...
...
<%= link_to user_user_profile_path(user), data: {remote: "true"} do %>
<span class="absolute inset-0"></span>
<%= user.user_profile.name %>
<% end %>
...
data: {remote: "true"}(data-remote="true")を書くだけで以下のことが行われます。
Accept: text/javascriptをつけて、JavaScriptのレスポンスを要求します。var frame = document.getElementById("user-profile"); // [サイドパネルの表示内容] frame.innerHTML = "<%= j render 'show', user_profile: @user_profile %>"; // ...
.js)frame.innerHTMLを使って、app/views/users/user_profiles/_show.html.erb partialの中身をサイドパネル枠の中に挿入しています。
#jはJavaScript用にエスケープするヘルパーです。