spring model binding with disabled input - spring

sorry for a dumb question but i can't understand quite what happens, and if it is what i suspect.. well i am really at a loss.
i am using spring boot + thymeleaf + materialize css to show and validate a form.
now what i don't meet in many examples that i see is this case:
some form fields are pre-filled and should seem disabled to the client, showing their pre-filled values. this pre-filling takes place in the controller, while i handle some other request, and redirect to this view
i am binding a pojo to the form using th:object like this
<form id="register_form" action="#" th:action="#{/showform}" th:object="${userInfo}" method="post">
<div class="input-field">
<label th:text="#{label.surname}" for="surname"></label>
<input type="text" th:field="*{surname}" id="surname" th:attr="value=${userInfo.surname}" />
</div>
<div class="input-field">
<label th:text="#{label.name}" for="givenname"></label>
<input type="text" th:field="*{givenname}" id="givenname" th:attr="value=${userInfo.givenname}" disabled="disabled" />
</div></form>
and getting it in the POST handler of the controller like this:
#RequestMapping(value = {"/showform"}, method = RequestMethod.POST)
public ModelAndView submitFormPage(#ModelAttribute("userInfo") #Valid UserInfo userInfo,
BindingResult bindingResult, RedirectAttributes redir)
{
ModelAndView mview = new ModelAndView();
if (bindingResult.hasErrors())
{
// show form again with error messages
mview.addObject("userInfo", userInfo);
mview.setViewName("/showform");
}
else
{
// ...
}
return mview;
}
RedirectAttributes is there for some other reason. As you can see, there are two elements on a form, and first one is enabled, and the second disabled.
Their values are populated correctly with pre-filled values from the POJO i pass to the view via the ModelMap. i can also trace it in the GET handler.
but the ModelMap i get back from the view contains the aforementioned POJO with NULL values in place of the elements that are bound to the disabled controls. i would expect them to be populated by the contents of the value attribute, even though those controls are disabled. the enabled controls carry their values alright.
or is it just that disabled controls simply are not included in the postback? if this is the case, how would you suggest me to do it? some suggested adding an obscure CSS that would "fake" the behaviour of a disabled control. or have i missed something in the general wiring?
i think with horror of possible workarounds - but i must be doing something wrong.. th:attr was one of the workarounds i tried, but it doesn't seem to do the trick. i also tried using th:id and th:disabled but it didn't help either.

There is a misunderstanding here I think about the use of disabled.
A readonly element is just not editable, but gets sent when the
according form submits. a disabled element isn't editable and isn't
sent on submit. Another difference is that readonly elements can be
focused (and getting focused when "tabbing" through a form) while
disabled elements can't.
More detailed comparison
So to answer your question: you should opt for readonly if you want to bind your attributes to your pojo and still the user can't edit them.

Related

Pass data from Thymeleaf template to springboot controller

I have simple web application written using Springboot and Thymeleaf templates. Report controller receives the data from form and builds the TestPlanReportResponse object which is added as model attribute like this:
#PostMapping("/report")
public String homeSubmit(#ModelAttribute HomeFormInput homeFormInput, Model model, Errors errors) {
final TestPlanReportResponse response = new TestPlanReportResponse(homeFormInput);
model.addAttribute("allData", response);
return "charts";
}
I can work with that data in "charts" thymeleaf template and show the data I need, but I need to send exactly the same object back to controller when button is clicked, but i getting TestPlanReportResponse
object as parameter with nulls set.
#PostMapping("/report/send")
public String sendReport(#ModelAttribute TestPlanReportResponse reportData, Model model) {
//reportData contains just nulls
}
Here is how my button is set in charts template:
<form action="#" th:action="#{/report/send}" th:object="${allData}" method="post">
<button type="submit">Send the report</button>
</form>
So my question is how to send the object back from thymeleaf template? Should i create a hidden input and put there the "allData" object just to send it back? It looks for me like dirty hack. What would be the appropriate way to pass data back? I want to have this app stateless so don't to store the data on a server side.
When I used to work with Spring and Thymeleaf and form, we had the same issue, passing the data back and forth between a form, the template, and different controllers.
And what you suggest is what we did, we used hidden input as dirty as it may look,it was the standard suggested answer, we did not find anything better.
You need to create an input, with a type a value and link it to a field, like this:
<form action="#" th:action="#{/report/send}" th:object="${allData}" method="post">
<input type="hidden" th:value="*{allDataValue1}" th:field="*{allDataField1}" />
//Do this for all your attributes/values that you wish to pass to the controller
<button class="btn btn-info btn-lg btn-block" type="submit">Send the report</button>
</form>
Though, i found this answer, you can try looking into this thread

retain model values in case of error and showing same thymeleaf template

As per my understanding, model attributes are associated with every request and they can not survive multiple requests, until we add them as flashAttributes.
I have a simple controller method which shows a couple of options to user to select from. However, those options are being attached to thymeleaf template using model attributes.
<div class="input-group mb-3" th:each="ingredient : ${recipes.ingredients}">
<div class="input-group-prepend">
<div class="input-group-text">
<input aria-label="Checkbox for following text input" name="ingredient"
th:value="${ingredient.name}" type="checkbox">
</div>
<input aria-label="Text input with checkbox" class="form-control" disabled
th:value="${ingredient.name + ' ' + ingredient.price + 'Rs.'}"
type="text">
</div>
assume "recipes" as model attribute here, which was injected to modelMap inside the controller.
when bean validation fails, below line exectutes.
if (errors.hasErrors()) return "selectItem";
and selectItem template is re-rendered, but whatever model attributes I have set inside previous controller vanishes.
I have solved this using a #ModelAttribute method inside the same controller to set model attributes for every HTTP requests for the specific controller(until it is not in controllerAdvice for global effect).
I am being confused if I am on right way || is there any elegant way to achieve this.
Setting Model attribute for every request is kind of overhead, when I want them to be available for handful of request mappings.
When you say:
selectItem template is re-rendered, but whatever model attributes I
have set inside previous controller vanishes.
You mean that when the page reloads due to validation errors, your model attributes are no longer existing and Thymeleaf probably returns an error, because it cannot find them, correct?
If this is the case, then you have to manually prepare the same model attributes within the if statement (i.e. adding them to your MapModel):
if (errors.hasErrors()) {
map.addAttribute("recipes", recipes);
return "selectItem";
}
Alternatively, if you need this model attribute also on other pages in your controller, you can reduce code duplication by declaring a method with the ModelAttribute annotation, which will add this attribute to all models in your controller:
#ModelAttribute("recipes")
public Recipes loadRecipes() {
// get list of Recipes
return list;
}

How can i get the data from a second form

This is an add page. Person contains a list of addresses so when i try to add an address to that person that is not yet in the database i need to be able to retrieve the person form when an address is submitted and assign that address to that person.
<form:form action="${addAction}" modelAttribute="person">
<form:label path="name">
<spring:message text="Name"/>
</form:label>
<form:input path="name" />
... More labels and inputs
</form:form>
<form:form action="${addAddress}" modelAttribute="address">
... Labels
<input type="submit" value="<spring:message text="Add Address"/>" />
</form:form>
In my controller i have "#ModelAttribute("person") Person p" line which should retrieve the form which has "modelAttribute="person"" in it. But the person retrieved is an empty entity which i'm assuming is because the person form has to be submitted in order to retrieve the data.
#RequestMapping(value = "/person/addAddress", method = RequestMethod.POST)
public String addAddress(#ModelAttribute("person") Person p, #ModelAttribute("address") Address a, RedirectAttributes redirectAttrs) {
p.getAddresses().add(a);
redirectAttrs.addFlashAttribute("person", p);
return "redirect:/person";
}
I probably can retrieve the inputs instead of the form and use them to create a new entity with those values but if i were to do that controllers' passing attributes will be full of inputs and would look ugly. Is there a way for me to retrieve those values as a Person entity?
EDIT
Sanjay's first option is the most logical way to do it but since what i wanted to design does not fit for it i can't do it. But Sanjay's comment about making it in one form helped me so i'm selecting Sanjay's answer as the solution but here is how i fixed it
Since i had form actions saved in c:url's i changed the buttons' onclick function such that when clicked forms' action would change depending on the button and i already had corresponding controllers for the actions. For the address list inside my person i had to make a workaround by first adding an empty address to the list in my page controller and then using
<c:forEach items="${person.addresses}" varStatus="loop">
<c:if test="${loop.last}">
<form:input path="addresses[${loop.index}].street" />
...
the code above i was able to fill the previously added empty address.
I'm still in the process of fixing everything but this is the general idea of how i fixed it. Thanks for the help.
I think you may need to revisit your UI, add some hidden field etc. I can think of some solutions:
Have an "Add Address" button which appends a blank row of address into the form using JavaScript, but it doesn't submit to the server. Have the real "Submit" button at the bottom, which would submit the entire form including the person and addresses.
Have a submit button to save the person without address. Then, on a second screen, display the person, and have the address submission button.
Do the above, in reverse, if saving the Address first suits your requirements
I probably can retreieve the inputs instead of the form and use them to create a new entity with those values but if i were to do that controllers' passing attributes will be full of inputs and would look ugly. Is there a way for me to retrieve those values as a Person entity?
For cleaner code -
1.Create JSON object (say formFields) with all your form input data.
2.send formFields to server using ajax call.
3.Read formFields as String in controller
#ModelAttribute("formFields") final String formFields
4.You should have DTO matching formFields name say FormDTO.
5.convert formFields of String type to FormDTO using ObjectMapper API.
FormDTO formDto = null;
try {
ObjectMapper mapper = new ObjectMapper();
formDto = mapper.readValue(formDto, FormDTO.class);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
P.S. ObjectMapper is part of jackson-databind-2.4.4.jar

Thymeleaf, Spring nested backing object is not binding the values on form submit

I have a nested object and I'm using it as a model for a form.
public AgeBracketSet implements Serializable{
private String id;
private List<AgeBracket> ageBrackets;
/* Getters and Setters */
}
I have successfully bound all the properties of this object to the form and I can visualize their values when the view state is rendered. Here's a simplified version of how I'm doing it with Thymeleaf. Essentially, I loop through the items of the list and get their attributes.
<form id="bracketForm" role="form" th:action="${flowExecutionUrl}" th:object="${ageBracketSet}" method="post">
<input th:id="'bracketSet_'+*{id}" th:field="*{id}" />
<th:block th:each="bracket,loop : *{ageBrackets}" th:id="'bracket_'+${bracket.id}">
<input th:id="'fromAge_'+${bracket.id}" th:field="*{ageBrackets[__${loop.index}__].fromAge}" />
<input th:id="'toAge_'+${bracket.id}" th:field="*{ageBrackets[__${loop.index}__].toAge}" />
</th:block>
</form>
However, when I make changes in the form and submit it, the model remains unchanged. I have confirmed this by debugging the service that receives the form data. The model does not have the changes made in the form. Am I doing anything wrong here?
I am embarrassed to say I've found the solution. The values simply weren't posted to the webflow for a lack of a 'name' attribute in each input. Using the same dynamically generated ID as a name did the job, and the bindings were correct for each item of the list. Like this:
<input th:id="'fromAge_'+${bracket.id}" th:name="'fromAge_'+${bracket.id}" th:field="*{ageBrackets[__${loop.index}__].fromAge}" />
Thanks to everyone who took the time to read this silly post. I'll be more careful next time ;)

Spring MVC JSP Form not pre-populating (or just back)

I am new to Spring MVC (and front end for that matter). I have a jsp with a form on. In my controller's GET method I add the command to the ModelMap. The page does some validation (greys out things when checkboxes clicked, etc). Then I go to the next page. The user is suppose to be able to click the back button (which is wired up in an tag - for graphics reasons apparently) and then make changes to their form. Except...the form is empty.
So my main question - what is the best way to go back (to my .do) and retain all the values in the form? There are some things that runs on my GET method...so this still needs to happen.
What I tried: I read somewhere that the command is suppose to pre-populate the form? So I allready have a command which I use to get the info....this is what I did (but it doesn't work). (I debugged and the command is populated with the values)
<form class="form-horizontal" commandName="myCommand" name="formdetail" id="formdetail" method="post">
Controller
#RequestMapping(method = RequestMethod.POST)
public View handleSubmit(#ModelAttribute MyCommand myCommand, BindingResult result, HttpServletRequest aHttpServletRequest){
WebUtils.setSessionAttribute(aHttpServletRequest, "goalDetailCommand", goalDetailCommand);
//Then do some redirecting
#RequestMapping(method = RequestMethod.GET)
public String show(ModelMap model, HttpServletRequest aHttpServletRequest, #ModelAttribute MyCommand myCommand) throws Exception {
myCommand = (MyCommand)WebUtils.getSessionAttribute(aHttpServletRequest, "myCommand");
model.addAttribute("myCommand", myCommand);
Thanks
EDIT:
I didn't have the path part in. Added it but still no luck. Is something else wrong?
<input type="text" class="amount input-medium" path= "amountToSave" id="amountToSave" name="amountToSave" placeholder="0000.00">
I debugged and the command is populated with the values
its working as expected, just reference the command object's values/fields in the form, if the command object has a getTitle for example do this :
<form:input path="title" maxlength="90" id="title"/>

Resources