Comment faire pour lier un objet de la liste avec thymeleaf?
J'ai beaucoup de difficulté avec la Publication d'un formulaire pour le contrôleur, qui doit contenir tout simplement une arraylist d'objets que l'utilisateur peut modifier.
Le formulaire se charge correctement, mais quand il est affiché, il ne semble jamais vraiment publier quoi que ce soit.
Voici mon formulaire:
<form action="#" th:action="@{/query/submitQuery}" th:object="${clientList}" method="post">
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>Select</th>
<th>Client ID</th>
<th>IP Addresss</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" /></td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
</tbody>
</table>
<button type="submit" value="submit" class="btn btn-success">Submit</button>
</form>
Ci-dessus fonctionne très bien, il charge la liste correctement. Cependant, quand je POSTE, elle retourne un objet vide (de taille 0). Je crois que c'est en raison de l'absence de th:field
, mais de toute façon, ici, est contrôleur de la méthode POST:
...
private List<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
//GET method
...
model.addAttribute("clientList", allClientsWithSelection)
....
//POST method
@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute(value="clientList") ArrayList clientList, Model model){
//clientList== 0 in size
...
}
J'ai essayé d'ajouter un th:field
mais peu importe ce que je fais, il provoque une exception.
J'ai essayé:
...
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" th:field="*{}" /></td>
<td th th:field="*{currentClient.selected}" ></td>
...
Je ne peux pas accéder currentClient (erreur de compilation), je ne peux même pas sélectionner clientList, il me donne des options comme get()
, add()
, clearAll()
etc, de sorte qu'il des choses qu'elle devrait avoir un tableau, cependant, je ne peux pas passer un tableau.
J'ai aussi essayé d'utiliser quelque chose comme th:field=${}
, cela provoque une exception d'exécution
J'ai essayé
th:field = "*{clientList[__currentClient.clientID__]}"
mais aussi erreur de compilation.
Des idées?
Mise à JOUR 1:
Tobias a suggéré que j'ai besoin d'envelopper ma liste dans une wraapper. Donc, c'est ce que j'ai fait:
ClientWithSelectionWrapper:
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public List<ClientWithSelection> getClientList(){
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients){
this.clientList = clients;
}
}
Ma page:
<form action="#" th:action="@{/query/submitQuery}" th:object="${wrapper}" method="post">
....
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td th:text="${stat}"></td>
<td>
<input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}" />
</td>
<td th:text="${currentClient.getClientID()}" ></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}" ></td>
</tr>
Au-dessus de charge très bien:
Puis mon contrôleur:
@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, Model model){
...
}
La page se charge correctement, les données s'affichent comme prévu. Si je poste le formulaire sans aucune sélection, j'obtiens ceci:
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'clientList' cannot be found on null
Pas sûr pourquoi elle se plaindre
(Dans la Méthode qu'il a: model.addAttribute("wrapper", wrapper);
)
Si je puis faire une sélection, c'est à dire cocher la première entrée:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='clientWithSelectionListWrapper'. Error count: 1
Je devine que mon POSTE de contrôleur n'est pas le clientWithSelectionListWrapper. Je ne sais pas pourquoi, depuis que j'ai mis de l'objet wrapper pour être posté à l'arrière via le th:object="wrapper"
dans l'en-tête du FORMULAIRE.
Mise à JOUR 2:
J'ai fait quelques progrès! Enfin, le formulaire soumis est récupéré par la méthode POST dans le contrôleur. Cependant, toutes les propriétés semblent être nulle, sauf si l'objet a été cochée ou non. J'ai fait plusieurs changements, c'est la façon dont il est à la recherche:
<form action="#" th:action="@{/query/submitQuery}" th:object="${wrapper}" method="post">
....
<tr th:each="currentClient, stat : ${clientList}">
<td th:text="${stat}"></td>
<td>
<input type="checkbox"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:checked="${currentClient.selected}"
th:field="*{clientList[__${stat.index}__].selected}">
</td>
<td th:text="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
th:value="${currentClient.getClientID()}"
></td>
<td th:text="${currentClient.getIpAddress()}"
th:field="*{clientList[__${stat.index}__].ipAddress}"
th:value="${currentClient.getIpAddress()}"
></td>
<td th:text="${currentClient.getDescription()}"
th:field="*{clientList[__${stat.index}__].description}"
th:value="${currentClient.getDescription()}"
></td>
</tr>
J'ai également ajouté un défaut param-moins de constructeur de ma classe wrapper et ajouté un bindingResult
paramètre à la méthode POST (pas sûr si nécessaire).
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, BindingResult bindingResult, Model model)
Ainsi lorsqu'un objet est affiché, c'est la façon dont il est à la recherche:
Bien sûr, la systemInfo est censé être nul (à ce stade), mais le clientID est toujours 0, et ipAddress/Description toujours null. Sélectionné booléenne est correct, bien que pour toutes les propriétés. Je suis sûr que j'ai fait une erreur sur l'une des propriétés quelque part. De retour de l'enquête.
Mise à JOUR 3:
Ok j'ai réussi à remplir toutes les valeurs correctement! Mais j'ai dû changer mon td
pour inclure une <input />
qui n'est pas ce que je voulais... Néanmoins, les valeurs sont peupler correctement, ce qui suggère looks du printemps pour une balise input peut-être pour le mappage de données?
Voici un exemple de la façon dont j'ai changé le clientID données de la table:
<td>
<input type="text" readonly="readonly"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
/>
</td>
Maintenant, j'ai besoin de comprendre comment les afficher sur la plaine de données, idéalement sans la présence d'une zone de saisie...
- La liaison fonctionne qu'avec
input
éléments, avec quelque chose de poste client à l'arrière du serveur. D'autres cadres peuvent utiliser une sorte d'état d'affichage ou de la session et de cacher les détails de la part du développeur, mais autant que je sache timeleaf fais pas le faire. Dans ce cas particulier, vous pouvez lier les valeurs de champs cachés. - ouais, vous avez raison, littéralement la trouve 30secs avant votre commentaire. Ouais, doit être un thymeleafff chose, je suis sûr que lorsque j'ai fait quelque chose de similaire dans asp.net il ramassa directement. De toute façon, je vais certainement faire besoin d'écrire cela comme un rappel!
Vous devez vous connecter pour publier un commentaire.
Vous avez besoin d'un objet wrapper de tenir le soumis de données, comme celle-ci:
et l'utiliser comme la
@ModelAttribute
dans votreprocessQuery
méthode:En outre, la
input
élément a besoin d'unname
et unvalue
. Si vous directement créer le code html, puis prendre en compte que le nom doit êtreclientList[i]
, oùi
est la position de l'élément dans la liste:Noter que
clientList
peut contenirnull
àdes positions intermédiaires. Par exemple, si les données validées sont:
l'résultant
ArrayList
sera:[null, B, null, D]
Mise à JOUR 1:
Dans mon exemple ci-dessus,
ClientForm
est un wrapper pourList<String>
. Mais dans votre casClientWithSelectionListWrapper
contientArrayList<ClientWithSelection>
. À cet effetclientList[1]
devrait êtreclientList[1].clientID
et ainsi de suite avec les autres propriétés que vous souhaitez envoyer en arrière:J'ai construit une petite démo, de sorte que vous pouvez tester:
Application.java
ClientWithSelection.java
ClientWithSelectionListWrapper.java
TestController.java
test.html
Mise à JOUR 1.B:
Ci-dessous est le même exemple en utilisant
th:le champ
et le renvoi de tous les autres attributs que les valeurs cachées.stat.pos
j'obtiens une exception:org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 5): Property or field 'pos' cannot be found on object of type 'org.thymeleaf.processor.attr.AbstractIterationAttrProcessor$StatusVar' - maybe not public?
- j'ai Donc essayé d'utiliserpos.index
mais alors si je poste sans cochant quoi que ce soit, sa valeur est null, mais si je coche-je obtenirValidation failed for object='clientWithSelectionListWrapper'. Error count:1
currentClient.selected
? De toute façon il fonctionne, alors, merci beaucoup, j'ai été bloqué (ne pas comprendre pourquoi) la liaison a échoué pour le th:un champ.Lorsque vous souhaitez sélectionner des objets dans thymeleaf, vous n'avez pas réellement besoin de créer un wrapper pour les besoins de stockage d'un
boolean
sélectionnez le champ. À l'aide dedynamic fields
que par la thymeleaf guide avec la syntaxeth:field="*{rows[__${rowStat.index}__].variety}"
est bon pour quand vous voulez accéder à un ensemble d'objets dans une collection. Ce n'est pas vraiment conçu pour faire des sélections à l'aide de wrapper objets de l'OMI comme il crée inutilement du code réutilisable et est une sorte de hack.Considérer cet exemple simple, un
Person
pouvez sélectionnerDrinks
qu'ils aiment. Remarque: les Constructeurs, Accesseurs et mutateurs sont omis pour plus de clarté. En outre, ces objets sont normalement stockés dans une base de données mais je suis en utilisant dans la mémoire des tableaux pour expliquer le concept.Printemps contrôleurs
La chose principale ici est que nous sommes de stockage de la
Person
dans leModel
de sorte que nous pouvons lier à la forme dansth:object
.Deuxièmement, la
selectableDrinks
sont les boissons, une personne peut choisir dans l'INTERFACE utilisateur.Code du modèle
Attention à la
li
boucle et commentselectableDrinks
est utilisée pour obtenir tous les possible de boissons qui peuvent être sélectionnés.La case
th:field
vraiment étend àperson.drinks
depuisth:object
est lié àPerson
et*{drinks}
est tout simplement le raccourci de la référence à une propriété sur laPerson
objet. Vous pouvez penser ce que dit printemps/thymeleaf que toute sélectionnéDrinks
vont être mis dans leArrayList
à l'emplacementperson.drinks
.Toute façon...le secret de la sauce à l'aide de
th:value=${drinks.id}
. Cela repose sur le printemps des convertisseurs. Lorsque le formulaire est affiché, le printemps va essayer de recréer unPerson
et pour ce faire, il a besoin de savoir comment faire pour convertir n'importe queldrink.id
des chaînes dans le réelDrink
type. Remarque: Si vous n'avezth:value${drinks}
lavalue
clé dans la case html serait letoString()
représentation d'unDrink
qui n'est pas ce que vous voulez, d'où le besoin d'utiliser l'id!. Si vous êtes en train de suivre, tout ce que vous devez faire est de créer votre propre convertisseur si on n'est pas déjà créé.Sans convertisseur vous recevrez un message d'erreur comme
Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'drinks'
Vous pouvez activer la journalisation dans
application.properties
pour voir le détail des erreurs.logging.level.org.springframework.web=TRACE
Cela signifie simplement que le printemps ne sais pas comment faire pour convertir un string id représentant un
drink.id
dans unDrink
. Ci-dessous est un exemple deConverter
qui résout ce problème. Normalement, vous permettrait d'injecter un référentiel d'obtenir l'accès à la base de données.Si une entité a un ressort correspondant référentiel de données, le printemps crée automatiquement les convertisseurs et traite de l'extraction de l'entité lorsqu'un code est fourni (string id semble être bien aussi si le printemps n'certains des conversions supplémentaires par l'apparence). C'est vraiment cool, mais peut être déroutant au premier abord.