Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
102 views
in Technique[技术] by (71.8m points)

java - Spring web mvc + Thymeleaf ModelAttribute editing the list in the object

I have a form for editing a user, the user has roles, which are a list of objects of type Authority, I want to be able to use checkboxes (optional) to set the roles that the user will have, but I have no idea how to implement the form in thymeleaf and how to pass the user object with the given roles to the controller.

It's my user

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "users")
@Data
@NoArgsConstructor
public class User implements UserDetails {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    @Column(name="username", nullable = false, unique = true)
    @NotBlank @Size(min=5, message = "Не менeе 5 знаков")
    private String username;

    @NotBlank @Size(min=5, message = "Не менeе 5 знаков")
    @Column(name = "password")
    private String password;

    @Column(name = "enabled")
    private boolean enabled;

    @Column(name = "name")
    private String name;

    @Column(name = "surname")
    private String surname;

    @Column(name = "email", nullable = false, unique = true)
    private String email;

    @ManyToMany(cascade = {
            CascadeType.PERSIST,
            CascadeType.MERGE,
            CascadeType.DETACH,
            CascadeType.REFRESH
    }, fetch = FetchType.EAGER)
    @JoinTable(
            name = "users_authorities",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "authority_id")
    )
    private Set<Authority> authorities = new HashSet<>();

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

    public void addAuthority(Authority authority) {
        this.authorities.add(authority);
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

}



It's my edit user form contoller

@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {

    model.addAttribute("user", userService.findById(id));
    model.addAttribute("allAuthorities", authorityService.findAll());

    return "users/edit-user";
}



It's my edit user form view

<body>
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">
    <input type="hidden" th:field="*{id}" id="id">

    <label for="username">Username: </label>
    <input type="text" th:field="*{username}" id="username" placeholder="username">
    <br><br>

    <div sec:authorize="hasRole('ROLE_ADMIN')">
        <label for="enabled">Enabled </label>
        <input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
        <br><br>
    </div>

    <label for="name">Name: </label>
    <input type="text" th:field="*{name}" id="name" placeholder="Name">
    <br><br>

    <label for="surname">Surname: </label>
    <input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
    <br><br>

    <label for="email">Email: </label>
    <input type="text" th:field="*{email}" id="email" placeholder="Email">
    <br><br>

    <div th:each="auth:${allAuthorities}">
        <label>
            <span th:text="${auth.authority}"></span>
            <input type="checkbox" name="authorities" th:checked="${user.authorities.contains(auth)}">
        </label>
    </div>

    <input type="submit" value="Edit">
</form>
</body>



It's put contoller, it getting the data from my form

@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
                       @ModelAttribute("user") User user,
                       @RequestParam("authorities") List<Authority> authorities) {
    user.setId(id);
    userService.update(user);

    return "redirect:/admin/users";
}



And it's my Authority class if you need

@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
    @Id
    private Long id;

    @Column(name = "authority")
    private String authority;

    @Transient
    @ManyToMany(mappedBy = "authorities")
    private Set<User> users;

    public Authority(Long id, String authority) {
        this.id = id;
        this.authority = authority;
    }
}

I'm try to pass the list of roles separately from the user object, but this also doesn't work and gives a bad request error.

question from:https://stackoverflow.com/questions/65860212/spring-web-mvc-thymeleaf-modelattribute-editing-the-list-in-the-object

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

To solve the problem, I added a new checked field to Entity authority

@Entity
@Table(name = "authorities")
@Data
@NoArgsConstructor
public class Authority implements GrantedAuthority {
    @Id
    private Long id;

    @Column(name = "authority", nullable = false, unique = true)
    private String authority;

    @Transient
    private boolean checked;

    public Authority(Long id, String authority) {
        this.id = id;
        this.authority = authority;
    }
}

Before sending the view to it, I changed the authorites field of the user object.

@GetMapping("/users/{id}/edit")
public String editUser(@PathVariable("id") Long id, Model model) {
    User user = userService.findById(id);
    List<Authority> allAuthorities = authorityService.findAll();

    for(Authority auth : allAuthorities) {
        if(user.getAuthorities().contains(auth)) {
            auth.setChecked(true);
        }
    }

    user.setAuthorities(allAuthorities);

    model.addAttribute("user", user);
    return "users/edit-user";
}

I also changed the thymeleaf template
<form th:method="PUT" th:action="@{/admin/users/{id}(id=${user.getId()})}" th:object="${user}">

    <label for="username">Username: </label>
    <input type="text" th:field="*{username}" id="username" placeholder="username">
    <br><br>

    <div sec:authorize="hasRole('ROLE_ADMIN')">
        <label for="enabled">Enabled </label>
        <input type="checkbox" name="enabled" th:field="*{enabled}" id="enabled">
        <br><br>
    </div>

    <label for="name">Name: </label>
    <input type="text" th:field="*{name}" id="name" placeholder="Name">
    <br><br>

    <label for="surname">Surname: </label>
    <input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
    <br><br>

    <label for="email">Email: </label>
    <input type="text" th:field="*{email}" id="email" placeholder="Email">
    <br><br>

    <div th:each="auth, itemStat: ${user.authorities}">
        <label>
            <span th:text="${auth.authority}"></span>
            <input type="hidden"
                   th:field="*{authorities[__${itemStat.index}__].id}">
            <input type="hidden"
                   th:field="*{authorities[__${itemStat.index}__].authority}">
            <input type="checkbox" th:checked="${auth.checked}"
                   th:field="*{authorities[__${itemStat.index}__].checked}">
        </label>
    </div>

    <input type="submit" value="Edit">
</form>

My put controller looks like this

@PutMapping("/users/{id}")
public String editUser(@PathVariable("id") Long id,
                       @ModelAttribute("user") User user) {
    List<Authority> userAuthorities = user.getAuthorities();
    userAuthorities.removeIf(auth -> !auth.isChecked());

    user.setId(id);
    userService.update(user);

    return "redirect:/admin/users";
}

My save and delete methods from UserService look like this(If that would be useful)
@Override
@Transactional
public User save(User user) {
    Optional<User> userFromDB = userRepository.findByUsername(user.getUsername());
    Optional<Authority> userRole;
    
    if(userFromDB.isPresent()) {
        return null;
    }

    userRole = authorityRepository.findByAuthority("ROLE_USER");
    user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));

    userRole.ifPresent(user::addAuthority);

    return userRepository.save(user);
}

@Override
@Transactional
public User update(User user) {
    Optional<User> userFromDB = userRepository.findById(user.getId());

    if(userFromDB.isPresent()){
        //Adding the password for successfully update user in DB
        user.setPassword(userFromDB.get().getPassword());
        return userRepository.save(user);
    }

    return null;
}

This may not be the most elegant solution but I haven't found another one yet


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...