UPDATE 1/31/10: Since this thread continues to get a lot of views...I am curious if it has been of help to anyone recently? Feel free to leave comments/feedback, thanks.
I have a Spring form where I would like to reuse the search page to include the results under the search form. Currently when I do this I get the following error on loading the success view:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'searchAccounts' available as request attribute
Here is my bean configuration:
<bean name="/search.html" class="myapp.web.AccountSearchController">
<property name="sessionForm" value="true"/>
<property name="commandName" value="searchAccounts"/>
<property name="commandClass" value="myapp.service.AccountSearch"/>
<property name="validator">
<bean class="myapp.service.AccountSearchValidator"/>
</property>
<property name="formView" value="accountSearch"/>
<property name="successView" value="accountSearchResults"/>
</bean>
Here is the snippet of JSP that includes the search form:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<form:form method="post" commandName="searchAccounts">
<table valign="top" cellspacing="0" cellpadding="0" width="500" border="0">
<tr>
<td valign="top">
<div class="border-title">Account Search</div>
<div id="navhome">
<div class="border">
<div id="sidebarhome">
<table id="form">
<tr>
<td colspan="2">Search by Account ID or Domain Name. If
values are provided for both, only accounts matching both values
will be returned.</td>
</tr>
<tr>
<td colspan="2"> </td>
</tr>
<tr>
<td align="right" valign="top"><form:label path="accountId">Account ID</form:label>:</td>
<td><form:input path="accountId" size="30"/></td>
</tr>
<c:set var="accountIdErrors"><form:errors path="accountId"/></c:set>
<c:if test="${not empty accountIdErrors}">
<tr>
<td> </td>
<td>${accountIdErrors}</td>
</tr>
</c:if>
<tr>
<td align="right" valign="top"><form:label path="domainName">Domain Name</form:label>:</td>
<td><form:input path="domainName" size="30"/></td>
</tr>
<c:set var="domainNameErrors"><form:errors path="domainName"/></c:set>
<c:if test="${not empty domainNameErrors}">
<tr>
<td> </td>
<td>${domainNameErrors}</td>
</tr>
</c:if>
<tr>
<td colspan="2"> </td>
</tr>
<tr>
<td> </td>
<td><input type="submit" name="submit" value="Search">
</td>
</tr>
</table>
</div>
</div>
</div>
</td>
</tr>
</table>
</form:form>
And...here is my form controller class (less the imports):
public class AccountSearchController extends SimpleFormController {
protected final Log logger = LogFactory.getLog(getClass());
public ModelAndView onSubmit(Object command, BindException errors) throws ServletException {
String accountId = ((AccountSearch) command).getAccountId();
String domainName = ((AccountSearch) command).getDomainName();
logger.info("User provided search criteria...\n\tDomain Name: " + domainName + "\n\tAccountId: " + accountId);
//TODO do search
logger.info("returning from AccountSearch form view to " + getSuccessView());
return new ModelAndView(getSuccessView());
}
protected Object formBackingObject(HttpServletRequest request) throws ServletException {
AccountSearch accountSearch = new AccountSearch();
return accountSearch;
}
}
Thanks in advance for your help!
-aj
UPDATE:
I ported this to an annotated controller per answer below. Here is the new/working code:
@Controller
@RequestMapping("/search.html")
public class AccountSearchController {
// note: this method does not have to be called setupForm
@RequestMapping(method = RequestMethod.GET)
public String setupForm(Model model) {
AccountSearchCriteria accountSearchCriteria = new AccountSearchCriteria();
model.addAttribute("accountSearchCriteria", accountSearchCriteria);
model.addAttribute("title", "Account Search");
return "accountSearch";
}
// note: this method does not have to be called onSubmit
@RequestMapping(method = RequestMethod.POST)
public String onSubmit(@ModelAttribute("accountSearchCriteria") AccountSearchCriteria accountSearchCriteria, BindingResult result, SessionStatus status, Model model) {
new AccountSearchValidator().validate(accountSearchCriteria, result);
if (result.hasErrors()) {
return "accountSearch";
} else {
ArrayList<AccountSearchCriteria> accountSearchResults = new ArrayList<AccountSearchCriteria>();
AccountSearchCriteria rec = new AccountSearchCriteria();
rec.setDomainName("ajcoon.com");
accountSearchResults.add(rec);
AccountSearchCriteria rec2 = new AccountSearchCriteria();
rec2.setDomainName("ajcoon2.com");
accountSearchResults.add(rec2);
//TODO do search
//ArrayList<HashMap<String,String>> accountSearchResults = new AccountSearchService().search(accountId,domainName);
if( accountSearchResults.size() < 1 ){
result.rejectValue("domainName", "error.accountSearch.noMatchesFound", "No matching records were found.");
return "accountSearch";
} else if(accountSearchResults.size() > 1){
model.addAttribute("accountSearchResults", accountSearchResults);
return "accountSearch";
} else {
status.setComplete();
return "redirect:viewAccount?accountId=";
//return "redirect:viewAccount?accountId=" + accountSearchResults.get(0).getAccountId();
}
}
}
}