0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Spring Boot】生年月日などの日付プルダウンを作る

Last updated at Posted at 2023-10-05

はじめに

Spring Bootで個人開発をしています(実務経験なし)。
Java自体、実務でのコーディング経験が浅いため、拙いコードかと思いますがよろしくお願いいたします。

ユーザ登録画面で生年月日など「年」「月」「日」で分かれている日付プルダウンを作ってみました。

Thymeleafで作成する

1年前に作成したものが以下です。
記事を参考に作成しており、一つの方法として問題ないとは思います。
※生年月日のみ抜粋

html上で1ずつ増加させるプルダウンを作成していたため、エンティティは日付型のLocalDateでも、画面表示や入力の際(html、Formクラス)はintになっており、登録する際にはint→String→LocalDateと変換していました。

Users.java
@Data
public class Users {
    // 生年月日
    private LocalDate birth_date;
}

UsersController.java
@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";
    }

UsersForm.java
@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のクラスが入っており見にくくてすみません

create.html
<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での記載とします。

UsersController.java
@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";
    }
}
UsersForm.java
@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;

}
create.html
<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追加済)。

create.js
/* --------------------------------
    日プルダウン生成
-------------------------------- */
$(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に教えてもらって初めて知りました。現場では自作で判定と日付設定をしていたような。
    ※しかし、うるう年に対応していないので別途チェックが必要

【修正後】

create.js
$(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));
        }
    });
});
create.html
<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は初期処理のプルダウン作成の処理削除。
登録処理は変更なしです。

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についてはまた時間を取って学習したいと思います。

0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?