LoginSignup
0

More than 1 year has passed since last update.

Railsで非同期処理 セレクトメニューの値による項目の表示制御

Posted at

まえがき

 Railsでアプリを作った際に、プルダウンの値によってフォームを追加したり非表示したりする必要が出てきた。これは非同期処理といって、ちょっと難解なものらしかった。実装するとたしかにややこしいものだったので、やりかたを書いていくことにする。

完成イメージ

タイトルなし.gif

 画面遷移せずにフォームが出たり消えたりする。

動作フロー

 動作は以下のようになる。

  1. 値が変更されたらjsファイルの関数が発火
  2. Ajax通信が行われる(指定したコントローラーへデータ送信)
  3. Ajax通信のレスポンスデータをもとにviewファイル書き換え

 少しずつ説明していく。

0. 下準備:jQueryの導入

 やっていく前にjQueryをRailsに導入する。いろいろあったが、私は以下のようにした。

app/javascript/packs/application.js
// 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)

app/javascript/home.js
// jsファイルとして新規作成

home.js に処理をいろいろ書いていく。

1. 値が変更されたらjsファイルの関数が発火

app/views/reviews/_form.html.erb
<!-- 略 --> 
<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に以下のように書く。

app/javascript/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に書いていく。

app/javascript/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を編集する。

config/routes.rb
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アクションへ送ってくれる。

コントローラーの設定

コントローラーはこのように編集する。

app/controllers/reviews_controller.rb
# 略

  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に格納される。

app/javascript/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){ 
      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は以下のようになっている。

app/views/reviews/_form.html.erb
<!-- 略 -->
<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>
<!-- 略 -->
app/assets/stylesheets/application.scss
//
 .visible {
  display:inline;  
 }

 .imvisible {
  display:none; 
 }
//

完成。

おわり

なんとか記事が書けた。これからもつまづいたところがあったら文章に残しておく。

めっちゃ参考になったリンク

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0