スポンサーリンク

【SpringBoot+Thymeleaf】select + optionにて selectedとdisabledを同時に使用したい

仕事で
「select + optionタグを使用し、ユーザ操作で選択はさせたくないけど、初期状態では選択して表示したい」
ということをしたい時があったので、その備忘録です。

言葉では判りづらいので実例で説明すると

販売するため「机、椅子、棚」のリスト(select + option)を表示します。
ある時点で「椅子」を販売しました。
その後「椅子」が販売中止になったので、選択できないように(disabled)、さらにformしてもエラーにしたい。
でも、販売情報を見る際は「椅子」を選択(selected)して初期表示したい。

といった感じです。(さらに判りづらいかも…)

簡潔に言うと「selectのoptionタグで、selectedとdisabledを同時に使用したい」ということです。

スポンサーリンク

前提

環境構築、th:fieldの使い方などは知っているものとして記載しています。

単にソースコードを書いてもわかりづらいので、以下の目的に沿って書いてます。

  • 家具リストを「机、椅子、棚」とし、「椅子」を初期選択して表示。
  • それぞれの家具は有効無効状態を持っており、「机、棚」は有効、「椅子」は無効。
  • 椅子が選択されたまま form post するとエラー表示。

尚、ソースコードには必要最低限しか書いてません。

Formクラスについて

Formクラスを以下とします。

KaguPageForm.java

public class KaguPageForm {
  @NotNull(message="選択された家具は、販売中止です。")
  private Integer selectedKaguId; // 選択された家具

  private ArrayList<KaguInfo> kaguInfoList = new ArrayList<>(); // 家具情報リストを格納

  // getter/setterは省略
  // 必要に応じて、コンストラクタを追加
}

disabled状態で選択表示されている時にpostするとnullが送信されます。
そのため、@NotNullで検出することができます。

 

KaguInfo.java

public class KaguInfo {
  private Integer id;      // 家具ID
  private String name;     // 家具名
  private Integer isValid; // 有効無効。 0:無効、1:有効

  // getter/setterは省略
}

注意点としては、KaguInfoクラスをKaguPageFormクラス内で宣言しないようにしてください(インナークラスにしない)

Thymeleafについて

次は今回メインのThymeleaf部分です。

<form th:action="@{/kagupage}" method="post" th:object="${kaguPageForm}">

  <select id="selectedKaguId" name="selectedKaguId">
    <option th:each="kagu : *{kaguInfoList}" th:value="${kagu.id}" th:text="${kagu.name}"
           th:selected="${kagu.id} == *{selectedKaguId}" th:disabled="${kagu.isValid == 0}">
    </option>
  </select>

</form>

 ※th:errorclassなどエラー表示部については省略しています。

ここでのポイントは

  • selectでは、th:fieldを使用せずに id / nameで記述する。
  • option内のth:selectedでは、表示しようとしている家具IDが、選択された家具IDと同じなら selectedを表示させるようにする。
  • option内のth:disabledでは、表示しようとしている家具が無効なら disabledを表示させるようにする。

です。

th:fieldを使用すると、disabledのみ効きselectedが効きません。
そのため id/nameで記述させ、且つ、th:selectedにて明示的にselectedを付加させています。

Controllerクラスについて

Controller側のコードです。

KaguController.java

// GET時(初期表示)
@GetMapping(value = "/kagupage")
public ModelAndView get(
            KaguPageForm formData,
            ModelAndView mav) {

  // 初期表示用

  KaguInfo info = new KaguInfo();
  info.setId(1);
  info.setName("机");
  info.setIsValid(1); // 有効
  formData.getKaguInfoList.add(info);

  info = new KaguInfo();
  info.setId(2);
  info.setName("椅子");
  info.setIsValid(0); // 無効
  formData.getKaguInfoList.add(info);

  info = new KaguInfo();
  info.setId(3);
  info.setName("棚");
  info.setIsValid(1); // 有効
  formData.getKaguInfoList.add(info);

  formData.setSelectedKaguId(2); // 椅子を初期選択

  mav.addObject("kaguPageForm", formData);
  return mav;
}

// POST時
@PostMapping(value = "/kagupage")
public ModelAndView post(
            @ModelAttribute("kaguPageForm") @Validated KaguPageForm formData,
            ModelAndView mav) {

  if (result.hasErrors()) {
    // disabled状態のoptionを選択したままpostすると、@NotNullに引っかかり、このルートを通る。
  }

  .. 以下、省略 ..
}

KaguPageFormクラスのselectedKaguIdに@NotNullを設定しておくと、disabledで初期選択されている状態でform postが行われた時に、エラーを検出することができます。

最後に

いかがでしたでしょうか?

私は、最初素直にselectにth:fieldを、optionにdisabledを表示するように記述したのですが、
期待通りに表示されず、いろいろと調べてやっとできるようになりました。

プログラムを組んでるとたまに、理論的にはこうすれば良いはずだけど何故かうまくいかない、といった状況に陥ることがあり、とても悩みます。

同じように悩んでいる人が、ここを見て解決に一歩前進できたら嬉しく思います。

コメント