⇧ Gitに慣れていかねばですね...
JSP(JavaServer Pages)のラジオボタンのcheckedでハマる
いや~、JSP(JavaServer Pages)で、泥沼にハマりましたわ...
前任の開発者の人が初期状態でcheckedを付けたかったんだとは思うけど、
<div> <form:radiobutton path="fileType" label="text" value="text" /> <form:radiobutton path="fileType" label="xml" value="xml" checked="checked" /> <form:radiobutton path="fileType" label="json" value="json" /> <form:radiobutton path="fileType" label="yaml" value="yaml" /> </div>
⇧ みたいな感じで、固定でchecked="checked"とか付けてくれちゃってたせいで、ラジオボタンのチェックを変更した時にサーバーサイド(Java)にリクエストを送ると、JSP(JavaServer Pages)が再レンダリングされた際に、後続のラジオボタンの要素のcheckedが優先されるらしく、一番初めの要素にチェックが永久に付かない...
どういうことかと言うと、「ファイル形式」のラジオボタンの話なのだけど、最初は、2つ目の要素の『xml』にcheckedが付いている。
3つ目の要素の『json』に変更してみる。
⇧ 単一選択のラジオボタンでcheckedが2つ付いてしまってるのがそもそも問題なのだが...
続いて、1つ目の要素である『text』に変更しようとすると、
⇧ 2つ目の要素『xml』のcheckedが優先される。
どうやら、単一選択のラジオボタンでcheckedが複数付いてる場合、後続の要素のcheckedが優先されるということらしい...
メモ: 複数のラジオボタンに checked
属性を指定した場合、後から指定したものが先に指定したものを上書きします。つまり、最後に checked
されたラジオボタンが選択されることになります。これは、一度に選択できるラジオボタンはグループ内の 1 つだけであり、ユーザーエージェントは新しいラジオボタンがチェックされるたびに、他のラジオボタンの選択を自動的に解除するからです。
⇧ HTMLの想定だと、ラジオボタンはcheckedされたものが選択されるらしいのだけど、checkedが2つある場合については何も言及してくれていない...
JSP(JavaServer Pages)のせいだと思うけど、checkedが2つある状況ってのは、おそらく正常な状態とは言えないんではないかと。
ということで、
<div> <form:radiobutton path="fileType" label="text" value="text" checked="${fileType == 'text' ? 'checked': ''}" /> <form:radiobutton path="fileType" label="xml" value="xml" checked="${fileType == 'xml' ? 'checked': ''}" /> <form:radiobutton path="fileType" label="json" value="json" checked="${fileType == 'json' ? 'checked': ''}" /> <form:radiobutton path="fileType" label="yaml" value="yaml" checked="${fileType == 'yaml' ? 'checked': ''}" /> </div>
⇧ 上記のようにすれば、切り替わるようになった。
ソースコードは、
⇧ 上記の記事の時のものに追記してます。
■/spring-mvc-jsp/src/main/java/com/form/SettingForm.java
package com.form; import lombok.Data; @Data public class SettingForm { private String elevetorHandlingFlg; private String wheelchairHandlingFlg; private String aedHandlingFlg; private String fileType; private Boolean completeUpdate; private Integer updateCount; }
■/spring-mvc-jsp/src/main/java/com/controller/helper/SettingControllerHelper.java
package com.controller.helper; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import com.controller.exception.BusinessException; import com.dto.form.RadioButtonDto; @Component public class SettingControllerHelper { public List<RadioButtonDto> makeRadioButton(String currentValue) throws BusinessException { Map<String, String> map = makeMapForHandlingFlg(); if (CollectionUtils.isEmpty(map)) { throw new BusinessException("00000001"); } List<RadioButtonDto> RadioButtonDtoList = makeRadioButton(map, currentValue); if (CollectionUtils.isEmpty(RadioButtonDtoList)) { throw new BusinessException("00000002"); } return RadioButtonDtoList; } /** * ラジオボタンを作成 * @param radioButtonMap * @param currentValue * @return */ private static List<RadioButtonDto> makeRadioButton(Map<String, String> radioButtonMap, String currentValue) { List<RadioButtonDto> radioButtonDtoList = new ArrayList<>(); for (Map.Entry<String, String> entry: radioButtonMap.entrySet()) { RadioButtonDto radioButtonDto = new RadioButtonDto(); radioButtonDto.setLabel(entry.getKey()); radioButtonDto.setValue(entry.getValue()); if (entry.getValue().equals(currentValue)) { radioButtonDto.setChecked(true); } else { radioButtonDto.setChecked(false); } radioButtonDtoList.add(radioButtonDto); } return radioButtonDtoList; } /** * 二択(有・無)のラジオボタン * @return */ public static Map<String, String> makeMapForHandlingFlg() { Map<String, String> handlingFlgMap = new LinkedHashMap<String, String>(){ { put("有", "1"); put("無", "0"); } }; return handlingFlgMap; } }
■/spring-mvc-jsp/src/main/java/com/controller/SettingController.java
package com.controller; import java.util.Collections; import java.util.List; import java.util.Objects; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import com.controller.exception.BusinessException; import com.controller.helper.SettingControllerHelper; import com.dto.form.RadioButtonDto; import com.form.SettingForm; @Controller @RequestMapping("settingController") public class SettingController { @Autowired private SettingControllerHelper settingControllerHelper; @ModelAttribute public SettingForm initForm() { SettingForm settingForm = new SettingForm(); return settingForm; } /** * 表示 * @param settingForm * @return */ @RequestMapping("currentSetting") public ModelAndView settingForm(SettingForm settingForm) { ModelAndView mv = new ModelAndView("setting/index"); // TODO:データベースから取得 // ラジオボタンを生成 // List<RadioButtonDto> elevetorHandlingFlgList = makeRadioButton(makeMapForHandlingFlg(), settingForm.getElevetorHandlingFlg()); // List<RadioButtonDto> wheelchairHandlingFlgList = makeRadioButton(makeMapForHandlingFlg(), settingForm.getWheelchairHandlingFlg()); // List<RadioButtonDto> aedHandlingFlgList = makeRadioButton(makeMapForHandlingFlg(), settingForm.getAedHandlingFlg()); List<RadioButtonDto> elevetorHandlingFlgList = Collections.emptyList(); List<RadioButtonDto> wheelchairHandlingFlgList = Collections.emptyList(); List<RadioButtonDto> aedHandlingFlgList = Collections.emptyList(); try { elevetorHandlingFlgList = settingControllerHelper.makeRadioButton(settingForm.getElevetorHandlingFlg()); wheelchairHandlingFlgList = settingControllerHelper.makeRadioButton(settingForm.getWheelchairHandlingFlg()); aedHandlingFlgList = settingControllerHelper.makeRadioButton(settingForm.getAedHandlingFlg()); } catch (BusinessException e) { if ("00000001".equals(e.getCode())) { mv.addObject("displayMessage", "【エラーコード】00000001"); return mv; } if ("00000002".equals(e.getCode())) { mv.addObject("displayMessage", "【エラーコード】00000002"); return mv; } } mv.addObject("elevetorHandlingFlgList", elevetorHandlingFlgList); mv.addObject("wheelchairHandlingFlgList", wheelchairHandlingFlgList); mv.addObject("aedHandlingFlgList", aedHandlingFlgList); if (StringUtils.hasLength(settingForm.getFileType())) { mv.addObject("fileType", settingForm.getFileType()); } else { mv.addObject("fileType", "xml"); } if (Objects.nonNull(settingForm.getCompleteUpdate()) && settingForm.getCompleteUpdate()) { settingForm.setCompleteUpdate(false); mv.addObject("completeUpdate", true); int updateCount = settingForm.getUpdateCount(); settingForm.setUpdateCount(++updateCount); } else { mv.addObject("completeUpdate", false); settingForm.setUpdateCount(0); } return mv; } /** * 更新 * @param settingForm * @return */ @RequestMapping("update") public ModelAndView updateSetting(SettingForm settingForm) { // TODO:データベースの更新 settingForm.setCompleteUpdate(true); return settingForm(settingForm); } /** * 削除 * @param settingForm * @return */ @RequestMapping("delete") public ModelAndView deleteSetting(SettingForm settingForm) { // TODO:データベースの更新 // フォームをリセット settingForm = deleteForm(settingForm); settingForm.setCompleteUpdate(true); return settingForm(settingForm); } @RequestMapping("changeFileType") public ModelAndView changeFileType(SettingForm settingForm) { return settingForm(settingForm); } // /** // * ラジオボタンを作成 // * @param radioButtonMap // * @param currentValue // * @return // */ // private static List<RadioButtonDto> makeRadioButton(Map<String, String> radioButtonMap, String currentValue) { // // List<RadioButtonDto> radioButtonDtoList = new ArrayList<>(); // for (Map.Entry<String, String> entry: radioButtonMap.entrySet()) { // RadioButtonDto radioButtonDto = new RadioButtonDto(); // radioButtonDto.setLabel(entry.getKey()); // radioButtonDto.setValue(entry.getValue()); // // if (entry.getValue().equals(currentValue)) { // radioButtonDto.setChecked(true); // } else { // radioButtonDto.setChecked(false); // } // radioButtonDtoList.add(radioButtonDto); // } // return radioButtonDtoList; // } // // /** // * 二択(有・無)のラジオボタン // * @return // */ // private static Map<String, String> makeMapForHandlingFlg() { // Map<String, String> handlingFlgMap = new LinkedHashMap<String, String>(){ // { // put("有", "1"); // put("無", "0"); // } // }; // return handlingFlgMap; // } /** * 削除の場合のフォームの内容をリセット * @param settingForm * @return */ private static SettingForm deleteForm(SettingForm settingForm) { settingForm.setElevetorHandlingFlg(null); settingForm.setWheelchairHandlingFlg(null); settingForm.setAedHandlingFlg(null); return settingForm; } }
■/spring-mvc-jsp/src/main/webapp/WEB-INF/views/setting/index.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%> <%@ taglib uri="http://www.springframework.org/tags" prefix="spring"%> <%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>設定画面</title> </head> <body> <c:if test="${not empty displayMessage}"> <script> alert("${displayMessage}"); </script> </c:if> <form:form method="post" modelAttribute="settingForm" name="settingForm" > <c:if test="${settingForm.updateCount ge 0 }"> 更新回数:${settingForm.updateCount} <div> <span>エレベーター取扱有無</span> </div> <c:forEach var="obj" items="${elevetorHandlingFlgList}" varStatus="status"> <c:choose> <c:when test="${obj.checked}"> <form:radiobutton path="elevetorHandlingFlg" id="elevetor-${status.index}" label="${obj.label}" value="${obj.value}" checked="checked" /> </c:when> <c:otherwise> <form:radiobutton path="elevetorHandlingFlg" id="elevetor-${status.index}" label="${obj.label}" value="${obj.value}" /> </c:otherwise> </c:choose> </c:forEach> <div> <span>車椅子利用可能有無</span> </div> <c:forEach var="obj" items="${wheelchairHandlingFlgList}" varStatus="status"> <c:choose> <c:when test="${obj.checked}"> <form:radiobutton path="wheelchairHandlingFlg" id="wheelchair-${obj.checked }" label="${obj.label}" value="${obj.value}" checked="checked" /> </c:when> <c:otherwise> <form:radiobutton path="wheelchairHandlingFlg" id="wheelchair-${obj.checked }" label="${obj.label}" value="${obj.value}" /> </c:otherwise> </c:choose> </c:forEach> <div> <span>AED設置有無</span> </div> <c:forEach var="obj" items="${aedHandlingFlgList}" varStatus="status"> <c:choose> <c:when test="${obj.checked }"> <form:radiobutton path="aedHandlingFlg" id="aedHandlingFlg-${obj.checked }" label="${obj.label}" value="${obj.value}" checked="checked" /> </c:when> <c:otherwise> <form:radiobutton path="aedHandlingFlg" id="aedHandlingFlg-${obj.checked }" label="${obj.label}" value="${obj.value}" /> </c:otherwise> </c:choose> </c:forEach> <div> <span>ファイル形式</span> </div> <div> <form:radiobutton path="fileType" label="text" value="text" checked="${fileType == 'text' ? 'checked': ''}" /> <form:radiobutton path="fileType" label="xml" value="xml" checked="${fileType == 'xml' ? 'checked': ''}" /> <form:radiobutton path="fileType" label="json" value="json" checked="${fileType == 'json' ? 'checked': ''}" /> <form:radiobutton path="fileType" label="yaml" value="yaml" checked="${fileType == 'yaml' ? 'checked': ''}" /> </div> </c:if> <form:hidden path="updateCount" value="${updateCount}" /> <div> <input type="submit" value="更新" formAction="${pageContext.request.contextPath}/settingController/update" /> <input type="submit" value="削除" formAction="${pageContext.request.contextPath}/settingController/delete" /> </div> </form:form> <script> let radio_btns = document.querySelectorAll(`input[type='radio'][name='fileType']`); for (let target of radio_btns) { target.addEventListener(`change`, () => { document.settingForm.action="${pageContext.request.contextPath}/settingController/changeFileType"; document.settingForm.submit(); }); } </script> </body> </html>
という感じで、checkedの判定を入れる必要がありそう。
別ページに遷移した時にも情報が引き継がれるようにする場合は、セッションを使った方が良さ気ですね。
⇧ 上記サイト様によりますと、Spring Frameworkでセッションを扱うのに3パターンほど方法があるようです。
今回のようなケースだと、「セッションスコープBeanをつかう」が当てはまるかと。
何にせよ、JSP(JavaServer Pages)で固定でchecked="checked"は宜しくないかと...。
JSP(JavaServer Pages)がイケてないだけなのかもしらんけど...
毎度モヤモヤ感が半端ない...
今回はこのへんで。