1. kazutosato

    No comment

    kazutosato
Changes in body
Source | HTML | Preview
@@ -1,228 +1,229 @@
環境: Rails 6.1、Turbo 7.0.0-beta.5
[Hotwire(Turbo)を試す その1: 導入、作成・更新フォーム](https://qiita.com/kazutosato/items/10a5bc04443d6b7e5bf8) の続きです。
## Turbo Streamsとは
Turbo Streamsとは、サーバーからHTMLの一部を送信して、ページ中の一部を差替、追加、削除するものです。送信の方法には、普通のHTTPのレスポンスのほかにAction Cableが使えます。この「その2」のサンプルは、普通のHTTPのレスポンスを使うだけです。
-ページ中に次のような`<turbo-frame>`要素があるとします。id属性で名前を付けます。
+ページ中に次のような`<turbo-frame>`要素があるとします。id属性でフレーム名を付けます。
```html:送信前のページ中のHTMLの一部
<turbo-frame id="message">
<div>こんにちは</div>
</turbo-frame>
```
-これが含まれたページに対して、次のような`<turbo-stream>`要素のHTML片を送信します。target属性で差替対象となる`<turbo-frame>`の名前を指定します。差替なのでaction属性は"replace"です。`<turbo-stream>`の内容は、`<template>`で囲む必要があります。
+これが含まれたページに対して、次のような`<turbo-stream>`要素のHTML片を送信します。target属性で差替対象となるフレーム名 "message" を指定します。差替なのでaction属性は"replace"です。`<turbo-stream>`の内容は、`<template>`で囲む必要があります。
```html:送信するHTML片
<turbo-stream action="replace" target="message">
<template>
<div>こんばんは</div>
</template>
</turbo-stream>
```
HTML片を送信すると、`<turbo-frame>`の内容が入れ替わります。
```html:送信後のページ中のHTMLの一部
<turbo-frame id="message">
<div>こんばんは</div>
</turbo-frame>
```
## 「いいね」ボタンの例
[その1](https://qiita.com/kazutosato/items/10a5bc04443d6b7e5bf8)で作ったサンプルに「いいね」ボタンを追加してTurbo Streamsを試します。記事の詳細ページの中に❤️ボタンを置き、クリックするとカウンタが1上がる、ということにします。
テーブルにカウンタを保存するカラムを追加します。
```ruby:db/migrate/20210419022554_add_likes_count_to_entries.rb
class AddLikesCountToEntries < ActiveRecord::Migration[6.1]
def change
add_column :entries, :likes_count, :integer
end
end
```
いいね用のアクションを追加します。
```ruby:config/routes.rb
resources :entries do
patch :like, on: :member
end
```
-記事ページに埋め込む❤️ボタンとカウンタの数字です。`turbo_frame_tag "名前"` メソッドは `<turbo-frame id="名前">〜</turbo-frame>` になります。
+記事ページに埋め込む❤️ボタンとカウンタの数字です。`turbo_frame_tag "フレーム名"` メソッドは `<turbo-frame id="フレーム名">〜</turbo-frame>` になります。
```erb:app/views/entries/_likes.html.erb
<%= turbo_frame_tag 'entry-likes' do %>
<div>
<%= link_to '❤️', like_entry_path(@entry), method: :patch %>
<span style="color:red"><%= @entry.likes_count %></span>
</div>
<% end %>
```
これを記事ページに埋め込みます。
```erb:app/views/entries/show.html.erb
<%= render 'likes' %>
```
クリックで呼び出されるコントローラのいいね用アクションです。
```ruby:app/controllers/entries_controller.rb
def like
@entry.increment!(:likes_count)
render turbo_stream: turbo_stream.replace('entry-likes', partial: 'likes')
end
```
-`render turbo_stream: turbo_stream.replace('名前', partial: 'テンプレート')`は、次のようなHTML片を送信し、`<turbo-frame id="名前">`の内容を差し替えます。このとき、HTTPレスポンスのContent-typeは、`text/vnd.turbo-stream.html` になります。
+`render turbo_stream: turbo_stream.replace('名前', partial: 'テンプレート')`は、次のようなHTML片を送信し、`<turbo-frame id="フレーム名">`の内容を差し替えます。このとき、HTTPレスポンスのContent-typeは、`text/vnd.turbo-stream.html` になります。
```html
-<turbo-stream action="replace" target="名前">
+<turbo-stream action="replace" target="フレーム名">
<template>
partialのテンプレート
</template>
</turbo-stream>
```
❤️をクリックすると数字が増えるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308595/cfa26c9e-c7ca-976f-763c-de087003fd83.png)
-Turbo Streamsのテンプレートと送信には、もう1つやり方があります。次のように`<turbo-stream>`をテンプレートの中に書く方法です。`turbo_stream.replace "名前"` は `<turbo-stream action="replace" target="名前"><template>〜</template></turbo-stream>` になります。
+Turbo Streamsのテンプレートと送信には、もう1つやり方があります。次のように`<turbo-stream>`をテンプレートの中に書く方法です。`turbo_stream.replace "フレーム名"` は `<turbo-stream action="replace" target="フレーム名"><template>〜</template></turbo-stream>` になります。
```erb:app/views/entries/like.html.erb
<%= turbo_stream.replace 'entry-likes' do %>
<%= render 'likes' %>
<% end %>
```
このテンプレートをContent-typeを指定して送信すると、同じ結果になります。
```ruby:app/controllers/entries_controller.rb
def like
@entry.increment!(:likes_count)
render layout: false, content_type: 'text/vnd.turbo-stream.html'
end
```
## 「もっと見る」ボタンの例
差替の次は、コンテンツの追加を試すために、記事一覧に「もっと見る」ボタンを実装します。まず、一覧のテンプレートを修正します。scaffoldのテンプレートはテーブルを使っているため、Turbo StreamsでHTML要素をうまく扱えません。divに変えます。
-追加のターゲットになる記事の一覧は、`<turbo-frame>`で囲み、名前を"entries"としています。
+追加のターゲットになる記事の一覧は、`<turbo-frame>`で囲み、フレーム名を "entries" としています。
```erb:app/views/entries/index.html
<div>
<%= turbo_frame_tag 'entries' do %>
<% @entries.each do |entry| %>
<%= render 'entry', entry: entry %>
<% end %>
<% end %>
</div>
<br>
<%= render 'more_button' %>
```
```erb:app/views/entries/_entry.html
<div class="entry">
<%= link_to entry.title, entry %> |
<%= entry.created_at.strftime('%m/%d %H:%M') %> |
<%= link_to 'Edit', edit_entry_path(entry) %> |
<%= link_to 'Destroy', entry, method: :delete, data: { confirm: 'Are you sure?' } %>
</div>
```
-「もっと見る」ボタンのテンプレートです。10個ずつ記事を表示し、すべて表示されたらボタンを非表示とします。「もっと見る」も`<turbo-frame>`で囲み、名前を"more-button"としています。
+「もっと見る」ボタンのテンプレートです。10個ずつ記事を表示し、すべて表示されたらボタンを非表示とします。「もっと見る」も`<turbo-frame>`で囲み、フレーム名を "more-button" としています。
```erb:app/views/entries/_more_button.html
<% if @offset + 10 < @entries_count %>
<%= turbo_frame_tag 'more-button' do %>
<div>
<%= link_to 'もっと見る', more_entries_path(offset: @offset + 10) %>
</div>
<% end %>
<% end %>
```
「もっと見る」用のルーティングです。
```ruby:config/routes.rb
resources :entries do
get :more, on: :collection
patch :like, on: :member
end
```
コントローラの記事一覧と「もっと見る」用のアクションです。記事は10個ずつ、offsetパラメータの位置から取得、作成日時の降順、とします。
moreアクションでは、`render turbo_stream: 〜` を使わない形にします。
```ruby:app/controllers/entries_controller.rb
def index
@entries_count = Entry.count
@offset = params[:offset].to_i
@entries = Entry.offset(@offset).order(created_at: :desc).limit(10)
end
def more
index
render layout: false, content_type: 'text/vnd.turbo-stream.html'
end
```
「もっと見る」用のTurbo Streamsのテンプレートです。ここでは`<turbo-stream>`要素を2つ送信していいます。記事一覧の追加分と「もっと見る」の差替分です。追加するときは、replace の代わりに append を使います。
```erb:app/views/entries/more.html
<%= turbo_stream.append 'entries' do %>
<% @entries.each do |entry| %>
<%= render 'entry', entry: entry %>
<% end %>
<% end %>
<%= turbo_stream.replace 'more-button' do %>
<%= render 'more_button' %>
<% end %>
```
記事の数を増やして試すためにシードデータを用意します。
```ruby:db/seeds.rb
32.times do |idx|
entry = Entry.create!(
title: %w(foo bar baz).sample(3).join(' '),
body: 'blah, blah, blah...',
created_at: idx.hours.ago
)
end
```
「もっと見る」をクリックすると記事が10個ずつ追加され、何度もクリックすると「もっと見る」が消えるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308595/edb2ce72-412f-89b5-007f-2a6a333648b0.png)
## turbo-frame の中のリンクの挙動
さて、この記事一覧の中のリンクをクリックすると、ページが切り替わらずに、記事一覧、つまり`<turbo-frame id="entries">〜</turbo-frame>`で囲んだ部分が消えてしまいます。
-`<turbo-frame id="名前">`の中にあるリンクは、レスポンスのHTMLにも`<turbo-frame id="名前">`があることを期待し、元の`<turbo-frame>`を新しい`<turbo-frame>`で置き換える、というのがデフォルトの動作になっています。
+`<turbo-frame id="フレーム名">`の中にあるリンクは、レスポンスのHTMLにも`<turbo-frame id="フレーム名">`があることを期待し、元の`<turbo-frame>`を新しい`<turbo-frame>`で置き換える、というのがデフォルトの動作になっています。
この動作を変えて別のページに切り替わるようにするには、`<turbo-frame>`に属性`target="_top"`を加えます。
```erb:app/views/entries/index.html
<%= turbo_frame_tag 'entries', target: '_top' do %>
```
## ここまでの感想
- Turboでこのサンプルを実装するのは、けっこう面倒でした。実際のアプリケーションに導入するには学習コストが気になります。まだドキュメントも解説サイトも揃っていない段階ではしょうがありませんが。
- 実際に導入するときは、Turbo+Stimulusの簡単な部分だけ使い、Vue.jsやjQueryと組み合わせるのがいいかもしれない。
+- Turbo Streamsによるページの内容更新は、すべてサーバーを経由するものなので、たとえば「追加ボタンを押したら入力欄が増える」みたいな動きには使えないと思う。
続き: [Hotwire(Turbo)を試す その3: Trubo Streamsでのブロードキャスト](https://qiita.com/kazutosato/items/4b2d4177fc92335536e9)