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
Collection
, pas d'un tableau), doncnew StringJoiner(", ", "{", "}")
→new StringJoiner(", ", "[", "]")
p>用户名:$('input[name=username]').val()
→用户名:$(this).find('input[name=username]')。 val()
或更好用户名:$(this).find('[name=username]').val()
Attendezdepartment
由représentation des éléments, donc
'input[name=department]'
→'select[name=department]'
或'[name=department]'