Spring MVC return object from JSP - spring

I have a Spring MVC controller that sends a list to the view:
modelAndView.addObject("myList", List<foo>);
On the JSP, I iterate over this list creating a table where each row is a form with a submit button representing one foo instance. How can I get the single instance of foo represented by the row into the next controller? I tried putting it in an input type="hidden" but that didn't work.

A JSP is processed and rendered into HTML, which is just text.
When you submit a form, the browser takes the values of the <input> fields and serializes them in a specific format. With the typical application/x-www-form-urlencoded content type, the following <input> elements
<form action="/some/url" method="POST">
<input name="someField" value"someValue" type="text" />
<input name="someOtherField" value"someOtherValue" type="text" />
<input name="submit" value"submit" type="submit" />
</form>
The serialized format will look something like
someField=someValue&someOtherField=someOtherValue&submit=submit
Spring will take this String from the body of the request as request parameters and try to reconstruct your command object (ex. foo) from its values. The request above could be mapped to a class like
class Foo {
private String someField;
private String someOtherField;
public String getSomeField() {
return someField;
}
public void setSomeField(String someField) {
this.someField = someField;
}
public String getSomeOtherField() {
return someOtherField;
}
public void setSomeOtherField(String someOtherField) {
this.someOtherField = someOtherField;
}
}
Any fields it can't map, it ignores.

Related

tymeleaf checkbox without binding to an object or entity

i need to use check box to fire off a java if statement without having to bind the check box to any entity or object here is what i need in code
<input type="checkbox" th:field="${deleteImages}" th:checked="*{false}">Delete all images</input>
the controller would be this
public String updateProduct(#ModelAttribute("product") Product product,
#ModelAttribute("deleteImages") Boolean deleteImages) {
if (deleteImages) {
imageService.deleteImages(savedProduct.getId());
}
i can not figure out how to fix this checkbox.. i don't need it tied up to any object just return true when clicked and false when unchecked
If you want to do this without binding it to an object, just use RequestParams. Like this:
Form:
<form method="POST" class="action-buttons-fixed">
<input type="checkbox" name="deleteImages" />
<button>Go</button>
</form>
Java:
#PostMapping("/whatever")
public String updateProduct(#RequestParam(required = false, defaultValue = "false") boolean deleteImages) {
if (deleteImages) {
imageService.deleteImages(savedProduct.getId());
}
}
You can't, however, mix bound and unbound properties on the same form. If you want to do that, you should just create a new Form object that contains the both the product and the deleteImages variable.
class Form {
private Product product;
private boolean deleteImages;
}
And then change your form bindings appropriately.

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.

Thymeleaf + Spring: get rid of the default element id

Is there any way to suppress auto-generating ID attribute for elements while using th:field in Thymeleaf (2.1.4.RELEASE)? For example, given code:
<input type="text" th:field="*{year}" />
will produce the following HTML:
<input type="text" id="year" name="year" value="" />
What I want to achieve is (no id attribute):
<input type="text" name="year" value="" />
In JSP it was as easy as setting empty id:
<form:input path="year" id="" />
but Thymeleaf just replaces this empty attribute with the default-generated one.
Ok, I have looked inside the source code of Thymeleaf (2.1.4.RELEASE) and the method responsible for setting element id in Spring dialect is org.thymeleaf.spring4.processor.attr.SpringInputGeneralFieldAttrProcessor.doProcess(...) (source on Github) that calls org.thymeleaf.spring4.processor.attr.AbstractSpringFieldAttrProcessor.computeId(...) (source on Github). If you look at computeId(...), you will see that there is no simple way to set empty id.
So we need to do it in not a simple way :) Here it is:
I created a custom dialect and defined a custom attribute noid. The markup looks like this:
<input type="text" th:field="*{year}" thex:noid="true" />
There is a great tutorial explaining how to create and use custom dialects in Thymeleaf and below is the most important part: attribute processor responsible for removing id attribute from given element.
Important things to note:
high precedence value (9999) guarantees that this processor will be executed as the last one (so no other processors will modify id after this one is executed)
modification type is set to substitution so we are completely replacing value of id element
removeAttributeIfEmpty(...) returns true, rather self-explanatory, remove attribute if empty
getModifiedAttributeValues(...) sets id to empty value and because above-mentioned method returns true, id attribute is removed
Code:
public class NoIdAttrProcessor extends AbstractAttributeModifierAttrProcessor {
public NoIdAttrProcessor() {
super("noid");
}
#Override
public int getPrecedence() {
return 9999;
}
#Override
protected ModificationType getModificationType(Arguments arguments, Element element, String attributeName, String newAttributeName) {
return ModificationType.SUBSTITUTION;
}
#Override
protected boolean removeAttributeIfEmpty(Arguments arguments, Element element, String attributeName, String newAttributeName) {
return true;
}
#Override
protected boolean recomputeProcessorsAfterExecution(Arguments arguments, Element element, String attributeName) {
return false;
}
#Override
protected Map<String, String> getModifiedAttributeValues(Arguments arguments, Element element, String attributeName) {
Map<String, String> values = new HashMap<>(1);
values.put("id", "");
return values;
}
}
If you dont want to use this in id of you input field just assign the value to only the th:name field,
<input type="text" th:name="*{year}" />
will give you output like,
<input type="text" name="2015" />
Or You can use a string at the end to make the id generate different from the name attribute like this
<input type="text" th:name="*{year}" th:id="*{year} + '-year' " />
will give you the output,
<input type="text" name="2015" id="2015-year"/>

Spring MVC: How do I preserve model attributes in spring validation errors

I searched around on Stack Overflow, but could not find the solution to my query. I have a controller function that adds multiple model attributes on a GET request
#RequestMapping(method = RequestMethod.GET, value = "/showdeletesearchqueryform")
public String showDeleteSearchQuery(final Model model) {
if (LOG.isDebugEnabled()) {
LOG.debug("Fetching all the search query results.");
}
ImmutableList<ArtQueryResults> results = this.searchQueriesService
.getSearchQueries(APPNAME);
// Adding model attribute # 1
model.addAttribute("searchResults", results);
if (LOG.isDebugEnabled()) {
LOG.debug("\"searchResults\" model attribute has been intialized from "
+ results);
}
ArtDeleteQueryRequest request = new ArtDeleteQueryRequest();
request.setAppName(APPNAME);
if (LOG.isDebugEnabled()) {
LOG.debug("Model attribute initialized = " + request);
}
// Adding model attribute # 2
model.addAttribute("deletedAttributes", request);
return "deletesearchqueries";
}
My JSP
<div class="column-group">
<form:form method="POST" action="${pageContext.request.contextPath}/arttestresults/showdeletesearchqueryform" modelAttribute="deletedAttributes">
<form:errors path="*" cssClass="alert alert-danger column lg-units-5 units-2" element="div"/>
<form:hidden path="appName" id="appNameId" htmlEscape="true"/>
<div class = "units-1 column lg-units-12">
<!-- Hidden Key for app name. -->
<form:select path="idsToBeDeleted" id="IdsToBeDeletedSelectId">
<c:forEach items="${searchResults}" var="searchResult" varStatus="loop">
<form:option label="${searchResult.searchQuery}" value="${searchResult.id}" />
</c:forEach>
</form:select>
</div>
<div class="units-1 column lg-units-12">
<%-- This is a hack that make sure that form is submitted on a click. Not sure why form is not being submitted. --%>
<button class="button" type="submit" onclick="javascript:$('form').submit();">Delete Selected Queries</button>
</div>
</form:form>
My controller POST function
#RequestMapping(method = RequestMethod.POST, value = "/showdeletesearchqueryform")
public String deleteSearchQueries(
Model model,
#ModelAttribute(value = "deletedAttributes") #Valid final ArtDeleteQueryRequest request,
final BindingResult result) {
if (result.hasErrors()) {
LOG.warn("There are " + result.getErrorCount() + " validation errors.");
return "deletesearchqueries";
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("The ids to be deleted are " + request.getIdsToBeDeleted());
}
this.searchQueriesService.deleteSearchQueriesById(
ImmutableList.copyOf(request.getIdsToBeDeleted()));
return "redirect:/arttestresults/showdeletesearchqueryform";
}
}
If there is a validation failure, the model attribute searchResults is not being picked up when I return a view on error condition? Is there a way to preserve the other defined model attributes as well?
Seems that you need flash attributes which were added in spring 3.1. Please take a look at example/explanation:
http://viralpatel.net/blogs/spring-mvc-flash-attribute-example/
The get and the post are different requests. What you get in the post request, is only what comes from the form, so only the "deletedAttributes" model attribute and only the fields that are <input> in the JSP.
You need to put again the searchResults model attribute explicitely like you did in get method.
As suggested by M. Deinum, if one or more attribute(s) will be used by all methods in a controller, you can use a #ModelAttribute annotated method to put it (them) in model automatically.
You can also use SessionAttributes model attributes, that is attributes that are stored in session and not in request. But it is hard to have them properly cleaned from session if user do not post the form but go into another part of the application. You have an example of usage ofSessionAttributes` in Spring's Petclinic example.

Handling Multiple forms of the same type - impossible?

Consider the following scenario:
My form model
public class PersonForm {
#NotNull
private String name;
/*usual getters and setters*/
}
My controller:
#Controller
#SessionAttribute(types={ PersonForm.class })
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
And finally my JSP view
...
<c:forEach ...>
<form:form commandName="p${counter}">
... other form:elements and submit button goes here
</form:form>
</c:forEach>
...
As you can see I am trying to handle multiple forms of the same class type. The submit works -- it gets me to the submit(...) method just fine, and so does the validation. However re-rendering the page does not show me the expected error messages!
Even worse -- I checked what is being passed in the submit header and there is no indication whatsoever which form submitted, so there is no way to discriminate between one form on another. This led me to believe multiple forms of the same class type is not possible ...
Is there any other way I could do this (apart from Ajax) ?
Many thanks.
I managed to get this 'hack' to work. It is as what jelies has recommended so the credit goes all to him.
In simple terms, the concept is to pre-fill your view using the traditional <c:forEach> construct. The tricky part is whenever the 'Submit' button of that respective row is pressed, all of the information must be injected into a hidden form and force-submitted to the Controller. If the screen is rendered again with some errors, the script must be responsible of injecting the values back to the respective rows including the errors.
1) My model
public class PersonForm {
private String id;
#NotNull
private String name;
/*usual getters and setters*/
}
2) My controller
#Controller
#SessionAttribute(/* the hidden form name, the person list */)
public class MyController {
#RequestAttribute(...)
public String render(final ModelMap map) {
/* get list of info and for each info
* create a PersonForm and put it in the modelmap
* under key p0, p1, p2, ..., pn
*/
}
public String submit(final ModelMap map,
#Valid final PersonForm form,
final BindingResult result) {
if (result.hasErrors()) {
// return to page
} else {
// do necessary logic and proceed to next page
}
}
}
3) My view
...
<form:form commandName="personForm" cssStyle="display: none;">
<form:hidden path="id"/>
<form:hidden path="name" />
<form:errors path="name" cssStyle="display: none;" />
</form:form>
...
<c:forEach var="p" items="${pList}">
<input type="text" id="${ p.id }Name" value="${ p.name }" />
<!-- to be filled in IF the hidden form returns an error for 'name' -->
<span id="${ p.id }nameErrorSpan"></span>
<button type="button" value="Submit" onclick="injectValuesAndForceSubmit('${ p.id }');" />
</c:forEach>
...
<script type="text/javascript">
injectValuesAndForceSubmit = function(id) {
$('#id').val( id ); // fill in the hidden form's id
$('#name').val( $('#'+id+'name').val() ); //fill in the hidden form's name
$('#personForm').submit(); //submit!
}
$(document).ready(function() {
var id = $('#id').val();
if (id.trim().length == 0) {
//Empty. Nothing to do here as this is a simple render.
} else {
//The page seems to be returning from some sort of error ... pre-fill the respective row!
$('#'+id+'name').val($('#name').val());
var hiddenNameErrorSpan = $('#name.errors');
if (hiddenNameErrorSpan) {
$('#'+id+'nameErrorSpan').text(hiddenNameErrorSpan.html());
}
} //else
}
</script>
As you can see the view has the hairiest parts -- hopefully it will still proves to be useful for anyone who (unfortunately) comes across the same situation as mine. Cheers!
IMHO having multiple forms makes things overcomplicated (or at least with spring). Also, you are using multiple forms but only one is going to be submitted.
So, I suggest that the easiest way to manage this is using a unique hidden external form with person properties. When one of the buttons is pressed, fill accordingly the person properties of form and submit it. With this you are achieving the tipical spring form submit/validation.
Maybe this solution requires a bit work with JavaScript, but I don't know how to handle spring-mvc with multiple forms, I always tried to avoid it, due to previous unsuccessful attemps.

Resources