2
votes

I am learning how to use Spring Boot and Thymeleaf. I have an issue where I provide a list of a specific Objects in the Form List to the a Thymeleaf page. When the user selects the values and posts the results, the result is a String of the selected objects and incompatible with the Object I wanted to store the values in.

This may sound like a mouthful so below is the code.

My question is: Is there anyway to ensure that Thymeleaf returns a List of Objects?

Input: A class passes a bunch of Ingredients to the form This class passes in a List of class Ingredient to the form (The filtering does not matter for this - A list is inserted as the value of the model attribute, with the key being a type of ingredient)

@GetMapping
public String showDesignForm(Model model) {
    List<Ingredient> ingredients = new ArrayList<>();
    ingredientRepo.findAll().forEach(i -> ingredients.add(i));  
    Type[] types = Ingredient.Type.values();
    for (Type type : types) {
        model.addAttribute(type.toString().toLowerCase(), filterByType (ingredients, type));
    }   
    model.addAttribute("taco", new Taco()); 
    return "design";
}

Thymeleaf takes that list and displays it in checkboxes

<!DOCTYPE html>
<html
xmlns:th="http://www.thymeleaf.org">
<head>
<title>Taco Cloud</title>
<link rel="stylesheet" th:href="@{/styles.css}" />
</head>
<body>
<h1>Design your taco!</h1>
<img th:src="@{/images/TacoCloud.png}"/>
<form method="POST" th:object="${taco}">
<div class="grid">
<div class="ingredient-group">
<h3>Designate your wrap:</h3>
<div th:each="ingredient : ${wrap}">
<input name="ingredients" type="checkbox" th:value="${ingredient}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group">
<h3>Pick your protein:</h3>
<div th:each="ingredient : ${protein}">
<input name="ingredients" type="checkbox" th:value="${ingredient}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group">
<h3>Choose your cheese:</h3>
<div th:each="ingredient : ${cheese}">
<input name="ingredients" type="checkbox" th:value="${ingredient}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group">
<h3>Determine your veggies:</h3>
<div th:each="ingredient : ${veggies}">
<input name="ingredients" type="checkbox" th:value="${ingredient}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
<div class="ingredient-group">
<h3>Select your sauce:</h3>
<div th:each="ingredient : ${sauce}">
<input name="ingredients" type="checkbox" th:value="${ingredient}" />
<span th:text="${ingredient.name}">INGREDIENT</span><br/>
</div>
</div>
</div>
<div>
<h3>Name your taco creation:</h3>
<input type="text" th:field="*{name}"/>
<br/>
<button>Submit your taco</button>
</div>
</form>
</body>
</html>

Destination class: Expects the returned value to be List

package tacos;

import java.util.Date;
import java.util.List;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class Taco {

    private Long id;

    private Date createdAt;

    @NotNull
    @Size(min=5, message="Name must be at least 5 characters long")
    private String name;
    @Size(min=1, message="You must choose at least 1 ingredient")
    private List<Ingredient> ingredients;
}

Error:

Field error in object 'taco' on field 'ingredients': rejected value [Ingredient(id=FLTO, name=Flour Tortilla, type=WRAP),Ingredient(id=CHED, name=Cheddar, type=CHEESE)]; codes [typeMismatch.taco.ingredients,typeMismatch.ingredients,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [taco.ingredients,ingredients]; arguments []; default message [ingredients]]; default message [Failed to convert property value of type 'java.lang.String[]' to required type 'java.util.List' for property 'ingredients'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'tacos.Ingredient' for property 'ingredients[0]': no matching editors or conversion strategy found]

3

3 Answers

2
votes

I encountered same problem in some code but I overcame it through using Spring JPA. But If you were using other than Spring JPA, you could use this converter for your application. According to your code, property id of Ingredient is String. So code will be.

@Component
public class IngredientConverter implements Converter<String, Ingredient> {

private final IngredientRepo ingredientRepo;

@Autowired
public IngredientConverter(IngredientRepo ingredientRepo) {

    this.ingredientRepo = ingredientRepo;
}

@Override
public Ingredient convert(String source) {

      List<Ingredient> ingredients = new ArrayList<>();

      ingredientRepo.findAll().forEach(i -> ingredients.add(i));

      for (Ingredient ingredient : ingredients) {

        // You may use equal() method
        if (ingredient.getId() == source)

            return ingredient;
      }

      return null;
   }

}
1
votes

I came across a video which helped create a converter between the String result of the Thymeleaf page and the POST method in Spring.

https://www.youtube.com/watch?v=e9mlrHyn73w&t=627s

0
votes

You should change private List<Ingredient> ingredients; as private List<String> ingredients;