Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

thymeleaf binding collections

I have a problem with binding collections using spring and thymeleaf. Every time I send form, my object collections are set to null (User.postions), my example below:

My Controller:

@RequestMapping(value = urlFragment + "/add", method = RequestMethod.GET)
public String addPosition(Model model) {

    HashSet<Position> positions = new HashSet<Position>(positionRepository.findByEnabledTrueOrderByNameAsc());

    User employee = new User();

    for (Position position : positions) {
        employee.addPosition(position);
    }

    model.addAttribute("employee", employee);

    return "crud/employee/add";
}

@RequestMapping(value = urlFragment + "/add", method = RequestMethod.POST)
public String processNewEmployee(Model model, @Valid @ModelAttribute("employee") User employee, BindingResult result) {
    String templatePath = "crud/employee/add";

    if (!result.hasErrors()) {
        userRepository.save(employee);
        model.addAttribute("success", true);
    }

    return templatePath;
}

And my employee form:

<form action="#" th:action="@{/panel/employee/add}" th:object="${employee}" method="post">

    <div class="row">
        <div class="col-md-6">
            <label th:text="#{first_name}">First name</label>
            <input class="form-control" type="text" th:field="*{userProfile.firstName}"/>
        </div>
    </div>

    <div class="row">
        <div class="col-md-6">
            <label th:text="#{last_name}">Last name</label>
            <input class="form-control" type="text" th:field="*{userProfile.lastName}"/>
        </div>
    </div>

    <div class="row">
        <div class="col-md-6">
            <label th:text="#{email}">Email</label>
            <input class="form-control" type="text" th:field="*{email}"/>
        </div>
    </div>

    <div class="row">
        <div class="col-md-6">
            <label th:text="#{position}">Position</label>
            <select th:field="*{positions}" class="form-control">
                <option th:each="position : *{positions}"
                        th:value="${position.id}"
                        th:text="${position.name}">Wireframe
                </option>
            </select>
        </div>
    </div>


    <div class="row">
        <div class="col-md-5">
            <div class="checkbox">
                <button type="submit" class="btn btn-success" th:text="#{add_employee}">
                    Add employee
                </button>
            </div>
        </div>
    </div>
</form>

User entity:

@Entity
@Table(name="`user`")
public class User extends BaseModel {

    @Column(unique = true, nullable = false, length = 45)
    private String email;

    @Column(nullable = false, length = 60)
    private String password;

    @Column
    private String name;

    @Column
    private boolean enabled;

    @ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name = "user_id", nullable = false)},
            inverseJoinColumns = {@JoinColumn(name = "role_id", nullable = false)}
    )
    private Collection<Role> roles = new HashSet<Role>();

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = "user_position",
            joinColumns = {@JoinColumn(name = "user_id", nullable = false)},
            inverseJoinColumns = {@JoinColumn(name = "position_id", nullable = false)}
    )
    private Collection<Position> positions = new HashSet<Position>();

    public User() {
    }

    public User(String email, String password, boolean enabled) {
        this.email = email;
        this.password = password;
        this.enabled = enabled;
    }

    public User(String email, String password, boolean enabled, Set<Role> roles) {
        this.email = email;
        this.password = password;
        this.enabled = enabled;
        this.roles = roles;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Collection<Position> getPositions() {
        return positions;
    }

    private void setPositions(Collection<Position> positions) {
        this.positions = positions;
    }

    public boolean addPosition(Position position) {
        return positions.add(position);
    }

    public boolean removePosition(Position position) {
        return positions.remove(position);
    }

    public Collection<Role> getRoles() {
        return roles;
    }

    private void setRoles(Collection<Role> roles) {
        this.roles = roles;
    }

    public boolean addRole(Role role) {
        return roles.add(role);
    }

    public boolean removeRole(Role role) {
        return roles.remove(role);
    }

    @Override
    public String toString() {
        return User.class + " - id: " + getId().toString() + ", email: " + getEmail();
    }
}

I have read somewhere that I have to create equals() and hashCode(), so I did it in my Position Entity.

public boolean equals(Position position) {
    return this.getId() == position.getId();
}

public int hashCode(){
    return this.getId().hashCode() ;
}

Here are data sent by post method: POST data

And here is my result: debugger result

My spring version: 4.1.6.RELEASE thymeleaf-spring4 version: 2.1.4.RELEASE thymeleaf-layout-dialect version: 1.2.8

O course I wish positions to were HashCode with one element of object Position with id = 2. Could you help me? What I am doing wrong?

like image 603
Purzynski Avatar asked Sep 08 '26 20:09

Purzynski


1 Answers

It's because you're using ${position.id} for your option value. This means spring can't work out the relationship between the id used in the value and the actual Position objects. Try just ${position} for your value and it should work:

                    <select th:field="*{positions}" class="form-control">
                        <option th:each="position : *{positions}"
                                th:value="${position}"
                                th:text="${position.name}">Wireframe
                        </option>
                    </select>

(Make sure you've implemented hashCode and equals on your Position class)

If that still doesn't work you might have to implement a Formatter for Position, to make the conversion explicit. See this example thymeleafexamples-selectmultiple.

like image 117
Andrew Avatar answered Sep 10 '26 09:09

Andrew