thymeleaf-spring
thymeleaf-spring copied to clipboard
Problem inserting a form from a fragment
I've used Thymeleaf 3.0.11, and spring boot 2.3.0
I've put the problem on stackoverflow problem, no one responded: https://stackoverflow.com/questions/62694597/create-a-fragment-for-a-form-in-thymeleaf-and-spring
Putting the 2 fragments below in the same file didn't work, kept throwing: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'strada' available as request attribute So the fix was to move the fragments to different files and the problem disappeared but the rendering takes 4s which is quite huge ( the loss in performance comes from rendering combos, cause if i eliminate them the rendering takes 50ms). As long as they are in the same file the problem persist and rarely it only shows on the logs.
fragment 1:
<div th:fragment="form(adresa)" >
<form action="#" th:action="@{/adresa}" th:object="${adresa}" method="post">
<input type="hidden" th:field="*{id}">
<table>
<tr><td>Strada: </td><td><input type="text" th:field="*{strada}"></td></tr> <--error always here
......
</table>
<p>
<input type="submit" value="Save"/>
<input type="reset" value="Reset">
</p>
</form>
</div>
fragment 2:
<table th:fragment="table(list)" border="1">
<tr>
<th>.</th>
<th>Strada</th>
...
</tr>
<tr th:each="row : ${list}">
<td><a th:href="@{|/adresa/${row.id}|}">Edit</a></td>
<td th:text="${row.strada}" ></td>
....
</tr>
</table>
caller (index file):
...
<div th:replace="adresa::form(${adresa})"></div>
<div th:insert="adresa::table(${adresa.adresaList})"></div>
...
java controller:
...
@GetMapping(value="/adresa")
public String newAdresa(AdresaModel adresa, Model model) {
adresa.setAdresaList(adresaService.list());
model.addAttribute("adresa", adresa);
return "index";
}
@GetMapping(value="/adresa/{id}")
public String editAdresa(Model model, @PathVariable("id") Long id) {
AdresaModel adresa = adresaService.load(id);
adresa.setAdresaList(adresaService.list());
model.addAttribute("adresa", adresa);
return "index";
}
@PostMapping(value="/adresa")
public String save(AdresaModel adresaModel) {
Adresa adresa = buildDomain(adresaModel);
adresaService.save(adresa);
....
}
...
AdresaModel bean:
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AdresaModel {
private Long id;
private String localitate;
private String sublocalitate;
private Long tara;
private Long judet;
private Long tipStrada;
private String strada;
private String numar;
private String bloc;
private String scara;
private String etaj;
private String apartament;
private String codPostal;
private List adresaList;
private List judetList;
private List tipStradaList;
private List taraList;
...
Thank you, hope to hear from you soon!
I think you should rewrite your code to make it clearer, then the error will be easier to spot. Now I see that AdresaModel contains a list of Adresa but it's called "adresa" in the Model attributes and is passed to the form as if it were a single "adresa" with a "strada" field. A little messy. Much easier if you call "adresaModel" the instance of AdresaModel. Or even better call the AdresaModel class something more meaningful: if it's just a list of addresses, call it AdresaList.
I got a page which contains 2 tiles: one with a form and another with a list of addresses. So all the info in that page is included in the model, in the AdresaModel. I cannot use a fat bean and pass parts of it to the fragments? I only use the form data in the form fragment. Is there a problem if the bean passed to the form fragment contains other infos also? I don't understand what is the error here? Please be more specific. My point of view is that thymeleaf is not capable of detecting that *{strada} refers to the ${adresa} which should refer the bean passed as parameter: form(adresa), So we cannot use forms easily in fragments in thymeleaf, can we? Cause for me as a beginner I see Thymeleaf as an unnecessarily complicated framework which doesn't work what one would expect it to work.
As I don't see the full source code, I can't tell where the problem is. I'm just giving you some clues that might help you. Thymeleaf is complicated only if you make it so. My guess was that you're mixing things up.
- Does the AdreasaModel.strada field exist at all?
- You don't need to pass a parameter to a fragment if it exists already with the same name, so "adresa::form" is enough and makes things easier to read.
- I wouldn't use @GetMapping in the java method if the form uses a POST. I use @RequestMapping and always give a meaningful name to the action, like @RequestMapping("newAddress").
Hope this helps.
1 yes AdresaModel contains everything: all the fields in the form + all the combos lists + the list of adresa that is displayed in a table below the form 2 I want to pass a parameter to use the fragment like you use a function (fully encapsulated, with restrained namespace and not javascript-like), and to be able to reuse the form in other contexts where adresa would have other names. 3 I had those GetMapping functions and also a function for saving which has PostMapping and wasn't showed here but its somehow like this @PostMapping(value="/adresa") public String save(AdresaModel adresaModel) { Adresa adresa = buildDomain(adresaModel); adresaService.save(adresa); return "redirect:/... } So in the end I think Thymeleaf has some issues on using arguments in fragments and setting the correct namespace in the forms. So for the moment I gave up reusing the forms. I will go back to that later if Thymeleaf doesn't disappoint me more with these js-like approaches or other unnecessarily complicated ones.
So in the end I think Thymeleaf has some issues on using arguments in fragments
We'll never know until you post the full sources. The link at the top of this thread doesn't work.
So you should have told me that you dont understand because you don't see the full code and not that is messy. I added more code. Also fixed the url so you can use it with only 1 click, but this might not help much.
The code at that page is different from the one posted here:
<div th:fragment="form(adresa)" >
vs
<form th:fragment="form(address)"
In any case, I'd do another test using a different name for the fragment because "form" is both a fragment name and a HTML tag name, both matching your selector in th:replace="adresa::form(${adresa})"
. This may cause problems I guess: I don't know which one has precedence.
Having executable full sources would help here :-)
unfortunately the original code was rewritten in order to avoid this problem, im using layouts now, cause it took too long for you to responde. so i dont have the original code anymore but i will try to replicate this issue in the next days and i will post full code. i will take into account your idea also, thank you. the difference between the cod posted here and the one on stackoverflow is that the one on stackoverflow is translated into english so more people will understand. here is the same with the original.
So the question here is: @ultraq what happens when the name of a fragment equals the name of a HTML tag? Will the tag have higher priority and prevent fragment parameters from being added? If so, should this be highlighted in the docs?
I'll have to dig into the attoparser code (Thymeleaf dependency that's used for parsing templates) or run a few test cases to see what will happen if the fragment and HTML element names are the same, but from the problems shown in this issue it looks like the HTML element wins out.
Re: docs - the impression I get from reading the attoparser docs and the section in the Thymeleaf docs about the selector syntax (https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-c-markup-selector-syntax) is that both will be included:
x
is exactly equivalent to//x
(search an element with name or referencex
at any depth level, a reference being ath:ref
or ath:fragment
attribute).
This is an area I often forget about as it's in a dependency, but I often end up advising developers to avoid fragment names that clash with existing HTML element names because of this unknown behaviour.