When I access a variable in a -tag it is working fine and give me [] because ists an empty List. If I try to access it the same way in a th:field it leads to a NotReadablePropertyException:
Caused by: org.springframework.beans.NotReadablePropertyException: Invalid property 'votes.get(1)' of bean class [foo.ChangeVotesForm]: Bean property 'votes.get(1)' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
I cant figure out why it is working in a a-tag and not in the field. Because both should do votes.get(1)... And I tried voteValues = List and voteValue = Integer both with the same Result.
Any Idea whats wrong here? And how to fix this?
I have the following:
Form:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Validated
public class ChangeVotesForm {
private Map<Integer, Map<Integer, List<Vote>>> votes;
private Map<Integer, Map<Integer, List<Item>>> dataMap;
}
Vote:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "vote")
public class Vote {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(updatable = false, nullable = false)
private int id;
@ManyToOne(fetch = FetchType.LAZY)
private Item item;
@ElementCollection
@Builder.Default
private List<Integer> voteValues = new ArrayList<>();
@Column(length = 2000)
private String detailedValue;
}
Item:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "item")
public class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(updatable = false, nullable = false)
private int id;
private int block;
private int subblock;
private Question question;
}
Question:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "question")
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(updatable = false, nullable = false)
private int id;
@Column(name = "TOPICSTRING", length = 2000)
private String topicString;
@OneToMany
private List<Answer> answers;
}
Answer:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Cacheable
@org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "answer")
public class Answer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(updatable = false, nullable = false)
private int id;
private String answertext;
}
Controller:
@GetMapping("/")
public String index(Model model, Authentication auth) {
ChangeVotesForm form = ChangeVotesForm.builder().votes(ChangeVotesForm.getVotesList(this.votesService.createVotesList())).dataMap(ChangeVotesForm.getDataList(this.itemsService.getItems())).build();
//Just getting all Questions and Answers from the DB and put them in die Form object grouptBy Block / Subblock
model.addAttribute("form", form);
model.addAttribute("view", "feedbackItems");
}
return "index";
}
Template:
<div th:fragment="feedbackItems" id="content"
xmlns:th="http://www.w3.org/1999/xhtml">
<div class="row flex-fill">
<div class="col-8 d-flex flex-column">
<div class="form-group" th:each="blockEntry, blockIndex : *{dataMap}">
<div th:each="subBlockEntry, subBlockIndex : ${blockEntry.value}">
<div th:each="item, itemIndex : ${subBlockEntry.value}">
<a th:utext="${item.question.topicString}"></a>
<ul>
<li th:each="answer : ${item.question.answers}">
<a th:text="*{votes.get(__${item.getBlock()}__).get(__${item.getSubblock()}__)[__${itemIndex.index}__].voteValues}"></a>
<!-- a-tag is working fine and is [] -->
<!-- the following leads to the Exception -->
<input type="radio"
th:field="*{votes.get(__${item.getBlock()}__).get(__${item.getSubblock()}__)[__${itemIndex.index}__].voteValues}"
th:value="${answer.id}" />
<label th:for="${answer.id}"
th:text="${answer.answertext}"></label>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
Update:
If I change the Map<Map<List<>>> to List<> it works fine. Example: like above only ChangeVotesForm:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Validated
public class ChangeVotesForm {
private List<Vote> votes;
private Map<Integer, Map<Integer, List<Item>>> dataMap;
}
And in the Template:
<div th:fragment="feedbackItems" id="content"
xmlns:th="http://www.w3.org/1999/xhtml">
<div class="row flex-fill">
<div class="col-8 d-flex flex-column">
<div class="form-group" th:each="blockEntry, blockIndex : *{dataMap}">
<div th:each="subBlockEntry, subBlockIndex : ${blockEntry.value}">
<div th:each="item, itemIndex : ${subBlockEntry.value}">
<a th:utext="${item.question.topicString}"></a>
<ul>
<li th:each="answer : ${item.question.answers}">
<input type="radio"
th:field="*{votes[__${itemIndex.index}__].voteValues}"
th:value="${answer.id}" />
<label th:for="${answer.id}"
th:text="${answer.answertext}"></label>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
Whats the problem with thymeleaf and Maps? The Map Values are generated, so just take multiple Lists in the form wouldn`t be a real solution.