[初心者のJava] 編集画面を作ってみる

引き続きサッカー試合一覧の編集画面を作っていきます。

前回は新規試合情報の登録を行いましたが、今回は試合情報の変更を扱っていこうと思います。

試合情報の変更も新規試合情報の登録と同じく試合開催日、節番号、ホームチーム、アウェイチーム、ホームチーム得点、アウェイチーム得点を入力するのですが、変更の場合には対象となる試合を特定する情報が必要となることが異なる点となります。

また、入力する情報に関しても、既に入力済みの情報を入力欄に反映することが望ましいと思われます。

ということで編集画面の作成を進めていきたいと思います。

前回、試合一覧画面の各行にある編集ボタンをクリックした際には/match/edit/1のように試合を特定するIDを含むURLに対してGETアクセスがされるようにしました。

このURLでアクセスされるメソッドをまず定義します。

@GetMapping(value = "/match/edit/{id}")
public String editMatch(
    @PathVariable(name="id") Integer id,

    Model model) 

{

  Optional<Match> match = matchRepository.findById(id);

  if (match == null || match.isEmpty()) {

    return "redirect:/matches";

  }

  model.addAttribute("match", match.get());

  List<Team> teams = teamRepository.findAll();

  model.addAttribute("teams", teams);

  return "edit_match";

}

1行目の@GetMapping(value="/match/edit/{id}")でGETアクセスされるURLを指定しています。

3行目の@PathVariable(name="id") Integer idによって、URLの{id}と引数idがSpringフレームワークによって紐づけられ、引数idの型Integerに合わせてURLの文字列が数値として処理されます。

数値以外の文字列がURLに指定されていた場合にはエラーになりますが、エラー処理は後で考えることとして先に進めます。

6行目のOptional<Match> match = matchRepository.findById(id)でidで指定されている試合情報を取得しています。

findByIdはデータベースのテーブルで主キーとして指定されているカラムを指定された引数で検索し、結果をOptional型で返します (実際に主キーを検索しているのか、エンティティクラスで@Idアノテーションが付されたフィールドに対応するカラムを検索しているのかは確認していません。)

主キーには重複する値は登録されていないというのが前提ですので、検索結果は高々1個となります。

そのため、戻り値型は試合一覧で使用したfindAllメソッドやこの先使っていく一般の検索メソッドがList型を返すの対して、findByIdメソッドはOptional型を返すようになっています。

高々1個の値しか返さないので、エンティティ型の参照を返して、見つからなかった場合にはnullを返せばいいのにとも思いますが、なぜかそうなっていません。

ちなみにエンティティ型の参照を返すgetReferenceByIdメソッドというのもあるのですが、こちらは検索に失敗した場合に例外を返すのでこちらはこちらで使いにくい感じです。

7行目で念のためfindByIdメソッドの戻り値を確認し、検索に失敗した場合には試合一覧画面にリダイレクトするようにしています。

編集画面のボタンからアクセスした場合には検索に失敗するはずがないので、適当なURLでアクセスしてきたことを想定しての措置ですが、数値以外の文字列が入力された場合のエラーチェックを行っていないので、ほとんど自己満足にしかすぎません。

10行目のmodel.addAttribute("match", match.get())でidで指定された試合情報をmatchという名前でテンプレートに渡しています。

11行目、12行目はチーム名を表示するためにチームテーブルの値をteamsという名前でテンプレートに渡すもので、新規試合情報登録と同じコードとなっています。

そしてedit_matchテンプレートを指定してこのメソッドを終了しています。

次にテンプレートであるedit_match.htmlを見ていきます。

基本的な構成はnew_match.htmlとあまり違いはないので、差分のある部分を抽出して示していきます。

<form th:action="'/match/edit/'+${match.id}" method="post">

  <table>
    <!-- 途中省略 -->

  </table>

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

</form>

まず、入力テーブルを囲むform要素ですが、th:action="'/match/edit/'+${match.id}"という形でaction項目に試合IDを埋め込むようにしています。

もちろん縦棒を使用してth:action="|/match/edit/${match.id}|"としても問題ありません。

これにより確認ボタンをクリックすると/match/edit/1のようなURLにPOST要求が送られるようになります。

次に節の入力部分を見てみます。

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

    <select name="section">

      <option th:each="sec: ${#numbers.sequence(1,34)}" th:value="${sec}" 

        th:text="'第' + ${sec} + '節'" th:selected="${sec == match.section}">

      </option>

    </select>

  </td>

</tr>

選択入力項目であるoptionにth:selected="${sec == match.section}"を追加しています。

これにより選択入力項目の節番号として表示されるsecと編集対象の試合の節番号match.sectionが一致した項目が選択された状態で表示されるようになります。

同じように選択入力項目を使用しているホームチームの入力部分も見てみます。

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

    <select name="home">

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

        th:selected="${team.abbr == match.home}">

      </option>

    </select>

  </td>

</tr>

こちらではth:selected="${team.abbr == match.home}"によって選択入力項目のチーム名として表示されるteam.abbrと編集対象試合のホームチーム名match.homeが一致した選択入力項目が選択された状態で表示されるようにしています。

アウェイチーム入力部分も同じですので説明は省略します。

続いて試合開催日の入力項目です。

<tr>
  <th style="text-align: end;">日付</th>
  <td><input type="date" name="date" th:value="${match.date}"></td>

</tr>

こちらは日付入力項目ですが、th:value="${match.date}"を指定することで編集対象試合の開催日を初期値として設定しています。

同様にホームチーム得点は以下のようになります。

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

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

      th:value="${match.goalsFor}">

    -

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

      th:value="${match.goalsAgainst}">

  </td>

</tr>

こちらは数値入力型ですが、ホームチーム得点、アウェイチーム得点それぞれにth:value="${match.goals_for}"、th:value="${match.goals_against}"を指定することでそれぞれの初期値を設定しています。

以上をまとめたedit_match.htmlの全体は以下のようになります。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>

  <meta charset="UTF-8">

  <title>Edit Match Result</title>

</head>

<body>

  <h1>試合情報編集</h1>

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

  <hr>

  <form th:action="'/match/edit/'+${match.id}" method="post">

    <table>

      <tr>

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

        <td>

          <select name="section">

            <option th:each="sec: ${#numbers.sequence(1,34)}" th:value="${sec}" 

              th:text="'第' + ${sec} + '節'" th:selected="${sec == match.section}">

            </option>

          </select>

        </td>

      </tr>

      <tr>

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

        <td><input type="date" name="date" th:value="${match.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}" 

              th:selected="${team.abbr == match.home}">

            </option>

          </select>

        </td>

      </tr>

      <tr>

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

        <td>

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

            th:value="${match.goalsFor}">

          -

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

            th:value="${match.goalsAgainst}">

        </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}" 

              th:selected="${team.abbr == match.away}">

            </option>

          </select>

        </td>

      </tr>

    </table>

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

  </form>

</body>

</html>

以上で試合情報編集画面が表示できるようになったので、次は試合情報編集画面からのPOST要求を受け取るコントローラを作っていきます。

@PostMapping(value="/match/edit/{id}")
public String editMatch(
    @PathVariable(name="id") Integer id,

    @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) 

{

  Optional<Match> o = matchRepository.findById(id);

  if (o == null || o.isEmpty()) {

    return "redirect:/matches";

  }

  Match match = o.get();

  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/edit/{id}")で受け取るURLとメソッドを指定しています。

URLのidで指定された値は@PathVariable(name="id") Integer idでInteger型の引数idに整数型に変換されて入力されます。

その他のパラメータについては新規試合情報登録メソッドと同様です。

メソッド本体ではまずOptional<Match> o = matchRepository.findById(id)でidで指定された試合情報をデータベースから取得しています。

試合情報が取得できなかった場合には引き続くif文でとりあえず試合情報編集画面にリダイレクトしています。

次にMatch match = o.get()で試合情報を取り出し、引き続くセッタでフォームから受け取ったデータを設定しています。

ここでidには何も設定していないので、データベースから取得した試合情報に設定されていたidがそのまま使用されます。

Match型のオブジェクトを新規に生成してURLの一部として受け取ったidを設定してもいいのですが、idの正当性チェックのためにオブジェクトを取得しているので、こちらを使用しています。

最後にレポジトリのsaveAndFlushメソッドを呼び出してデータベースを更新しています。

saveAndFlushメソッドは主キーが一致するレコードがテーブルにあればそのレコードを更新するので、新規登録と更新の両方で同じメソッドが使用できます。

テーブルの更新が終了したら試合情報編集画面にリダイレクトして終了しています。

以上で、編集も大体できました。

次はもう少しデータベース検索について進めていきたいと思います。


0コメント

  • 1000 / 1000