LoginSignup
0
0

More than 3 years have passed since last update.

表がクロスした箇所の登録・更新機能の作成方法

Posted at

作成の背景

現在、野球のスコアブックを記録するアプリの作成を実施しています。
野球の1回、1打席目の人の成績を登録したいという場合に、
スコアブックにて1回と1打席がクロスした箇所をクリックすれば登録フォームが表示できる機能があればと考えミニアプリを作成して挙動の確認を実施することにしました。

ミニアプリの仕様

生徒がどの科目で何点とったかが分かるマトリックス表を作成しました。
表のクロスした箇所をクリックすると点数を記入できるようにしていきます。
また、既存に入力した項目に関しては入力値の更新ができるようにします。

DB設計

Image from Gyazo

完成イメージ図

Image from Gyazo

作成方法

  1. ルーティングの作成
  2. コントローラの作成
  3. ビューの作成
  4. Javascriptの作成

1. ルーティングの作成

routes.rb
Rails.application.routes.draw do
  root to: 'cross_answers#index' 
  resources :students, only: [:index, :new, :create]
  resources :lessons, only: [:index, :new, :create]
  resources :cross_answers, only: [:index, :create, :update, :destroy]
end

2. コントローラの作成

cross_answers_controller.rb
class CrossAnswersController < ApplicationController
  def index
    @lessons = Lesson.all
    @students = Student.all
    @cross_answers = CrossAnswer.all
    パラメータが表示されていれば指定されたクロスアンサーのデータを表示
    if params[:lesson_id] && params[:student_id]
      set_cross_answer
    else
    @cross_answer = CrossAnswer.new
    end

  end
  def create
    @cross_answer = CrossAnswer.new(cross_answer_params)
    if @cross_answer.save
      redirect_to root_path
    else
      render :index
    end
  end

  def update
    @cross_answer = CrossAnswer.find(params[:id])
    if @cross_answer.update(cross_answer_params)
      redirect_to root_path
    else
      render :index
    end
  end

  private
  def cross_answer_params
    params.require(:cross_answer).permit(:lesson_id, :student_id, :writing, :mark)
  end

  def set_cross_answer
    @cross_answer = CrossAnswer.find_by(lesson_id: params[:lesson_id], student_id: params[:student_id])
  end

end

補足

注目していただきたいのは、indexアクションの時にパラメータが送られてきた場合と処理を分岐している点です。
パラメータがない時は登録、ある時は更新の処理を実行するためにインスタンス変数の格納する値を変えております。更新時はstudent_id,lesson_idをパラメータで送付し、そのIDが合致した一意のデータを変数に格納してビューで利用してます。

3. ビューの作成

cross_answers/index.html
<div id="cross-matrix">
  <table class="matrix">
    <thead>
      //マトリックス表の科目名を上端に表示
      <tr class="matrix">
        <td class="matrix"></td>
        <% @lessons.each do |lesson| %>
          <td class="matrix">
            <%= lesson.name %>
          </td>
        <% end %>
      </tr>
    <thead>
    <tbody>
      //生徒名分項目を出力
      <% @students.each do |student| %>
        <tr class="matrix">
          //生徒名を左端に表示
          <th class="matrix">
            <%= student.name %>
          </th>
          //科目項目分列を表示
          <% @lessons.each do |lesson| %>
            <td class="grade-info matrix">
             //非表示の生徒テーブル、科目テーブルのIDを格納
              <div class="hidden lesson-num">
                <%= lesson.id%>
              </div>
              <div class="hidden student-num">
                <%= student.id %>
              </div>
              //生徒IDと科目のIDを持つクロスアンサーテーブルがあれば、テストの点数を2つ表示
              <% if @cross_answers.find_by(lesson_id: lesson.id, student_id: student.id).present? %>
                <% cross_answer = @cross_answers.find_by(lesson_id: lesson.id, student_id: student.id) %>
                //点数を表示されている時には、登録フォームではなく編集フォームを立ち上げる。
                <%= link_to cross_answers_path(lesson_id: cross_answer.lesson_id, student_id: cross_answer.student_id) do %>
                  <%= cross_answer.writing %>
                  <%= cross_answer.mark %>
                <% end %>
              <% end %>
            </td>
          <% end %>
        </tr>
      <% end %>
    </tbody>
  </table>
</div>
//パラメータに生徒IDと科目IDがない場合、クロス箇所をクリックしたら登録フォームが立ち上がる
<% unless params[:lesson_id] && params[:student_id] %>
  <div id="mask" class="hidden">
    <section id="modal" class="hidden">
      <div id="close">
        閉じる
      </div>
      <%= form_with model: @cross_answer, local: true do |f| %>
        <%= f.text_field :lesson_id, class:"hidden",id:"lesson_id-input"%>
        <%= f.text_field :student_id, class:"hidden", id:"student_id-input" %>
        <div>
          <label>筆記点数</label> 
          <%= f.text_field :writing %>
        </div>
        <div>
          <label>マーク点数</label>
          <%= f.text_field :mark %>
        </div>
        <%= f.submit %>
      <% end %>
    </section>
  </div>
<% else %>
  //パラメータに生徒ID、科目IDがあれば、編集フォームが表示されっぱなしになる。
  <div id="edit-mask">
    <section id="modal">
      //閉じるをクリックでクロスアンサーのパラメータなしのページに遷移
      <%= link_to cross_answers_path do %>
        <div id="edit-close">
          閉じる
        </div>
      <% end %>
      <%= form_with model: @cross_answer, url:cross_answer_path(@cross_answer.id), local: true do |f| %>
        <%= f.hidden_field :id %>
        <%= f.text_field :lesson_id, class:"hidden",id:"lesson_id-input"%>
        <%= f.text_field :student_id, class:"hidden", id:"student_id-input" %>
        <div>
          <label>筆記点数</label> 
          <%= f.text_field :writing %>
        </div>
        <div>
          <label>マーク点数</label>
          <%= f.text_field :mark %>
        </div>
        <%= f.submit %>
      <% end %>
    </section>
  </div> 
<% end %>

補足

マトリックスの表の作り方について
tableタグで表を作成しております。
1行目はeachメソッドを使い科目名分列を増やし、科目名を表示してます。
2行目以降はeachメソッドを使い生徒名分行を増やします。
1列目に生徒名を表示し、それ以降の列は再度eachメソッドを使い科目名分列を増やします。
eachメソッドの中にeachメソッドを格納するネスト構造になっております。
これでマトリックス表を作成することができましす。

表の格納データについて
表には4つのデータを格納できるようにしております。
・非表示の科目ID
・非表示の生徒ID
上記2点のIDを持つクロスアンサーテーブルのデータがあれば表示される
・マーク点数
・筆記点数

クロスした場所をクリックしたら表の生徒IDと科目IDをコピーして、登録フォームの非表示になっている科目IDと生徒IDを入力するテキストボックスにペーストするようにJavaScriptで実装しております。

登録と更新の切替について
データがない場合はマトリックスの空白部分をクリックしてJavaScriptで登録フォームを表示
データがある場合は、数値をクリックしたら生徒IDと科目IDのパラメータをコントローラに送り、その2つのIDに合致した一意のクロスアンサーデータをインスタンス変数として格納し、クライアントに返す。また、ビューの設定において、パラメータがある場合は、更新用のフォームが常時立ち上がるようにしています。

クラス名について
マトリックス表で科目と生徒のクロスした箇所には全て共通のクラス名を付与している。これはJavaScriptにおいてクリックした箇所でイベントを発火できるようにする為である。

ID名について
JavaScriptで使用したいHTML要素にID名をつける。

4. Javascriptの作成

modal.js
function modalAddJoin(){
  //フォームを入力するHTML要素に共通のクラス名をつけているので全要素を取得
  let cross_info = document.querySelectorAll(".grade-info");
  let info_num = cross_info.length;
  //クリックした箇所でイベントが発生するようにする。
  for(let i=0; i<=info_num - 1; i++){
    cross_info[i].onclick = function(){
      //生徒ID、科目IDを取得して、登録フォームの入力項目に転記
      let lessonId = this.children[0].innerHTML.trim();
      let studentId = this.children[1].innerHTML.trim();
      const modal = document.getElementById("modal");
      const mask = document.getElementById("mask");
      const close = document.getElementById("close");
      const studentInput = document.getElementById("student_id-input");
      const lessonInput = document.getElementById("lesson_id-input");
      studentInput.value = Math.floor(studentId);
      lessonInput.value = Math.floor(lessonId);
      //非表示のクラス名を除去
      modal.classList.remove("hidden");
      mask.classList.remove("hidden")
      //閉じるを押した時、非表示のクラス名を追記
      close.onclick = function(){
        modal.classList.add("hidden");
        mask.classList.add("hidden");
      };
    }
  }
}
window.addEventListener("load", modalAddJoin);

今後の展望について

マトリックス表のクロスした箇所をクリックして登録と表示ができたことにより、本番環境の野球のスコアブックでも活用できそうだと感じました。
唯、1点だけ懸念点があれば、パラメータを送付して登録と更新のフォームの切替を実施しているので、余計なページ遷移が発生することが挙げられます。これを改良して非同期通信で登録や更新してその問題を解消していきたいと考えております。
近日中に改良した記事を書けるように挑戦してみます。

最後に

ここまで読んで下さりありがとうございます。模索しながら機能を実装しておりますので効率が悪い部分があるかと存じます。もしご意見やご指摘がございましたらよろしくお願いいたします。

0
0
0

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
0