はじめに
Ruby on Railsを使用して開発を行っていますが、モーダルウィンドウの実装で詰まりました。
備忘録として問題解決までの手順を書き記していきます。
※環境や、知識の足りていないところがあるため、おおまかな情報になります。
環境
- Docker
- Ruby 3.3.3
- Rails 7.1.2
- bootstrap 5.3.0
- Hotwire(Turbo Stream)
- stimulus
やりたかったこと
とあるページのログイン、新規登録処理をモーダルウィドウで行いたかった。
・turbo_flame_tag
を使用し、Bootstrapのモーダルを読み込む。
・バリデーションエラーをモーダルで表示するためsubmitするためのform
に
<%= turbo_frame_tag dom_id(@user, :form) do %>
<%= render 'error' %>
# フォームのsubmitが完了した際に発火するjavascriptを指定
# turbo:submit-end->login_modal#close'
<%= form_with url: login_path, html: { data: {action: 'turbo:submit-end->login_modal#close'}} do |f| %>
<div class="w-75 mx-auto">
<div class="mb-3">
<%= f.label :email, t('activerecord.attributes.user.email'), class: "form-label" %>
<%= f.email_field :email, class: "form-control", placeholder: t('placeholder.email') %>
</div >
<div class="mb-4">
<%= f.label :password, t('activerecord.attributes.user.password') ,class: "form-label" %>
<%= f.password_field :password, class: "form-control", placeholder: t('placeholder.password') %>
</div >
<div class="mt-4">
<%= f.submit t('dictionary.login'), class: "btn btn-success w-100 rounded-3" %>
</div >
</div>
<% end %>
<% end %>
-
<%= turbo_frame_tag dom_id(@user, :form) do %>
→フォームを部分的に読み込ませるための目印
-
<%= form_with url: login_path, html: { data: {action: 'turbo:submit-end->login_modal#close'}} do |f| %>
→フォームの送信完了後に読み込むstimulusで実装した、jsコントローラの指定。今回はapp/javascript/controllers/login_modal_controller.jsのclose
が呼び出される。
import { Controller } from "@hotwired/stimulus"
import { Modal } from "bootstrap"
// Connects to data-controller="modal"
// 画面表示時に呼び出される
export default class extends Controller {
connect() {
// すでに開いているモーダルを確認し・閉じる
var registerModalElement = document.getElementById('registerModal');
if (registerModalElement !== null ) {
var modalInstance = Modal.getInstance(registerModalElement);
if (modalInstance._isShown) {
modalInstance.hide();
}
}
// モーダルを開く
this.modal = new Modal(this.element)
this.modal.show()
}
// eventがsuccessの場合にウィンドウを閉じる
close(event) {
if (event.detail.success) {
this.modal.hide()
}
}
}
上記の設定により、バリデーションエラーが発生しても、画面が閉じられず、モーダルにエラーが表示される。
本題 turbo streamのリダイレクト処理
■問題点
モーダルを開いたままにしておくために、この処理はturbo stram
を使用し、画面の一部を更新する方式をとっている。
そのため正常処理完了後にリダイレクトを行うと、更新対象がturboフレーム内になってしまい画面のリロードが行われない。
class UserSessionsController < ApplicationController
skip_before_action :require_login, only: %i[new create]
# モーダルを表示する
def new
@user = User.new
end
# ログイン処理
def create
@user = login(params[:email], params[:password])
if @user
# 正常終了
# --------------------
# ここの処理をどうするべきか
# --------------------
else
# バリデーションエラー
@user = User.new # エラーを保持するためのユーザーオブジェクト
@user.errors.add(:base, 'メールアドレスまたはパスワードが無効です')
render :new, status: :unprocessable_entity
end
end
def destroy
logout
redirect_to root_path
end
end
■解決方法
この問題はTurbo.visit
を使用することで解消できた。
※今回の方法以外に以下の方法があった
・javascriptを使用する方法
・create.turbo_stram.erb内でリダイレクト用のリンクを作成しjavascriptでリンククリックイベントを発火させる
が、最も簡単そうな以下の方法を用いることにする。
1. app/javascript/application.js
以下の内容を追加する。
Turbo.StreamActions.redirect = function () {
Turbo.visit(this.target);
};
これはHotwire
のTurboライブラリのStreamActions
オブジェクトに、新しいメソッドredirect
を追加している。
Turbo.visit
は指定されたURLにブラウザをリダイレクトさせる。
2. controllerを修正する
class UserSessionsController < ApplicationController
# ~~~省略~~~
def create
@user = login(params[:email], params[:password])
if @user
# ▼▼▼この行を追加▼▼▼
flash[:success] = 'ログイン完了!!'
render turbo_stream: turbo_stream.action(:redirect, root_path)
# ▲▲▲この行を追加▲▲▲
else
@user = User.new # エラーを保持するための新しいユーザーオブジェクト
@user.errors.add(:base, 'メールアドレスまたはパスワードが無効です') 加
render :new, status: :unprocessable_entity
end
end
# ~~~省略~~~
end
この処理を追加することで、画面がturboフレームを抜け出し、画面がリロードされる。
またflashメッセージも画面に表示することができる。
最後に
意外と簡単な処理のみで実装が行えたが、これに問題があるかは現在わかっていない。
StreamActionsの使い方をもっと勉強しないと、すんなり実装とはいかないなと思いました。
完全な情報ではない可能性があるため、補足・指摘等ある場合はご教示ください
参考
- turbo stimulusでのモーダルウィンドウの呼び出し
- turbo stream からのredirect方法について