まえがき
Railsでアプリを作った際に、プルダウンの値によってフォームを追加したり非表示したりする必要が出てきた。これは非同期処理といって、ちょっと難解なものらしかった。実装するとたしかにややこしいものだったので、やりかたを書いていくことにする。
完成イメージ
画面遷移せずにフォームが出たり消えたりする。
動作フロー
動作は以下のようになる。
- 値が変更されたらjsファイルの関数が発火
- Ajax通信が行われる(指定したコントローラーへデータ送信)
- Ajax通信のレスポンスデータをもとにviewファイル書き換え
少しずつ説明していく。
0. 下準備:jQueryの導入
やっていく前にjQueryをRailsに導入する。いろいろあったが、私は以下のようにした。
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
// -- いろいろ
require("jquery")
require ('home')
// -- いろいろ
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
// jsファイルとして新規作成
home.js
に処理をいろいろ書いていく。
1. 値が変更されたらjsファイルの関数が発火
<!-- 略 -->
<div class="col-8 my-4 px-3">
<label for="label-report" class="form-label">変更後のステータス</label>
<p><%= f.select :status, ["進行中","中止","完了"] ,{class: "form-control"}%></p>
<% if plan.status == "進行中" %>
<p id = "review_plans_<%= plan.id %>_status_disp" class ="visible">
<% else %>
<p id = "review_plans_<%= plan.id %>_status_disp" class ="imvisible">
<% end %>
変更後の締め切り:<%= f.date_field :deadline ,{class: "form-control"} %>
</p>
<% end %>
</div>
<!-- 略 -->
ここの「変更後のステータス」のプルダウンの値が変化したことを検出したい。home.jsに以下のように書く。
var select_field = $('select[id^="review"]');
$(select_field).change(function(){
let state = this.value;
let state_id = this.id;
console.log("changed");
});
ここの$('select[id^="review"]')
はselect要素でidがreviewで始まるものを指定している。
2. Ajax通信が行われる(指定したコントローラーへデータ送信)
RailsでAjax通信を行うには方法がいくつかある。今回はjQueryの$.ajax()
を使う方法を書く。
home.jsの編集
home.jsに書いていく。
var select_field = $('select[id^="review"]');
$(select_field).change(function(){
let state = this.value;
let state_id = this.id;
$.ajax({
type: 'GET',
url: '/reviews/change_state',
data: { 'state' : state, 'id' : state_id },
dataType: 'json'
})
.done(function(data){ // dataにはレスポンスされたデータが入る
})
});
$.ajax()はjQueryでのajax通信を行う関数である。パラメーターは以下のようになる。
type: HTTP通信の種類
url: リクエストを送信する先のURL
data: サーバーに送信するデータ
dataType: データのタイプ
ルーティングの設定
リクエストの送信先は/reviews/change_state
としているが、まだルーティングの設定をしていない。これではrailsがどこへ送ればいいか迷ってしまう。routeを編集する。
Rails.application.routes.draw do
# 略 ...
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
# 略 ...
resources :reviews do
# 略 ...
collection do
get 'change_state'
end
end
end
resourcesを使って、reviewのリソースベースなルーティングをつくったのちに、collectionを使う。これでidを伴わないパスを認識して、reviewsのchange_stateアクションへ送ってくれる。
コントローラーの設定
コントローラーはこのように編集する。
# 略
def change_state
@messages = params
respond_to do |format| # リクエスト形式によって処理を切り分ける
format.html { redirect_to :root } # html形式の場合
format.json { render json: @messages } # json形式の場合
end
end
# 略
respond_to
を使って、リクエストされた方法によって処理を場合分けする。単純にURLを送信された場合はrootへリダイレクトされるようにしておく。リンクはアプリ上で貼っていないので、URL直打ちを想定する。
ajax通信でJSON形式でリクエストされた場合はJSONに変換した@message を返すようにする。
この@messages (params)には何が入っているか。binding.pryを挟んで処理中に覗いてみる。
params
=> <ActionController::Parameters {"state"=>"中止", "id"=>"review_plans_9_status", "controller"=>"reviews", "action"=>"change_state"} permitted: false>
stateとidが付与されている。これは$.ajax()で指定したデータである。render json
すると、以下のようになる。
render json:params
=> "{\"state\":\"中止\",\"id\":\"review_plans_9_status\",\"controller\":\"reviews\",\"action\":\"change_state\"}"
3. Ajax通信のレスポンスデータをもとにviewファイル書き換え
コントローラーの戻り値は$.ajax()の.done(function(data){
のdata
に格納される。
var select_field = $('select[id^="review"]');
$(select_field).change(function(){
let state = this.value;
let state_id = this.id;
$.ajax({
type: 'GET',
url: '/reviews/change_state',
data: { 'state' : state, 'id' : state_id },
dataType: 'json'
})
.done(function(data){
const p1 = document.getElementById(`${data.id}_disp`);
p1.classList.remove('visible','imvisible');
if (data.state == "進行中"){
p1.classList.add('visible');
}else{
p1.classList.add('imvisible');
}
})
});
document.getElementById
でdata.idがある要素を取得している。それをp1に代入。そのp1のclasslistから一旦表示非表示を制御しているクラスを除去する。
その後でstateに応じてvisible/imvisible クラスを付与している。このクラスはcssに書いている。
viewファイルとcssは以下のようになっている。
<!-- 略 -->
<div class="col-8 my-4 px-3">
<label for="label-report" class="form-label">変更後のステータス</label>
<p><%= f.select :status, ["進行中","中止","完了"] ,{class: "form-control"}%></p>
<% if plan.status == "進行中" %>
<p id = "review_plans_<%= plan.id %>_status_disp" class ="visible">
<% else %>
<p id = "review_plans_<%= plan.id %>_status_disp" class ="imvisible">
<% end %>
変更後の締め切り:<%= f.date_field :deadline ,{class: "form-control"} %>
</p>
<% end %>
</div>
<!-- 略 -->
//略
.visible {
display:inline;
}
.imvisible {
display:none;
}
//略
完成。
おわり
なんとか記事が書けた。これからもつまづいたところがあったら文章に残しておく。
めっちゃ参考になったリンク