Search code examples
javaspringjpathymeleafh2

Thymeleaf's form dont return the object to controller class's method


I have a SpringBoot-MVC Java application with JPA/Hibernate, using a H2 database to store data and I'm trying to read and change lines of this database through web browser. I had success with the reading, but the edit page's form with thymeleaf does not send the object I alterated to controller class.

The formulary:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width" />
<title>Cadastro de Clientes</title>
<link href="/webjars/bootstrap/4.5.0/css/bootstrap.min.css"
    rel="stylesheet"></link>
<script src="/webjars/jquery/3.5.1/jquery.min.js"></script>
<script src="/webjars/bootstrap/4.5.0/js/bootstrap.min.js"></script>
</head>
<body>
    <div class="panel panel-default">
        <div class="panel-heading">
            <strong>Edicao de Clientes</strong>
        </div>
        <div class="panel-body">
            <form class="form-horizontal" th:object="${customer}"
                th:action="@{/save}" method="post" style="margin: 10px">
                <div class="form-group">
                    <fieldset>
                        <div class="form-group row">
                            <div class="alert alert-danger" th:if="${#fields.hasAnyErrors()}">
                                <div th:each="detailedError : ${#fields.detailedErrors()}">
                                    <span th:text="${detailedError.message}"></span>
                                </div>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-1">
                                <input type="hidden" class="form-control input-sm"
                                    th:field="*{id}" readonly="readonly" style="display: none;" />
                            </div>
                            <div class="col-md-1">
                                <label>CÓD. DO CLIENTE</label> <input type="text"
                                    class="form-control input-sm" th:field="*{customerId}"
                                    readonly="readonly" />
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('companyName')}? 'has-error'">
                                <label>RAZAO SOCIAL</label> <input type="text"
                                    class="form-control input-sm" th:field="*{companyName}"
                                    autofocus="autofocus" placeholder="Informe o texto"
                                    maxlength="50" />
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('tradeName')}? 'has-error'">
                                <label>NOME FANTASIA</label> <input type="text"
                                    class="form-control input-sm" th:field="*{tradeName}"
                                    maxlength="150" placeholder="Informe o texto" />
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('sectorId')}? 'has-error'">
                                <label>SETOR</label> <input type="text"
                                    class="form-control input-sm" th:field="*{sectorId}" />
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('neighborhood')}? 'has-error'">
                                <label>BAIRRO</label>
                                <textarea class="form-control input-sm"
                                    th:field="*{neighborhood}" placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('place')}? 'has-error'">
                                <label>LOGRADOURO</label>
                                <textarea class="form-control input-sm" th:field="*{place}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('neighborhood')}? 'has-error'">
                                <label>NUMERO</label>
                                <textarea class="form-control input-sm" th:field="*{placeId}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('city')}? 'has-error'">
                                <label>CIDADE</label>
                                <textarea class="form-control input-sm" th:field="*{city}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('visitDay')}? 'has-error'">
                                <label>DIA DE VISITA</label>
                                <textarea class="form-control input-sm" th:field="*{visitDay}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('region')}? 'has-error'">
                                <label>REGIAO</label>
                                <textarea class="form-control input-sm" th:field="*{region}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('latitude')}? 'has-error'">
                                <label>LATITUDE</label>
                                <textarea class="form-control input-sm" th:field="*{latitude}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <div class="col-md-2"
                                th:classappend="${#fields.hasErrors('longitude')}? 'has-error'">
                                <label>LONGITUDE</label>
                                <textarea class="form-control input-sm" th:field="*{longitude}"
                                    placeholder="Informe o texto"></textarea>
                            </div>
                        </div>
                        <div class="form-group row">
                            <button type="submit" class="btn btn-sm btn-primary">Salvar</button>
                            <a th:href="@{/}" class="btn btn-sm btn-default">Cancelar</a>
                        </div>
                    </fieldset>
                </div>
            </form>
        </div>
    </div>
</body>
</html>

The method that should receive the object of formulary:

package com.br.aloi.planner.controller;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;

import com.br.aloi.planner.model.Customer;
import com.br.aloi.planner.service.CustomerService;

@Controller
public class CustomerController {

    @Autowired
    private CustomerService service;

(...)

    @PostMapping("/save")
    public ModelAndView save(@Valid Customer customer, BindingResult result) {
        if (result.hasErrors()) {
            return edit(customer.getId());
        }
        service.save(customer);
        return findAll();
    }

}

The objects is returning null to the save method on controller class. Otherwise when I select a line of the database in browser and click on 'edit', the edit page open for edition with the attributes of objects created just perfectly.


Solution

  • Before demanding an object from Thymeleaf you have to pass the object via your model for Thymeleaf to have it.

    Add the following to your controller method:

    @ModelAttribute("customer")
    public Customer thisPartCanBeCalledWhatever() {
        return new Customer(); 
    }
    

    Make sure Customer class has getters, setters and default constructor.