
⇧ 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)がイケてないだけなのかもしらんけど...
毎度モヤモヤ感が半端ない...
今回はこのへんで。