はじめに
Spring Bootで個人開発をしています(実務経験なし)。
Java自体、実務でのコーディング経験が浅いため、拙いコードかと思いますがよろしくお願いいたします。
ユーザ登録画面で生年月日など「年」「月」「日」で分かれている日付プルダウンを作ってみました。
Thymeleafで作成する
1年前に作成したものが以下です。
記事を参考に作成しており、一つの方法として問題ないとは思います。
※生年月日のみ抜粋
html上で1ずつ増加させるプルダウンを作成していたため、エンティティは日付型のLocalDateでも、画面表示や入力の際(html、Formクラス)はintになっており、登録する際にはint→String→LocalDateと変換していました。
@Data
public class Users {
// 生年月日
private LocalDate birth_date;
}
@Controller
public class UsersController {
@Autowired
UsersMapper usersMapper;
// 登録画面初期表示
@GetMapping("/user/create")
public String create(@ModelAttribute UsersForm usersForm, Model model) {
// 生年月日プルダウン
int currentYear = YearMonth.now().getYear();
int birthYearFrom = currentYear - 70;
int birthYearTo = currentYear - 20;
int birthYearDefault = currentYear - 30;
model.addAttribute("birthYearFrom", birthYearFrom);
model.addAttribute("birthYearTo", birthYearTo);
model.addAttribute("birthYearDefault", birthYearDefault);
return "user/create";
}
// 登録画面登録処理
@PostMapping("/user/create")
public String create(@ModelAttribute @Validated(GroupOrder.class) UsersForm usersForm, BindingResult result, Model model) {
// 入力エラーチェック
if (result.hasErrors()) {
return create(usersForm, model);
}
Users users = new Users();
// 生年月日
String birthYear = String.valueOf(usersForm.getBirthYear());
String birthMonth = String.valueOf(usersForm.getBirthMonth());
String birthDay = String.valueOf(usersForm.getBirthDay());
if (birthMonth.length() == 1) {
birthMonth = "0" + birthMonth;
}
if (birthDay.length() == 1) {
birthDay = "0" + birthDay;
}
users.setBirth_date(LocalDate.parse(birthYear + birthMonth + birthDay, DateTimeFormatter.ofPattern("yyyyMMdd")));
usersMapper.insert(users);
return "redirect:/user/list";
}
@Data
public class UsersForm {
// 生年月日_年
@Positive(groups = ValidGroup1.class, message = "選択してください")
private int birthYear;
// 生年月日_月
@Positive(groups = ValidGroup1.class, message = "選択してください")
private int birthMonth;
// 生年月日_日
@Positive(groups = ValidGroup1.class, message = "選択してください")
private int birthDay;
}
※Bootstrap5のクラスが入っており見にくくてすみません
<form method="POST" th:action="@{/user/create}" th:object="${usersForm}">
<div class="row mb-3">
<div class="col-12 col-md-3 text-md-end">
<label for="birthDay" class="col-form-label">生年月日
<span class="badge bg-secondary ms-1">必須</span>
</label>
</div>
<div class="col-12 col-md-8 d-flex align-items-center">
<select class="form-select" th:errorclass="is-invalid" name="birthYear">
<option th:each="birthYear : ${#numbers.sequence(birthYearFrom, birthYearTo)}"
th:value="${birthYear}"
th:text="${birthYear}"
th:selected="${birthYear == birthYearDefault}">
</option>
</select>
<span class="p-2">年</span>
<div class="input-group has-validation">
<select class="form-select w20" th:errorclass="is-invalid" name="birthMonth">
<option value="0"></option>
<option th:each="birthMonth : ${#numbers.sequence(1, 12)}"
th:value="${birthMonth}"
th:text="${birthMonth}">
</option>
</select>
<span class="invalid-feedback" th:if="${#fields.hasErrors('birthMonth')}" th:errors="*{birthMonth}"></span>
</div>
<span class="p-2">月</span>
<div class="input-group has-validation">
<select class="form-select w20" th:errorclass="is-invalid" name="birthDay">
<option value="0"></option>
<option th:each="birthDay : ${#numbers.sequence(1, 31)}"
th:value="${birthDay}"
th:text="${birthDay}">
</option>
</select>
<span class="invalid-feedback" th:if="${#fields.hasErrors('birthDay')}" th:errors="*{birthDay}"></span>
</div>
<span class="p-2">日</span>
</div>
</div>
</form>
Controllerで作成する
別言語での現場経験を経て、画面表示や入力値はStringで登録の際はLocalDateに変換がいいのではないかと思い修正しました。
Serviceに書くのがいいかもしれませんが手っ取り早くControllerでの記載とします。
@Controller
public class UsersController {
@Autowired
UsersMapper usersMapper;
// 登録画面初期表示
@GetMapping("/user/create")
public String create(@ModelAttribute UsersForm usersForm, Model model) {
// 年プルダウン
List<String> birthYearList = new ArrayList<>();
for (int i = currentYear-70; i <= currentYear-20; i++) {
birthYearList.add(String.valueOf(i));
}
// 月プルダウン
List<String> monthList = new ArrayList<>();
for (int i = 1; i <= 12; i++) {
monthList.add(String.valueOf(i));
}
// 初期値
String birthYearDefault = String.valueOf(currentYear-30);
model.addAttribute("birthYearList", birthYearList);
model.addAttribute("birthYearDefault", birthYearDefault);
model.addAttribute("monthList", monthList);
return "user/create";
}
// 登録画面登録処理
@PostMapping("/user/create")
public String create(@ModelAttribute @Validated(GroupOrder.class) UsersForm usersForm, BindingResult result, Model model) {
// 入力エラーチェック
if (result.hasErrors()) {
return create(usersForm, model, loginUser);
}
Users users = new Users();
// 生年月日
String birthYear = usersForm.getBirthYear();
String birthMonth = usersForm.getBirthMonth();
String birthDay = usersForm.getBirthDay();
if (birthMonth.length() == 1) {
birthMonth = "0" + birthMonth;
}
if (birthDay.length() == 1) {
birthDay = "0" + birthDay;
}
users.setBirth_date(LocalDate.parse(birthYear + birthMonth + birthDay, DateTimeFormatter.ofPattern("yyyyMMdd")));
usersMapper.insert(users);
return "redirect:/user/list";
}
}
@Data
public class UsersForm {
// 生年月日_年
@NotBlank(groups = ValidGroup1.class, message = "選択してください")
private String birthYear;
// 生年月日_月
@NotBlank(groups = ValidGroup1.class, message = "選択してください")
private String birthMonth;
// 生年月日_日
@NotBlank(groups = ValidGroup1.class, message = "選択してください")
private String birthDay;
}
<form method="POST" th:action="@{/user/create}" th:object="${usersForm}">
<div class="row mb-3">
<div class="col-12 col-md-3 text-md-end">
<label for="birthDay" class="col-form-label">生年月日
<span class="badge bg-secondary ms-1">必須</span>
</label>
</div>
<div class="col-12 col-md-8 d-flex align-items-center">
<select class="form-select" th:errorclass="is-invalid" name="birthYear" id="yearList">
<option th:each="birthYear : ${birthYearList}"
th:value="${birthYear}"
th:text="${birthYear}"
th:selected="${birthYear.equals(birthYearDefault)}">
</option>
</select>
<span class="p-2">年</span>
<div class="input-group has-validation">
<select class="form-select w20" th:errorclass="is-invalid" name="birthMonth" id="monthList">
<option value=""></option>
<option th:each="birthMonth : ${monthList}"
th:value="${birthMonth}"
th:text="${birthMonth}">
</option>
</select>
<span class="invalid-feedback" th:if="${#fields.hasErrors('birthMonth')}" th:errors="*{birthMonth}"></span>
</div>
<span class="p-2">月</span>
<div class="input-group has-validation">
<select class="form-select w20" th:errorclass="is-invalid" name="birthDay" id="dayList"></select>
<span class="invalid-feedback" th:if="${#fields.hasErrors('birthDay')}" th:errors="*{birthDay}"></span>
</div>
<span class="p-2">日</span>
</div>
</div>
</form>
プルダウンを動的に作成する
日は選択した月によって30日だったり31日だったりします。
それを動的に作成するにはJavaScriptを使用します。
2023/10/7修正
@jsdtue55さんからコメントいただき、私がわかる範囲で修正いたしました(うるう年判定も不要でした)。
私はjQueryで書いておりますが、JavaScriptで書かれる方はご参考ください。
また、日だけでなく年月も全てjs側でやればいいのではないかと思い追加しています。
ちなみに元コードは折りたたみの中
日だけjQueryにしました(BingのGPT-4を参考に書き換えたもの)
上記にプラスして以下を追加すればよいです(htmlにidを追加。上記のコードはすでにid追加済)。
/* --------------------------------
日プルダウン生成
-------------------------------- */
$(function() {
$('#yearList, #monthList').on('change', function() {
var year = $('#yearList').val();
var month = $('#monthList').val();
if (month === "") {
return
}
var daysInMonth = new Date(year, month, 0).getDate();
$('#dayList').empty();
for (var i = 1; i <= daysInMonth; i++) {
$('#dayList').append($('<option>', {
value: i,
text: i
}));
}
if (month === 2 && isLeapYear(year)) {
$('#dayList').append($('<option>', {
value: 29,
text: 29
}));
}
});
});
/* --------------------------------
うるう年判定
-------------------------------- */
function isLeapYear(year) {
if (year % 4 === 0) {
if (year % 100 === 0){
if (year % 400 !== 0) return false;
}
return true;
}
return false;
}
- new Date(year, month, 0).getDate():指定された年月の最終日を取得し、その日数分の配列を生成
→BingのGPT-4に教えてもらって初めて知りました。現場では自作で判定と日付設定をしていたような。
※しかし、うるう年に対応していないので別途チェックが必要
【修正後】
$(function() {
// 現在
const nowYear = new Date().getFullYear();
// 年プルダウン作成
$('#yearList').empty();
for (let i = nowYear-70; i <= nowYear-20; i++) {
$('#yearList').append($('<option>').val(i).text(i));
}
$('#yearList').val(nowYear-30);
// 月プルダウン作成
$('#monthList').empty();
$('#monthList').append($('<option>').val('').text(''));
for (let i = 1; i <= 12; i++) {
$('#monthList').append($('<option>').val(i).text(i));
}
// 日プルダウン作成
$('#yearList, #monthList').on('change', function() {
const year = $('#yearList').val();
const month = $('#monthList').val();
if (month === '') {
return;
}
const daysInMonth = new Date(year, month, 0).getDate();
$('#dayList').empty();
$('#dayList').append($('<option>').val('').text(''));
for (let i = 1; i <= daysInMonth; i++) {
$('#dayList').append($('<option>').val(i).text(i));
}
});
});
<form method="POST" th:action="@{/test/create}" th:object="${usersForm}">
<div class="row mb-3">
<div class="col-12 col-md-3 text-md-end">
<label for="birthDay" class="col-form-label">生年月日
<span class="badge bg-secondary ms-1">必須</span>
</label>
</div>
<div class="col-12 col-md-8 d-flex align-items-center">
<select class="form-select" th:errorclass="is-invalid" name="birthYear" id="yearList"></select>
<span class="p-2">年</span>
<div class="input-group has-validation">
<select class="form-select w20" th:errorclass="is-invalid" name="birthMonth" id="monthList"></select>
<span class="invalid-feedback" th:if="${#fields.hasErrors('birthMonth')}" th:errors="*{birthMonth}"></span>
</div>
<span class="p-2">月</span>
<div class="input-group has-validation">
<select class="form-select w20" th:errorclass="is-invalid" name="birthDay" id="dayList"></select>
<span class="invalid-feedback" th:if="${#fields.hasErrors('birthDay')}" th:errors="*{birthDay}"></span>
</div>
<span class="p-2">日</span>
</div>
</div>
</form>
UsersController.javaは初期処理のプルダウン作成の処理削除。
登録処理は変更なしです。
@Controller
public class UsersController {
@Autowired
UsersMapper usersMapper;
// 登録画面初期表示
@GetMapping("/user/create")
public String create(@ModelAttribute UsersForm usersForm, Model model) {
// プルダウン作成の処理削除
return "user/create";
}
// 登録画面登録処理
@PostMapping("/user/create")
public String create(@ModelAttribute @Validated(GroupOrder.class) UsersForm usersForm, BindingResult result, Model model) {
// 入力エラーチェック
if (result.hasErrors()) {
// リダイレクトだと入力エラーの値が引き継がれない
// return "redirect:/user/create";
return create(usersForm, model, loginUser);
}
Users users = new Users();
// 生年月日
String birthYear = usersForm.getBirthYear();
String birthMonth = usersForm.getBirthMonth();
String birthDay = usersForm.getBirthDay();
if (birthMonth.length() == 1) {
birthMonth = "0" + birthMonth;
}
if (birthDay.length() == 1) {
birthDay = "0" + birthDay;
}
users.setBirth_date(LocalDate.parse(birthYear + birthMonth + birthDay, DateTimeFormatter.ofPattern("yyyyMMdd")));
}
さいごに
今回はJavaScriptがメインではなかったのですが、結果としてjQueryでプルダウンを作成しました。
JavaScriptについてはまた時間を取って学習したいと思います。