How evaluating fields from nested object in thymeleaf template? - spring-boot

Given two objects:
public class MVCmodel {
UUID uuid;
NestedObject nestedObject = new NestedObject ();
}
public class NestedObject {
#Size(min = 2, max = 99)
String name = "";
}
thymeleaf template
<tr>
<td>
<input required="true" type="text" th:name="name"
th:value="${mVCmodel.getNestedObject.getName()}"
>
<span th:if="${#fields.hasErrors('name')}"
th:errors="${mVCmodel.getNestedObject().getName()}"
>Name Error
</span>
</td>
</tr>
I get this Error:
Exception evaluating SpringEL expression: "fields.hasErrors('name')"
SpringEL tries to evaluat mVCmodel.name and must fail because there is no field name in MVCmodel.
How to evaluate the field mVCmodel.nestedObject.name?

Your class NestedObject has no field named 'name'.

If you're not using in a form using th:object, you need to use #fields.hasErrors like this:
${#fields.hasErrors('${mVCmodel.nestedObject.name}')}
If you are using th:object, something like this should work:
${#fields.hasErrors('nestedObject.name')}
Difficult to tell from the code you posted. Do you have getters and setters for each of your fields?

Related

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 can i store the value of path variable and use it in my controller method?

When i launch the below url
http://localhost:8090/QuickStartConsulting/quickstart/email?key=6226f7cbe59e99a90b5cef6f94f966fd
The following controller method is invoked
#RequestMapping(value = "/quickstart/email")
public String viewQuickStartEmailForm(#ModelAttribute(value = "quickbean")
QuickStartBean quickbean,BindingResult result,Model model) {
try {
//System.out.println(quickbean.getKey());
email=quickbean.getEmail();
model.addAttribute("email", email);
model.addAttribute("quickstartdatabean",new QuickStartBean() );
} catch (Exception e) {
e.printStackTrace();
}
return "quickstart/emaillogin";
}
Here is my emaillogin jsp page
<form:form id="requestForm" method="GET" modelAttribute="quickbean" ACTION="${pageContext.request.contextPath}/quickstart/email" >
<form:hidden path="key" />
<table>
<tr>
<td><span><img src="<c:url value="/views/images/youremailid.png"/>"> </img></span></td>
<td>
<form:input type="text" style="width: 300px;" id="name" path="email" title="xyz#email.com"/>
</td></tr>
<tr>
<td>
<input type="image" id="invite_btn" src="<c:url value="/views/images/submit.png"/>" title="create invite" width="170" height="32"/>
</td>
</tr>
</table>
</form:form>
How can i store the value of path variable 'key' and use it in my controller method? the emaillogin page is the first page and is not redirected from any other jsp page.
Use the #RequestParam annotation
public String viewQuickStartEmailForm(#RequestParam("key") String key, ....) {...}
Btw: #See #RequestParam vs #PathVariable for details and difference between request params and path variables
First define pathVariables as follows:
public String viewQuickStartEmailForm(#PathVariable Map pathVariables,..)
You can get the values by using below code snippet:
if (pathVariables != null) {
if (pathVariables.containsKey("fieldName") &&
!"undefined".equals(
(String)pathVariables.get("fieldName")) &&
!"null".equals(
(String)pathVariables.get("fieldName"))) {
params.setFieldName((String)pathVariables.get("fieldName"));
}
}
There is a difference between #PathVariable and #RequestParam. For example :
#RequestParam used for accessing the values of the query parameters.
URL : http://localhost:8081/Test/card?cardId=1012111
#RequestMapping("/card")
public String getCard(#RequestParam(value="cardId", required=true) String cardId){
System.out.println("print the value of the cardId : "+cardId)
.......
}
where as #PathVariable used for accessing the values from the URI template.
URL : http://localhost:8081/Test/card/1012111
#RequestMapping("/card/{cardId}")
public String updateCard(#PathVariable(value="cardId") String cardId){
System.out.println("print the value of the cardId : "+cardId)
.......
}
So you can correct your code and use any one of the above mentioned approach rather then mixing both of them.

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"/>

Simple JSP-Spring 3 dropdown list not working

I know this should be pretty easy but I'm stuck after trying several things.
I'm only trying to display in my jsp a basic dropdown list. Spring version is 3 so I want everything to work with annotations.
JSP form with dropdown list:
<form:form method="post" commandName="countryForm">
<table>
<tr>
<td>Country :</td>
<td><form:select path="country">
<form:option value="Select" label="Select" />
</form:select>
</td>
<tr>
<td colspan="3"><input type="submit" /></td>
</tr>
</table>
</form:form>
CountryForm.java is a plain object with a single String attribute "country", with its getters and setters.
Controller who deals with the GET request is the following:
#Controller
public class CountryFormController {
#RequestMapping(value = "MainView", method = RequestMethod.GET)
public String showForm(Map model) {
CountryForm cform = new CountryForm();
model.put("countryForm", cform);
return "MainView";
}
}
However, when I redirect to the JSP "MainView" I get the typical error:
org.apache.jasper.JasperException: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'countryForm' available as request attribute
org.apache.jasper.servlet.JspServletWrapper.handleJspException(JspServletWrapper.java:502)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:424)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
What am I doing wrong?
The select tag in the Spring TagLib needs to be provided with a collection, map or array of options. I'm not sure what you would like these to be so I will make some assumptions.
You need to include a collection, map or array of objects in your controller. Ideally you would have a Country class and create new instances for a set of countries. For the example to work with your code, I just created a static list of countries. Add the list to your model and then modify the select tag, setting the options to ${countries}. Assuming country is a field of type String on CountryForm with appropriate get/set methods, the country should data-bind to field when the form is submitted.
Controller
#Controller
public class CountryFormController {
#RequestMapping(value = "MainView", method = RequestMethod.GET)
public String showForm(Map model) {
List<CountryForm> cfs = new ArrayList<CountryForm>();
cfs.add("United States");
cfs.add("Canada");
model.put("countries", cfs);
model.put("countryForm", cform);
return "MainView";
}
}
JSP
<form:select path="countryForm.country" options="${countries}"/>
I have sample code at GitHub, try it an let me know. Look at landing.jsp and UserController
<form:select path="users[${status.index}].type" >
<form:option value="NONE" label="--- Select ---"/>
<form:options itemValue="name" itemLabel="description" />
</form:select>
HTH

Resources