Easy way to submit an array field with Spring MVC forms? - spring

I have a form with a list of nested fieldsets corresponding to a collection of objects, backed by a form object server-side. Fieldsets can be added or removed client-side. I want to submit a form without caring about object indices or sparse lists in command object.
Here's my Controller method code:
#PostMapping("/foobar")
public String doPost(FoobarForm form) {
//...
}
In PHP, Rails etc it's very easy:
<input name="prop[]">
, and it will automatically populate $_POST["prop"] with all the values.
Working with Spring MVC, I tried these things:
<input name="prop[]"> - doesn't work saying Invalid property 'prop[]' of bean class [...]: Invalid index in property path 'prop[]'; nested exception is java.lang.NumberFormatException: For input string: ""
<input name="prop"> - will not bind to a list-typed bean property, even when multiple fields present.
<input name="prop[${i}]"> - implies all that hassle with sparse list and index handling, both client-side and server-side. Certainly not the right way to do things when working with a powerful web framework.
I'm wondering why can't I just use [] in property name and let Spring create a list automatically? It was asked three times on Spring JIRA without any reasonable responses.

Spring form binding makes this more easy. You need to add List object in your bean and bind that in jsp using spring form.
class FoobarForm {
List<String> prop;
}
In jsp, if you need to show/edit value all at once then <form:input path="prop" />
.if you want to show one by one then use indexing<form:input path="prop[0]" />. Use proper CommandName in form. It will work.

I found the answer here.
You can use the MultiValueMap<String, String>
#RequestMapping("foo")
public String bar(#RequestParam MultiValueMap<String, String> parameters){ ... }
With these two inputs:
<input name="prop" value="One">
<input name="prop" value="Two">
the result will be {prop=["One","Two"]}
The code below will work too.
public String bar(#RequestParam("prop") List<String> props){ ... }

Related

HTML Form send an array of objects using thymeleaf and spring boot

I am trying to built an application where I need to send a list of objects, having a variable length.
[Form view]
(https://i.stack.imgur.com/ynE2T.png)
Is there any way I can capture such fields in the controller? I was trying something in the lines of:
public String setNewTratament(
#RequestParam("medicamente") MedicamentatieStartStop[] input,
Model model) {
// ...
}
and in the post request something like:
<input x-bind:name="`medicamente[${index}][medicament]\`" type="text" class="input input-bordered w-full"\>
(using alpineJS), where the last field will be the properties of the object "MedicamentatieStartStop". The names are copied from an th:inline script, pasting exactly one such array.
The problem was it throws the following error: 'required request parameter 'medicamente' for method parameter type MedicamentatieStartStop[] is not present.
Any ideas how I should approach this situation?
Solved it by adding a th:object on the form with a DTO and instead of [attribute] i changed it to .attribute, everything else stayed the same.

In JSP, Field Path Uses HashMap<String,ArrayList<String>> (SpringMVC Form)

Suppose my model field is a HashMap of Strings to ArrayList's of Strings.
HashMap<String,ArrayList<String>> map = new HashMap<String,ArrayList<String>>();
For field binding, I need to point to an i-th item of the ArrayList of a specific key in my JSP. How do I do it? Will this double-bracket notation work? Assume that 'key' and 'index' are known JSTL variables in my JSP.
<form:checkbox path="map[${key}][${index}]" />
What I'm essentially doing is, for a field value, store its path e.g.
map['testkey'][4]
which would point to
map.get("testkey").get(4);
That doesn't work:
org.springframework.beans.NullValueInNestedPathException: Invalid property 'map[TestKey][0]'
NOTE According to this (Binding a map of lists in Spring MVC), this will be a problem because sub-objects are not auto-growable, and I need to implement a LazyList or a growable list? An ArrayList by itself is growable for Spring MVC's forms, but when used as a sub-collection, it's not? Very tricky.
I solved this problem by following the LazyList/LazySet implementation in the link above,
Binding a map of lists in Spring MVC
From their example,
public class PrsData {
private Map<String, List<PrsCDData>> prsCDData;
public PrsData() {
this.prsCDData = MapUtils.lazyMap(new HashMap<String,List<Object>>(), new Factory() {
public Object create() {
return LazyList.decorate(new ArrayList<PrsCDData>(),
FactoryUtils.instantiateFactory(PrsCDData.class));
}
});
}
}
Once I had a data structure like that mapped to my JSP Path, the field binding magically started working and I could then make my path e.g. map["key"][index] in the JSP.
As they replied, SpringMVC auto-converts simple lists and maps to Lazy collections, but does not do it when the collection is a subobject.

How to control two operations for same url in Spring mvc?

Consider the following problem.
The user has chosen to create a document by clicking on the Create document and then he writes data into the document. The url for creating the document is /document/save.
For the subsequent write up, the existing document must be saved instead of creating a new one.
Here is my code for that.
#Controller
public MyController implements Controller, TemplateTypeAware
{
#RequestMapping("/document/save")
public String saveOrCreateDocument(#ModelAttribute DocumentWrapper wrapper, ModelAndView m)
{
if(m.getModel().get("document_id")==null)
{
Document doc=createDocument(wrapper);
m.addObject("document_id",doc.getId());
}
else
{
saveDocument(m.getModel().get("document_id"), wrapper);
}
return documentView;
}
}
Template:
<form>
<input type="hidden" name="document_id" th:value="*{document_id}"/>
<!-- other fields -->
</form>
The problem here is, I am getting document_id always null. Is there any work around for this problem?
Thanks in advance. Hope you will reply as soon as possible.
Form fields will be automatically bound to your DocumentWrapper fields if they have matching names, that means DocumentWrapper needs a field named document_id, otherwise the document_id request parameter won't be bound to your object.
Model attributes will be exposed to the view, at this point the model will be empty, you can add attributes in your handler method and they will become in your view, but request parameters won't be in the model. That explains why you always get null.
If you just need the document_id parameter, use #RequestParam:
#RequestMapping("/document/save")
public String saveOrCreateDocument(#RequestParam("document_id") Long documentId, Model m) {
...
}
Please refer to the binding section of Spring MVC: http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestparam

Spring #MVC how to obtain a List of JavaBeans from request params without wrapping the list inside an object

I need to have this controller:
#RequestMapping("/path/to/my/app")
public String process(List<JavaBean> javaBeans){
// Do something with the list
}
At client I need to manually create the list on user actions, like so
<form action="/path/to/my/app">
<input name="javaBeans[0].field1"/>
<input name="javaBeans[0].field2"/>
<input name="javaBeans[0].field3"/>
<br/>
<input name="javaBeans[1].field1"/>
<input name="javaBeans[2].field2"/>
<input name="javaBeans[3].field3"/>
<!-- and so forth -->
</form>
My primary problem is that I receive empty list inside the contoller. I have to wrap it inside another javabean to make the code work. Like following
public class BeanWrapper{
private List<JavaBean> javaBeans;
// Getter and setter
}
and in controller
#RequestMapping("/path/to/my/app")
public String process(BeanWrapper wrapper){
List<JavaBean> list = wrapper.getJavaBeans();
// Do something with the list
}
This version works but I have to unnecessarily wrap my list around an object. Is there a way around?
Thanks in advance.
You can try this:
public String process(#RequestParam("javaBeans[]") List<JavaBean> javaBeans) {
Update:
The syntax using indices (javaBeans[1]) is currently not supported by the Spring default binding. As mentioned in the comments you can get around this limitation by using the #RequestBody annotation.
public String process(#RequestBody List<JavaBean> javaBeans)
This requires a POST request, and either the data to be sent in a different format (e.g. JSON) or implementing your own message converter.

Does a variable with #ModelAttribute get populated from request parameters?

I am interested in the specifics of Spring #ModelAttribute's work on method parameters.
As we know, when a requested attribute is absent from the model, then its instance gets created and populated from the view form.
My question concerns the scenario when the form does not have expected properties but when such properties are available in URL template parameters.
I would like to know if in this case our variable will be populated with the values of those request parameters?
Like here, for instance, will the variable attributeToPopulate get populated with the parameters 1,2,3 from the URL http://localhost:8080/MyApp/parameter1=whatever?parameter2=whatever?parameter3=whatever?:
RequestMapping(method = RequestMethod.GET)
public String fooMethod(#ModelAttribute("attributeName") FooClass attributeToPopulate){
// method implementation
return "view";
}
Neither Spring documentation, reference documentation, nor Q&A sites refer to such situations explicitly. However, one post here on Stack Overflow does mention that variables annotated with ModelAtrribute get populated in this way (answer of the user Xelian):
name="Dmitrij"&countries=Lesoto&sponsor.organization="SilkRoad"&authorizedFunds=&authorizedHours=&
Considering a small number of upvotes for that answer, I am bit skeptical but at the same time curious about whether #ModelAttribute indeed functions in such way.
Any informative input will be greatly appreciated.
You can set default values to fields in the FooClass instead of setting them in RequestMapping annotation. In this case you will not have to copypaste RequestMapping with default values in all the methods where you are working with FooClass as a ModelAttribute.
#tomatefraiche I have tried to do this by
<spring:url value="/hello?name=default" var="userActionUrl" />
<form:form method="get" modelAttribute="user" action="${userActionUrl}">
<form:input path="name" type="text" disabled="true" />
</form:form>
and
#GetMapping("/hello")
public String hello(#ModelAttribute("user") User user, Model model) {
model.addAttribute("name", user.name);
model.addAttribute("user", user);
return "hello";
}
and looks like Spring override values in the url. In the controller there is an empty value. Also URL is hello?name= after form submission. So looks like you can not set default values through url since it will be replaced.

Resources