HTML Form send an array of objects using thymeleaf and spring boot - 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.

Related

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

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

passing a model to the next controller from view

I have a form with few inputs, name, email, message and some checkboxes. I've created a model for these inputs and set all the validations i require.
But now I also want to pass my model (i.e. from #model MyModel) or rather some object property of my model together with those inputs.
Is populating a VewBag/viewData with my model a way to go?
#{
ViewBag.MyModel = Model;
// or ViewBag.ThatProperty = Model.ThatProperty
}
or do i still have a better way up my sleeve?
ViewBag and ViewData persist in one trip from server to client, and not the other way around.
There is no way to pass an object from the view to the controller. If it's a database object, you can pass the object Id using one of the two methods described below, then query the DB on post.
If you have no other way, you can encode the object as a JSON string (using the Newtonsoft package, for example) and pass it also using one of the two methods described below, but this isn't the best option.
To pass a property from the View to the Controller, you have two options:
Url Parameter
Hidden field
Url Parameter
<form ... asp-route-ThatProperty="#Model.ThatProperty">
...
</form>
Form Field
<form>
<input type="hidden" name="ThatProperty" value="#Model.ThatProperty" />
</form>
Controller Action
If 'ThatProperty' doesn't exist on your model, receive it as an extra parameter.
public IActionResult MyAction (MyModel model, string ThatProperty)
{
...
}

Spring Boot Post Request Method not having all Object values

Using Spring Boot, Hibernate JPA and Thymeleaf.
I have an Order database table which currently only holds 1 record. This record has a few columns and some of the columns are not seen on any forms, they are set upon saving the Order, for instance the creation date.
On the GET request below I select the specific Order and all values are returned into the Order object as expected.
This is my GET Request method:
#RequestMapping(value = "/editorder/{orderId}", method = RequestMethod.GET)
public String editOrderGet(Model model, #PathVariable long orderId)
{
Order order = orderService.findById(orderId);
model.addAttribute("order", order);
return "/editorder";
}
This is a small snippit of my edit order html form using Thymeleaf, binding the Order object to the form using th:object as below:
<form role="form" th:action="#{/editorder}" th:object="${order}" method="post">
<input type="hidden" th:field="*{orderId}"/>
<button type="submit" class="btn btn-primary">Update Order</button>
.
.
</form>
And this is my POST Request method:
#RequestMapping(value = "/editorder", method = RequestMethod.POST)
public String editOrderPost(Model model,
#Valid #ModelAttribute("order") Order order, BindingResult bindingResult)
{
//rest of code here
}
As you can see, on the GET request I am adding the Order object to the model.
On the html form, I am binding the Order object to the entire form. Then on the POST request I am getting the Order object.
But on the POST it is seen as a new Order and only contains the fields as specified in the form, it does for instance not contain the creation date as seen in the GET request.
My question is this:
Am I missing something or do I explicitly need to go set each of those fields as hidden fields on my form?
In your GET response you may be returning the whole Order object into the Model, but Thymeleaf when trying to build the actual html from template will pick only the items it needs to build the template. So only the fields that are used in the form are used to build the form in your html page.
So when u resubmit the form to the POST service only those fields that are available in the form is reposted.
If u want these fields to be displayed on the page then add these fields in the Form. Thymeleaf picks them and displays in the form. If you dont want them to be shown in the Page then just ignore them. The Order object which u receive in the POST would not have that fields as they were not available in original form.
U can get them by querying the database, any how you do have the order id saved as the Hidden field.
public String editOrderPost(Model model,
#Valid #ModelAttribute("order") Order order, BindingResult bindingResult){
Order orderFromDB = orderService.findById(order.getId());
// Code to update the orderFromDB from order object
orderService.save(order);
}
This will save the updated fields to the database.
Generally its not a good practice to expose the Entity objects to the API. Try using a DTO/value object. This can have only fields that define your business fields. Also u can use BeanMapper frameworks like dozer/mapstruct/modelmapper to copy from DTO to Entity and vice versa.

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.

Resources