如何用百里香叶绑定对象列表? [英] How to bind an object list with thymeleaf?

查看:40
本文介绍了如何用百里香叶绑定对象列表?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在将表单回传到控制器时遇到了很多困难,该表单应该只包含用户可以编辑的对象数组列表.

I am having a lot of difficulty with POSTing back a form to the controller, which should contain simply an arraylist of objects that the user may edit.

表单正确加载,但是当它被发布时,它似乎从来没有真正发布过任何东西.

The form loads up correctly, but when it's posted, it never seems to actually post anything.

这是我的表格:

<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>

以上工作正常,它正确加载了列表.但是,当我 POST 时,它返回一个空对象(大小为 0).我相信这是由于缺少 th:field,但无论如何这里是控制器 POST 方法:

Above works fine, it loads up the list correctly. However, when I POST, it returns a empty object (of size 0). I believe this is due to the lack of th:field, but anyway here is controller POST method:

...
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
    ...
}

我尝试添加一个 th:field 但不管我做什么,它都会导致异常.

I have tried adding a th:field but regardless of what I do, it causes an exception.

我试过了:

...
<tr th:each="currentClient, stat : ${clientList}">   
     <td><input type="checkbox" th:checked="${currentClient.selected}"  th:field="*{}" /></td>

    <td th th:field="*{currentClient.selected}" ></td>
...

我无法访问 currentClient(编译错误),我什至无法选择 clientList,它给了我诸如 get()add() 之类的选项clearAll() 等,所以它应该有一个数组,但是,我不能传入一个数组.

I cannot access currentClient (compile error), I can't even select clientList, it gives me options like get(), add(), clearAll() etc, so it things it should have an array, however, I cannot pass in an array.

我也尝试过使用类似 th:field=${} 的东西,这会导致运行时异常

I've also tried using something like th:field=${}, this causes runtime exception

我试过了

th:field = "*{clientList[__currentClient.clientID__]}" 

但也编译错误.

有什么想法吗?

Tobias 建议我需要将我的列表包装在一个包装器中.所以这就是我所做的:

Tobias suggested that I need to wrap my list in a wraapper. So that's what I did:

ClientWithSelectionWrapper:

public class ClientWithSelectionListWrapper {

private ArrayList<ClientWithSelection> clientList;

public List<ClientWithSelection> getClientList(){
    return clientList;
}

public void setClientList(ArrayList<ClientWithSelection> clients){
    this.clientList = clients;
}
}

我的页面:

<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>

以上负载很好:

然后是我的控制器:

@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, Model model){
... 
}

页面加载正确,数据按预期显示.如果我在没有任何选择的情况下发布表单,我会得到:

The page loads correctly, the data is displayed as expected. If I post the form without any selection I get this:

org.springframework.expression.spel.SpelEvaluationException: EL1007E:(pos 0): Property or field 'clientList' cannot be found on null

不知道为什么会抱怨

(在 GET 方法中它有:model.addAttribute("wrapper", wrapper);)

(In the GET Method it has: model.addAttribute("wrapper", wrapper);)

如果我进行了选择,即勾选第一个条目:

If I then make a selection, i.e. tick the first entry:

There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='clientWithSelectionListWrapper'. Error count: 1

我猜我的 POST 控制器没有获取 clientWithSelectionListWrapper.不知道为什么,因为我已将包装器对象设置为通过 FORM 标头中的 th:object="wrapper" 回发.

I'm guessing my POST controller is not getting the clientWithSelectionListWrapper. Not sure why, since I have set the wrapper object to be posted back via the th:object="wrapper" in the FORM header.

我取得了一些进展!最后提交的表单被控制器中的 POST 方法接收.但是,所有属性似乎都为空,除了项目是否已被勾选.我进行了各种更改,这就是它的外观:

I've made some progress! Finally the submitted form is being picked up by the POST method in controller. However, all the properties appear to be null, except for whether the item has been ticked or not. I've made various changes, this is how it is looking:

<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>

我还在包装类中添加了一个默认的无参数构造函数,并在 POST 方法中添加了一个 bindingResult 参数(不确定是否需要).

I also added a default param-less constructor to my wrapper class and added a bindingResult param to POST method (not sure if needed).

public String processQuery(@ModelAttribute ClientWithSelectionListWrapper wrapper, BindingResult bindingResult, Model model)

所以当一个对象被发布时,它是这样的:

So when an object is being posted, this is how it is looking:

当然,systemInfo应该是null(在这个阶段),但是clientID总是0,ipAddress/Description总是null.尽管对于所有属性,选定的布尔值都是正确的.我确定我在某处的其中一个属性上犯了错误.回到调查.

Of course, the systemInfo is supposed to be null (at this stage), but the clientID is always 0, and ipAddress/Description always null. The selected boolean is correct though for all properties. I'm sure I've made a mistake on one of the properties somewhere. Back to investigation.

好的,我已经设法正确填写了所有值!但是我不得不更改我的 td 以包含一个 <input/> 这不是我想要的...... 尽管如此,这些值填充正确,表明 spring 看起来用于输入标签可能用于数据映射?

Ok I've managed to fill up all the values correctly! But I had to change my td to include an <input /> which is not what I wanted... Nonetheless, the values are populating correctly, suggesting spring looks for an input tag perhaps for data mapping?

以下是我如何更改 clientID 表数据的示例:

Here is an example of how I changed the clientID table data:

<td>
 <input type="text" readonly="readonly"                                                          
     th:name="|clientList[${stat.index}]|"
     th:value="${currentClient.getClientID()}"
     th:field="*{clientList[__${stat.index}__].clientID}"
  />
</td>

现在我需要弄清楚如何将它显示为纯数据,理想情况下没有任何输入框......

Now I need to figure out how to display it as plain data, ideally without any presence of an input box...

推荐答案

你需要一个包装对象来保存提交的数据,像这样:

You need a wrapper object to hold the submited data, like this one:

public class ClientForm {
    private ArrayList<String> clientList;

    public ArrayList<String> getClientList() {
        return clientList;
    }

    public void setClientList(ArrayList<String> clientList) {
        this.clientList = clientList;
    }
}

并将其用作 @ModelAttribute 在您的 processQuery 方法中:

and use it as the @ModelAttribute in your processQuery method:

@RequestMapping(value="/submitQuery", method = RequestMethod.POST)
public String processQuery(@ModelAttribute ClientForm form, Model model){
    System.out.println(form.getClientList());
}

此外,input 元素需要一个 name 和一个 value.如果直接构建html,那么考虑到名字必须是clientList[i],其中i是item在列表中的位置:

Moreover, the input element needs a name and a value. If you directly build the html, then take into account that the name must be clientList[i], where i is the position of the item in the list:

<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>

注意 clientList 可以包含 null 在中间位置.例如,如果发布的数据是:

Note that clientList can contain null at intermediate positions. Per example, if posted data is:

clientList[1] = 'B'
clientList[3] = 'D'

结果 ArrayList 将是:[null, B, null, D]

更新 1:

在我上面的例子中,ClientFormList 的包装器.但在您的情况下,ClientWithSelectionListWrapper 包含 ArrayList.因此 clientList[1] 应该是 clientList[1].clientID 等等,还有你想发回的其他属性:

In my exmple above, ClientForm is a wrapper for List<String>. But in your case ClientWithSelectionListWrapper contains ArrayList<ClientWithSelection>. Therefor clientList[1] should be clientList[1].clientID and so on with the other properties you want to sent back:

<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>

我已经构建了一个小演示,所以你可以测试它:

I've built a little demo, so you can test it:

应用程序.java

@SpringBootApplication
public class Application {      
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }       
}

ClientWithSelection.java

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 ... */
}

ClientWithSelectionListWrapper.java

ClientWithSelectionListWrapper.java

public class ClientWithSelectionListWrapper {

   private ArrayList<ClientWithSelection> clientList;

   public ArrayList<ClientWithSelection> getClientList() {
      return clientList;
   }
   public void setClientList(ArrayList<ClientWithSelection> clients) {
      this.clientList = clients;
   }
}

TestController.java

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";
   }
}

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>

更新 1.B:

下面是使用 th:field 并将所有其他属性作为隐藏值发回.

Below is the same example using th:field and sending back all other attributes as hidden values.

 <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>

这篇关于如何用百里香叶绑定对象列表?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆