SimonA
SimonA

Reputation: 135

Thymeleaf bean validation for entity in List field

I am using Springboot and Thyemleaf, trying to validate my form data, with javax.validation.constraints annotations.

In my template I'm using a Thyemleaf command object which is my model class Quiz. In the model I have some fields which are validated like so -

@NotBlank(message = "Title must not be blank")
private String title;

@NotBlank(message = "Text must not be blank")
private String text;

@NotNull
@Size(min = 2, max = 6, message = "Must be at least two, no more than six options")
private List<Option> options = new ArrayList<>(); // for Thymeleaf form

Here is the HTML for input for text/title -

<form th:action="@{/create}" th:object="${quiz}" method="post">
            <div class="flex-column">
                <label>Title</label>
                <textarea type="text" class="form-control" th:field="*{title}" rows="1"></textarea>
                <div class="error" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Error</div>
            </div>
            <div class="flex-column">
                <label>Question</label>
                <textarea type="text" class="form-control" th:field="*{text}" rows="2"></textarea>
                <div class="error" th:if="${#fields.hasErrors('text')}" th:errors="*{text}">Error</div>
            </div>

Here is the controller for the endpoint when a form is submitted -

@RequestMapping(value = "/create", params = {"save"})
public String saveQuizFromForm(@Valid final Quiz quiz, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
        for (Option option : quiz.getOptions()) {
            System.out.println("Option is empty? " + option.getOption().isEmpty());
        }
        bindingResult.getAllErrors().forEach(System.out::println);
        log.info("Binding result error: {}", quiz);
        return "create";
    }
    log.info("Quiz form submitted: {}", quiz); // todo save quiz to db
    return "welcome"; // todo return form submitted result
}

In my template I can submit the form and bindingResult will return with a field error if title or text is empty and a new div is inserted with the message.

Field error in object 'quiz' on field 'text': rejected value []; codes [NotBlank.quiz.text,NotBlank.text,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [quiz.text,text]; arguments []; default message [text]]; default message [Text must not be blank]

I am encountering a problem with the field List<Option> options. The model is annotated like so -

@Entity(name = "options_table")
public class Option {

    @NotEmpty(message = "Must enter an option")
    private String option;

bindingResult.getAllErrors() does not return an error on this models field despite the input being empty, as I checked in the controller option.getOption().isEmpty() returns true.

This is the HTML I'm using to try and display the error -

<div th:each="option, rowStat : *{options}">
    <input type="text" th:field="*{options[__${rowStat.index}__].option}">
    <ul th:if="${#fields.hasErrors('${quiz.options[__${rowStat.index}__].option}')}">
        <li
            class="error"
            th:each="err : 
                ${#fields.errors('${quiz.options[__${rowStat.index}__].option}')}"
            th:text="${err}">
                Input is incorrect
        </li>
    </ul>
</div>

I have also tried

th:if="${#fields.hasErrors('*{options[__${rowStat.index}__].option}')}"
th:each="err : ${#fields.errors('*{options[__${rowStat.index}__].option}')}"

I am getting no exceptions, but no error li are created either.

I have checked this very similar question here but it has not fixed the problem. I have the Spring Boot starter validation dependency in my build.gradle file. implementation 'org.springframework.boot:spring-boot-starter-validation'

Upvotes: 0

Views: 1143

Answers (1)

SimonA
SimonA

Reputation: 135

I added @Valid annotation to the field in model Quiz. This recursively checks for validated objects and fixed my mistake.

    @NotNull
    @OneToMany(mappedBy = "quiz", cascade = CascadeType.ALL)
    @JsonManagedReference
    @Valid
    @Size(min = 2, max = 6, message = "Must be at least two, no more than six options")
    private List<Option> options = new ArrayList<>();

Upvotes: 1

Related Questions