Hotwire: Stimulus と Turbo Streams の組み合わせで定期的にページの特定箇所を更新する
前回作成した習作アプリはページをリロードするとスクロール位置が最上部に戻ってしまいます。
今回は Turbo Streams を用いてページの特定箇所の更新を行いたいと思います。
Hotwire v7.0.0-rc.1
要求
複数箇所の要素の文字列を更新する。
1秒間隔で更新する。更新を3回行う。
更新時にスクロール位置を変えない。
対象の文字列をページの上下に用意する。
画面
仕様
Stimulus コントローラーが定期的に更新処理を行う。
要更新フラグが false なら更新をやめる。
習作アプリなのでフラグは確認しやすいようにページに表示する。
Turbo Streams の MIME タイプで get 'home/status' する。
status アクションは文字列置換用のメッセージを返す。
返されたメッセージを Turbo.renderStreamMessage で処理する。
(参考)Is there a way to programmatically invoke a Turbo Stream? · Issue #34 · hotwired-turbo
前回との違い
Turbo Streams には属性だけを置き換える方法がない。
属性を含む要素を置き換えると Stimulus コントローラーが初期化される。
このため今回のアプリは 要更新フラグを別の要素の文字列に持たせる。
今回のアプリは Stimulus の Change Callback 機能は使用しない。
Stimulus コントローラーは生き続けるため setInterval で更新を繰り返す。
追加または変更するファイル
app/assets/javascripts/controllers/reload_controller.js
app/controllers/home_controller.rb
app/views/home/index.html.erb
app/views/home/status.turbo_stream.erb
config/routes.rb
rails new hotwire_update_elements \
--skip-action-mailbox \
--skip-action-text \
--skip-skip-active-storage \
--skip-action-cable \
--skip-javascript \
--skip-turbolinks \
--skip-jbuilder \
--skip-test \
&& cd hotwire_update_elements && tmux
bin/bundle add hotwire-rails
bin/rails hotwire:install
bin/rails g controller home index
# config/routes.rb
'home/status'を追加する。
Rails.application.routes.draw do
get 'home/index'
get 'home/status'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
# app/controllers/home_controller.rb
更新を3回行う。
Stimulus コントローラーは @reload がtrueなら更新、falseなら更新しない。
class HomeController < ApplicationController
RELOAD_MAX = 3
def index
@@reload_count = 1
end
def status
@textContent = "#{@@reload_count}/#{RELOAD_MAX}, #{Time.now}"
@@reload_count += 1
@reload = (@@reload_count <= RELOAD_MAX)
end
end
// app/assets/javascripts/controllers/reload_controller.js
canReload() が true なら、1秒周期でメッセージを取得する。
fetch に 'text/vnd.turbo-stream.html' を設定して Stream を受け取る。
import { Controller } from "stimulus"
export default class extends Controller {
static values = { url: String }
initialize() {
this.intervalId = 0
}
connect() {
this.intervalId = setInterval(() => {
if (this.canReload()) {
this.updateElements()
} else {
this.stopReloading()
}
}, 1000);
}
disconnect() {
this.stopReloading()
}
updateElements() {
fetch(this.urlValue, { headers: { 'Accept': 'text/vnd.turbo-stream.html' } })
.then(response => response.text())
.then(message => Turbo.renderStreamMessage(message))
.catch (() => this.stopReloading())
}
canReload() {
const reload = document.getElementById('reload').textContent
return (reload === 'true')
}
stopReloading() {
if (this.intervalId !== 0) {
clearInterval(this.intervalId)
this.intervalId = 0
}
}
}
<!-- app/views/home/index.html.erb -->
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>
<hr>
<div id="status-1">Status1</div>
<hr>
<% 20.times do %>
<p>paragraph</p>
<% end %>
<hr>
<div id="status-2">Status2</div>
<hr>
<div id="reload">true</div>
<div data-controller="reload"
data-reload-url-value="/home/status">
Reload Div
</div>
<!-- app/views/home/status.turbo_stream.erb -->
<%= turbo_stream.update "status-1", @textContent %>
<%= turbo_stream.update "status-2", @textContent %>
<%= turbo_stream.update "reload", @reload.to_s %>
以上です。
追記:Stream で update する要素(ID)を動的に生成する
Update 対象の ID を @reload_ids に設定して、テンプレートで<turbo-stream>タグを動的に生成する。
def status
@reload_ids = ["1", "2"]
@textContent = "#{@@reload_count}/#{RELOAD_MAX}, #{Time.now}"
@@reload_count += 1
@reload = (@@reload_count <= RELOAD_MAX)
end
<% @reload_ids.each do |id| %>
<%= turbo_stream.update "status-#{id}", @textContent %>
<% end %>
<%= turbo_stream.update "reload", @reload.to_s %>
以上です。
この記事が気に入ったらサポートをしてみませんか?