作成の背景
現在、野球のスコアブックを記録するアプリの作成を実施しています。
野球の1回、1打席目の人の成績を登録したいという場合に、
スコアブックにて1回と1打席がクロスした箇所をクリックすれば登録フォームが表示できる機能があればと考えミニアプリを作成して挙動の確認を実施することにしました。
ミニアプリの仕様
生徒がどの科目で何点とったかが分かるマトリックス表を作成しました。
表のクロスした箇所をクリックすると点数を記入できるようにしていきます。
また、既存に入力した項目に関しては入力値の更新ができるようにします。
DB設計
完成イメージ図
作成方法
- ルーティングの作成
- コントローラの作成
- ビューの作成
- Javascriptの作成
1. ルーティングの作成
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. コントローラの作成
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. ビューの作成
<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の作成
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点だけ懸念点があれば、パラメータを送付して登録と更新のフォームの切替を実施しているので、余計なページ遷移が発生することが挙げられます。これを改良して非同期通信で登録や更新してその問題を解消していきたいと考えております。
近日中に改良した記事を書けるように挑戦してみます。
最後に
ここまで読んで下さりありがとうございます。模索しながら機能を実装しておりますので効率が悪い部分があるかと存じます。もしご意見やご指摘がございましたらよろしくお願いいたします。