Подтвердить что ты не робот

Как связать список объектов с тимелеафом?

У меня много трудностей с отправкой назад формы на контроллер, который должен содержать просто аррайалист объектов, которые пользователь может редактировать.

Форма загружается правильно, но когда она отправляется, она никогда ничего не публикует.

Вот моя форма:

<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 контроллера:

...
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, но независимо от того, что я делаю, это вызывает исключение.

Я пробовал:

...
<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() и т.д., поэтому он должен иметь массив, однако я не может пройти в массив.

Я также пытался использовать что-то вроде th:field=${}, это вызывает исключение времени выполнения

Я пробовал

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

но также и ошибку компиляции.

Любые идеи?


ОБНОВЛЕНИЕ 1:

Тобиас предположил, что мне нужно обернуть свой список в wapapper. Итак, что я сделал:

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

Страница загружается правильно, данные отображаются как ожидалось. Если я отправлю форму без какого-либо выбора, я получаю следующее:

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

Не уверен, почему он жалуется

(В методе GET он имеет: model.addAttribute("wrapper", wrapper);)

введите описание изображения здесь

Если я затем сделаю выбор, т.е. отметьте первую запись:

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

Я предполагаю, что мой контроллер POST не получает clientWithSelectionListWrapper. Не знаю, почему, поскольку я установил, чтобы оберточный объект был отправлен обратно через th:object="wrapper" в заголовке FORM.


ОБНОВЛЕНИЕ 2:

Я добился определенного прогресса! Наконец, представленная форма подбирается методом 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>

Я также добавил конструктор param-less по умолчанию в мой класс оболочки и добавил параметр bindingResult в метод POST (не уверен, если это необходимо).

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

Итак, когда объект отправляется, вот как он выглядит: введите описание изображения здесь

Конечно, системная информация должна быть нулевой (на данном этапе), но clientID всегда 0, а ipAddress/Description всегда null. Выбранное логическое значение корректно, хотя для всех свойств. Я уверен, что я допустил ошибку в одном из свойств где-то. Вернемся к расследованию.


ОБНОВЛЕНИЕ 3:

Хорошо, мне удалось правильно заполнить все значения! Но мне пришлось изменить 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>

Теперь мне нужно выяснить, как отображать его как простые данные, в идеале без присутствия окна ввода...

4b9b3361

Ответ 1

Вам нужен объект-оболочка для хранения переданных данных, например:

public class ClientForm {
    private ArrayList<String> clientList;

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

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

и используйте его как @ModelAttribute в вашем методе processQuery:

@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 - позиция элемента в списке:

<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 в промежуточные позиции. Например, если опубликованные данные:

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

получившийся 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>

Я создал небольшую демоверсию, поэтому вы можете ее протестировать:

Application.java

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

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

public class ClientWithSelectionListWrapper {

   private ArrayList<ClientWithSelection> clientList;

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

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 и отправка всех остальных атрибутов в виде скрытых значений.

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