ben*_*bia 39 java spring spring-mvc thymeleaf spring-boot
将表单发布回控制器时,我遇到了很多困难,控制器应该只包含用户可以编辑的对象的arraylist.
表单正确加载,但是当它发布时,它似乎永远不会发布任何内容.
这是我的表格:
<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>
Run Code Online (Sandbox Code Playgroud)
以上工作正常,它正确加载列表.但是,当我POST时,它返回一个空对象(大小为0).我相信这是由于缺乏th:field,但无论如何这里是控制器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
...
}
Run Code Online (Sandbox Code Playgroud)
我试过添加一个,th:field但无论我做什么,它都会导致异常.
我试过了:
...
<tr th:each="currentClient, stat : ${clientList}">
<td><input type="checkbox" th:checked="${currentClient.selected}" th:field="*{}" /></td>
<td th th:field="*{currentClient.selected}" ></td>
...
Run Code Online (Sandbox Code Playgroud)
我无法访问currentClient(编译错误),我甚至不能选择客户端列表,它给了我类似的选项get(),add(),clearAll()等等,所以它的东西应该有一个数组,但是,我不能在传递数组.
我也试过使用类似的东西th:field=${},这会导致运行时异常
我试过了
th:field = "*{clientList[__currentClient.clientID__]}"
Run Code Online (Sandbox Code Playgroud)
但也编译错误.
有任何想法吗?
托比亚斯建议我需要将我的清单包装在一个破坏者中.这就是我做的:
ClientWithSelectionWrapper:
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public List<ClientWithSelection> getClientList(){
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients){
this.clientList = clients;
}
}
Run Code Online (Sandbox Code Playgroud)
我的页面:
<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>
Run Code Online (Sandbox Code Playgroud)
然后我的控制器:
@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, Model model){
...
}
Run Code Online (Sandbox Code Playgroud)
页面正确加载,数据按预期显示.如果我在没有任何选择的情况下发布表单,我会得到:
org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'clientList' cannot be found on null
Run Code Online (Sandbox Code Playgroud)
不知道为什么抱怨
(get方法有:model.addAttribute("wrapper", wrapper);)
如果我然后做出选择,即勾选第一个条目:
There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='clientWithSelectionListWrapper'. Error count: 1
Run Code Online (Sandbox Code Playgroud)
我猜我的POST控制器没有得到clientWithSelectionListWrapper.不知道为什么,因为我已经将包装器对象设置为通过th:object="wrapper"FORM标头回发.
我取得了一些进展!最后,提交的表单由控制器中的POST方法获取.但是,除了项目是否已勾选之外,所有属性都显示为空.我做了各种更改,这是它的样子:
<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>
Run Code Online (Sandbox Code Playgroud)
我还在我的包装器类中添加了一个默认的无参数构造函数,并bindingResult为POST方法添加了一个参数(不确定是否需要).
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, BindingResult bindingResult, Model model)
Run Code Online (Sandbox Code Playgroud)
当然,systemInfo应该为null(在此阶段),但clientID始终为0,并且ipAddress/Description始终为null.对于所有属性,所选布尔值都是正确的.我确定我在其中一个属性上犯了一个错误.回到调查.
好的,我已经设法正确填写所有值!但我不得不改变我的方式td来包含一个<input />不是我想要的东西......尽管如此,这些值正确填充,这表明spring可能会为数据映射寻找输入标签?
以下是我如何更改clientID表数据的示例:
<td>
<input type="text" readonly="readonly"
th:name="|clientList[${stat.index}]|"
th:value="${currentClient.getClientID()}"
th:field="*{clientList[__${stat.index}__].clientID}"
/>
</td>
Run Code Online (Sandbox Code Playgroud)
现在我需要弄清楚如何将它显示为普通数据,理想情况下不存在任何输入框......
Tob*_*ías 47
您需要一个包装器对象来保存提交的数据,如下所示:
public class ClientForm {
private ArrayList<String> clientList;
public ArrayList<String> getClientList() {
return clientList;
}
public void setClientList(ArrayList<String> clientList) {
this.clientList = clientList;
}
}
Run Code Online (Sandbox Code Playgroud)
并@ModelAttribute在您的processQuery方法中使用它:
@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientForm form, Model model){
System.out.println(form.getClientList());
}
Run Code Online (Sandbox Code Playgroud)
而且,input元素需要a name和a value.如果你直接构建html,那么考虑到名称必须是clientList[i],i列表中项目的位置在哪里:
<tr th:each="currentClient, stat : ${clientList}">
<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>
Run Code Online (Sandbox Code Playgroud)
注意,clientList可以包含null在中间位置.例如,如果发布的数据是:
clientList[1] = 'B'
clientList[3] = 'D'
Run Code Online (Sandbox Code Playgroud)
结果ArrayList将是:[null, B, null, D]
更新1:
在上面的例子中,ClientForm是一个包装器List<String>.但在你的情况下ClientWithSelectionListWrapper包含ArrayList<ClientWithSelection>.为此clientList[1]应clientList[1].clientID与要发回的其他属性等:
<tr th:each="currentClient, stat : ${wrapper.clientList}">
<td><input type="checkbox" th:name="|clientList[${stat.index}].clientID|"
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>
Run Code Online (Sandbox Code Playgroud)
我已经构建了一个小演示,所以你可以测试它:
Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Run Code Online (Sandbox Code Playgroud)
ClientWithSelection.java
public class ClientWithSelection {
private Boolean selected;
private String clientID;
private String ipAddress;
private String description;
public ClientWithSelection() {
}
public ClientWithSelection(Boolean selected, String clientID, String ipAddress, String description) {
super();
this.selected = selected;
this.clientID = clientID;
this.ipAddress = ipAddress;
this.description = description;
}
/* Getters and setters ... */
}
Run Code Online (Sandbox Code Playgroud)
ClientWithSelectionListWrapper.java
public class ClientWithSelectionListWrapper {
private ArrayList<ClientWithSelection> clientList;
public ArrayList<ClientWithSelection> getClientList() {
return clientList;
}
public void setClientList(ArrayList<ClientWithSelection> clients) {
this.clientList = clients;
}
}
Run Code Online (Sandbox Code Playgroud)
TestController.java
@Controller
class TestController {
private ArrayList<ClientWithSelection> allClientsWithSelection = new ArrayList<ClientWithSelection>();
public TestController() {
/* Dummy data */
allClientsWithSelection.add(new ClientWithSelection(false, "1", "192.168.0.10", "Client A"));
allClientsWithSelection.add(new ClientWithSelection(false, "2", "192.168.0.11", "Client B"));
allClientsWithSelection.add(new ClientWithSelection(false, "3", "192.168.0.12", "Client C"));
allClientsWithSelection.add(new ClientWithSelection(false, "4", "192.168.0.13", "Client D"));
}
@RequestMapping("/")
String index(Model model) {
ClientWithSelectionListWrapper wrapper = new ClientWithSelectionListWrapper();
wrapper.setClientList(allClientsWithSelection);
model.addAttribute("wrapper", wrapper);
return "test";
}
@RequestMapping(value = "/query/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, Model model) {
System.out.println(wrapper.getClientList() != null ? wrapper.getClientList().size() : "null list");
System.out.println("--");
model.addAttribute("wrapper", wrapper);
return "test";
}
}
Run Code Online (Sandbox Code Playgroud)
的test.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<form action="#" th:action="@{/query/submitQuery}" th:object="${wrapper}" 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 : ${wrapper.clientList}">
<td><input type="checkbox" th:name="|clientList[${stat.index}].clientID|"
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>
</tbody>
</table>
<button type="submit" value="submit" class="btn btn-success">Submit</button>
</form>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
更新1.B:
下面是使用th:field并将所有其他属性作为隐藏值发回的相同示例.
<tbody>
<tr th:each="currentClient, stat : *{clientList}">
<td>
<input type="checkbox" th:field="*{clientList[__${stat.index}__].selected}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].clientID}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].ipAddress}" />
<input type="hidden" th:field="*{clientList[__${stat.index}__].description}" />
</td>
<td th:text="${currentClient.getClientID()}"></td>
<td th:text="${currentClient.getIpAddress()}"></td>
<td th:text="${currentClient.getDescription()}"></td>
</tr>
</tbody>
Run Code Online (Sandbox Code Playgroud)
当您想要在 thymeleaf 中选择对象时,实际上不需要创建包装器来存储boolean选择字段。dynamic fields当您想要访问集合中现有的一组对象时,按照 thymeleaf 指南使用语法th:field="*{rows[__${rowStat.index}__].variety}"非常有用。在我看来,它并不是真正为使用包装对象进行选择而设计的,因为它创建了不必要的样板代码,并且有点像黑客。
考虑这个简单的例子,aPerson可以选择Drinks他们喜欢的。注意:为了清楚起见,省略了构造函数、Getter 和 Setter。此外,这些对象通常存储在数据库中,但我使用内存数组来解释这个概念。
public class Person {
private Long id;
private List<Drink> drinks;
}
public class Drink {
private Long id;
private String name;
}
Run Code Online (Sandbox Code Playgroud)
弹簧控制器
这里最主要的是我们将 存储Person在 中,Model以便我们可以将其绑定到 中的表单th:object。其次,selectableDrinks人们可以在用户界面上选择饮料。
@GetMapping("/drinks")
public String getDrinks(Model model) {
Person person = new Person(30L);
// ud normally get these from the database.
List<Drink> selectableDrinks = Arrays.asList(
new Drink(1L, "coke"),
new Drink(2L, "fanta"),
new Drink(3L, "sprite")
);
model.addAttribute("person", person);
model.addAttribute("selectableDrinks", selectableDrinks);
return "templates/drinks";
}
@PostMapping("/drinks")
public String postDrinks(@ModelAttribute("person") Person person) {
// person.drinks will contain only the selected drinks
System.out.println(person);
return "templates/drinks";
}
Run Code Online (Sandbox Code Playgroud)
模板代码
密切注意循环li以及如何selectableDrinks使用它来获取所有可以选择的饮料。
该复选框th:field实际上扩展为person.drinks“since th:objectis 绑定到” Person,并且*{drinks}只是引用对象属性的快捷方式Person。您可以将其视为只是告诉 spring/thymeleaf 任何选定的内容都Drinks将被放入ArrayListat 位置person.drinks。
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" >
<body>
<div class="ui top attached segment">
<div class="ui top attached label">Drink demo</div>
<form class="ui form" th:action="@{/drinks}" method="post" th:object="${person}">
<ul>
<li th:each="drink : ${selectableDrinks}">
<div class="ui checkbox">
<input type="checkbox" th:field="*{drinks}" th:value="${drink.id}">
<label th:text="${drink.name}"></label>
</div>
</li>
</ul>
<div class="field">
<button class="ui button" type="submit">Submit</button>
</div>
</form>
</div>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
无论如何……秘密武器正在使用th:value=${drinks.id}。这依赖于弹簧转换器。当表单被发布时,spring将尝试重新创建一个Person,为此它需要知道如何将任何选定的drink.id字符串转换为实际Drink类型。注意:如果您在复选框 html 中输入了键,则th:value${drinks}a的表示形式不是您想要的,因此需要使用 id!。如果您按照步骤进行操作,那么您所需要做的就是创建自己的转换器(如果尚未创建)。valuetoString()Drink
如果没有转换器,您将收到类似的错误
Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'drinks'
您可以打开登录application.properties来查看错误的详细信息。
logging.level.org.springframework.web=TRACE
这只是意味着 spring 不知道如何将表示 a 的字符串 id 转换drink.id为 a Drink。Converter下面是解决此问题的示例。通常,您会在获取数据库访问权限时注入存储库。
@Component
public class DrinkConverter implements Converter<String, Drink> {
@Override
public Drink convert(String id) {
System.out.println("Trying to convert id=" + id + " into a drink");
int parsedId = Integer.parseInt(id);
List<Drink> selectableDrinks = Arrays.asList(
new Drink(1L, "coke"),
new Drink(2L, "fanta"),
new Drink(3L, "sprite")
);
int index = parsedId - 1;
return selectableDrinks.get(index);
}
}
Run Code Online (Sandbox Code Playgroud)
如果一个实体有一个相应的 spring 数据存储库,spring 会自动创建转换器,并在提供 id 时处理获取实体(字符串 id 似乎也很好,所以 spring 看起来会在那里进行一些额外的转换)。这确实很酷,但一开始可能会让人难以理解。
| 归档时间: |
|
| 查看次数: |
53324 次 |
| 最近记录: |