[初心者のJava] 入力画面を作ってみる

今回はSpring Bootで作ったサッカー成績表の入力画面を作っていこうと思います。

前回作成した試合一覧画面に入力・編集用のボタンを追加してもいいのですが、とりあえず表示用の画面と編集用の画面を分けるということで、新たに編集用試合一覧画面を作っていこうと思います。

編集用試合一覧画面の作成

まず編集用試合一覧画面のHTMLファイルを作成します。

前回作成したmatches.htmlを右クリックして、右クリックメニューから「コピー(C)」を選択してコピーします。

次にtemplatesを右クリックして「貼り付け(P)」を選択すると、以下の画面が表示されます。


ここで適当なファイル名 (ここではedit_matches.htmlとしました) に変更して、「OK」をクリックするとファイルが作成されるので、そのファイルをダブルクリックして編集します。

title要素とh1要素を適当に編集しておきます。

これからが本番ということで、試合一覧の各行に編集ボタンを追加するとともに、新規登録ボタンを新しく作成します。

まず、テーブルの見出し行に見出し要素thを1個追加して「編集」とします。

これはなくても構わないのですが、ないと寂しいのでつけておきます。

編集ボタンの追加

次に表示行の最後に編集ボタンを追加します。

編集ボタンをクリックすると、該当行の試合情報を編集できるようにするのですが、そのためには試合情報を特定できる情報をブラウザからサービスに通知する必要があります。

試合情報を特定する情報はデータベースの主キーに当たるidフィールドが最も適切と思われますので、これを使用することとします。

また、試合を特定する情報のサービスへの渡し方とサービスの呼び出し方ですが、何らかの可変情報をサービスに渡す方法として、URLに組み入れる方法とパラメータとして渡す方法が考えられます。

どちらを使ってもいいのですが、今回はHTMLファイルが少しだけ簡単になることからURLに組み入れる方法をとることとしました。

また、サービスの呼び出し方としてPOSTとGETの2種類があります。

GETはブラウザのアドレスバーに直接入力する、ブックマークに登録するといったことに対応できるという利点があり、POSTはそれらができないということが利点になります。

例えば検索に使用したURLなどは繰り返し使いたいという要望が考えられるので、GETが必須であり、オンラインショッピングの購入操作のように誤って繰り返しの操作がされないようにしたい場合はPOSTの方が多少有用ということになります。

今回はどちらでもあまり変わりがないので、とりあえずGETを使用することにします。さもしい話ですが、ここをGETにしておくと、POSTで同じURLが使いまわせるからというせこい魂胆で選択しています。

編集ボタンを配置するためのカラムのためにtd要素を追加して、その中に以下のようにform要素を作成します。

<tr th:each="match: ${matches}">
  <td th:text="${match.section}"></td>
  <td th:text="${match.date}"></td>
  <td th:text="${match.home}"></td>
  <td th:text="${match.goalsFor} + ' - ' + ${match.goalsAgainst}"></td>

  <td th:text="${match.away}"></td>

  <td>

    <form th:action="|/match/edit/${match.id}|" method="get">

      <input type="submit" value="編集">

    </form>

  </td>

</tr>

5個目までのtd要素は前回作成した試合一覧と同じもので、最後のtd要素が今回追加したものです。

form要素にはaction属性をthymeleafテンプレートにより書き換えるよう指定し、送信先URLとして"|/match/edit/${match.id}|"を指定しています。

thymeleafテンプレートでは縦棒 "|" で文字列を挟むことで文字列連結を指定します。この例で、例えばmatch.idに1が指定されていると"/match/edit/1"という文字列に置き換えが行われます。

URLに編集対象となる試合を特定する情報を織り込むことができたので、フォームを送信するボタンだけを配置するだけになっています。

なお、試合を特定する情報をパラメータとして渡す場合には以下のように書きます。

<form action="/match/edit" method="get">
  <input type="hidden" name="id" th:value="${match.id}">
  <input type="submit" value="編集">
</form>

hidden型のinput要素として試合を特定する情報を渡すようにすることで、画面上の表示なしにパラメータを指定できるようになります。

また、試合情報を追加するボタンも合わせて作成します。

こちらは特に表の中に入れる必要もないのですが、何となく表の中に作りこむことにしました。

<tr>
  <td colspan="6">

    <form action="/match/new" method="get">
      <input style="width: 100%;" type="submit" value="新規">
    </form>

  </td>

</tr>

試合一覧を表示しているテーブルの最後に1行追加して、colspan="6"で6列通しを指定したtd要素を作成、その中にフォームを入れています。

ボタンの入力項目にはstyle="width: 100%"を指定することで表の幅いっぱいにボタンを引き延ばしています。

新規試合情報登録

上で作った編集ボタンをクリックすると送信される情報を取得する側のコードを作成します。

まず、新規試合情報の登録を行うページを作っていきます。

まず、登録すべき内容を確認すると、試合開催日、節番号、ホームチーム、アウェイチーム、ホームチーム得点、アウェイチーム得点になります。

試合開催日、各チーム得点は入力してもらわずを得ませんが、節番号とチーム名は入力ミスを避けるという観点から選択式にするのがよさそうです。

節番号は数値ですので、数値入力で上限と下限を指定すればよさそうですが、チーム名はプルダウンメニューにするのが適当だと思われます。

そしてチームはシーズンごとに入れ替えがありますので、固定的にコーディングするのではなく、データベースにチーム名のリストを持っておくのがよさそうな気がします。

ということで、チーム名のデータベーステーブルを作ることにしました。

CREATE TABLE teams (
  id INT AUTO_INCREMENT,
  name VARCHAR(40) NOT NULL,
  abbr VARCHAR(8) NOT NULL,
  PRIMARY KEY (id)

);

idは自動生成のID、nameは正式名称、abbrは略称として、これまで作ってきた試合一覧のチーム名と略称が一致していることを前提にしています。

チーム名の入力・編集画面はそのうち作るとして、今回はこのテーブルに手入力しています。

ということで、チーム名テーブルができたので、チーム名テーブルに対応したエンティティクラスとレポジトリクラスを作成します。

まずはエンティティクラスから。

package com.example.demo;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

import javax.persistence.Table;

import lombok.Getter;

import lombok.Setter;

@Entity

@Table(name="teams")

public class Team {

  @Getter @Setter

  @Id @GeneratedValue(strategy=GenerationType.IDENTITY)

  private Integer id;

  @Getter @Setter

  private String name;

  @Getter @Setter

  private String abbr;

}

簡単にデータベースカラムそのままで作成し、コンストラクタもデフォルトコンストラクタだけにしています (今回はデータベースを引くだけなのでSetterもいらない気もします。)

レポジトリもとりあえず最小限で。

package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TeamRepository extends JpaRepository<Team, Integer> {

}

ほぼ名前だけの定義になっています。

ここまで用意したらコントローラクラスに以下のコードを作成します。

@GetMapping(value = "/match/new")
public String newMatch(Model model) {
  List<Team> teams = teamRepository.findAll();
  model.addAttribute("teams", teams);
  model.addAttribute("sections", 34);

  return "new_match";

}

あと、teamRepositoryフィールドを定義する以下のコードも必要です。

 @Autowired
TeamRepository teamRepository;

コードを説明すると、まずteamRepository.findAll()でチーム名テーブルの全レコードを取得して、それをmodel.addAttributeを使用してteamsという名前でテンプレートに渡しています。

次にsectionsという名前で節数34をテンプレートに渡しています。今回はハードコードしていますが、これもデータベースを参照するように変えたいという意図のもと、HTMLにハードコードするのではなく、modelを通してテンプレートに渡しています。

最後にテンプレート名new_matchを返しています。

次に新規試合情報を入力するnew_match.htmlを作成します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8">
  <title>Add New Match Result</title>

</head>

<body>

  <h1>試合情報追加</h1>

  <a href="/edit_matches">試合一覧編集に戻る</a>

  <hr>

  <form action="/match/new" method="post">

    <table>

      <tr>

        <th style="text-align: end;">節</th>

        <td>

          <input type="number" name="section" min="1" th:max="${sections}">

        </td>

      </tr>

      <tr>

        <th style="text-align: end;">日付</th>

        <td><input type="date" name="date"></td>

      </tr>

      <tr>

        <th style="text-align: end;">ホーム</th>

        <td>

          <select name="home">

            <option th:each="team: ${teams}" th:value="${team.abbr}" th:text="${team.abbr}">

            </option>

          </select>

        </td>

      </tr>

      <tr>

        <th style="text-align: end;">得点</th>

        <td>

          <input style="width: 3em;" type="number" name="goals_for" min="0">

          -

          <input style="width: 3em;" type="number" name="goals_against" min="0">

        </td>

      </tr>

      <tr>

        <th style="text-align: end;">アウェイ</th>

        <td>

          <select name="away">

            <option th:each="team: ${teams}" th:value="${team.abbr}" th:text="${team.abbr}">

            </option>

          </select>

        </td>

      </tr>

    </table>

    <input type="submit" value="確認">

  </form>

</body>

</html>

かなり長くなりましたので、少しずつ説明していきます。

まず、<form action="/match/new" method="post">から</form>までが入力項目で、見た目をそれらしくしようということでtableを使用し、各行ごとに1列目にラベル、2列目に入力項目を配置しています。

また、メソッドはPOST、URLは/match/newを指定しています。

テーブルの1行目は節番号で、入力項目は<input type="number" name="section" min="1" th:max="${sections}>と数値入力型にして、入力できる最大値をJavaメソッドから渡された節数にしています。

テーブルの2行目は試合開催日で、date型の入力項目をそのまま使っています。

テーブルの3行目はホームチームの選択項目で、プルダウンメニューのselectを使用しています。

メニュー項目は<option th:each="team: ${teams}" th:value="${team.abbr}" th:text="${team.abbr}">としています。

最初のth:each="team: ${teams}"により、Javaコードから渡されたTeamオブジェクトのリストからTeamオブジェクトを1個ずつteamに設定してoption要素を生成する操作を繰り返すよう指定しています。

th:value="${team.abbr}"により、このメニュー項目が選択されている場合にフォームのパラメータに設定される値をTeamオブジェクトのabbrプロパティ、つまり略称としています。

th:text="${team.abbr}"はこのメニュー項目で表示される文字列で、これも略称としています。

テーブルの4行目はホームチームとアウェイチームの得点項目で、2個のnumber型入力項目を「-」でつないでいます。ここで「-」の前後にある空白と改行はそれぞれ1個の空白に置き換えられるので、画面上は2個の入力項目が1行で表示されます。

テーブルの5行目はアウェイチームの選択項目で、テーブル3行目のホームチーム選択項目と基本的には同じものです。

最後にテーブルの外に「確認」ボタンを配置して、入力が行われるようにしています。

新規試合情報のデータベース登録

では、「確認」ボタンをクリックした後の処理を作っていきます。

@PostMapping(value="/match/new")
public String newMatch(
    @RequestParam(name="section") Integer section,
    @RequestParam(name="date") @DateTimeFormat(iso=DateTimeFormat.ISO.DATE)
    LocalDate date,

    @RequestParam(name="home") String home,

    @RequestParam(name="away") String away,

    @RequestParam(name="goals_for") Integer goalsFor,

    @RequestParam(name="goals_against") Integer goalsAgainst,

    Model model) 

{

  Match match = new Match();

  match.setSection(section);

  match.setDate(date);

  match.setHome(home);

  match.setAway(away);

  match.setGoalsFor(goalsFor);

  match.setGoalsAgainst(goalsAgainst);

  matchRepository.saveAndFlush(match);

  return "redirect:/edit_matches";

}

引数が多いので、少し複雑に見えますが、エラーチェックを行っていないので、コードとしてはとても簡単なものになっています。

まず、1行目で@PostMapping(value="/match/new")を指定して、/match/newへのPOSTアクセスでこのメソッドが呼ばれるよう指定を行っています。

メソッドへの引数のうち、HTMLフォームからの入力は@RequestParamアノテーションを付加して、HTMLフォームのname属性と引数との間の関係を指定しています。

基本的にはHTMLフォームから入力された値を必要な型の引数にアノテーションで割り当てるだけで型変換などはフレームワークが自動的に行いますが、日付型だけは@DateTimeFormat(iso=DateTimeFormat.ISO.DATE)アノテーションによる型変換の指定が必要となります。

メソッドの本体では試合情報オブジェクトmatchを作成して、引数で受け取った情報を設定し、試合情報レポジトリにsaveAndFlushで反映させるだけでデータベースに反映されます。

データベースへの反映後、redirect:/edit_matchesを戻り値とすることで試合情報編集画面にリダイレクトさせています。

以上で新規登録画面は終わりになります。

情報の更新画面についてはまた次回としたいと思います。


0コメント

  • 1000 / 1000