質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Thymeleaf

Thymeleaf(タイムリーフ)とは、Java用のテンプレートエンジンで、特定のフレームワークに依存せず使用することが可能です。

Q&A

解決済

1回答

8375閲覧

Spring Boot+Thymeleaf 画面から入力された二次元配列の情報をフォームで受け取りたい

chocolate_pie

総合スコア26

Thymeleaf

Thymeleaf(タイムリーフ)とは、Java用のテンプレートエンジンで、特定のフレームワークに依存せず使用することが可能です。

0グッド

0クリップ

投稿2020/02/18 02:23

編集2020/02/20 05:25

前提・実現したいこと

いつもお世話になっております。
Spring Boot+Thymeleafで画面から入力されたリストの情報をフォームで受け取りたいです。
正確にはリストの中にあるリスト(二次元配列)の情報を受け取りたいです。

Springにおいて画面のリスト状になっている情報をコントローラーで取得したいを参照して、リストになっている情報をコントローラーで受け取る処理の実装を試みましたがうまく値が渡らず、formから受け取る値がnullになってしまいます。
これは、リストの中にリストがない場合なので、リストの中にリストがある書き方に躓いてしまいました。

前回質問した、SpringBoot フォームで複数レコードの値を受け取れないでは、リストの0番目の情報がすべての番号に反映されて更新されるだけの処理でした。

イメージ画像は以下の通りです。送信ボタンを押されると、一気にレコードが更新されるイメージです。
イメージ説明
今回リストで更新したいのは、期中・期末の目標ウエイト(goalWeight)のみです。
目標番号のリストの中にウェイトのリストが入っていて、その値をformに送信します。

表示自体はできるのですが、javaに値がどうしても渡りません。
Thymeleafの書き方がおかしいのだと思います。
ご教授お願いします。

発生している問題・エラーメッセージ

Formからリストの値が受け取れない

該当のソースコード

GoalSeqForm.java

java

1package jp.co.itc.mbo.form; 2 3import java.util.List; 4 5import javax.persistence.OneToMany; 6 7import jp.co.itc.mbo.entity.Meisai; 8 9public class GoalSeqForm { 10 11 private String goaltitle; 12 private Integer year; 13 private String goaldetail; 14 15 @OneToMany(mappedBy = "goalseqid") 16 private List<Meisai> meisaiList; 17 18 19 public String getGoaltitle() { 20 return goaltitle; 21 } 22 public void setGoaltitle(String goaltitle) { 23 this.goaltitle = goaltitle; 24 } 25 public Integer getYear() { 26 return year; 27 } 28 public void setYear(Integer year) { 29 this.year = year; 30 } 31 public String getGoaldetail() { 32 return goaldetail; 33 } 34 public void setGoaldetail(String goaldetail) { 35 this.goaldetail = goaldetail; 36 } 37 38 public List<Meisai> getMeisaiList() { 39 return meisaiList; 40 } 41 public void setMeisaiList(List<Meisai> meisaiList) { 42 this.meisaiList = meisaiList; 43 } 44 45 46 47} 48 49

MeisaiForm.java

java

1package jp.co.itc.mbo.form; 2 3import java.util.List; 4 5import javax.persistence.JoinColumn; 6import javax.persistence.ManyToOne; 7 8import jp.co.itc.mbo.entity.GoalSeq; 9 10public class MeisaiForm { 11 12 private Boolean target; 13 private List <Integer> goalWeight; 14 private Integer evalution; 15 private String selfComment; 16 private String bossComment; 17 private Integer reviewCheck; 18 private Integer status; 19 private Double score; 20 21 @ManyToOne(targetEntity = GoalSeq.class) 22 @JoinColumn( name = "goalseq_id") 23 private GoalSeq goalseqid; 24 25 public Boolean getTarget() { 26 return target; 27 } 28 29 public void setTarget(Boolean target) { 30 this.target = target; 31 } 32 33 public List<Integer> getGoalWeight() { 34 return goalWeight; 35 } 36 37 public void setGoalWeight(List<Integer> goalWeight) { 38 this.goalWeight = goalWeight; 39 } 40 41 public Integer getEvalution() { 42 return evalution; 43 } 44 45 public void setEvalution(Integer evalution) { 46 this.evalution = evalution; 47 } 48 49 public String getSelfComment() { 50 return selfComment; 51 } 52 53 public void setSelfComment(String selfComment) { 54 this.selfComment = selfComment; 55 } 56 57 public String getBossComment() { 58 return bossComment; 59 } 60 61 public void setBossComment(String bossComment) { 62 this.bossComment = bossComment; 63 } 64 65 public Integer getReviewCheck() { 66 return reviewCheck; 67 } 68 69 public void setReviewCheck(Integer reviewCheck) { 70 this.reviewCheck = reviewCheck; 71 } 72 73 public Integer getStatus() { 74 return status; 75 } 76 77 public void setStatus(Integer status) { 78 this.status = status; 79 } 80 81 public Double getScore() { 82 return score; 83 } 84 85 public void setScore(Double score) { 86 this.score = score; 87 } 88 89 public GoalSeq getGoalseqid() { 90 return goalseqid; 91 } 92 93 public void setGoalseqid(GoalSeq goalseqid) { 94 this.goalseqid = goalseqid; 95 } 96 97 98} 99

Controller

java

1 @RequestMapping(value = "{id}/weight_input") 2 String weightInput(@PathVariable Integer id,Model model) { 3 List<GoalSeq> goalseqs = goalseqservice.findCurrent(id); 4 model.addAttribute("goalseqs", goalseqs); 5 return "goals/weight_input";} 6 7 @RequestMapping(value = "/weight_complete/{id}", method = RequestMethod.POST) 8 String weightRegist(@PathVariable Integer id,@Valid GoalSeqForm goalseqform, Principal principal,BindingResult bindingResult) { 9 if (bindingResult.hasErrors()) { 10 //処理未記述 11 } 12 //↓ここでヌルポ 13 GoalSeqForm forms=goalseqform; 14 List <GoalSeq> goalseqs=goalseqservice.findCurrent(id); 15 for(GoalSeq goalseq: goalseqs) { 16 for (int i = 0; i <= 2; i++) { 17 //処理は未記述ですがここで、フォームから受け取った値を更新する記述をします 18 } 19 } 20 return "redirect:/{id}"; 21 }

weight_input.html

HTML

1<!DOCTYPE html> 2<html> 3<head> 4<meta charset="UTF-8"> 5<title>Insert title here</title> 6</head> 7<body> 8 <form method="post" 9 th:action="@{/weight_complete/}+${goalseqs[0].userid.id}" 10 th:object="${meisaiForm}"> 11 <table> 12 <tr> 13 <th>目標番号</th> 14 <th>期中ウエイト</th> 15 <th>期末ウエイト</th> 16 <th></th> 17 </tr> 18 <tr th:each="goalseq,st: ${goalseqs}"> 19 <td><p th:text="${goalseq.goalid}"></p></td> 20 <td><input type=number name="meisaiList[1].goalWeight" 21 th:name="'MeisaiList[' +${st.index} +'].[1].goalWeight'" 22 th:value="${goalseq.meisaiList[1].goalWeight}"></td> 23 <td><input type=number name="meisaiList[2].goalWeight" 24 th:name="'MeisaiList[' +${st.index}+ '].[2].goalWeight'" 25 th:value="${goalseq.meisaiList[2].goalWeight}"></td> 26 </tr> 27 </table> 28 <button type=submit>送信</button> 29 </form> 30</body> 31</html>

試したこと

・GoalSeqでeachを回すのではなく、Meisaiで回したらフォームに値が渡りました
→この方法で解決しましたがこの解決方法は、Springにおいて画面のリスト状になっている情報をコントローラーで取得したいと同じになってしまうので質問を残してあります。

補足情報(FW/ツールのバージョンなど)

FormクラスのGoalSeqのレコード1つに対して、MeisaiのレコードはmeisaiListとして3つOneToManyで紐づいています。

目標1つに対して、外部テーブルで期初、期中、期末のステータスを紐づけています。
期初にあたるmeisaiListの0番目は更新する必要がないので、nameのところでは、
期中を「meisaiList[1].goalWeight」
期末を「meisaiList[2].goalWeight」
と表記しています。

初心者で至らない点があると思いますがよろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubytomato

2020/02/20 04:09

3点確認させてください。 1) weightInputメソッドで取得する下記のリストのデータが2件あると、イメージ画像のようにフォームに2件表示されるということでしょうか?(5件あればフォーム上も5行というように) List<GoalSeq> goalseqs = goalseqservice.findCurrent(id); 2) イメージ画像中にある目標番号というのは、2つあるフォームのどちらの、何という名前のフィールドで受け取るのでしょうか? 3) フォームクラスで@ManyToOneや@OneToManyというアノテーションを利用されていますが、これは何を期待して付けているのでしょうか?(付けないとどんなエラーが起きますか?)
chocolate_pie

2020/02/20 05:13

rubytomato様 コメントありがとうございます。 質問の回答をさせていただきます。 1)その通りです。件数の数だけ行が表示されます。 2)目標番号は、GoalSeq(GoalSeqFormのエンティティ)の、goalidというフィールドです。こちらは値を更新しない表示のみです。混乱してしまうので、input タグ→pタグに変更します。 3)GoalSeqForm1件に対してMeisaiFormは3件紐づいているので@ManyToOneと@OneToManyを付けないと、MeisaiFormの値が取れなくなってしまいます。
guest

回答1

0

ベストアンサー

表示自体はできるのですが、javaに値がどうしても渡りません。

送信するフォームデータを受け取れないのは、受け取るフォームクラスの実装に問題があるようです。

1)その通りです。件数の数だけ行が表示されます。

1つのフォーム内に複数件のGoalSeqのデータがあり、さらにGoalSeqは複数件のMeisaiListを持つという、テーブル状のフォームデータを送信する方法について、下記のサンプルコードで動作確認しました。

複数件のGoalSeqのデータが送信されるという仕様なので、フォームクラスの実装は下記のようになります。
※なおクラス名はもともとのGoalSeqFormから、より適切なGoalsFormに変えています。
※この変更でMeisaiFormは不要になります。

java

1public class GoalsForm { 2 3 private String goaltitle; 4 private Integer year; 5 private String goaldetail; 6 7 // ここはGoalSeqのコレクション 8 private List<GoalSeq> goalseqs; 9 10 // getter/setterは省略 11 12}

この修正により、コントローラも以下のように修正します。所々動作確認用のコードが入っていますのでご留意ください。

java

1@GetMapping(value = "/weight_input/{id}") 2String weightInput(@PathVariable Integer id, @ModelAttribute GoalsForm goalsForm, Model model) { 3 // 確認用コード 4 model.addAttribute("userid", id); 5 6 // 確認用コード 7 goalsForm.setGoaltitle("test"); 8 goalsForm.setYear(2020); 9 goalsForm.setGoaldetail("test detail"); 10 11 List<GoalSeq> goalseqs = goalseqservice.findCurrent(id); 12 13 // 取得したGoalSeqのコレクションをフォームにセット 14 goalsForm.setGoalseqs(goalseqs); 15 16 return "goals/weight_input"; 17} 18 19 20@PostMapping(value = "/weight_complete/{id}") 21String weightRegist(@PathVariable Integer id, @Valid @ModelAttribute GoalsForm goalsForm) { 22 // 確認用コード 23 System.out.println("GoalSeqForm#goaltitle: " + goalsForm.getGoaltitle()); 24 25 List <GoalSeq> goalseqs = =goalseqservice.findCurrent(id); 26 27 // フォームからGolaSeqのコレクションが渡される 28 // 確認用コード 29 for (GoalSeq goalseq : goalsForm.getGoalseqs()) { 30 System.out.println(String.format("id:%d, goalid:%d, goalWeight[0]:%d, goalWeight[1]:%d, goalWeight[2]:%d", 31 goalseq.getId(), goalseq.getGoalid(), 32 goalseq.getMeisaiList().get(0).getGoalWeight(), 33 goalseq.getMeisaiList().get(1).getGoalWeight(), 34 goalseq.getMeisaiList().get(2).getGoalWeight())); 35 } 36 37 return "redirect:/{id}"; 38}

テンプレートの修正は以下の通りです。
大きな変更点はformタグのth:objectで指定するフォームをmeisaiFormからgoalsFormへ変えている点です。

html

1<form method="post" th:action="@{/weight_complete/{userid}(userid=${userid})}" th:object="${goalsForm}"> 2 <input type="hidden" th:field="*{goaltitle}" /> 3 <input type="hidden" th:field="*{year}" /> 4 <input type="hidden" th:field="*{goaldetail}" /> 5 <table> 6 <tr> 7 <th>no</th> 8 <th>目標番号</th> 9 <th>期初ウエイト</th> 10 <th>期中ウエイト</th> 11 <th>期末ウエイト</th> 12 </tr> 13 <tr th:each="goal, st : *{goalseqs}"> 14 <td th:text="${st.index}">index</td> 15 <td> 16 <input type="hidden" th:name="|goalseqs[${st.index}].id|" th:value="${goal.id}" /> 17 <input type="number" th:name="|goalseqs[${st.index}].goalid|" th:value="${goal.goalid}" /> 18 </td> 19 <td> 20 <input type="number" th:name="|goalseqs[${st.index}].meisaiList[0].goalWeight|" th:value="${goal.meisaiList[0].goalWeight}" /> 21 </td> 22 <td> 23 <input type="number" th:name="|goalseqs[${st.index}].meisaiList[1].goalWeight|" th:value="${goal.meisaiList[1].goalWeight}" /> 24 </td> 25 <td> 26 <input type="number" th:name="|goalseqs[${st.index}].meisaiList[2].goalWeight|" th:value="${goal.meisaiList[2].goalWeight}" /> 27 </td> 28 </tr> 29 </table> 30 <button type=submit>送信</button> 31</form>

このテンプレートがレンダリングされると以下のhtmlコードになります。
※データはテスト用のダミーです。

html

1<form method="post" action="/weight_complete/10"> 2 <input type="hidden" id="goaltitle" name="goaltitle" value="test"> 3 <input type="hidden" id="year" name="year" value="2020"> 4 <input type="hidden" id="goaldetail" name="goaldetail" value="test detail"> 5 <table> 6 <tbody><tr> 7 <th>no</th> 8 <th>目標番号</th> 9 <th>期初ウエイト</th> 10 <th>期中ウエイト</th> 11 <th>期末ウエイト</th> 12 </tr> 13 <tr> 14 <td>0</td> 15 <td> 16 <input type="hidden" name="goalseqs[0].id" value="1"> 17 <input type="number" name="goalseqs[0].goalid" value="66"> 18 </td> 19 <td> 20 <input type="number" name="goalseqs[0].meisaiList[0].goalWeight" value="50"> 21 </td> 22 <td> 23 <input type="number" name="goalseqs[0].meisaiList[1].goalWeight" value="51"> 24 </td> 25 <td> 26 <input type="number" name="goalseqs[0].meisaiList[2].goalWeight" value="52"> 27 </td> 28 </tr> 29 <tr> 30 <td>1</td> 31 <td> 32 <input type="hidden" name="goalseqs[1].id" value="2"> 33 <input type="number" name="goalseqs[1].goalid" value="1"> 34 </td> 35 <td> 36 <input type="number" name="goalseqs[1].meisaiList[0].goalWeight" value="60"> 37 </td> 38 <td> 39 <input type="number" name="goalseqs[1].meisaiList[1].goalWeight" value="61"> 40 </td> 41 <td> 42 <input type="number" name="goalseqs[1].meisaiList[2].goalWeight" value="62"> 43 </td> 44 </tr> 45 <tr> 46 <td>2</td> 47 <td> 48 <input type="hidden" name="goalseqs[2].id" value="3"> 49 <input type="number" name="goalseqs[2].goalid" value="35"> 50 </td> 51 <td> 52 <input type="number" name="goalseqs[2].meisaiList[0].goalWeight" value="70"> 53 </td> 54 <td> 55 <input type="number" name="goalseqs[2].meisaiList[1].goalWeight" value="71"> 56 </td> 57 <td> 58 <input type="number" name="goalseqs[2].meisaiList[2].goalWeight" value="72"> 59 </td> 60 </tr> 61 <tr> 62 <td>3</td> 63 <td> 64 <input type="hidden" name="goalseqs[3].id" value="4"> 65 <input type="number" name="goalseqs[3].goalid" value="8"> 66 </td> 67 <td> 68 <input type="number" name="goalseqs[3].meisaiList[0].goalWeight" value="80"> 69 </td> 70 <td> 71 <input type="number" name="goalseqs[3].meisaiList[1].goalWeight" value="85"> 72 </td> 73 <td> 74 <input type="number" name="goalseqs[3].meisaiList[2].goalWeight" value="89"> 75 </td> 76 </tr> 77 </tbody></table> 78 <button type="submit">送信</button> 79</form>

これで、テーブル上のフォームデータの送信と受信のサンプルコードは以上になります。

以下、補足になります。

3)GoalSeqForm1件に対してMeisaiFormは3件紐づいているので@ManyToOneと@OneToManyを付けないと、MeisaiFormの値が取れなくなってしまいます。

フォームクラスにはこれらのアノテーションは不要です。(通常フォームクラスにJPAのアノテーションはつけません)
これらのアノテーションはJPAを使ってデータベース上のテーブルとリレーションをエンティティクラスにマッピングするために使うものです。
質問したのはフォームクラスとエンティティクラスを兼ねているのかどうかが知りたかったためです。

つぎに、フォームクラスのフィールドにエンティティクラスを使用する点ですが、このサンプルコードでも以下のように実装していますが、

java

1public class GoalsForm { 2 3 private List<GoalSeq> goalseqs; 4 5}

フォーム上で編集するフィールドが期中/期末ウェイトだけであれば、それ用のフィールドを定義したフォームクラスを実装することもご検討ください。
特に期初/期中/期末をインデックスで扱うのはバグにつながるかもしれません。
以下はイメージです。

java

1public class GoalsForm { 2 3 private String goaltitle; 4 private Integer year; 5 private String goaldetail; 6 7 private List<GoalSeqDto> goalseqs; 8 9} 10 11public class GoalSeqDto { 12 13 // 目標番号 14 private Integer goalid; 15 // 期中ウェイト 16 private Integer duringOfgoalWeight; 17 // 期末ウェイト 18 private Integer endOfgoalWeight; 19 20}

投稿2020/02/20 07:53

rubytomato

総合スコア1752

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

chocolate_pie

2020/02/21 00:50

Thymeleafの書き方ではなくFormの書き方が間違っていたのですね。 また、FormにOneToMany等のアノテーションは使わないことや、インデックスで扱うとバグにつながるということを教えていただきありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問