Search code examples
springspring-bootspring-mvcthymeleaf

Spring MVC + Thymeleaf divs on the page are not generated


I am new to Spring and I am trying to make web application using Spring MVC and Thymeleaf. I have the page with the form to create custom pizza and corresponding controller. User can enter the name of the pizza so I added input validation. Before I enter invalid name everything is ok, but then when I return a new view of the page, divs with my checkboxes do not appear, they just don't generate. It seems like my Model after refreshing is empty but I don't understand how I could debug this, my shortcuts in intellij idea just don't work.

CreatePizzaController:

@Slf4j
@Controller
@RequestMapping("/create")
public class CreatePizzaController {
    @GetMapping
    public String showCreationForm(Model model){
        List<Ingredient> ingredients = Arrays.asList(
                new Ingredient("CLS22", "Classic base 22cm", Ingredient.Type.BASIC),
                new Ingredient("CLS30", "Classic base 30cm", Ingredient.Type.BASIC),
                new Ingredient("PEPE", "Pepperoni", Ingredient.Type.MEAT),
                new Ingredient("HAM", "Ham", Ingredient.Type.MEAT),
                new Ingredient("CHMPG", "Champignons", Ingredient.Type.VEGGIES),
                new Ingredient("CUCU", "Cucumbers", Ingredient.Type.VEGGIES),
                new Ingredient("PARM", "Parmesan cheese", Ingredient.Type.CHEESE),
                new Ingredient("MOZ", "Mozzarella cheese", Ingredient.Type.CHEESE),
                new Ingredient("BARBS", "Barbecue sauce", Ingredient.Type.SAUCE),
                new Ingredient("TOMAS", "Tomato sauce", Ingredient.Type.SAUCE)
                );

        Ingredient.Type[] types = Ingredient.Type.values();
        for(Ingredient.Type type: types){
            model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
        }
        model.addAttribute("create", new Pizza());
        return "create";
    }

    @PostMapping
    public String processCreation(@Valid @ModelAttribute("create") Pizza pizza, BindingResult result){
        if(result.hasErrors()){
            return "create";
        }
        //Save creation...
        log.info("Processing creation: " + pizza);
        return "redirect:/orders/current";
    }

    private List<Ingredient> filterByType(List<Ingredient> ingredients, Ingredient.Type type) {
        List<Ingredient> filtered = new ArrayList<>();
        for (Ingredient ingredient:ingredients){
            if(ingredient.getType() == type){
                filtered.add(ingredient);
            }
        }
        return filtered;
    }
}

create.html:

<!DOCTYPE html>
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" th:href="@{/styles/style.css}" />
</head>
<body>
<h3>Create your pizza:</h3>
<img th:src="@{/images/pizza_logo.jpg}" width="200" height="200"/>

<form method="post" th:object="${create}">
    <div class="grid">
        <div class="ingredient-group" id="basics">
            <h3>Choose the basic: </h3>
            <div th:each="ingredient : ${basic}">
                <input name="ingredients" type="radio" checked th:value="${ingredient.id}"/>
                <span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
            </div>
        </div>
        <div class="ingredient-group" id="meat">
            <h3>Choose meat: </h3>
            <div th:each="ingredient : ${meat}">
                <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                <span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
            </div>
        </div>
        <div class="ingredient-group" id="veggies">
            <h3>Choose vegetables: </h3>
            <div th:each="ingredient : ${veggies}">
                <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                <span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
            </div>
        </div>
        <div class="ingredient-group" id="sauce">
            <h3>Choose sauce: </h3>
            <div th:each="ingredient : ${sauce}">
                <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                <span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
            </div>
        </div>
        <div class="ingredient-group" id="cheese">
            <h3>Choose cheese: </h3>
            <div th:each="ingredient : ${cheese}">
                <input name="ingredients" type="checkbox" th:value="${ingredient.id}"/>
                <span class="checktext" th:text="${ingredient.name}">INGREDIENT</span><br/>
            </div>
        </div>
    </div>
    <div class="submit-button">
        <h3>Name your pizza:</h3>
        <input class="submit-field" type="text" th:field="*{name}" />
        <span class="validationError"
              th:if="${#fields.hasErrors('name')}"
              th:errors="*{name}">Error</span>
        <br/>
        <button>Submit your pizza</button>
    </div>
</form>
</body>
</html>

page before invalid input

pafe after invalid input

full code on github


Solution

  • Your problem is here:

    @PostMapping
    public String processCreation(@Valid @ModelAttribute("create") Pizza pizza, BindingResult result){
        if(result.hasErrors()){
            return "create";
        }
        //Save creation...
        log.info("Processing creation: " + pizza);
        return "redirect:/orders/current";
    }
    

    You need to add your model attributes again before returning "create". Thymeleaf isn't incorrect, the model is simply missing attributes and can't populate the checkboxes. Something like this should work:

    @GetMapping
    public String showCreationForm(Model model){
        addCreationFormModelAttributes(model, new Pizza());
        return "create";
    }
    
    @PostMapping
    public String processCreation(Model model, @Valid @ModelAttribute("create") Pizza pizza, BindingResult result){
        if(result.hasErrors()){
            addCreationFormModelAttributes(model, pizza);
            return "create";
        }
        //Save creation...
        log.info("Processing creation: " + pizza);
        return "redirect:/orders/current";
    }
    
    private static addCreationFormModelAttributes(Model model, Pizza pizza) {
        List<Ingredient> ingredients = Arrays.asList(
                new Ingredient("CLS22", "Classic base 22cm", Ingredient.Type.BASIC),
                new Ingredient("CLS30", "Classic base 30cm", Ingredient.Type.BASIC),
                new Ingredient("PEPE", "Pepperoni", Ingredient.Type.MEAT),
                new Ingredient("HAM", "Ham", Ingredient.Type.MEAT),
                new Ingredient("CHMPG", "Champignons", Ingredient.Type.VEGGIES),
                new Ingredient("CUCU", "Cucumbers", Ingredient.Type.VEGGIES),
                new Ingredient("PARM", "Parmesan cheese", Ingredient.Type.CHEESE),
                new Ingredient("MOZ", "Mozzarella cheese", Ingredient.Type.CHEESE),
                new Ingredient("BARBS", "Barbecue sauce", Ingredient.Type.SAUCE),
                new Ingredient("TOMAS", "Tomato sauce", Ingredient.Type.SAUCE)
        );
    
        Ingredient.Type[] types = Ingredient.Type.values();
        for(Ingredient.Type type: types){
            model.addAttribute(type.toString().toLowerCase(), filterByType(ingredients, type));
        }
        model.addAttribute("create", pizza);
    }