※当サイトの記事には、広告・プロモーションが含まれます。

JSP(JavaServer Pages)のラジオボタンのcheckedでハマる

japan.zdnet.com

github.blog

⇧ 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が優先されるということらしい...

developer.mozilla.org

メモ: 複数のラジオボタンに checked 属性を指定した場合、後から指定したものが先に指定したものを上書きします。つまり、最後に checked されたラジオボタンが選択されることになります。これは、一度に選択できるラジオボタンはグループ内の 1 つだけであり、ユーザーエージェントは新しいラジオボタンがチェックされるたびに、他のラジオボタンの選択を自動的に解除するからです。

<input type="radio"> - HTML: ハイパーテキストマークアップ言語 | MDN

⇧ 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>

⇧ 上記のようにすれば、切り替わるようになった。

ソースコードは、

ts0818.hatenablog.com

⇧ 上記の記事の時のものに追記してます。

Java側も合わせた全体のソースコードは以下。

■/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の判定を入れる必要がありそう。

別ページに遷移した時にも情報が引き継がれるようにする場合は、セッションを使った方が良さ気ですね。

www.memory-lovers.blog

⇧ 上記サイト様によりますと、Spring Frameworkでセッションを扱うのに3パターンほど方法があるようです。

今回のようなケースだと、「セッションスコープBeanをつかう」が当てはまるかと。

何にせよ、JSP(JavaServer Pages)で固定でchecked="checked"は宜しくないかと...。

JSP(JavaServer Pages)がイケてないだけなのかもしらんけど...

毎度モヤモヤ感が半端ない...

今回はこのへんで。