0

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.

Jonny
  • 78
  • 1
  • 11
  • The issue is that neither `Map` nor `List` have a set method which takes a parameter like `answer.id`. You will need to create a wrapper method that allows you to set the correct property/value. [Similar question](https://stackoverflow.com/questions/36500731/how-to-bind-an-object-list-with-thymeleaf) – XtremeBaumer Jun 20 '22 at 11:21
  • with: `"*{votes.get(__${item.getBlock()}__).get(__${item.getSubblock()}__)[__${itemIndex.index}__].voteValues}"` iam reffering to: `votes` = the outer map `.get(0)` = the inner map `.get(0)` = the list `[0]` = Vote `.voteValues` = List of Integer (or as I mentioned just a simple Integer) so at the End I bind a Integer to answer.id. – Jonny Jun 20 '22 at 12:02
  • [Maybe this helps](https://stackoverflow.com/questions/60567954/how-to-fix-notreadablepropertyexception-invalid-property-freepasses-of-bean-c) – XtremeBaumer Jun 20 '22 at 12:38
  • thanks, but I dont see why this should help here. As I mentioned in the Update, if i cut the map map list down to a simple list its works. So i dont think this has something to do with type missmatch. – Jonny Jun 20 '22 at 12:46

1 Answers1

0

You can't bind th:field to functions like get() -- it doesn't make sense (how would it know what function to call to set the value?). It has to be a regular dotwalk and/or array accessor.

That being said, this should work (as you can use [] to access map values):

*{votes[__${item.block}__][__${item.subblock}__][__${itemIndex.index}__].voteValues}
Metroids
  • 18,999
  • 4
  • 41
  • 52
  • If I use this, it works until I post the result back to the server. `Exception: Cannot access indexed value of property referenced in indexed property path 'votes[1][1][0]': returned null`. I checked the model and `votes[1][1][0]` (wrong object but should still work) / `votes[0][0][0]` (the right object) both are not null. I also tried in the template to `-1` on the `block` and `subblock` to get the index, but throw a `NullValueInNestedPathException: Invalid property 'votes[0][0][0]'`. – Jonny Jun 21 '22 at 05:13