After searching everywhere, I was unable to find a clear example of how to make jQuery Ajax calls to update a page fragment, but
I finally figured it out, so I am posting my solution here for all to see.
(Note: My example adds a row to a table, i.e. an item to a list.)
Spring Configuration:
Code:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/tiles-defs.xml</value>
</list>
</property>
</bean>
<bean id="tilesViewResolver" class="org.springframework.js.ajax.AjaxUrlBasedViewResolver">
<property name="viewClass" value="org.springframework.webflow.mvc.view.FlowAjaxTilesView"/>
</bean>
<bean id="viewFactoryCreator" class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
<property name="viewResolvers">
<list>
<ref bean="tilesViewResolver"/>
</list>
</property>
</bean>
<webflow:flow-builder-services id="flowBuilderServices" view-factory-creator="viewFactoryCreator"/>
<webflow:flow-registry id="flowRegistry" flow-builder-services="flowBuilderServices">
<webflow:flow-location-pattern value="/WEB-INF/flows/**/*-flow.xml" />
</webflow:flow-registry>
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry"/>
<bean name="/app.htm" class="org.springframework.webflow.mvc.servlet.FlowController">
<property name="flowExecutor" ref="flowExecutor"/>
<property name="flowUrlHandler">
<bean class="org.springframework.webflow.context.servlet.WebFlow1FlowUrlHandler"/>
</property>
</bean>
<bean id="personAction" class="com.app.action.PersonAction">
<property name="formObjectName" value="personForm"/>
<property name="formObjectClass" value="com.app.form.PersonForm"/>
<property name="formObjectScope" value="FLOW"/>
</bean>
(Note: I am using FlowController instead of FlowHandlerAdapter, but I believe it is recommended to use FlowHandlerAdapter for new applications.)
Tiles Configuration:
Code:
<definition name="editPerson" template="/WEB-INF/jsp/editPerson.jsp">
<put-attribute name="title" value="Edit Person"/>
<put-attribute name="pets" value="/WEB-INF/jsp/fragments/pets.jsp"/>
</definition>
JSP:
editPerson.jsp
Code:
<head>
<title><tiles:insertAttribute name="title"/></title>
</head>
<body>
<form:form modelAttribute="personForm" method="post" id="personForm">
<input id="key" type="hidden" name="key" value="${key}" />
<div id="pets"><tiles:insertAttribute name="pets"/></div>
</form:form>
</body>
pets.jsp
Code:
<input type="button" value="Add Row" οnclick="addPet();"/>
<table>
<tr><td>
<c:forEach items="${personForm.pets}" varStatus="row" var="pet">
<spring:bind path="personForm.pets[${row.index}].name">
<input type="text" name="${status.expression}" value="${status.value}"/>
</spring:bind>
</c:forEach>
</td></tr>
</table>
(Note: The fragment will not render on an Ajax call using <form:*> tags if your <form:form> tag is outside of the fragment JSP. You must use the <spring:bind> tags instead. Someone has made a request to fix this issue
here.)
jQuery:
Code:
function addPet() {
$.ajax({
url: '?_flowExecutionKey=' + $('#key').val() + '&_eventId=addPet'&ajaxSource=true',
success: function(data) {
$('#pets').html(data);
}
});
}
(Note: It is important to have the "ajaxSource=true", since this is what SpringJavascriptAjaxHandler checks to determine isAjaxRequest(). There are other things that can be done to make this method return true as well. Look at the class for more info.)
Flow:
Code:
<var name="personForm" class="com.app.form.PersonForm" />
<view-state id="newPerson" view="editPerson" model="personForm">
<on-entry>
<evaluate expression="personAction.setupForm" result="flowScope.personForm"/>
</on-entry>
<transition on="addPet">
<evaluate expression="personAction.addPet"/>
<render fragments="pets"/>
</transition>
<transition on="save" to="savePerson"/>
</view-state>
(Note: The transition on addPet does not have a "to". This tells Webflow that we are staying on the same page.)
Java:
Code:
public class PersonAction extends FormAction {
public Event setupForm(RequestContext context) throws Exception {
PersonForm form = new PersonForm();
context.getFlowScope().put("personForm", personForm);
return success();
}
public Event addPet(RequestContext context) {
PersonForm form = (PersonForm)context.getFlowScope().get("personForm");
form.getPets().add(new Pet());
return success();
}
}
As a disclaimer, I have not run this exact code. I have modified it from my application code to be more simple and to remove company-specific information.