Handling map-like request parameters in Spring MVC - spring-mvc

Say I have a form for a list of questions where I need a 0/1 answer. I could easily model a static list with radios, something like
<input type="radio" name="question1" value="0">
<input type="radio" name="question1" value="1">
<br>
<input type="radio" name="question2" value="0">
<input type="radio" name="question2" value="1">
#RequestMapping("/answer")
public String answer(Integer question1, Integer question2) {
But I have a dynamic list of questions instead, where each question has a numeric ID. I therefore tried to model it like the following (the HTML is dynamically created with an iteration on the question list):
<input type="radio" name="question[42]" value="0">
<input type="radio" name="question[42]" value="1">
<br>
<input type="radio" name="question[51]" value="0">
<input type="radio" name="question[51]" value="1">
where 42 and 51 are the question id.
I was expecting to capture all values in a Map parameter of my Spring controller, like so:
#RequestMapping("/answer")
public String answer(#RequestAttribute("question") HashMap<Integer, Integer> question) {
It didn't work (the method isn't called).
I also tried with string ids:
<input type="radio" name="question['42']" value="0">
#RequestMapping("/answer")
public String answer(#RequestAttribute("question") HashMap<String, Integer> question) {
Same as before.
It only works if I use a map of string/string, but in this case I get all request parameters in the map, which I will then need to parse:
#RequestMapping("/answer")
public String answer(#RequestAttribute("question") HashMap<String, String> question) {
--> question.keys: "question[42]", "question[51]"
So what is the proper way of handling dynamic radios, or more generally map-like request parameters?

I don't know why, but it works if I put the map inside a bean:
public class QuestionForm {
private HashMap<Long, String> question;
public HashMap<Long, String> getQuestion() {
return question;
}
public void setQuestion(HashMap<Long, String> question) {
this.question = question;
}
}
#RequestMapping("/answer")
public String answer(QuestionForm questionForm) {

Related

Data binding for 20 <inputs> but only one common onchange-method

I have a calculation sheet where the user can enter several positions (basic price, multiple options) that should be added up to a total. I am struggling in binding the events to add up all the positions to a final total. The problem is, that the #bind="" does not allow me to call one onchange events for every input, hence I would have to implement one event handler per position (circa 20 in total).
<input type="number" #bind="calc.BasicPrice" class="form-control" />
<input type="number" #bind="calc.Option" class="form-control" />
<input type="number" #bind="calc.Total" class="form-control" readonly/>
#code
{
private CalculationModel calc = new CalculationModel();
}
What I have tried so far is to bind a UpdateBottomLine() method to the input onchange events like that:
<input type="number" value="#calc.BasicPrice"
#onchange="(e) => CalculateBottomLines(calc.BasicPrice.GetType(), e)"
class="form-control" />
#code {
private void CalculateBottomLines(Type field, ChangeEventArgs input)
{
input.Value = input.Value.ToString().Replace(".", ",");
// Update the input variable
calc.BasicPrice = Convert.ToDecimal( input.Value.ToString() ); // <-- The name of the receiving variable has to be determined dynamically, depending on the field that was changed
// Update the total
calc.Total = calc.BasicPrice + calc.Option;
}
}
Now unfortunately, while this changes my total when i change the basic price, this does not yet work dynamically but is only implemented for the basic price. I would need a solution where I can pass a reference to the field that was changed to the CalculateBottomLines() method.
Here I am using the property setter to recalculate the total and #bind:event="oninput" to trigger on input.
<input type="number" #bind="calc.BasicPrice" #bind:event="oninput" class="form-control" />
<input type="number" #bind="calc.Option" #bind:event="oninput" class="form-control" />
<input type="number" #bind="calc.Total" class="form-control" readonly />
#code {
SomeModel calc = new SomeModel();
public class SomeModel
{
public double BasicPrice
{
get => basicPrice;
set { basicPrice = value; UpdateTotal(); }
}
public double Option
{
get => option;
set { option = value; UpdateTotal(); }
}
public double Total { get; set; }
internal void UpdateTotal()
{
Total = BasicPrice + Option;
}
private double basicPrice;
private double option;
}
}

Saving record in a proper way

I have a problem with saving records to DB with Spring-Mvc and Thymeleaf.
When I click "Update" button on record, to enter the update form (included beneath), all values are in place correctly, but, when I want to subbmit, an error occur. There is no any stacktrace in console, only error in web page, that I am not able to solve.
This is my code:
Controller:
#GetMapping("/{maltId}")
public ModelAndView showMalt(#PathVariable("maltId") Long maltId) {
ModelAndView mav = new ModelAndView("malt/malt-show");
mav.addObject(maltService.findById(maltId));
return mav;
}
#GetMapping("/{maltId}/edit")
public String initUpdateMaltForm(#PathVariable("maltId") Long maltId, Model model) {
model.addAttribute("malt", maltService.findById(maltId));
return VIEWS_MALT_CREATE_OR_UPDATE_FORM;
}
#PostMapping("/{maltId}/edit")
public String processUpdateMaltForm(#Valid Malt malt, BindingResult result, #PathVariable("maltId") Long maltId) {
if (result.hasErrors()) {
return VIEWS_MALT_CREATE_OR_UPDATE_FORM;
} else {
malt.setId(maltId);
Malt savedMalt = maltService.save(malt);
return "redirect:/malt/" + savedMalt.getId();
}
}
Model:
#Column(name="malt_name")
private String maltName;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="producer_id")
private Producer producer;
#Column(name="malt_filling")
private int maltFilling;
#Column(name="malt_ebc")
private int maltEbc;
#Column(name="malt_usage")
private String maltUsage;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="country_id")
private Country country;
#ManyToMany(mappedBy="malts")
private Set<Batch> batches;
This is the view:
<body>
<form th:object="${malt}" th:action="#{/malt/}" method="post">
<input type="hidden" th:field="*{id}" />
<label>Malt name:</label>
<input type="text" class="form-control" th:field="*{maltName}" />
<label>Producer:</label>
<input type="text" class="form-control"
th:field="*{producer.producerName}" />
<label>Country:</label>
<select class="form-control" th:field="*{country.id}">
<option value="0">Select country</option>
<option th:each="country : ${countries}"
th:value="${country?.id}"
th:text="${country?.countryName}">
</option>
</select>
<label>Malt filling:</label>
<input type="text" class="form-control"
th:field="*{maltFilling}" />
<label>Malt usage:</label>
<input type="text" class="form-control"
th:field="*{maltUsage}" />
<label>Malt EBC:</label>
<input type="number" class="form-control"
th:field="*{maltEbc}" />
<button class="submit-button" type="submit">Submit</button>
</form>
</body>
When I hit Submit button, I get this error:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed May 15 22:46:22 CEST 2019
There was an unexpected error (type=Not Found, status=404).
No message available
I have tried couple of different approaches, but nothing helps, and since there is no stacktrace in console, I have no idea what is wrong here.
Link to repo: https://github.com/fangirsan/maruszka-new
No stack trace 404 normally indicates that there is no mapping. Since you have, potentially, provided only a part of your Controller I assume that the causation for this is the code in your view right here:
<form th:object="${malt}" th:action="#{/malt/}" method="post">
The action takes to ("/malt/"), however, your controller has not got mapping for this?!
I expect that this should fix it:
<form th:object="${malt}" th:action="#{${'/' + malt.id + '/edit'}}" method="post">
Update
Had a look at your controller and you have the following annotations on your class
#Controller
#RequestMapping("/malt")
public class MaltController{..
#RequestMapping("/malt") will now make your path to save ../malt/{id}/edit'. The code below now should work:
<form th:object="${malt}" th:action="#{${'/malt/' + malt.id + '/edit'}}" method="post">
On using "#{${...}}"
#{} is a link variable, contents within this tag will be appended to the applications root context, e.g., at Stack Overflow #{'/posts'} would result with https://stackoverflow.com/posts
The ${} is a variable expression which will return a String or the object's .toString() value.
If we want to pass a variable within #{} link variable we must include the ${} variable within it, thus resulting in :
#{${'/hope/this/helps' + yourVariable}}

How to use input radio button with thymeleaf and Spring MVC

I would like to get a destination address from a input radio button list. The DestinationAddress class is the following:
public class DestinationAddress {
private Integer destinationAddressId;
private String name;
private Location location;
private User owner;
public DestinationAddress(String name, Location location, User owner) {
this.name = name;
this.location = location;
this.owner = owner;
}
public DestinationAddress() {
}
// getter and setter
}
The controller who handles the get and post is the following:
#PreAuthorize("hasRole('USER')")
#GetMapping(value = "/select-address")
public String selectAddress(Principal principal, Model model) {
List<DestinationAddress> addresses = destinationAddressService.getAllByUsername(principal.getName());
model.addAttribute("destinationAddresses", addresses);
model.addAttribute("destinationAddress", new DestinationAddress());
return "purchase/select-address";
}
#PreAuthorize("hasRole('USER')")
#PostMapping(value = "/select-address")
public String selectAddress(#ModelAttribute DestinationAddress destinationAddress, Principal principal) {
Purchase purchase = purchaseService.addPurchase(principal.getName(), destinationAddress);
return "redirect:/purchases/pay/" + purchase.getPurchaseId();
}
And the html page is the following:
<form th:object="${destinationAddress}" method="post">
<fieldset>
<legend>Your addresses</legend>
<ul>
<li th:each="destinationAddress : ${destinationAddresses}">
<input type="radio" th:field="*{destinationAddressId}" th:value="${destinationAddress.destinationAddressId}" />
<label th:for="${#ids.prev('destinationAddress.destinationAddressId')}" th:text="${destinationAddress}"></label>
</li>
</ul>
</fieldset>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
The error message is the following:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'destinationAddressId' available as request attribute
I don't know what's the problem here. I don't know which type will the form return to the controller. So I don't know which variable pass to the model and which one to get from the post controller method. Integer or DestinationAddress? I cannot find anything googling it, just small pieces of code without any explanations. Any suggestions?
I found a solution to my problem. I changed the html page, now it looks like this:
<form th:object="${address}" method="post">
<fieldset>
<legend>Your addresses</legend>
<ul>
<li th:each="destinationAddress : ${destinationAddresses}">
<input type="radio" th:field="${address.destinationAddressId}" th:value="${destinationAddress.destinationAddressId}" />
<label th:for="${destinationAddress.destinationAddressId}" th:text="${destinationAddress}"></label>
</li>
</ul>
</fieldset>
<p><input type="submit" value="Submit" /> <input type="reset" value="Reset" /></p>
</form>
I changed the name of the object inside the model because it was the same as the name of the temp destinationAddress of the loop. I also replaced '{#ids.prev(' because it was giving me an error:
Cannot obtain previous ID count for ID ...
Now it works fine.

ASP.NET WEB API Not able to retrieve Post Parameters

I trying to retrieve post parameters in web API but I do get null values everytime.
My html
<form method="POST" action="http://localhost:16192/update" name="myform">
<input name="title" type="text"/>
<input name="isbn" type="text"/>
<input name="author" type="text"/>
<input type="submit" value="Submit"/>
</form>
And My WebAPI
[HttpPost]
[Route("UPDATE/")]
public String updateRecord([FromBody]String title,String isbn="", String author="")
{
return "Updated";
}
The updateRecord method is being called but I always get null values. Any help would be greatly appreciated.
[HttpPost]
[Route("UPDATE/")]
public String updateRecord([FromBody]dynamic values)
{
var title = values.title.Value;
....
return "Updated";
}
or you can create a DTO object (paragraph 2):
http://encosia.com/using-jquery-to-post-frombody-parameters-to-web-api/

Why is my hidden input writing: value="value" instead of true/false?

I have an MVC4 site, with (as part of a hidden form):
<input name="somefield" type="hidden" value="#ViewBag.Test"/>
The value of ViewBag.Test is true. The form field is posting to an input parameter of the form:
public ActionResult SomeAction(bool somefield = false, ...)
but somefield is always false. Upon investigating, I see that the source code has:
<input name="somefield" type="hidden" value="value"/>
However, I know this used to work. What has happened, and what can I do?
This behaviour changed between MVC3 and MVC4. In MVC3, if you have:
<input name="somefield" type="hidden" someprop="#(SomeBooleanExpression)"/>
it would write very literally:
<input name="somefield" type="hidden" someprop="True"/>
However, in MVC4, it follows the "checkbox" etc rules, so if the value is true you get:
<input name="somefield" type="hidden" someprop="someprop"/>
and if it is false it is omitted completely:
<input name="somefield" type="hidden"/>
To get around this, consider .ToString():
<input name="somefield" type="hidden"
someprop="#(SomeBooleanExpression.ToString())"/>
which then follows string rules rather than boolean rules.
initialized Boolean values bool something =false;
then convert this value into string like,
<input name="somefield" type="hidden" value="#something.ToString()>
OR
initialized string something ="false";
< input name="somefield" type="hidden" value="#something" >
then we can read
public ActionResult Somemethod(bool something) => you can get Boolean value here
{
}

Resources