Reputation: 9357
I have a HashMap which looks pretty much like this..
class MyDTO
{
private Map<Long, StringListWrapper> myMap=new HashMap<>();
private List<Long> keys=new ArrayList<>();
private List<String> values=new ArrayList<>();
// setter and getter methods
}
class StringListWrapper
{
private List<String> st=new ArrayList<>();
// setter and getter methods
}
Now, for the map keys, I have a select which will contain the keys, and a list box which will be used for values.
<select th:field="*{myMap}">
<option th:each="key : *{keys} th:value="${key}" th:text="${key}"></option>
</select>
Here above, keys
refer to the keys
in MyDTO
. I used it to show the keys in the <select>
. However, this select must be mapped to the myMap
to make sure that the option selected here acts as a key.
<select multiple="multiple">
<option th:each="value : *{values}" th:value="${value}" th:text="${value}"></option>
</select>
Now, also this above multiple select must be mapped to myMap
so that the options selected here get into the StringListWrapper.st
. Now, how and where to put the th:field
attribute for multiple select above?
Thanks in advance. Hope you will reply as soon as possible.
Upvotes: 1
Views: 9354
Reputation: 593
There are a few things unclear about the question, but based on the code, I'm assuming that the myMap
property is a Map
because you will be providing multiple pairs of these select fields for uses to select the values for some number of keys.
If the assumption above is false, then why have myMap
as a Map
instead of two different properties/attributes of MyDTO
?
Going with my assumption, you'll need the select pairs to be converted into a List
or array in MyDTO
and have a getter (getMyMap()
) that creates a Map
based of the list. The entry in the list above would be a simple value object with Long key
and List<String>
as its properties. Here is an working example of this use case. See how this is transformed by Thymeleaf and you might find a tweaked version that works with your solution.
Also, minor thing: IMHO, I don't believe it's appropriate to have the keys
and values
properties be within MyDTO
, though it might just be simplified for this question only. They should be model attributes instead since they are not user inputs of the form. It technically works but doesn't strictly adhere to separation of concerns.
References/Credits: Dynamic Form Fields - http://www.thymeleaf.org/doc/thymeleafspring.html#dynamic-fields
Spring Controller:
package net.martian111.examples.spring.web;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/public/stackoverflow/q26181188")
public class StackoverflowQ26181188Controller {
public static class Entry {
private Long id;
private List<String> values;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public List<String> getValues() {
return values;
}
public void setValues(List<String> values) {
this.values = values;
}
}
public static class FormBackingBean {
List<Entry> entries;
public List<Entry> getEntries() {
return entries;
}
public void setEntries(List<Entry> entries) {
this.entries = entries;
}
public Map<Long, List<String>> getMyMap() {
Map<Long, List<String>> map = new HashMap<>();
for (Entry entry : entries) {
// StringListWrapper constructed from entry.getValues() here...
map.put(entry.getId(), entry.getValues());
}
return map;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
int i = 1;
for (Entry entry : entries) {
sb.append("Pair #" + i + ": ID=" + entry.getId() + ", Values="
+ entry.getValues() + "\n");
++i;
}
return sb.toString();
}
}
// Can be set within the @RequestMapping methods too (mv.addObject())
@ModelAttribute("keys")
public List<Long> getKeys() {
return Arrays.asList(null, 1L, 2L, 3L);
}
@ModelAttribute("values")
public List<String> getValues() {
return Arrays.asList(null, "abc", "def", "ghi");
}
@RequestMapping(method = RequestMethod.GET)
public ModelAndView get() {
ModelAndView mv = new ModelAndView("stackoverflow/q26181188");
// Blank Form Backing Bean
FormBackingBean fbb = new FormBackingBean();
fbb.setEntries(Arrays.asList(new Entry(), new Entry(), new Entry(),
new Entry(), new Entry()));
mv.addObject("fbb", fbb);
return mv;
}
@RequestMapping(method = RequestMethod.POST)
public ModelAndView post(FormBackingBean fbb) {
ModelAndView mv = new ModelAndView("stackoverflow/q26181188");
mv.addObject("fbb", fbb);
// Print Form Backing Bean
System.out.println("FBB: \n" + fbb);
// Redisplay submitted from
return mv;
}
}
Thymeleaf Template:
<div th:each="entry,entryStat : *{entries}">
Pair #<span th:text="${entryStat.count}">1</span>
<select th:field="*{entries[__${entryStat.index}__].id}">
<option th:each="key : ${keys}" th:value="${key}" th:text="${key}"></option>
</select>
<select multiple="multiple" th:field="*{entries[__${entryStat.index}__].values}">
<option th:each="value : ${values}" th:value="${value}" th:text="${value}"></option>
</select>
</div>
<button type="submit" name="submit" class="btn btn-primary">Submit</button>
</form>
</body>
</html>
EDIT: Additional details in response to the first comment
Both Map
and List
properties can be mapped to a collection of HTML form fields. Each Map.Entry
or list element is mapped to a single HTML form field, with the name in the form propertyName[index]
, where index is the integer index of the element for the case of a List
, or is the key value of the entry for the case of a Map
. The solution above illustrates this for the List
case.
To illustrate the Map
case, say you want an HTML form to result in a myMap
with the following contents:
123L : ["abc", "def"]
234L : ["abc", "ghi"]
Working backwards, the query string (before URL encoding) required for Spring MVC to create the Map
above will need to look like: myMap[123]=abc&myMap[123]=def&myMap[234]=abc&myMap[234]=ghi
. To get a browser to submit that query string, the HTML form will have to have two multi <select>
form elements, one with name="myMap[123]"
and the other with name="myMap[234]"
. However, the name of form elements cannot be set by another form field in standard HTML. In other words, there is no th:field
value for the key <select>
elements to do this (answering this Stackoverflow question).
With that said, an outside-the-box solution would be client side JavaScript scripting that gathers the necessary data from the form fields and create the required query string to submit the form. It would be a SO different question for a different audience, but I feel that would be an unnecessarily complex and specialized. Also, whereas the solution above works both to generate the HTML view from MyDTO
and back to MyDTO
from a form submission using the same HTML form, a JavaScript solution will require distinct specialized code for each direction.
Upvotes: 2