Как проверить элементы списка объектов внутри другого объекта, используя проверку Javax, тимилеф с Spring Boot? - PullRequest
0 голосов
/ 19 апреля 2019

У меня проблема с проверкой списка объектов внутри другого объекта с использованием проверки ThymeLeaf и javax.

Со стороны кода это выглядит так. Для бобов:

public class InvoiceData {

    @Id private String id;
    private ContractorData data;
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private Date receptionDate;
    private String invoiceNumber;
    @NotEmpty(message = "Lista zadań nie może być pusta!")
    private List<InvoiceTask> invoiceTasks = new ArrayList<>();

    // getters, setters below

}

Затем пользовательский валидатор:

@Component
public class InvoiceFormValidator implements Validator {

    Logger logger = LoggerFactory.getLogger(InvoiceFormValidator.class);

    @Override
    public boolean supports(Class<?> clazz) {
        return InvoiceData.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "receptionDate", "empty");
        ValidationUtils.rejectIfEmpty(errors, "invoiceNumber", "empty");
        ValidationUtils.rejectIfEmpty(errors, "invoiceTasks", "empty");
    }
}

Теперь для класса InvoiceTask:

@Data
@Builder
@Document
@AllArgsConstructor
public class InvoiceTask {

    private String taskName;
    private String project;
    private BigDecimal hoursCount;

}

И некоторые сообщения в messages.properties, они здесь неактуальны.

И последнее, но не менее важное, контроллер:

@RequestMapping(value = "/addinvoice", method = RequestMethod.POST, produces = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String addInvoice(@CurrentUser Contractor contractor, @ModelAttribute("invoicedata") @Validated InvoiceData invoicedata, BindingResult result, Model model, RedirectAttributes attr) {
        if (result.hasErrors()) {
            return "add";
        } else {
            invoicedata.setData(contractor.getContractorData());
            // TODO check if we can remove the next 3 lines of code.
            if (contractor.getInvoices() == null) {
                contractor.setInvoices(new ArrayList<InvoiceData>());
            }
            contractor.getInvoices().add(invoicedata);
            invoiceDataRepository.save(invoicedata);
            contractorRepository.save(contractor);
            model.addAttribute("contractor", contractor);
            logger.info("Invoice number "+ invoicedata.getInvoiceNumber() +" with ID " + invoicedata.getId() + " created for Contractor with ID " + contractor.getId());
            return "index";
        }
    }

Теперь для шаблона Thymeleaf: он основан на <ul> <form>, поэтому я дам вам интересный фрагмент для удобства чтения:

<li id="items">
        <fieldset>
            <div th:each="task, rowStat : ${invoicedata.invoiceTasks}">
                <input th:field="${invoicedata.invoiceTasks[__${rowStat.index}__].taskName}" placeholder="Nazwa zadania" required> <input th:field="${invoicedata.invoiceTasks[__${rowStat.index}__].project}" placeholder="Projekt" required> <input type="number" th:field="${invoicedata.invoiceTasks[__${rowStat.index}__].hoursCount}" placeholder="Liczba godzin" required>
                <button type="button" name="removeItem" th:value="${rowStat.index}">Remove task</button>

            </div>
        </fieldset>
        <button type="button" name="addItem">Add task</button>
        <script type="text/javascript">
            function replaceItems (html) {
                // Replace the <fieldset id="items"> with a new one returned by server.
                $('#items').replaceWith($(html));
            }

            $('button[name="addItem"]').click(function (event) {
                event.preventDefault();
                var data = $('form').serialize();
                // Add parameter "addItem" to POSTed form data. Button's name and value is
                // POSTed only when clicked. Since "event.preventDefault();" prevents from
                // actual clicking the button, following line will add parameter to form
                // data.
                data += '&addItem';
                $.post('/add', data, replaceItems);
            });

            $('button[name="removeItem"]').click(function (event) {
                event.preventDefault();
                var data = $('form').serialize();
                // Add parameter and index of item that is going to be removed.
                data += '&removeItem=' + $(this).val();
                $.post('/add', data, replaceItems);
            });
        </script>
    </li>
    <li>
        <input type="submit" value="Save protocol" />
    </li>
</ul>

Теперь, когда я пытаюсь добавить что-то подобное в <fieldset>:

<span class="error" th:if="${#fields.hasErrors('invoiceTasks')}" th:errors="*{invoiceTasks}">Generic error</span>

Просто чтобы доказать, что список не пуст, я ничего не получаю, сообщений нет, и я получаю ошибку вроде этого:

java.lang.IllegalStateException: ни BindingResult, ни обычный целевой объект для имени компонента 'invoiceTasks', доступный как запрос * приписывать * 1029

Что еще интереснее, JavaScript перестает работать правильно. Кнопка «Удалить задачу» по-прежнему работает нормально, но кнопка «Добавить задачу» перестает работать вообще ...

Попытка с

<span class="error" th:if="${#fields.hasErrors('invoicedata.invoiceTasks')}" th:errors="*{invoicedata.invoiceTasks}">Generic error</span>

Получает меня:

There was an unexpected error (type=Internal Server Error, status=500).
Exception evaluating SpringEL expression: "#fields.hasErrors('invoicedata.invoiceTasks')" (template: "add" - line 60, col 49)

и

org.springframework.beans.NotReadablePropertyException: Invalid property 'invoicedata' of bean class [com.look4app.generator.entity.InvoiceData]: Bean property 'invoicedata' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?

Да, у меня еще не настроена страница / error.

Все остальные валидаторы работают хорошо.

Цель состоит в том, чтобы иметь возможность проверить 3 поля внутри объекта InvoiceTask и проверить список invoiceTasks, чтобы он не был пустым.

Есть ли возможность достичь этого?

...