How to send String[] from Thymeleaf multiple select form to Controller - spring

I have a template with a form:
<h2>Favorite States</h2>
<form action="#" th:action="#{/states}" name="states" method="post">
<select class="js-example-basic-multiple" multiple="multiple" style="width: 60%">
<option th:each="state: ${states}" th:utext="${state.name}"></option>
</select>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
I want to get a String[] back from this form, for example if user selected NY, CA, MA states, I want to get String[] selectedStates = {"NY", "CA", "MA"}; in my post map Controller. How can I do that?
I have tried
#GetMapping(path="/states")
public #ResponseBody String statesList (#RequestParam String[] name) {
}
But it says required parameter is not received.
Thank you!

You can convert the javascript array to JSON using below code.
JSON.stringify(array);
And in controller, accept it as String parameter and then convert it back to array using Jackson library as below:
ObjectMapper mapper = new ObjectMapper();
String [] array = mapper.readValue(jsonString, String[].class):

You can wrap it and pass as a model attribute

Related

How to use spring mvc with thymeleaf to iterate over a list?

My goal is to cycle through a list of objects, some to be displayed on the screen, others to be passed into a form as an object of which I can define certain aspects and then return to the controller the object and attribute to be modified.
The problem with the following approach is that the object in the list is not passed correctly to the form and thus gives an error because it is trying to make changes to a non-existent object.
If, on the other hand, I try to pass it as an object via ModelAndView it obviously works but does not have all the characteristics of the object I passed via the list.
Controller
#GetMapping("/")
public ModelAndView home() throws IOException {
ModelAndView mv = new ModelAndView();
mv.setViewName("home");
List<Comics> allComics = cs.getAll();
mv.addObject("comics", allComics);
return mv;
}
#PostMapping("/update")
public ModelAndView update(Comics com, #RequestParam("attr") String attr) throws IOException {
ModelAndView mv = new ModelAndView();
com.setLastRead(attr);
cs.updateAttributes(com);
mv.setViewName("home");
List<Comics> allComics = cs.getAll();
mv.addObject("comics", allComics);
return mv;
}
home.html
<html xmlns:th="http://www.thymeleaf.org">
<tr th:each="comic : ${comics}">
<td th:text="${comic.title}"></td>
<td th:text="${comic.lastChapter}"></td>
<td>
<a th:href="${comic.lastChapterLink}" target="_blank"
role="button" class="btn btn-md btn-block btn-info"> Link
</a>
</td>
<td></td>
<td>
<form th:action="#{/update}" th:object="${comic}" method="post">
<input type="text" name="attr" id="attr"/>
<button type="submit">Sub</button>
</form>
</td>
</tr>
PS: I cut out the head of the html page because it was full of non-relevant CDNs
How can I integrate Spring MVC with Thymeleaf to achieve the result whereby passing a list of objects can be displayed on the screen and used for other purposes within the html page without throwing errors?
Obviously if you know of more efficient methods to achieve the result I'm listening; I only used this method because I didn't know of any others.
Thank you
Answer to #RafaeldaSilva:
I agree, but that does not solve the problem.
Let me explain: the attribute I am going to modify through the form already has its name to allow what you wrote.
But the object iterated through:
tr th:each="comic : ${comics}">
cannot be passed directly as input, as it is a value that is taken from a list and exists individually only in the html page.
One might think of passing it as hidden input, but in this case the result would be the same (I have tried):
<form th:action="#{/update}" th:object="${comic}" method="post">
<input type="hidden" value="${comic}" name="com"/>
<input type="text" name="attr" id="attr"/>
<button type="submit">Sub</button>
</form>
#PostMapping("/update")
public ModelAndView update(#RequestParam("com") Comics com, #RequestParam("attr") String attr) throws IOException {
ModelAndView mv = new ModelAndView();
com.setLastRead(attr);
System.out.println("Comic: " + com);
cs.updateAttributes(com);
mv.setViewName("home");
List<Comics> allComics = cs.getAll();
mv.addObject("comics", allComics);
return mv;
}
Error:
[org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'com' for method parameter type Comics is present but converted to null]
try removing the type="hidden" to see what is present in this input, as I understand you are inserting an object by doing value="${comic}", this way the input should not send the value wanted..
change this: <input type="hidden" value="${comic}" name="com"/>
for this: <input type="text" value="${comic}" name="com"/>
so you can see what the form is sending to the controller, I believe it's the object's memory path, and not the data that exists in it.
in the input you must inform the attributes of the object, not the complete object..

Form with a select (drop-down) doesn't show error code

I have a form that contains a select to list all teachers by id in the system but it is not working properly.
Here is the code part of the form
and the corresponding path controller requests
I'm Using Thymeleaf and Spring Boot, so 'pr' corresponds a name for a variable of a repository of teachers.
<form th:action="#{/professor/updateProfessor/}" method="post" th:object="${professor}">
<div class= "form-group">
<label th:for = "id">Id</label>
<select th:field="*{id}">
<option
th:value = "${id}"
th:text = "${professor.id}">
</option>
</select>
</div>
<input type = "submit" value = "Add Professor">Save</button>
</form>
#GetMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView professorSelecao(){
ModelAndView atualizaProfessor = new ModelAndView("/atualizaProfessor");
atualizaProfessor.addObject("Add Professor");
return atualizaProfessor;
}
#PostMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView selecaoProfessor(){
ModelAndView pagSucesso = new ModelAndView("/pagSucesso");
pagSucesso.addObject(pr.findAll());
return pagSucesso;
}
From your controller, send a list of professors as per following to your view. Here you are associating the list of professors to the "professorList" :
model.addAttribute("professorList", pr.findAll());
And then to access above "professorList" in your thymeleaf do (similar to) this :
<option th:each="professor: ${professorList}" th:value="${professor}"> </option>
Not a full code but i hope you got the idea to get started.
For a full example, take a look here and here.
First of all what is not working? because I see a lot of things that may not work maybe because I don't see the all code or I am guessing some things, let's see
When you enter to your controller using
localhost:8080/professor/selecionaProfessor
are you expecting to use the form you put right? (the next code)
<form th:action="#{/professor/updateProfessor/}" method="post" th:object="${professor}">
<div class= "form-group">
<label th:for = "id">Id</label>
<select th:field="*{id}">
<option
th:value = "${id}"
th:text = "${professor.id}">
</option>
</select>
</div>
<input type = "submit" value = "Add Professor">Save</button>
</form>
because if that's correct you have a problem in your method:
#GetMapping(value = {"/selecionaProfessor"})
#ResponseBody
public ModelAndView professorSelecao(){
ModelAndView atualizaProfessor = new ModelAndView("/atualizaProfessor");
atualizaProfessor.addObject("Add Professor");
return atualizaProfessor;
}
you will get an error saying:
Neither BindingResult nor plain target object for bean name 'professor' available as request attribute
So you're missing to add the Key professor and a List so change:
atualizaProfessor.addObject("Add Professor");
with something like:
atualizaProfessor.addObject("professor", someListOfProfessorHereFromTheService (List<Professor>));
and it should work if your profesor object have the attributes you have on your form.
Now let's suppose that that worked before and the error wasn't that.
When you enter to your form if you see here:
form th:action="#{/professor/updateProfessor/}"
you're using updateProfessor I don't see that on your controller you have
#PostMapping(value = {"/selecionaProfessor"})
So I think that you should change the url mapping inside the html page or the controller and use the same as error 1, map the object using a key and value and iterate the list into the html as I showed in the 1st error
Hope it helps

Spring Boot error while submitting form

I'm trying to add a table to the database via a form. The entity being created is called Album and it has 2 fields, Artist and Genre. Each of these two are separate entities. These 2 fields are annotated with #ManyToOne
#ManyToOne
private Artist artist;
#ManyToOne
private Genre genre;
When I submit the form, this is the error im getting:
There was an unexpected error (type=Internal Server Error, status=500).
Error during execution of processor 'org.thymeleaf.spring4.processor.attr.SpringOptionFieldAttrProcessor' (album/add:52)
The following code is part of my controller:
#RequestMapping({"/add", "/add/"})
public String adminAlbumAdd(Model model) {
model.addAttribute("album", new Album());
model.addAttribute("artists", artistService.list());
model.addAttribute("genres", genreService.list());
return "album/add";
}
#RequestMapping( value = "/save", method = RequestMethod.POST )
public String save(#Valid Album album, BindingResult bindingResult, Model model) {
if(bindingResult.hasErrors()) {
model.addAttribute("artists", artistService.list());
model.addAttribute("genres", genreService.list());
return "album/add";
} else {
Album savedAlbum = albumService.save(album);
return "redirect:/album/view/" + savedAlbum.getAlbumId();
}
}
And the following code is part of the thymeleaf template:
<div th:class="form-group" th:classappend="${#fields.hasErrors('artist')}? 'has-error'">
<label class="col-sm-2 control-label">Artist <span class="required">*</span></label>
<div class="col-md-10">
<select class="form-control" th:field="*{artist}">
<option value="">Select Artist</option>
<option th:each="artist : ${artists}" th:value="${artist.artistId}" th:text="${artist.artistFirstName + ' ' + artist.artistFirstName}">Artists</option>
</select>
<span th:if="${#fields.hasErrors('artist')}" th:errors="*{artist}" th:class="help-block">Artist Errors</span>
</div>
</div>
<div th:class="form-group" th:classappend="${#fields.hasErrors('genre')}? 'has-error'">
<label class="col-sm-2 control-label">Genre <span class="required">*</span></label>
<div class="col-md-10">
<select class="form-control" th:field="*{genre}">
<option value="">Select Genre</option>
<option th:each="genre : ${genres}" th:value="${genre.genreName}" th:text="${genre.genreName}">Genres</option>
</select>
<span th:if="${#fields.hasErrors('genre')}" th:errors="*{genre}" th:class="help-block">Genre Errors</span>
</div>
</div>
What is causing this error ?
The issue turned out to be related to the repository. I was extending CrudRepository, but the id was of type int. Once i changed that, it worked.
Firstly, you might consider using same mapping for GET/POST requests as a standard like:
#GetMapping("/new")
...
#PostMapping("/new")
Also #Valid Album album parameter should be annotated as #ModelAttribute.
You should not add model attributes if binding result has errors. (Actually, you should not add any model attribute for a POST method.)
You should not create that savedAlbum object with albumService.save().
That method should be void.
I will advise against posting directly to your database object. You should rather create a DTO class, say AlbumDto, that will map the classes like so:
public class AlbumDto {
...
private long genreId;
private long artistId;
// Getters & Setters
}
You can then convert it to your Album object, lookup the corresponding Genre and Artist in your controller, set them on the Album object and then save.

How do I iterate over all my model attributes on my JSP page?

I'm using Spring 3.2.11.RELEASE with JBoss 7.1.3.Final and Java 6. I have this method in a controller
#RequestMapping(value = "/method", method = RequestMethod.GET)
public String myMethod(final Model model,
final HttpServletRequest request,
final HttpServletResponse response,
final Principal principal)
...
model.addAttribute("paramName", "paramValue");
Notice how I add attributes into my model. My question is, on the JSP page that this page serves, how do I iterate over all the attributes in my model and output them as HIDDEN input fields with the name of the INPUT being the attribute name and the value being what I inserted in the model using that attribute?
Edit: In response to the answer given, here was the output to the JSP solution. Note there are no model attributes in there.
<input type='hidden' name='javax.servlet.jsp.jspRequest' value='org.springframework.web.context.support.ContextExposingHttpServletRequest#7a0a4c3f'>
<input type='hidden' name='javax.servlet.jsp.jspPageContext' value='org.apache.jasper.runtime.PageContextImpl#3939794a'>
<input type='hidden' name='appVersion' value='???application.version???'>
<input type='hidden' name='javax.servlet.jsp.jspResponse' value='org.owasp.csrfguard.http.InterceptRedirectResponse#722033be'>
<input type='hidden' name='javax.servlet.jsp.jspApplication' value='io.undertow.servlet.spec.ServletContextImpl#14c1252c'>
<input type='hidden' name='org.apache.taglibs.standard.jsp.ImplicitObjects' value='javax.servlet.jsp.el.ImplicitObjectELResolver$ImplicitObjects#23c27a49'>
<input type='hidden' name='javax.servlet.jsp.jspOut' value='org.apache.jasper.runtime.JspWriterImpl#b01a1ba'>
<input type='hidden' name='javax.servlet.jsp.jspPage' value='org.apache.jsp.WEB_002dINF.views.lti.launch_jsp#1dcc48bf'>
<input type='hidden' name='javax.servlet.jsp.jspConfig' value='io.undertow.servlet.spec.ServletConfigImpl#3fd40806'>
Model attributes are "request scope" objects
you can do the following (I use JSTL):
<c:forEach items="${requestScope}" var="par">
<c:if test="${par.key.indexOf('attrName_') > -1}">
<li>${par.key} - ${par.value}</li>
</c:if>
</c:forEach>
Since with no filter you will have all the request scope objects, I filtered by the model attributes we wanted to check
I tested by using this code:
#RequestMapping(method = { RequestMethod.GET }, value = { "/*" })
public String renderPage(Model model) throws Exception
{
String requestedUrl = req.getRequestURI();
int indice = requestedUrl.lastIndexOf('/');
String pagina = requestedUrl.substring(indice + 1);
try
{
String usernameUtente = "default username utente";
if (StringUtils.hasText(getPrincipal()))
{
usernameUtente = getPrincipal();
}
model.addAttribute("usernameUtente", usernameUtente);
model.addAttribute("webDebug", webDebug);
for(int i = 0; i<10; i++)
{
model.addAttribute("attrName_"+i, "attrValue_"+i);
}
return pagina;
}
catch (Exception e)
{
String message = "Errore nell'erogazione della pagina " + pagina;
logger.error(message, e);
return "genericError";
}
}
And this is what I see as output (I omitted the not relevant prints but please note you'll print ALL the request scope objects:
attrName_0 - attrValue_0
attrName_1 - attrValue_1
attrName_2 - attrValue_2
attrName_3 - attrValue_3
attrName_4 - attrValue_4
attrName_5 - attrValue_5
attrName_6 - attrValue_6
attrName_7 - attrValue_7
attrName_8 - attrValue_8
attrName_9 - attrValue_9
I hope this can help
Angelo
For avoid headache with parameters added by Spring and Servlet container, it is better to use separate map for pass values into the model. Just use #ModelAttribute and Spring will create and add it to the model automatically:
#RequestMapping(value = "/method", method = RequestMethod.GET)
public String myMethod(final Model model, #ModelAttribute("map") HashMap<String, Object> map) {
map.put("paramName1", "value1");
map.put("paramName2", "value2");
//...and so on
}
Now you can iterate this map in JSP:
<c:forEach items="${map.keySet()}" var="key">
<input type="hidden" name="${key}" value="${map[key]}"/>
</c:forEach>
Also you can access to every item of map next way:
<c:out value="${map.paramName1}"/>
<c:out value="${map.paramName2}"/>
...
If you don't need some parameter to be iterable, add it into the original ModelMap istead of separate map.
In essence all you need is to itterate on all the page attributes. Depending on what you use on your jsp (scriptlets, jstl, or smthing like thymeleaf for html):
Scriptlet:
<form>
<% Session session = request.getSession();
Enumeration attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()) {
String name = attributeNames.nextElement();
String value = session.getAttribute(name);
%>
<input type='hidden' name="<% name %>" value="<% value %>">
<%
}
%>
</form>
JSTL:
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<h3>Page attributes:</h3>
<form>
<c:forEach items="${pageScope}" var="p">
<input type='hidden' name='${p.key}' value='${p.value}'>
</c:forEach>
</form>
Thymeleaf:
<form>
<input th:each="var : ${#vars}" type='hidden' name="${var.key}" value="${var.value}">
</form>
Simply you can iterate using foreach tag of Jstl.
<c:forEach items="${requestScope}" var="var">
<c:if test="${ !var.key.startsWith('javax.') && !var.key.startsWith('org.springframework')}">
<input type="hidden" name="${var.key}" value="${var.value}" />
</c:if>
</c:forEach>
Request attributes from spring framework and from Servlet do have prefixes, you don't need to add prefix to your request attributes.
Rather you can ignore all those attributes which have prefix "org.springframework" or "javax.".
You can try this:
#RequestMapping(value = "/method", method = RequestMethod.GET)
public String myMethod(final Model model,
final HttpServletRequest request,
final HttpServletResponse response,
final Principal principal)
...
//Create list for param names and another list for param values
List<String> paramNames = new ArrayList();
List<String> paramValues = new ArrayList();
paramNames.add("paramName1");
paramValues.add("paramValue1");
paramNames.add("paramName2");
paramValues.add("paramValue2");
//paramValue1 is the value corresponding to paramName1 and so on...
//add as many param names and values as you need
...
//Then add both lists to the model
model.addAttribute("paramNames", paramNames);
model.addAttribute("paramValues", paramValues);
Then in the JSP, you can iterate over paramNames list, and use the varStatus.index to get the index of current round of iteration and use it to pull the value of corresponding param value from the paramValues list. Like this -
<form id='f' name='myform' method='POST' action='/path/to/servlet'>
<c:forEach items="${paramNames}" var="paramName" varStatus="status">
<input type='hidden' name='${paramName}' value='${paramValues[status.index]}'>
</c:forEach>
</form>
You can add other input elements to the form as needed but the above should generate all the hidden input elements for each of your parameter that you set in the Model.

how to do binding in Spring annotated request parameter?

i have a controller that is using annotation for request mapping and requestParam.
the controller is working fine. However when submitting a command object with array, spring will crap out saying array index out of bound. i am guessing there is something wrong with binding but don't know how to fix it.
to be more specific, in eclipse i would set debugger at the beginning of the controller, and when submitting the form (by hitting a input submit button) eclipse debugger will not trigger and i will see array index out of bound error in console.
the controller is something like this:
#RequestMapping(value = {"/internal/pcsearch.dex", "/external/pcsearch.dex"},
method = {RequestMethod.POST, RequestMethod.GET})
public ModelAndView executeProductCatalogSearch(
HttpServletRequest request,
#RequestParam(value = "cat" ,required = false) String cat,
#RequestParam(value = "brand" ,required = false) String brand,
#ModelAttribute("command") ProductCatalogCommand cmd
){
[edit]
and the jsp is like:
<form name="pForm"
id="pForm"
action="<c:url value="psearch.dex"><c:param name="cat" value="${cat}"/></c:url>"
method="POST"
style="display:inline;">
...
...
<c:forEach var="model" items="${models}" varStatus="modelLinkStatus">
<script>
var modelImg<c:out value="${modelLinkStatus.index}"/>Src = '<c:out value="${model.altModelImage}"/>';
</script>
<spring:bind path="command.models[${modelLinkStatus.index}].modelSkusDisplayed">
<input type="hidden" name="<c:out value="${status.expression}"/>" id="<c:out value="${status.expression}"/>" value="<c:out value="${status.value}"/>"/>
</spring:bind>
<spring:bind path="command.updateCartButton">
<input type="submit" value="<spring:message code="orderEntryMessages.ecatalog.button.addToCart" text="Add to Cart" htmlEscape="yes" />" name="<c:out value="${status.expression}"/>" id="<c:out value="${status.expression}"/>" class="sub_buttons"/>
</spring:bind>
...
and the command object declare the model array as:
private List<ModelLink> models = new ArrayList<ModelLink>();
where modelLink is a custom ds.
the first foreach tag handle the the model command object and the 2nd part is the submit button i clicked on.
i think you should use AutoPopulatingList as models to bind list to view and controller. for example please refer link. This might resolve your problem of index.

Resources