I have a simple mvc application, which lists all rules in a DB, and allows a user to select a specific row to delete that rule. The controller has a simple listRules() method which adds two model objects to the ModelMap.
@Controller
public class RulesController {
@Resource
private RuleManager ruleManager;
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView listRules() {
ModelAndView mv = new ModelAndView("/rules");
List<Rule> rules = this.ruleManager.getAllRules();
ListRulesModel listRulesModel = new ListRulesModel();
listRulesModel.setRules(rules);
mv.addObject("listRulesModel",listRulesModel);
SelectedRuleModel selectedRuleModel = new SelectedRuleModel();
mv.addObject("selectedRuleModel",selectedRuleModel);
return mv;
}
@RequestMapping(value = "/submit", method = RequestMethod.POST, params = {"delete"})
public ModelAndView deleteRule(@ModelAttribute("selectedRuleModel") SelectedRuleModel selectedRuleModel,ModelMap model) {
System.out.println("deleteRule "+selectedRuleModel.hashCode());
System.out.println("model "+model);
if(selectedRuleModel.getRuleId()!=null)
getRuleManager().deleteRule(getRuleManager().getRule(selectedRuleModel.getRuleId()));
return new ModelAndView("redirect:/");
}
My model objects are
public class ListRulesModel {
private List<Rule> rules = null;
public List<Rule> getRules() {
return rules;
}
public void setRules(List<Rule> rules) {
this.rules = rules;
}
}
and
public class SelectedRuleModel {
@NotNull
private Integer ruleId = null;
public Integer getRuleId() {
return ruleId;
}
public void setRuleId(Integer ruleId) {
this.ruleId = ruleId;
}
@Override
public String toString() {
return String.format("SelectedRuleModel [ruleId=%s]", ruleId);
}
}
The main elements of my view are a table form which shows each rule as a row. A radiobutton should populate the 'selectedRuleModel.ruleId' field with the value of the rule within the list.
<head>
<script type="text/javascript" src="<c:url value="/resources/js/jquery-2.1.1.min.js"/>"></script>
<script type="text/javascript" src="<c:url value="/resources/js/jquery.tablesorter.min.js"/>"> </script>
<script type="text/javascript" src="<c:url value="/resources/js/jquery.tablesorter.widgets.min.js"/>"></script>
<script type="text/javascript" src="<c:url value="/resources/js/rules.js"/>"></script>
<link href="<c:url value="/resources/css/base.css"/>" rel="stylesheet"/>
<link href="<c:url value="/resources/css/theme.blue.css"/>" rel="stylesheet"/>
<link href="<c:url value="/resources/css/rules.css"/>" rel="stylesheet"/>
</head>
<form:form method="POST" action="submit" modelAttribute="selectedRuleModel">
<button type="submit" name="delete" value="delete" class="btn btn-primary">Delete Selected Rule</button>
...
<c:forEach items="${listRulesModel.rules}" var="rule" varStatus="status">
<tr>
<td><form:radiobutton path="ruleId" value="${rule.id}"/></td>
<td>${rule.name}</td>
<td>${rule.batch}</td>
...
</form>
Everytime i submit the "delete row" form it appears that a new 'SelectedRuleModel' object is passed as a parameter to the deleteRule() method, such that the 'ruleId' value is always null. What am io doing wrong with my model/method mapping?
The generated HTML is
<form id="selectedRuleModel" action="submit" method="POST">
<button type="submit" name="amend" value="amend" class="btn btn-primary">Amend Selected Rule</button>
<button type="submit" name="branch" value="branch" class="btn btn-primary">Branch Selected Rule</button>
<button type="submit" name="delete" value="delete" class="btn btn-primary">Delete Selected Rule</button>
</div>
<!-- Add rule table -->
<table id="rulesTable" class="tablesorter">
<thead>
<tr>
<th>ID</th>
<th>Rule Name</th>
....
</tr>
</thead>
<tbody>
<tr>
<td><input id="ruleId2" name="ruleId" type="radio" value="20"/></td>
<td>Gender_Balance</td>
<td>*</td>
....
EDIT - I've update the controller method deleteRule() to include the BindResult object.
@RequestMapping(value = "/submit", method = RequestMethod.POST, params = {"delete"})
public ModelAndView deleteRule(
@Valid @ModelAttribute("selectedRuleModel") SelectedRuleModel selectedRuleModel,
BindingResult result, ModelMap model) {
System.out.println("deleteRule "+selectedRuleModel.getRuleId());
System.out.println("model "+model.toString());
System.out.println("BindingResult "+result.toString());
if(selectedRuleModel.getRuleId()!=null)
getRuleManager().deleteRule(getRuleManager().getRule(selectedRuleModel.getRuleId()));
return new ModelAndView("redirect:/");
}
As you can see in the logging for this method, the 'selectedRuleModel' is null but the model has a 'selectedRuleModel' with a null 'ruleId' value.
2014-10-14 11:18:21,428 INFO [STDOUT] (http-127.0.0.1-8080-2) deleteRule null
2014-10-14 11:18:21,428 INFO [STDOUT] (http-127.0.0.1-8080-2) model {selectedRuleModel=SelectedRuleModel [ruleId=null], org.springframework.validation.BindingResult.selectedRuleModel=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
2014-10-14 11:18:21,428 INFO [STDOUT] (http-127.0.0.1-8080-2) BindingResult org.springframework.validation.BeanPropertyBindingResult: 0 errors
EDIT Adding web.xml and applicationContext.xml in case someone spots that i'm not initialising some required component correctly.
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<servlet>
<servlet-name>hedgingcorrection</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>namespace</param-name>
<param-value>applicationContext</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hedgingcorrection</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<jsp-config>
<taglib>
<taglib-uri>/spring</taglib-uri>
<taglib-location>/WEB-INF/tld/spring-form.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
And my applicationContext.xml has
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<mvc:annotation-driven/>
<mvc:resources mapping="/resources/**" location="/resources/"/>
<mvc:view-controller path="/"/>
<context:component-scan base-package="abc.xwz.web.correction.controller"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
I removed the three mvc elements from my applicationContext.xml file it the form started working.
<mvc:annotation-driven/>
<mvc:resources mapping="/resources/**" location="/resources/"/>
<mvc:view-controller path="/"/>
FRIDAY EDIT - I've isolated the issue to the inclusion of the javascript elements in the jsp form. Without the javascript files included, the form submits data to the controller. When the scripts are added it seems that the form data is not correctly bound to the ModelAttribute object. The javascript files are primarily aimed at enabling the tablesorting features on the html table, so i can't understand why they are effecting the action of the submit buttons. 50 points for someone who can explain it.
See Question&Answers more detail:
os