Comment transmettre correctement une collection de mon formulaire à un gestionnaire d'événements JS puis à un contrôleur REST ?
P粉807397973
P粉807397973 2024-01-10 17:25:43
0
1
434

J'essaie de réécrire mon formulaire d'une manière qui n'implique aucun rafraîchissement de page. En d’autres termes, je ne veux pas que le navigateur fasse des requêtes GET/POST lors de la soumission. jQuery devrait pouvoir m'aider à résoudre ce problème. Voici mon tableau (j'en ai quelques-uns) :

    <!-- I guess this action doesn't make much sense anymore -->
    <form action="/save-user" th:object="${user}" method="post">
         <input type="hidden" name="id" th:value="${user.id}">
    
         <input type="hidden" name="username"
              th:value="${user.username}">
    
         <input type="hidden" name="password"
              th:value="${user.password}">
    
         <input type="hidden" name="name" th:value="${user.name}">
    
         <input type="hidden" name="lastName"
              th:value="${user.lastName}">
    
         <div class="form-group">
              <label for="departments">Department: </label>
              <select id="departments" class="form-control"
                       name="department">
                  <option th:selected="${user.department == 'accounting'}"
                          th:value="accounting">Accounting
                  </option>
                  <option th:selected="${user.department == 'sales'}"
                          th:value="sales">Sales
                  </option>
                  <option th:selected="${user.department == 'information technology'}"
                          th:value="'information technology'">IT
                  </option>
                  <option th:selected="${user.department == 'human resources'}"
                          th:value="'human resources'">HR
                  </option>
                  <option th:selected="${user.department == 'board of directors'}"
                          th:value="'board of directors'">Board
                  </option>
              </select>
          </div>
    
          <div class="form-group">
              <label for="salary">Salary: </label>
              <input id="salary" class="form-control" name="salary"
                     th:value="${user.salary}"
                     min="100000" aria-describedby="au-salary-help-block"
                     required/>
              <small id="au-salary-help-block"
                     class="form-text text-muted">100,000+
              </small>
          </div>
    
          <input type="hidden" name="age" th:value="${user.age}">
    
          <input type="hidden" name="email" th:value="${user.email}">
    
          <input type="hidden" name="enabledByte"
                 th:value="${user.enabledByte}">
          
          <!-- I guess I should JSON it somehow instead of turning into regular strings -->
          <input type="hidden" th:name="authorities"
                 th:value="${#strings.toString(user.authorities)}"/>
    
          <input class="btn btn-primary d-flex ml-auto" type="submit"
                 value="Submit">
      </form>

Voici mon JS :

$(document).ready(function () {
    $('form').on('submit', async function (event) {
        event.preventDefault();

        let user = {
            id: $('input[name=id]').val(),
            username: $('input[name=username]').val(),
            password: $('input[name=password]').val(),
            name: $('input[name=name]').val(),
            lastName: $('input[name=lastName]').val(),
            department: $('input[name=department]').val(),
            salary: $('input[name=salary]').val(),
            age: $('input[name=age]').val(),
            email: $('input[name=email]').val(),
            enabledByte: $('input[name=enabledByte]').val(),
            authorities: $('input[name=authorities]').val()

            /*
            ↑ i tried replacing it with authorities: JSON.stringify($('input[name=authorities]').val()), same result
            */
        };

        await fetch(`/users`, {
            method: 'PUT',
            headers: {
                ...getCsrfHeaders(),
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(user) // tried body : user too
        });

    });
});

function getCsrfHeaders() {
    let csrfToken = $('meta[name="_csrf"]').attr('content');
    let csrfHeaderName = $('meta[name="_csrf_header"]').attr('content');

    let headers = {};
    headers[csrfHeaderName] = csrfToken;
    return headers;
}

Voici mon gestionnaire de contrôleur REST :

    // maybe I'll make it void. i'm not sure i actually want it to return anything
    @PutMapping("/users")
    public User updateEmployee(@RequestBody User user) {
        service.save(user); // it's JPARepository's regular save()
        return user;
    }

User Entité :

@Entity
@Table(name = "users")
@Data
@EqualsAndHashCode
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private long id;
    @Column(nullable = false, unique = true)
    private String username;
    @Column(nullable = false)
    private String password;
    @Column
    private String name;
    @Column(name = "last_name")
    private String lastName;
    @Column
    private String department;
    @Column
    private int salary;
    @Column
    private byte age;
    @Column
    private String email;
    @Column(name = "enabled")
    private byte enabledByte;
    @ManyToMany
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id"),
                    @JoinColumn(name = "username", referencedColumnName = "username")},
            inverseJoinColumns = {@JoinColumn(name = "role_id", referencedColumnName = "id"),
                    @JoinColumn(name = "role", referencedColumnName = "role")})
    @EqualsAndHashCode.Exclude
    private Set<Role> authorities;

角色Entité :

@Entity
@Table(name = "roles")
@Data
@EqualsAndHashCode
public class Role implements GrantedAuthority {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private long id;
    @Column(name = "role", nullable = false, unique = true)
    private String authority;
    @ManyToMany(mappedBy = "authorities")
    @EqualsAndHashCode.Exclude
    private Set<User> userList;

Lorsque j'appuie sur le bouton Soumettre, je vois ceci dans la console

WARN 18252 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.util.HashSet<pp.spring_bootstrap.models.Role>` from String value (token `JsonToken.VALUE_STRING`)]

Il semble que je devrais réussir d'une manière ou d'une autre Collection 的 JSON 表示,而不仅仅是 String。在我之前的项目中,没有使用 jQuery,String 已使用我的自定义 Formatter désérialisation réussie

@Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatter(new Formatter<Set<Role>>() {
            @Override
            public Set<Role> parse(String text, Locale locale) {
                Set<Role> roleSet = new HashSet<>();
                String[] roles = text.split("^\[|]$|(?<=]),\s?");
                for (String roleString : roles) {
                    if (roleString.length() == 0) continue;
                    String authority =
                            roleString.substring(roleString.lastIndexOf("=") + 2,
                                    roleString.indexOf("]") - 1);
                    roleSet.add(service.getRoleByName(authority));
                }
                return roleSet;
            }

            @Override
            public String print(Set<Role> object, Locale locale) {
                return null;
            }
        });
    }

Je l'ai recherché sur Google et il semble que Thymeleaf n'ait aucune méthode toJson(). Je veux dire, je peux écrire mes propres méthodes mais je ne sais pas comment les utiliser dans les modèles Thymeleaf. De plus, ce n'est peut-être pas la solution optimale

Il s'agit d'un projet Boot, j'ai donc la bibliothèque de liaison de données Jackson

Comment transmettre correctement une collection de mon formulaire à un gestionnaire d'événements JS puis à un contrôleur REST ?

J'ai vérifié plusieurs questions similaires suggérées par StackOverflow. Ils n'ont pas l'air liés (par exemple, ils impliquent différents langages de programmation comme C# ou PHP)

UPD : Je viens d'essayer ça. Malheureusement, cela n’a pas fonctionné non plus ! (Même message d'erreur)

// inside my config
    @Bean
    public Function<Set<Role>, String> jsonify() {
        return s -> {
            StringJoiner sj = new StringJoiner(", ", "{", "}");
            for (Role role : s) {
                sj.add(String.format("{ \"id\" : %d, \"authority\" : \"%s\" }", role.getId(), role.getAuthority()));
            }
            return sj.toString();
        };
    }
    <input type="hidden" th:name="authorities" 
           th:value="${@jsonify.apply(user.authorities)}"/>

Cependant, la méthode fonctionne comme prévu

$(document).ready(function () {
    $('form').on('submit', async function (event) {
        /*
        ↓ logs:
          authorities input: {{ "id" : 1, "authority" : "USER" }}
        */
        console.log('authorities input: ' + 
                   $('input[name=authorities]').val());

UPD2 : GPT4 le recommande

authorities: JSON.parse($('input[name=authorities]').val())

C'est vraiment bizarre maintenant. Mais la base de données n’a toujours pas changé ! La console IDE n'a désormais aucune erreur et aucune mention de la requête PUT (qui était également présente lors de la tentative précédente) ! De plus, le journal du navigateur contient ce message

Uncaught (in promise) SyntaxError: Expected property name or '}' in JSON at position 1
    at JSON.parse (<anonymous>)
    at HTMLFormElement.<anonymous> (script.js:28:31)
    at HTMLFormElement.dispatch (jquery.slim.min.js:2:43114)
    at v.handle (jquery.slim.min.js:2:41098)

Je ne sais pas ce que cela signifie !

UPD3 : GPT4 est intelligent. En tout cas, plus intelligent que moi. C'est absolument vrai. La raison pour laquelle cela ne fonctionne pas dans UPD2 est que j'ai ignoré une autre chose qui dit :

Les champs d'autorisations doivent être envoyés sous forme de tableau d'objets plutôt que de chaînes.

Cela signifie que je dois utiliser des crochets, et non des accolades, pour mes StringJoiner préfixes et suffixes :

    // I also added some line breaks, but I doubt it was necessary
    @Bean
    public Function<Set<Role>, String> jsonify() {
        return s -> {
            StringJoiner sj = new StringJoiner(",\n", "[\n", "\n]");
            for (Role role : s) {
                sj.add(String.format("{\n\"id\" : %d,\n\"authority\" : \"%s\"\n}", role.getId(), role.getAuthority()));
            }
            return sj.toString();
        };
    }

Je l'ai aussi changé, comme ça

username: $('input[name=username]').val()

À ça (j'ai été stupide de ne pas l'avoir fait tout de suite)

username: $(this).find('input[name=username]').val()

Et - alto - il est maintenant disponible !

GPT4 a également remarqué que je l'utilisais

'input[name=department]'

au lieu de

'select[name=department]'

J'ai également résolu ce problème

P粉807397973
P粉807397973

répondre à tous(1)
P粉138871485
  1. Il devrait s'agir d'un tableau d'objets (même s'il s'agit d'un Collection, pas d'un tableau), donc

new StringJoiner(", ", "{", "}")new StringJoiner(", ", "[", "]")p>

  1. Il devrait cibler les enfants de la forme

用户名:$('input[name=username]').val()用户名:$(this).find('input[name=username]')。 val() 或更好用户名:$(this).find('[name=username]').val()Attendez

  1. department représentation des éléments, donc

'input[name=department]''select[name=department]''[name=department]'

Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal