Spring thymeleaf Form url cannot be resolved - spring-mvc

I want to know how to transfer parameters in spring form mvc platform.
First, Below code is spring form java file.
public class PostForm {
#NotNull
#Size(max=30, message="type id within 30 limits")
private String title;
#NotNull
#Size(max=100, message="type id within 100 limits")
private String Content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
}
And the next file is the bounded edit.html file
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="UTF-8">
<title>Blog modification</title>
</head>
<body>
<h1>Please, Modifiy.</h1>
<form method="post" th:object="${postForm}">
<div><label for="title">title</label></div>
<input id="title" type="text" name="title" th:value="*{title}" />
<span class="formError" th:if="${#fields.hasErrors('title')}" th:errors="*{title}">Input title is wrong</span>
<div><label for="content" th:value="*{title}">Content</label></div>
<textarea name="content" rows="20" width="200" th:value="*{content}"></textarea>
<span class="formError" th:if="${#fields.hasErrors('content')}" th:errors="*{content}">Input content is wrong</span>
<div>
<input type="submit" value="Modify" />
Cancel
</div>
</form>
</body>
</html>
Input link url to form is like below,
<td>
edit<br/>
delete
</td>
But the exception is thrown in the spring mvc controller codes.
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "posts/edit/{id}"; //This line throws exception.
}
#RequestMapping(value="/posts/edit/{id}", method = RequestMethod.POST)
public String edit(#PathVariable("id") Long id, #Valid PostForm postForm, BindingResult bindingResult) {
Post p = postService.findById(id);
postForm.setTitle(p.getTitle());
postForm.setContent(p.getBody());
.....
The exception is
ERROR 4024 --- [nio-8080-exec-4] org.thymeleaf.TemplateEngine : [THYMELEAF][http-nio-8080-exec-4] Exception processing template "posts/edit/{id}": Error resolving template "posts/edit/{id}", template might not exist or might not be accessible by any of the configured Template Resolvers
org.thymeleaf.exceptions.TemplateInputException: Error resolving template "posts/edit/{id}", template might not exist or might not be accessible by any of the configured Template Resolvers
at org.thymeleaf.engine.TemplateManager.resolveTemplate(TemplateManager.java:870) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.engine.TemplateManager.parseAndProcess(TemplateManager.java:607) ~[thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1098) [thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
at org.thymeleaf.TemplateEngine.process(TemplateEngine.java:1072) [thymeleaf-3.0.9.RELEASE.jar:3.0.9.RELEASE]
I have no idea how to transfer parameter in Spring Thymeleaf form template.

In Sprig MVC, when #ReqeustMapping annotation with GET Method is called, it tries to find the html template with the name defined in the return value.
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "posts/edit/{id}"; //This line throws exception.
}
So here you must return the name of the html template in the resources folder (not the url)
So I guess it's supposed to be
#RequestMapping("/posts/edit/{id}")
public String edit(PostForm postForm) {
return "views/mytemplate";
}
The error obviously indicates that it can't find the template under the resources folder. What your code does is try to locate the thymeleaf template in the 'edit' folder under 'posts' folder under the resources folder with the name of '{id}' but that's not there so it throws the error.
My suggestion is to change the return value of the GET method as I mentioned above.
If you need to pass any parameters to the view, use Model class.
If the parameters' value must be calculated from the {id} then you can use #PathVariable to map the id to a parameter.
#RequestMapping("/posts/edit/{id}")
public String edit(#PathVariable(value="id") String id, Model model) {
// do something here to get values using the id
....
model.addAttribute("parameter1", parameter1);
return "views/mytemplate";
}
By the way you don't need PostForm parameter in the GET method since it does not pass any postForm parameters in the body when it's called. You can leave it blank.
Hope this helps, have fun coding! :)

Related

Use single error message template for all invalid properties

Imagine a razor page with a Form that have many inputs that user fills them.
with post method when it wants to validate the posted model like this :
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
return Page(model);
}
}
If for example 3 property of that model (with names : a,b,c) are not valid, it turns back to the razor view and shows the error (because of asp-validation-for for each property) like this :
The a field is required.
The b field is not a valid e-mail address.
The c field is required.
I want to show a specific error for all of them like this :
This Input is not valid.
This Input is not valid.
This Input is not valid.
I know I can use (ErrorMessage ="") for each of them separately, but its not logical in big size! is there any way to show a specific massage for all of invalid ModelStates?
Edit:
For example before showing errors in View, change their error message like this :
#foreach (var error in modelStateErrors)
{
error.text = "Fill it";
}
I created a solution with an extension method for ModelState.
It basically removes any errors from the state and adds them back with the desired message.
Create a ModelStateExtensions.cs in your namespace:
public static class ModelStateExtensions
{
public static void SetAllErrorMessages(this ModelStateDictionary modelState, string errorMessage)
{
foreach (var state in modelState)
{
if (state.Value.Errors.Count > 0)
{
modelState.Remove(state.Key);
modelState.AddModelError(state.Key, errorMessage);
}
}
}
}
Then if your ModelState is invalid you can transform the message before returning the page:
public IActionResult OnPost()
{
if (!ModelState.IsValid)
{
ModelState.SetAllErrorMessages("Your message here");
return Page(model);
}
}
I know I can use ErrorMessage for each of them separately, but its not
logical! is there any short way to show a specific massage for all of
invalid ModelStates?
As for this issue, I think the easiest way to display the error message is using the ErrorMessage, If you want to display them at together, you could use the asp-validation-summary attribute, like this:
<div asp-validation-summary="All" class="text-danger"></div>
If you don't want to use the above method, you could also get the invalid fields from the ModelState dictionary, then, re-generate the error message. code like this:
public IActionResult OnPostAsync()
{
if (!ModelState.IsValid)
{
//get the new error message, you could also get all inValid fields.
var messages = ModelState.Keys
.SelectMany(key => ModelState[key].Errors.Select(x => string.Format("The {0} is invalid", key)))
.ToList();
ViewData["message"] = messages; //transfer the error message to the view
return Page();
}
return RedirectToPage("./Index");
}
View code (display the error message(without using asp-validation-for and asp-validation-summary)):
<div class="form-group">
#if (ViewData["message"] != null)
{
foreach (var item in (List<string>)ViewData["message"])
{
<span class="text-danger">#item</span><br/>
}
}
<div id="debug">
</div>
</div>
The output as below:
[Note] The above method is the server side validation. If you want to achieve the same behavior using Client validation, you have to get the client side validation result using JavaScript, and then generate the new error message.
So, in my opinion, I suggest you could try to use the first method (using Error Message and asp-validation-summary) to display the error message, and by using the Error Message for each of properties separators, user could easier to understand the validation rules.
If you don't want to make changes to each and every Razor Page, you can use a Page Filter to clear and rename the error messages automatically.
Here's an example Page Filter:
public class ModelStatePageFilter : IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext ctx) { }
public void OnPageHandlerExecuting(PageHandlerExecutingContext ctx)
{
foreach (var (k, v) in ctx.ModelState
.Where(x => x.Value.ValidationState == ModelValidationState.Invalid))
{
v.Errors.Clear();
v.Errors.Add("This Input is not valid.");
}
}
public void OnPageHandlerSelected(PageHandlerSelectedContext ctx) { }
}
You'll need to register this Page Filter in Startup.ConfigureServices. Here's an example of how to do that:
services.AddRazorPages()
.AddMvcOptions(o => o.Filters.Add(new ModelStatePageFilter()));
You can use the Validation Summary (see : https://learn.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms?view=aspnetcore-3.1#the-validation-tag-helpers).
#model RegisterViewModel
<form asp-controller="Demo" asp-action="RegisterValidation" method="post">
<div asp-validation-summary="ModelOnly"></div>
Email: <input asp-for="Email" /> <br />
<span asp-validation-for="Email"></span><br />
Password: <input asp-for="Password" /><br />
<span asp-validation-for="Password"></span><br />
<button type="submit">Register</button>
</form>
If you want to change the displayed error message, you can do it in your ViewModel:
[Required(ErrorMessage = "This Input is invalid")]
public string Email { get; set; }
[Required(ErrorMessage = "This Input is invalid")]
public string Password{ get; set; }

Asp.Net Core Remote validation attribute not making call

I have set the Remote attribute to validate that my username is Unique but when I debug it's not firing. What am I messing up?
In my View Model here is the attribute and property:
[Required]
[Remote("VerifyUsername", "Account")]
public string Username { get; set; }
In my form my form attribute is:
<input asp-for="Username" class="form-control mb-4" placeholder="Username" />
<span asp-validation-for="Username"></span>
And in my controller I have tried:
public JsonResult VerifyUsername(string username)
{
if (!_user.UsernameUnique(username))
{
return Json($"{username} is already in use.");
}
return Json(true);
}
And the method format:
[AcceptVerbs("Get", "Post")]
public IActionResult VerifyUsername(string username)
{
if (!_user.UsernameUnique(username))
{
return Json($" {username} is already in use.");
}
return Json(true);
}
I enter a usernam and click around and try tabbing... nothing gets the remote validation to fire. Anyone see what I am missing?
So I found it... kind of a facepalm. I was using another library that was loading another version of jquery. I was not getting an error though so that was weird. I removed that script reference for that other version of jquery and so it just had the current version and it all worked.

I have retrieved information from a database, but how do I format it properly? Perhaps using html [duplicate]

I'm implementing MVC using JSP and JDBC. I have imported a database class file to my JSP file and I would like to show the data of a DB table. I don't know how I should return the ResultSet from the Java class to the JSP page and embed it in HTML.
How can I achieve this?
In a well designed MVC approach, the JSP file should not contain any line of Java code and the servlet class should not contain any line of JDBC code.
Assuming that you want to show a list of products in a webshop, the following code needs to be created.
A Product class representing a real world entity of a product, it should be just a Javabean.
public class Product {
private Long id;
private String name;
private String description;
private BigDecimal price;
// Add/generate getters/setters/c'tors/equals/hashcode boilerplate.
}
A DAO class which does all the nasty JDBC work and returns a nice List<Product>.
public class ProductDAO {
private DataSource dataSource;
public ProductDAO(DataSource dataSource) {
this.dataSource = dataSource;
}
public List<Product> list() throws SQLException {
List<Product> products = new ArrayList<Product>();
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT id, name, description, price FROM product");
ResultSet resultSet = statement.executeQuery();
) {
while (resultSet.next()) {
Product product = new Product();
product.setId(resultSet.getLong("id"));
product.setName(resultSet.getString("name"));
product.setDescription(resultSet.getString("description"));
product.setPrice(resultSet.getBigDecimal("price"));
products.add(product);
}
}
return products;
}
}
A servlet class which obtains the list and puts it in the request scope.
#WebServlet("/products")
public class ProductsServlet extends HttpServlet {
#Resource(name="jdbc/YourDB") // For Tomcat, define as <Resource> in context.xml and declare as <resource-ref> in web.xml.
private DataSource dataSource;
private ProductDAO productDAO;
#Override
public void init() {
productDAO = new ProductDAO(dataSource);
}
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
List<Product> products = productDAO.list();
request.setAttribute("products", products); // Will be available as ${products} in JSP
request.getRequestDispatcher("/WEB-INF/products.jsp").forward(request, response);
} catch (SQLException e) {
throw new ServletException("Cannot obtain products from DB", e);
}
}
}
Finally a JSP file in /WEB-INF/products.jsp which uses JSTL <c:forEach> to iterate over List<Product> which is made available in EL by ${products}, and uses JSTL <c:out> to escape string properties in order to avoid XSS holes when it concerns user-controlled input.
<%# taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%# taglib uri="http://java.sun.com/jsp/jstl/format" prefix="fmt" %>
...
<table>
<c:forEach items="${products}" var="product">
<tr>
<td>${product.id}</td>
<td><c:out value="${product.name}" /></td>
<td><c:out value="${product.description}" /></td>
<td><fmt:formatNumber value="${product.price}" type="currency" currencyCode="USD" /></td>
</tr>
</c:forEach>
</table>
To get it to work, just call the servlet by its URL. Provided that the servlet is annotated #WebServlet("/products") or mapped in web.xml with <url-pattern>/products</url-pattern>, then you can call it by http://example.com/contextname/products
See also:
How to avoid Java code in JSP files?
doGet and doPost in Servlets
How should I connect to JDBC database / datasource in a servlet based application?
Design Patterns web based applications
RequestDispatcher.forward() vs HttpServletResponse.sendRedirect()
How to map a ResultSet with unknown amount of columns to a List and display it in a HTML table?
How do I pass current item to Java method by clicking a hyperlink or button in JSP page?
MVC, in a web application context, doesn't consist in using a class from a JSP. It consists in using the following model :
browser sends a request to a web server
the web server is configured so that the request is handled by a servlet or a filter (the controller : Java code, not JSP code)
The servlet/filter usually dispatches the request to a specific class (called an Action, the specific part of the controller), based on configuration/annotations
The action executes the business logic (i.e. fetch the data from the database in your example : the model)
The action forwards the request to a JSP. The role of the JSP is only to generate HTML code (i.e. display your data : the view)
Since the JSP usually uses JSP tags (the JSTL, for example) and the JSP expression language, and since JSP tags and the EL are designed to get information from JavaBeans, you'd better have your data available in the form of JavaBeans or collections of JavaBeans.
The role of the controller (the action class) is thus to fetch the data, to create JavaBean instances containing the data, in a suitable format for the JSP, to put them in request attributes, and then to dispatch to the JSP. The JSP will then iterate through the JavaBean instances and display what they contain.
You should not implement the MVC framework yourself. Use existing ones (Stripes, Struts, etc.)
I don't know how should I return the ResultSet from the class file to the JSP page
Well, you don't.
The point of MVC is to separate your model ( the M DB info in this case ) from your view ( V a jsp, in this case ) in such a way you can change the view without braking to application.
To do this you might use an intermediate object to represent your data ( usually called DTO - after Data Transfer Object -, don't know how they call it these days ), and other object to fetch it ( usually a DAO ).
So basically you have your JSP file, get the request parameters, and then invoke a method from the DAO. The dao, internally has the means to connect to the db and fetch the data and builds a collections of DTO's which are returned to the JSP for rendering.
Something like this extremely simplified ( and insecure ) code:
Employee.java
class Employee {
String name;
int emplid;
}
EmployeeDAO.java
class EmployeeDAO {
... method to connect
etc.
List<Employee> getAllNamed( String name ) {
String query = "SELECT name, emplid FROM employee where name like ?";
ResultSet rs = preparedStatement.executeQuery etc etc.
List<Employee> results = ....
while( rs.hasNext() ) {
results.add( new Employee( rs.getString("name"), rs.getInt("emplid")));
}
// close resources etc
return results;
}
}
employee.jsp
<%
request.setAttribute("employees", dao.getAllNamed( request.getParameter("name") );
%>
<table>
<c:forEach items="${employees}" var="employee">
<tr><td>${employee.emplid}</td><td>${employee.name}</td></tr>
</c:forEach>
</table>
I hope this give you a better idea.
I have a problem. I don't understand clearly the code. I have a similar problem with my code.
I have created database SQL and filled up. Then I want to implement a MainServlet (code below) that richieve data from database and in a different jsp page, I want to insert that data in section like h1, h2 ecc... I must use the ${} sintax but I don't know how do that.
Briefly, In jsp file (code below, I MUST USE ${} SINTAX) I want to "call" MainServlet and there I want to richieve data from database and view in jsp file.
I hope I have explained correctly, thank you very much!
MainServlet.java
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class MainServlet
*/
#WebServlet({ "/MainServlet" })
public class MainServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static final String PATH_JSP = "/WEB-INF/";
/**
* #see HttpServlet#HttpServlet()
*/
public MainServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* #see Servlet#init(ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
// TODO Auto-generated method stub
}
/**
* #see Servlet#destroy()
*/
public void destroy() {
// TODO Auto-generated method stub
}
/**
* #see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String doveAndare = request.getParameter("azione");
if(doveAndare==null)
doveAndare = "index";
try {
String driverString = "com.mysql.cj.jdbc.Driver";
Class.forName(driverString);
String connString = "jdbc:mysql://localhost:3306/ldd_jewels?user=root&password=";
Connection conn = DriverManager.getConnection(connString);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM JEWEL");
while (rs.next() == true) {
System.out.println(rs.getString("Category") + "\t" + rs.getString("Name"));
/* I try that but does not work
request.setAttribute("name", rs.getString("Name"));
javax.servlet.RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/widering_male.jsp");
dispatcher.forward(request, response); */
}
stmt.close();
conn.close();
} catch(Exception e) {
e.printStackTrace();
}
request.getRequestDispatcher(PATH_JSP+doveAndare+".jsp").forward(request, response);
}
/**
* #see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
doublerow.jsp
<section id="portfolio-details" class="portfolio-details">
<div class="container">
<div class="row gy-4">
<div class="col-lg-8">
<div class="portfolio-details-slider swiper">
<div class="swiper-wrapper align-items-center">
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_1.jpg" alt="" />
</div>
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_2.jpg" alt="" />
</div>
<div class="swiper-slide">
<img src="assets/img/jewels/doublerow_3.jpg" alt="" />
</div>
</div>
<div class="swiper-pagination"></div>
</div>
</div>
<div class="col-lg-4">
<div class="portfolio-info">
<h3>Product details</h3>
<ul>
<li><strong>Code</strong>: 1S3D5</li>
<li><strong>Category</strong>: Bracelets</li>
<li><strong>Name</strong>: Double Row Hinged Bangle</li>
<li><strong>Gender</strong>: Female</li>
<li><strong>Material</strong>: Yellow gold</li>
<li><strong>Size</strong>: 121mm</li>
<li><strong>Price</strong>: €5500</li>
</ul>
</div>
<div class="portfolio-description">
<h2>Description of product</h2>
<p>
The entwined ends of Tiffany Knot’s signature motif symbolize
the power of connections between people. Balancing strength
and elegance, each Tiffany Knot design is a complex feat of
craftsmanship. This bangle is crafted with yellow gold and
polished by hand for high shine. Wear on its own or partnered
with classic silhouettes for an unexpected pairing.
</p>
</div>
</div>
</div>
</div>
</section>
This is my database:
I want to insert each jewel in different pages (each jewel have a jsp file)
You can use the <c:forEach > tag
you can find a detailed example in the following link example use
I think it will be better for you to contain the data of the table into a collection such as list and return the list from the Java class and reuse this collection in the JSP.

Form binding for showing errors of a list

I've got a Product object that contains a Set<Provider> providers. I've annotated within the Provider a variable url with #NotEmpty and now I want to display a error, if this field is empty.
I'm not sure how I can access the field providers within the hasErrors method properly.
Form:
<form action="#" th:action="#{/saveDetails}" th:object="${selectedProduct}" method="post">
<!-- bind each input field to list (working) -->
<input th:each="provider, status : ${selectedProduct.providers}"
th:field="*{providers[__${status.index}__].url}" />
<!-- all the time 'false' -->
<span th:text="'hasErrors-providers=' + ${#fields.hasErrors('providers')}"></span>
<span th:text="'hasErrors-providers[0].url=' + ${#fields.hasErrors('providers[0].url')}"></span>
<!-- not working -->
<span class="help-block" th:each="provider, status : ${selectedProduct.providers}"
th:if="${#fields.hasErrors('providers[__${status.index}__].url')}"
th:errors="${providers[__${status.index}__].url}">Error Url
</span>
<!-- print errors (just for testing purpose) -->
<ul>
<li th:each="e : ${#fields.detailedErrors()}">
<span th:text="${e.fieldName}">The field name</span>|
<span th:text="${e.code}">The error message</span>
</li>
</ul>
</form>
Within the <ul> I receive for each error providers[].url as e.fieldName. I thought it would be having some indices like providers[0].url etc.
So my question is, how can I access the field providers within the hasErrors method properly to display the error messages.
EDIT
Controller:
#RequestMapping(value = "/saveDetails", method = RequestMethod.POST)
public String saveDetails(#Valid #ModelAttribute("selectedProduct") final Product selectedProduct,
final BindingResult bindingResult, SessionStatus status) {
if (bindingResult.hasErrors()) {
return "templates/details";
}
status.setComplete();
return "/templates/overview";
}
You cannot get an item from a Set using their index because sets don't have ordering. Set interface doesn't provide a method of getting an item based on index, so doing .get(index) to a Set will give you compile error. Use List instead. This way, you can access the objects using their index.
So change Set<Provider> providers to :
#Valid
List<Provider> providers;
Don't forget the #Valid annotation so that it will cascade down to the child objects.
Also, if th:errors is inside a form, it should be pointing to a property of the object that backs that form, using Selection Expression (*{...})
<span class="help-block" th:each="provider, status : ${selectedProduct.providers}"
th:if="${#fields.hasErrors('providers[__${status.index}__].url')}"
th:errors="*{providers[__${status.index}__].url}">Error Url
</span>
EDIT
I see that you want to access the errors collectively, instead of iterating through them. In that case, you can create your custom JSR 303 validator. See the following useful code fragments :
Usage
#ProviderValid
private List<Provider> providers;
ProviderValid annotation
//the ProviderValid annotation.
#Target({ ElementType.FIELD })
#Retention(RetentionPolicy.RUNTIME)
#Constraint(validatedBy = ProviderValidator.class)
#Documented
public #interface ProviderValid {
String message() default "One of the providers has invalid URL.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
ConstraintValidator
public class ProviderValidator implements ConstraintValidator<ProviderValid, List<Provider>>{
#Override
public void initialize(ProviderValid annotation) { }
#Override
public boolean isValid(List<Provider> value, ConstraintValidatorContext context) {
//...
//validate your list of providers here
//obviously, you should return true if it is valid, otherwise false.
//...
return false;
}
}
After doing these, you can easily get the default message you specified in the #ProviderValid annotation if ProviderValidator#isValid returns false by simply doing #fields.hasErrors('providers')

Spring 3 MVC: Issue binding to list form fields on submit

Let me introduce my issue by providing some of the code in question.
First my form object:
public class OrgChartForm {
List<OrgChartFormElement> orgChartFormElements;
public OrgChartForm() {
orgChartFormElements = new ArrayList<OrgChartFormElement>();
}
private OrgChartFormElement createOrgChartFormElementFromMprsStructureYear(MprsStructureYear structureYear){
OrgChartFormElement element = new OrgChartFormElement();
element.set.... // populate element based on attribute values from structureYear param
return element;
}
public void createOrgChartFormElements(List<MprsStructureYear> structureYears) {
orgChartFormElements = new ArrayList<OrgChartFormElement>();
for(MprsStructureYear structureYear:structureYears){
orgChartFormElements.add(createOrgChartFormElementFromMprsStructureYear(structureYear));
}
}
// expected getters and setters
}
The form contains a simple list of OrgChartFormElements
public class OrgChartFormElement {
private boolean selected;
private String elementLabel;
private Long id;
//default constructor, getters and setters
}
I am using context:component-scan and mvc:annotation-driven, so my controller looks like:
#Controller
public class OrganisationStatusController{
#Autowired(required=true)
// dependencies here
#RequestMapping(value="/finyear/{finyearId}/organisationstatus", method=RequestMethod.GET)
public String createRootOrg(#PathVariable(value="finyearId") Long finyearId, Model model) throws Exception {
List<MprsStructureYear> orgStructuure = getOrganisationService().getOrganisationStructureForFinyear(finyearId);
OrgChartForm orgChartForm = new OrgChartForm();
orgChartForm.createOrgChartFormElements(orgStructuure);
model.addAttribute("orgChartForm", orgChartForm);
return "finyear/organisationchart/view";
}
#RequestMapping(value="/finyear/{finyearId}/organisationstatus", method=RequestMethod.POST)
public String createRootOrg(#PathVariable(value="finyearId") Long finyearId,#ModelAttribute("orgChartForm") OrgChartForm orgChartForm, BindingResult result, Model model) throws Exception {
System.out.println("Found model attribute: " + model.containsAttribute("orgChartForm"));
List<OrgChartFormElement> elements = orgChartForm.getOrgChartFormElements();
System.out.println(elements);
return "redirect:/spring/finyear/" + finyearId + "/organisationstatus";
}
// expected getters and setters
}
The issue is with the POST handler. I realise that it isn't doing much now, but once I get it to work, I will be persisting the submitted values.
At the moment, the output i see from the two sysout statements are:
Found model attribute: true
[]
Here is my JSP snippet:
<sf:form modelAttribute="orgChartForm" method="post">
<c:forEach items="${orgChartForm.orgChartFormElements}" var="org" varStatus="status">
<sf:hidden id="${org.id}field" path="orgChartFormElements[${status.index}].id"/>
<sf:input id="${org.id}hidden" path="orgChartFormElements[${status.index}].selected"/>
<c:out value="${org.elementLabel}"/>(<c:out value="${org.id}"/>) - <c:out value="${status.index}"/>
</c:forEach>
<input type="submit" value="Submit" />
</sf:form>
When i make the GET request, the JSP renders, and i see my list of text input fields, with the expected values, which tells me that im using the spring-form tags properly. However, when i submit, the form backing object declared as a parameter (orgChartForm) in the POST handler method is initialised, but everything is null/default initialised. I don't know where the submitted data went! It seems that springMVC looses it, and simply constucts a new object.
I have used this pattern extensively in this application without a glitch. It just wont work here. I realise this is a special case in my application where the form field is not atomic but a list, However its really confusing me that the data binds in the GET request, but not on the POST.
Thanks in advance for any pointers!
I think the problem is that you are trying to bind an arbitrary number of form fields to an ArrayList, which is a list that has a predetermined size.
Spring has something called an AutoPopulatingList that is custom designed for this purpose. Please have a look at this link for more info on how to use it: http://blog.richardadamdean.com/?p=12
I think you will need to write PropertyEditorSupport for your class. Following is the example for your reference.
public class SampleEditor extends PropertyEditorSupport {
private final SampleService sampleService;
public SampleEditor(SampleService sampleService, Class collectionType) {
super(collectionType);
this.sampleService = sampleService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
Object obj = getValue();
List list = (List) obj;
for (String str : text.split(",")) {
list.add(sampleService.get(Long.valueOf(str)));
}
}
#Override
public String getAsText() {
return super.getAsText();
}
}
In controller, you should bind it using #InitBinder as follows:
#InitBinder
protected void initBinder(HttpServletRequest request, WebDataBinder binder) {
binder.registerCustomEditor(List.class, "list", new SampleEditor(this.sampleService, List.class));
}
Hope this will solve your problem.

Resources