Refer to html elements by class in spring mvc controller - java

I know how to refer to HTML elements by name with request.getParameter("foo");
But I have various groups of elements within a form that each have a seperate 'class' attribute. Is there any way to refer to these by their class names?
My controller is in the form below:
#Controller
#MultipartConfig()
public class FooController {
//get parameters
return "view";
}
My HTML input elements are in the form below:
<input class="bar" type="checkbox" name="elementName" />
Basically I want to say in my controller, "give me all the elements of class 'bar'". Is it possible?

No, a Controller doesn't actually know about the contents of the view.
Parameters such as
request.getParameter("foo")
comes from the HTTP request and not from reading the HTML page. The "foo" part comes from the "name" attribute of the form element when the form is submitted.
Instead, you could use some JavaScript to get a list of elements that match particular CSS classes and then dynamically edit your form submit to GET/POST the contents of these elements to your controller.

No it is not possible, because when the user POSTs data to your webapp, the HTML element classnames are not sent - only the name and values of the input elements.

As per the above, no. But: if you use Spring Forms with its taglib and a backing form class, Spring will automagically bind the form elements to the form class members. Your handler method then changes to:
#Controller
public class MyController {
#RequestMapping("/foo/somevalue.do")
public String FooController (
#ModelAttribute("myForm") MyFormBean formBean
){
return "view"; // name of the JSP
}
}
The form bean:
class MyFormBean
private String elementName
//getters and setters
Your JSP:
<form:input path="elementName" />

Related

Bind list or array to form in Thymleaf

On my website I have a few checkboxes each of them contains id in value attribute. After submitting form I'd like to have a list containing ids of checked checkboxes to be passed to the controller. That's how I want to make new page comparing n products.
Controller can accept List<Long> or long[]. That's what I have for now:
HTML:
<form th:action="#{/comparison}" th:object="${productsComparison}" target="_blank" method="post">
<table>
<tr data-th-each="item, iter : ${items.item}">
<td>
<input type="checkbox" th:name="|productsComparison.ids[${iter.index}]|" th:value="${item.id}"/>
</td>
</tr>
</table>
I've added to my controller List<Long> wrapped in ProductComparison with appropriate getters and setters. After submitting form list is always null.
Controller:
#RequestMapping("/productsPage")
public String showProducts(Model model) {
ProductsComparison productsComparison = new ProductsComparison();
model.addAttribute("productsComparison", productsComparison);
}
#RequestMapping("/comparison")
public String compareProducts(#ModelAttribute ProductsComparison productsComparison) {
System.out.println("List: " + productComparison.getIds());
// Always shows null
return "comparison";
}
public class ProductsComparison {
private List<Long> ids;
// Getters & setters
}
The underscores __ are ThymeLeaf's syntax for a preprocessing expression. Or in layman's terms, the stuff inside of the underscores are processed before the rest of the expression.
This is important because your expression is using an array, and the ${iter.index} portion of it gives the array index.
If you are curious or if anyone else stumbles upon this. th:name is no different than the html attribute name. The ThymeLeaf engine will just shove a name attribute into the html entity or overwrite the old one. ThymeLeaf has a ton of properties like this. th:field is a totally different beast though. It is what tells ThymeLeaf to bind the input of your form to the back forming bean or your model attribute, th:object="${productsComparison}". So with the expression th:field="*{ids[__${iter.index}__]}", Thymeleaf is going to call productsComparison.getIds[0] and the corresponding setter.
Note about th:name if you are submitting with Javascript of any kind, you probably want to use this. Most likely you are serializing your form, and the JSON created by this method call will use the name property.
If you want to know more about ThymeLeaf preprocessing expressions, there is a bit in the documentation about it.
I've finally found solution. Instead of th:name I had to use th:field.
th:field="*{ids[__${iter.index}__]}"
Because it's field I didn't have to specify object productsComparison before ids. ids field is List<String>.
To be honest I have no idea what underscores do, I will be glad if someone could explain.

How can I include(load) more than one jsp files in spring mvc controller class?

How can I include(load) more than one jsp files in spring mvc conroller class?
eg:
#RequestMapping("/")
public void welcome() {
include("file1.jsp");
include("file2.jsp");
include("file3.jsp");
}
At framework level, No you cannot do it. However, there is a hack you could try.
Create an array of name of pages you need to merge. Send it as a parameter to a dummy empty page.
The dummy page does this:
<c:forEach var="page" items="${pages}">
<c:import url="${page}"></c:import>
</c:forEach>
This will iterate all the JSP files that you wish to show as a single view.
Hope this helps. Let me know if any further help is required.

Java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'loginBean' available as request attribute [duplicate]

This is meant to be an extensive canonical question & answer post for these types of questions.
I'm trying to write a Spring MVC web application where users can add movie names to an in-memory collection. It's configured like so
public class Application extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {};
}
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { SpringServletConfig.class };
}
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
and
#Configuration
#ComponentScan("com.example")
public class SpringServletConfig extends WebMvcConfigurationSupport {
#Bean
public InternalResourceViewResolver resolver() {
InternalResourceViewResolver vr = new InternalResourceViewResolver();
vr.setPrefix("WEB-INF/jsps/");
vr.setSuffix(".jsp");
return vr;
}
}
There's a single #Controller class in the com.example package
#Controller
public class MovieController {
private final CopyOnWriteArrayList<Movie> movies = new CopyOnWriteArrayList<>();
#RequestMapping(path = "/movies", method = RequestMethod.GET)
public String homePage(Model model) {
model.addAttribute("movies", movies);
return "index";
}
#RequestMapping(path = "/movies", method = RequestMethod.POST)
public String upload(#ModelAttribute("movie") Movie movie, BindingResult errors) {
if (!errors.hasErrors()) {
movies.add(movie);
}
return "redirect:/movies";
}
public static class Movie {
private String filmName;
public String getFilmName() {
return filmName;
}
public void setFilmName(String filmName) {
this.filmName = filmName;
}
}
}
WEB-INF/jsps/index.jsp contains
<%# taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%# taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Movies</title>
</head>
<body>
Current Movies:
<c:forEach items="${movies}" var="movieItem">
<ul>
<li>${movieItem.filmName}</li>
</ul>
</c:forEach>
<form:form>
<div>Movie name:</div>
<form:input path="filmName" type="text" id="name" />
<input type="submit" value="Upload">
</form:form>
</body>
</html>
The application is configured with context path /Example. When I send a GET request to
http://localhost:8080/Example/movies
the request fails, Spring MVC responds with a 500 status code, and reports the following exception and stack trace
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
org.springframework.web.servlet.support.BindStatus.<init>(BindStatus.java:144)
org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getBindStatus(AbstractDataBoundFormElementTag.java:168)
org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getPropertyPath(AbstractDataBoundFormElementTag.java:188)
org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.getName(AbstractDataBoundFormElementTag.java:154)
org.springframework.web.servlet.tags.form.AbstractDataBoundFormElementTag.writeDefaultAttributes(AbstractDataBoundFormElementTag.java:117)
org.springframework.web.servlet.tags.form.AbstractHtmlElementTag.writeDefaultAttributes(AbstractHtmlElementTag.java:422)
org.springframework.web.servlet.tags.form.InputTag.writeTagContent(InputTag.java:142)
org.springframework.web.servlet.tags.form.AbstractFormTag.doStartTagInternal(AbstractFormTag.java:84)
org.springframework.web.servlet.tags.RequestContextAwareTag.doStartTag(RequestContextAwareTag.java:80)
org.apache.jsp.WEB_002dINF.jsps.index_jsp._jspx_meth_form_005finput_005f0(index_jsp.java:267)
org.apache.jsp.WEB_002dINF.jsps.index_jsp._jspx_meth_form_005fform_005f0(index_jsp.java:227)
org.apache.jsp.WEB_002dINF.jsps.index_jsp._jspService(index_jsp.java:142)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:438)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:396)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:340)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:168)
org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1257)
org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1037)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:980)
org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:861)
javax.servlet.http.HttpServlet.service(HttpServlet.java:622)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
I expected the JSP to generate an HTML <form> with a single text input, for a Movie name, and a submit button, that I can use to send a POST request with a new Movie. Why does the JSP servlet instead fail to render Spring's <form:form> tag?
You're trying to use Spring MVC's form tag.
This tag renders an HTML form tag and exposes a binding path to
inner tags for binding. It puts the command object in the PageContext
so that the command object can be accessed by inner tags. [..]
Let’s assume we have a domain object called User. It is a JavaBean
with properties such as firstName and lastName. We will use it as the
form backing object of our form controller which returns form.jsp.
In other words, Spring MVC will extract a command object and use its type as a blueprint for binding path expressions for form's inner tags, like input or checkbox, to render an HTML form element.
This command object is also called a model attribute and its name is specified in the form tag's modelAttribute or commandName attributes. You've omitted it in your JSP
<form:form>
You could've specified a name explicitly. Both of these are equivalent.
<form:form modelAttribute="some-example-name">
<form:form commandName="some-example-name">
NOTE: Spring 5 has removed the commandName attribute, see the upgrade notes, here.
The default attribute name is command (what you see in error message). A model attribute is an object, typically a POJO or collection of POJOs, that your application supplies to the Spring MVC stack and which the Spring MVC stack exposes to your view (ie. the M to the V in MVC).
Spring MVC collects all model attributes in a ModelMap (they all have names) and, in the case of JSPs, transfers them to the HttpServletRequest attributes, where JSP tags and EL expressions have access to them.
In your example, your #Controller handler method which handles a GET to the path /movies adds a single model attribute
model.addAttribute("movies", movies); // not named 'command'
and then forwards to the index.jsp. This JSP then tries to render
<form:form>
...
<form:input path="name" type="text" id="name" />
...
</form:form>
While rendering this, FormTag (in reality, the InputTag) tries to find a model attribute named command (the default attribute name) so that it can produce an HTML <input> element with a name attribute constructed from the path expression and the corresponding property value, ie. the result of Movie#getFilmName().
Since it cannot find it, it throws the exception you see
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'command' available as request attribute
The JSP engine catches it and responds with a 500 status code. If you want to take advantage of a Movie POJO to simply construct your form correctly, you can add a model attribute explicitly with
model.addAttribute("movie", new Movie());
or have Spring MVC create and add one for you (must have an accessible parameterless constructor)
#RequestMapping(path = "/movies", method = RequestMethod.GET)
public String homePage(#ModelAttribute("command") Movie movie, Model model) {...}
Alternatively, include a #ModelAttribute annotated method in your #Controller class
#ModelAttribute("command")
public Movie defaultInstance() {
Movie movie = new Movie();
movie.setFilmName("Rocky II");
return movie;
}
Note that Spring MVC will call this method and implicitly add the object returned to its model attributes for each request handled by the enclosing #Controller.
You may have guessed from this description that Spring's form tag is more suited for rendering an HTML <form> from an existing object, with actual values. If you want to simply create a blank <form>, it may be more appropriate to construct it yourself and not rely on any model attributes.
<form method="post" action="${pageContext.request.contextPath}/movies">
<input name="filmName" type="text" />
<input type="submit" value="Upload" />
</form>
On the receiving side, your POST handler method, will still be able to extract the filmName input value and use it to initialize a Movie object.
Common Errors
As we've seen, FormTag looks for a model attribute named command by default or with the name specified in either modelAttribute or commandName. Make sure you're using the right name.
ModelMap has a addAttribute(Object) method which adds
the supplied attribute to this Map using a generated name.
where the general convention is to
return the uncapitalized short name of the [attribute's] Class, according to
JavaBeans property naming rules: So, com.myapp.Product becomes
product; com.myapp.MyProduct becomes myProduct; com.myapp.UKProduct
becomes UKProduct
If you're using this (or a similar) method or if you're using one of the #RequestMapping supported return types that represents a model attribute, make sure the generated name is what you expect.
Another common error is to bypass your #Controller method altogether. A typical Spring MVC application follows this pattern:
Send HTTP GET request
DispatcherServlet selects #RequestMapping method to handle request
Handler method generates some model attributes and returns view name
DispatcherServlet adds model attributes to HttpServletRequest and forwards request to JSP corresponding to view name
JSP renders response
If, by some misconfiguration, you skip the #RequestMapping method altogether, the attributes will not have been added. This can happen
if your HTTP request URI accesses your JSP resources directly, eg. because they are accessible, ie. outside WEB-INF, or
if the welcome-list of your web.xml contains your JSP resource, the Servlet container will render it directly, bypassing the Spring MVC stack entirely
One way or another, you want your #Controller to be invoked so that the model attributes are added appropriately.
What does BindingResult have to do with this?
A BindingResult is a container for initialization or validation of model attributes. The Spring MVC documentation states
The Errors or BindingResult parameters have to follow the model object
that is being bound immediately as the method signature might have
more than one model object and Spring will create a separate
BindingResult instance for each of them [...]
In other words, if you want to use BindingResult, it has to follow the corresponding model attribute parameter in a #RequestMapping method
#RequestMapping(path = "/movies", method = RequestMethod.POST)
public String upload(#ModelAttribute("movie") Movie movie, BindingResult errors) {
BindingResult objects are also considered model attributes. Spring MVC uses a simple naming convention to manage them, making it easy to find a corresponding regular model attribute. Since the BindingResult contains more data about the model attribute (eg. validation errors), the FormTag attempts to bind to it first. However, since they go hand in hand, it's unlikely one will exist without the other.
I tried to migrate my application to Spring5 and noticed the same issue. It was caused by the moment that the 'commandName' attribute is not supported anymore and I had to use 'modelAttribute' instead.
To make things simple with the form tag just add a "commandName" which is a horrible name for what it is actually looking for...it wants the object you named in the MdelAttribute annotation. So in this case commandName="movie".
That'll save you reading long winded explanations friend.
I had this error on a screen with multiple forms that do a search. Each form posts to its own controller method with results shown on same screen.
Problem: I missed adding the other two forms as model attributes in each controller method causing that error when screen renders with results.
Form1 -> bound to Bean1 (bean1) -> Posting to /action1
Form2 -> bound to Bean2 (bean2) -> Posting to /action2
Form3 -> bound to Bean3 (bean2) -> Posting to /action3
#PostMapping
public String blah(#ModelAttribute("bean1") Bean1 bean, Model model){
// do something with bean object
// do not miss adding other 2 beans as model attributes like below.
model.addAttribute("bean2", new Bean2());
model.addAttribute("bean3", new Bean3());
return "screen";
}
#PostMapping
public String blahBlah(#ModelAttribute("bean2") Bean2 bean, Model model){
// do something with bean object
// do not miss adding other 2 beans as model attributes like below.
model.addAttribute("bean1", new Bean1());
model.addAttribute("bean3", new Bean3());
return "screen";
}
#PostMapping
public String blahBlahBlah(#ModelAttribute("bean3") Bean3 bean, Model model){
// do something with bean object
// do not miss adding other 2 beans as model attributes like below.
model.addAttribute("bean1", new Bean1());
model.addAttribute("bean2", new Bean2());
return "screen";
}
In my case, it worked by adding modelAttribute="movie" to the form tag, and prepending the model name to the attribute, something like <form:input path="filmName" type="text" id="movie.name" />
Updating from Spring version 3 to Spring version 5, produces the same error. All answers were satisfied already in my code. Adding the annotation #ControllerAdvice solved the problem for me.
If your Model object is correctly being passed to the GET API call but still have this error, you may look at the html or jsp page also to check whether correct variables names are provided and tags are used correctly. In my case, I forgot to include the objects under the closing <form> tag.

Is it possible to leverage an ArrayList as a session scoped component - ModelAttribute - rather than creating a pojo?

Is it possible to leverage an ArrayList or HashMap as a "session scoped component" (i.e., ModelAttribute object) - rather than creating a pojo?
If possible, I'd like to create a session scoped modelattribute like - e.g., "ArrayList<MyPojo>" or "HashMap<String, MyPojo>" - to share among different controllers.
But, the only examples of modelattribute components I've seen are POJO classes annotated with "#component" and "#Scope(value="session")...etc.
Thanks for any guidance on this.
sd
You can use a ModelMap as the command bean and set the ModelMap as a session attribute.
#SessionAttributes("testform")
public class testController{
public ModelAndView testmethod(#ModelAttribute("testform") ModelMap testMap,HttpServletRequest request){
/*
Access form variable using ModelMap.
*/
}
}
** Edit to Send ArrayList **
I am not sure if you can do HashMap, but you can definitely do Array of MyPojo i.e MyPojo[]. Please find the JSP and controller below where I have added an hidden type input html element.Code below assumes that your MyPojo class has a member named selected. Hope this helps.
<%#taglib uri="/spring.tld" prefix="spring"%>
<spring:bind path="MyPojo[${statusInd.index}].selected">
input type="hidden"
name='<c:out value="${status.expression}"/>'
id='<c:out value="${status.expression}" />'
value='<c:out value="${status.value}" />' />
</spring:bind>
#SessionAttributes("testform")
public class testController{
public ModelAndView testmethod(#ModelAttribute("testform") MyPojo[] testMyPojo,HttpServletRequest request){
/*
Access form variable using ModelMap.
*/
}
}
P:S :- You need to tell your JSP that testform is the name of the command Bean for this to work.

Do Spring property editors only work on forms?

I'm working on a Spring application that simply searches a set of data for things that match some criteria. There are two main views: one is a simple form that lets the user configure search criteria, while the other displays the results as a table.
One of the search fields is a closed set of options (about 10). Lower down in the code, I want to handle this as an enum class. The web form includes a drop-down that allows the user to select an option from this set. I've used a form:select to do this, populated with a set of strings that describe the values.
To keep the presentation and business logic separate, the enum class musn't have any knowledge of these strings, so I've created a Property Editor to convert between the two. When I load the form, the select control is set to the string associated with the enum value I gave it; when the form is submitted, the string is converted back to my enum type. This is all working fine.
For the results page (which isn't a form), I simply add the data to be displayed to a ModelMap. At the moment, I have to explicitly convert my enum type to a string before I add it to the map. What I'd like to do is just add the enum to the map and have the property editor convert it for me in the background, like it does for the form. I can't work out how though. Is it possible to do this? Maybe I'm missing something really obvious?
You can use Spring Tablib
<%#taglib uri="http://www.springframework.org/tags" prefix="spring"%>
And use transform markup
<!--If you have a command which command name is account-->
<!--Default command name used in Spring is called command-->
<spring:bind path="account.balance">${status.value}</spring:bind>
Or
<spring:bind path="account.balance">
<spring:transform value="${account.balance}"/>
</spring:bind>
Or
<!--Suppose account.balance is a BigDecimal which has a PropertyEditor registered-->
<spring:bind path="account.balance">
<spring:transform value="${otherCommand.anotherBigDecimalProperty}"/>
</spring:bind>
About value attribute
The value can either be a plain value to transform (a hard-coded String value in a JSP or a JSP expression), or a JSP EL expression to be evaluated (transforming the result of the expression). Like all of Spring's JSP tags, this tag is capable of parsing EL expressions itself, on any JSP version.
Its API
Provides transformation of variables to String, using an appropriate custom PropertyEditor from BindTag (can only be used inside BindTag)
If you use MultiActionController i advice you to use a Dummy Command class as bellow
public class Command {
public BigDecimal bigDecimal;
public Date date;
/**
* Other kind of property which needs a PropertyEditor
*/
// getter's and setter's
}
Inside your MultiActionController
public class AccountController extends MultiActionController {
#Autowired
private Repository<Account, Integer> accountRepository;
public AccountController() {
/**
* You can externalize this WebBindingInitializer if you want
*
* Here it goes as a anonymous inner class
*/
setWebBindingInitializer(new WebBindingInitializer() {
public void initBinder(WebDataBinder dataBinder, WebRequest request) {
binder.registerCustomEditor(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, numberFormat, true));
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("dd/MM/yyyy"), true));
}
});
}
public ModelAndView(HttpServletRequest request, HttpServletResponse response) throws Exception {
return new ModelAndView()
.addAllObjects(
createBinder(request, new Command())
.getBindingResult().getModel())
.addObject(accountRepository.findById(Integer.valueOf(request.getParameter("accountId"))));
}
}
Inside your JSP
<c:if test="{not empty account}">
<!--If you need a BigDecimal PropertyEditor-->
<spring:bind path="command.bigDecimal">
<spring:transform value="${account.balance}"/>
</spring:bind>
<!--If you need a Date PropertyEditor-->
<spring:bind path="command.date">
<spring:transform value="${account.joinedDate}"/>
</spring:bind>
</c:if>
It is useful when your Target command does not provide a PropertyEditor which needs to be used in another command.

Categories

Resources