- Updated to use Spring Faces Ajax controls.

- Moved hotelDetails page into the main flow.
- Using new custom OneSelectionTrackingDataModel from Spring Faces
This commit is contained in:
Jeremy Grelle
2007-12-04 13:27:01 +00:00
parent 433e003356
commit 1c1b5722a4
13 changed files with 220 additions and 178 deletions

View File

@@ -0,0 +1,70 @@
package org.springframework.webflow.samples.booking.flow.booking;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.model.SelectItem;
public class BookingOptions implements Serializable {
private List bedOptions;
private List smokingOptions;
private List creditCardExpMonths;
private List creditCardExpYears;
public List getBedOptions() {
if (bedOptions == null) {
bedOptions = new ArrayList();
bedOptions.add(new SelectItem(new Integer(1), "One king-size bed"));
bedOptions.add(new SelectItem(new Integer(2), "Two double beds"));
bedOptions.add(new SelectItem(new Integer(3), "Three beds"));
}
return bedOptions;
}
public List getSmokingOptions() {
if (smokingOptions == null) {
smokingOptions = new ArrayList();
smokingOptions.add(new SelectItem(Boolean.TRUE, "Smoking"));
smokingOptions.add(new SelectItem(Boolean.FALSE, "Non-Smoking"));
}
return smokingOptions;
}
public List getCreditCardExpMonths() {
if (creditCardExpMonths == null) {
creditCardExpMonths = new ArrayList();
creditCardExpMonths.add(new SelectItem(new Integer(1), "Jan"));
creditCardExpMonths.add(new SelectItem(new Integer(2), "Feb"));
creditCardExpMonths.add(new SelectItem(new Integer(3), "Mar"));
creditCardExpMonths.add(new SelectItem(new Integer(4), "Apr"));
creditCardExpMonths.add(new SelectItem(new Integer(5), "May"));
creditCardExpMonths.add(new SelectItem(new Integer(6), "Jun"));
creditCardExpMonths.add(new SelectItem(new Integer(7), "Jul"));
creditCardExpMonths.add(new SelectItem(new Integer(8), "Aug"));
creditCardExpMonths.add(new SelectItem(new Integer(9), "Sep"));
creditCardExpMonths.add(new SelectItem(new Integer(10), "Oct"));
creditCardExpMonths.add(new SelectItem(new Integer(11), "Nov"));
creditCardExpMonths.add(new SelectItem(new Integer(12), "Dec"));
}
return creditCardExpMonths;
}
public List getCreditCardExpYears() {
if (creditCardExpYears == null) {
creditCardExpYears = new ArrayList();
creditCardExpYears.add(new SelectItem(new Integer(2005), "2005"));
creditCardExpYears.add(new SelectItem(new Integer(2006), "2006"));
creditCardExpYears.add(new SelectItem(new Integer(2007), "2007"));
creditCardExpYears.add(new SelectItem(new Integer(2008), "2008"));
creditCardExpYears.add(new SelectItem(new Integer(2009), "2009"));
creditCardExpYears.add(new SelectItem(new Integer(2010), "2010"));
}
return creditCardExpYears;
}
}

View File

@@ -2,6 +2,7 @@ package org.springframework.webflow.samples.booking.flow.main;
import java.util.List;
import org.springframework.faces.model.OneSelectionTrackingListDataModel;
import org.springframework.webflow.action.MultiAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
@@ -9,7 +10,6 @@ import org.springframework.webflow.samples.booking.app.Booking;
import org.springframework.webflow.samples.booking.app.BookingService;
import org.springframework.webflow.samples.booking.app.Hotel;
import org.springframework.webflow.samples.booking.app.User;
import org.springframework.webflow.samples.booking.web.util.SerializableListDataModel;
/**
* Actions invoked by the main flow. These actions are extensions of the flow definition, called by the flow definition
@@ -48,7 +48,7 @@ public class MainActions extends MultiAction {
public Event findCurrentUserBookings(RequestContext context) {
User user = (User) context.getConversationScope().get("user");
List<Booking> bookings = bookingService.findBookings(user.getUsername());
context.getFlowScope().put("bookings", new SerializableListDataModel(bookings));
context.getFlowScope().put("bookings", new OneSelectionTrackingListDataModel(bookings));
return success();
}
@@ -61,7 +61,7 @@ public class MainActions extends MultiAction {
SearchCriteria search = (SearchCriteria) context.getFlowScope().get("searchCriteria");
List<Hotel> hotels = bookingService
.findHotels(search.getSearchString(), search.getPageSize(), search.getPage());
context.getFlowScope().put("hotels", new SerializableListDataModel(hotels));
context.getFlowScope().put("hotels", new OneSelectionTrackingListDataModel(hotels));
return success();
}
}

View File

@@ -1,8 +1,11 @@
package org.springframework.webflow.samples.booking.flow.main;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.event.ActionEvent;
import javax.faces.model.SelectItem;
import org.springframework.webflow.samples.booking.app.BookingService;
@@ -26,13 +29,18 @@ public class SearchCriteria implements Serializable {
/**
* The maximum page size of the Hotel result list
*/
private int pageSize;
private int pageSize = 5;
/**
* The current page of the Hotel result list.
*/
private int page;
/**
* The available page size options.
*/
private List pageSizeOptions;
/**
* Increase the current page
*/
@@ -78,4 +86,13 @@ public class SearchCriteria implements Serializable {
this.page = page;
}
public List getPageSizeOptions() {
if (pageSizeOptions == null) {
pageSizeOptions = new ArrayList();
pageSizeOptions.add(new SelectItem(new Integer(5), "5"));
pageSizeOptions.add(new SelectItem(new Integer(10), "10"));
pageSizeOptions.add(new SelectItem(new Integer(20), "20"));
}
return pageSizeOptions;
}
}

View File

@@ -1,89 +0,0 @@
package org.springframework.webflow.samples.booking.web.util;
import java.io.Serializable;
import java.util.List;
import javax.faces.model.DataModel;
import javax.faces.model.DataModelEvent;
import javax.faces.model.DataModelListener;
/**
* A simple List-to-JSF-DataModel adapter that is also serializable.
*/
public class SerializableListDataModel extends DataModel implements Serializable {
private static final long serialVersionUID = 1L;
private int _rowIndex = -1;
private List _data;
public SerializableListDataModel() {
super();
}
/**
* Adapt the list to a data model;
* @param list the list
*/
public SerializableListDataModel(List list) {
if (list == null)
throw new NullPointerException("list");
setWrappedData(list);
}
public int getRowCount() {
if (_data == null) {
return -1;
}
return _data.size();
}
public Object getRowData() {
if (_data == null) {
return null;
}
if (!isRowAvailable()) {
throw new IllegalArgumentException("row is unavailable");
}
return _data.get(_rowIndex);
}
public int getRowIndex() {
return _rowIndex;
}
public Object getWrappedData() {
return _data;
}
public boolean isRowAvailable() {
if (_data == null) {
return false;
}
return _rowIndex >= 0 && _rowIndex < _data.size();
}
public void setRowIndex(int rowIndex) {
if (rowIndex < -1) {
throw new IllegalArgumentException("illegal rowIndex " + rowIndex);
}
int oldRowIndex = _rowIndex;
_rowIndex = rowIndex;
if (_data != null && oldRowIndex != _rowIndex) {
Object data = isRowAvailable() ? getRowData() : null;
DataModelEvent event = new DataModelEvent(this, _rowIndex, data);
DataModelListener[] listeners = getDataModelListeners();
for (int i = 0; i < listeners.length; i++) {
listeners[i].rowSelected(event);
}
}
}
public void setWrappedData(Object data) {
_data = (List) data;
int rowIndex = _data != null ? 0 : -1;
setRowIndex(rowIndex);
}
}

View File

@@ -12,6 +12,9 @@
<import resource="application-layer-config.xml"/>
<web:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<web:flow-execution-attributes>
<web:alwaysRedirectOnPause value="false"/>
</web:flow-execution-attributes>
<web:flow-execution-listeners>
<web:listener ref="jpaFlowExecutionListener" criteria="*"/>
</web:flow-execution-listeners>
@@ -26,7 +29,7 @@
<bean id="flowBuilderServices" class="org.springframework.webflow.engine.builder.support.FlowBuilderServices">
<property name="expressionParser">
<bean class="org.springframework.webflow.core.expression.el.WebFlowELExpressionParser">
<constructor-arg>
<constructor-arg >
<bean class="org.jboss.el.ExpressionFactoryImpl"/>
</constructor-arg>
</bean>
@@ -38,8 +41,12 @@
<!-- Installs a listener that manages JPA persistence contexts for flows that require them -->
<bean id="jpaFlowExecutionListener" class="org.springframework.webflow.persistence.JpaFlowExecutionListener">
<constructor-arg ref="entityManagerFactory" />
<constructor-arg ref="transactionManager" />
<constructor-arg>
<ref bean="entityManagerFactory"/>
</constructor-arg>
<constructor-arg>
<ref bean="transactionManager"/>
</constructor-arg>
</bean>
</beans>

View File

@@ -4,6 +4,11 @@
"http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
<faces-config>
<managed-bean>
<managed-bean-name>testBean</managed-bean-name>
<managed-bean-class>org.springframework.webflow.samples.booking.TestJsfBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<application>
<!-- Enables Facelets -->
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>

View File

@@ -47,6 +47,12 @@
float: right;
}
#content.spring form div,
#content.spring form p {
padding: 0px;
margin: 0 0 .5em 0;
}
#content.spring {
width: 740px;
background: #fff url(../images/bg.gif) 0 0 repeat;
@@ -58,7 +64,7 @@
float: left;
}
#content.spring input[type="submit"], input[type="button"] {
#content.spring input[type="submit"], input[type="button"], button {
font-weight: bold;
color: #fff;
height: 20px;
@@ -67,6 +73,25 @@
vertical-align: middle;
}
#content.spring button
{
font-size: 1em;
font-family: arial,helvetica,verdana,sans-serif;
margin-top: 0pt;
margin-right: 0pt;
margin-bottom: 0pt;
margin-left: 0pt;
padding-top: 2px;
padding-right: 2px;
padding-bottom: 2px;
padding-left: 2px;
}
#content.spring button
{
vertical-align: middle;
}
.errors {
font-weight: bold;
text-align: center;

View File

@@ -12,24 +12,23 @@
-->
<attribute name="persistenceContext" value="true" />
<var name="bookingOptions" class="org.springframework.webflow.samples.booking.flow.booking.BookingOptions"/>
<input-mapper>
<input-attribute name="#{id}" scope="flow" />
</input-mapper>
<start-state idref="displayHotel"/>
<start-state idref="loadHotel"/>
<view-state id="displayHotel" view="hotelDetails.xhtml">
<render-actions>
<bean-action method="findHotelById" bean="bookingService">
<method-arguments>
<argument expression="#{id}" />
</method-arguments>
<method-result name="hotel" scope="flow" />
</bean-action>
</render-actions>
<transition on="book" to="createBooking" />
<transition on="cancel" to="cancel" />
</view-state>
<action-state id="loadHotel">
<bean-action method="findHotelById" bean="bookingService">
<method-arguments>
<argument expression="#{id}" />
</method-arguments>
<method-result name="hotel" scope="flow" />
</bean-action>
<transition on="success" to="createBooking"/>
</action-state>
<action-state id="createBooking">
<action method="createBooking" bean="bookingActions"/>

View File

@@ -72,9 +72,7 @@
</div>
<div class="input">
<h:selectOneMenu id="beds" value="#{booking.beds}">
<f:selectItem itemLabel="One king-size bed" itemValue="1"/>
<f:selectItem itemLabel="Two double beds" itemValue="2"/>
<f:selectItem itemLabel="Three beds" itemValue="3"/>
<f:selectItems value="#{bookingOptions.bedOptions}"/>
</h:selectOneMenu>
</div>
</div>
@@ -84,8 +82,7 @@
</div>
<div id="radio" class="input">
<h:selectOneRadio id="smoking" value="#{booking.smoking}" layout="pageDirection">
<f:selectItem itemLabel="Smoking" itemValue="true"/>
<f:selectItem itemLabel="Non Smoking" itemValue="false"/>
<f:selectItems value="#{bookingOptions.smokingOptions}"/>
</h:selectOneRadio>
</div>
</div>
@@ -115,25 +112,10 @@
</div>
<div class="input">
<h:selectOneMenu id="creditCardExpiryMonth" value="#{booking.creditCardExpiryMonth}">
<f:selectItem itemLabel="Jan" itemValue="1"/>
<f:selectItem itemLabel="Feb" itemValue="2"/>
<f:selectItem itemLabel="Mar" itemValue="3"/>
<f:selectItem itemLabel="Apr" itemValue="4"/>
<f:selectItem itemLabel="May" itemValue="5"/>
<f:selectItem itemLabel="Jun" itemValue="6"/>
<f:selectItem itemLabel="Jul" itemValue="7"/>
<f:selectItem itemLabel="Aug" itemValue="8"/>
<f:selectItem itemLabel="Sep" itemValue="9"/>
<f:selectItem itemLabel="Oct" itemValue="10"/>
<f:selectItem itemLabel="Nov" itemValue="11"/>
<f:selectItem itemLabel="Dec" itemValue="12"/>
<f:selectItems value="#{bookingOptions.creditCardExpMonths}" />
</h:selectOneMenu>
<h:selectOneMenu id="creditCardExpiryYear" value="#{booking.creditCardExpiryYear}">
<f:selectItem itemLabel="2005" itemValue="2005"/>
<f:selectItem itemLabel="2006" itemValue="2006"/>
<f:selectItem itemLabel="2007" itemValue="2007"/>
<f:selectItem itemLabel="2008" itemValue="2008"/>
<f:selectItem itemLabel="2009" itemValue="2009"/>
<f:selectItems value="#{bookingOptions.creditCardExpYears}"/>
</h:selectOneMenu>
</div>
</div>

View File

@@ -7,7 +7,7 @@
template="/template.xhtml">
<ui:define name="content">
<h:form id="main">
<h:form id="mainForm">
<div class="section">
<span class="errors">
@@ -15,6 +15,8 @@
</span>
<h2>Search Hotels</h2>
<div id="hotelsearch">
<br/>
<br/>
<fieldset>
<div class="searchGroup">
<div class="searchField">
@@ -24,52 +26,64 @@
</div>
<div class="searchSize">
<h:outputLabel for="pageSize">Maximum results:</h:outputLabel>
<h:selectOneMenu value="#{searchCriteria.pageSize}" id="pageSize">
<f:selectItem itemLabel="5" itemValue="5"/>
<f:selectItem itemLabel="10" itemValue="10"/>
<f:selectItem itemLabel="20" itemValue="20"/>
</h:selectOneMenu>
<sf:ajaxEvent actionListener="#{searchCriteria.findHotelsListener}" action="findHotels"
event="onchange" processIds="mainForm:searchString, mainForm:pageSize, mainForm:hotelsSection" renderIds="mainForm:hotelsSection">
<h:selectOneMenu value="#{searchCriteria.pageSize}" id="pageSize">
<f:selectItem itemLabel="5" itemValue="5"/>
<f:selectItem itemLabel="10" itemValue="10"/>
<f:selectItem itemLabel="20" itemValue="20"/>
</h:selectOneMenu>
</sf:ajaxEvent>
</div>
<div class="searchButton">
<h:commandButton id="findHotels" value="Find Hotels" actionListener="#{searchCriteria.findHotelsListener}" action="findHotels"/>
<sf:commandButton id="findHotels" value="Find Hotels" actionListener="#{searchCriteria.findHotelsListener}" action="findHotels"
processIds="mainForm:searchString, mainForm:pageSize, mainForm:hotelsSection" renderIds="mainForm:hotelsSection"/>
</div>
</div>
</fieldset>
</div>
<h:outputText value="No Hotels Found" rendered="#{hotels.rowCount==0}"/>
<h:dataTable id="hotels" styleClass="summary" value="#{hotels}" var="hotel" rendered="#{hotels.rowCount > 0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{hotel.name}
</h:column>
<h:column>
<f:facet name="header">Address</f:facet>
#{hotel.address}
</h:column>
<h:column>
<f:facet name="header">City, State</f:facet>
#{hotel.city}, #{hotel.state}, #{hotel.country}
</h:column>
<h:column>
<f:facet name="header">Zip</f:facet>
#{hotel.zip}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<h:commandLink id="viewHotelLink" value="View Hotel" action="selectHotel">
<f:param name="hotelId" value="#{hotel.id}"/>
</h:commandLink>
</h:column>
</h:dataTable>
<div class="next">
<h:commandLink id="nextPageLink" value="More results" actionListener="#{searchCriteria.nextPageListener}" action="findHotels" rendered="#{hotels != null and hotels.rowCount == searchCriteria.pageSize}"/>
</div>
<div class="prev">
<h:commandLink id="prevPageLink" value="Previous results" actionListener="#{searchCriteria.prevPageListener}" action="findHotels" rendered="#{searchCriteria.page > 0}"/>
</div>
<ui:fragment id="hotelsSection">
<div id="hotelsPlaceholder">
<h:outputText id="noHotelsTxt" value="No Hotels Found" rendered="#{hotels.rowCount==0}"/>
<h:dataTable id="hotels" styleClass="summary" value="#{hotels}" var="hotel" rendered="#{hotels.rowCount > 0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{hotel.name}
</h:column>
<h:column>
<f:facet name="header">Address</f:facet>
#{hotel.address}
</h:column>
<h:column>
<f:facet name="header">City, State</f:facet>
#{hotel.city}, #{hotel.state}, #{hotel.country}
</h:column>
<h:column>
<f:facet name="header">Zip</f:facet>
#{hotel.zip}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<!-- @TODO - THIS LINK DOES NOT WORK ON MYFACES 1.2.0 WHEN AJAX IS ENABLED BECAUSE THEY HAVE NOT IMPLEMENTED UIData.invokeOnComponent -->
<sf:commandLink id="viewHotelLink" value="View Hotel" ajaxEnabled="false" action="selectHotel"/>
</h:column>
</h:dataTable>
<div class="next">
<sf:commandLink id="nextPageLink" value="More Results" actionListener="#{searchCriteria.nextPageListener}" action="findHotels"
rendered="#{not empty hotels and hotels.rowCount == searchCriteria.pageSize}"
processIds="mainForm:hotelsSection"/>
</div>
<div class="prev">
<sf:commandLink id="prevPageLink" value="Previous results" actionListener="#{searchCriteria.prevPageListener}" action="findHotels"
rendered="#{searchCriteria.page > 0}"
processIds="mainForm:hotelsSection"/>
</div>
</div>
</ui:fragment>
</div>
<ui:fragment id="bookingsSection">
<div class="section">
<h2>Current Hotel Bookings</h2>
@@ -101,12 +115,12 @@
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<h:commandLink id="cancel" value="Cancel" action="cancelBooking">
<f:param name="bookingId" value="#{booking.id}"/>
</h:commandLink>
<!-- @TODO - THIS LINK DOES NOT WORK ON MYFACES 1.2.0 WHEN AJAX IS ENABLED BECAUSE THEY HAVE NOT IMPLEMENTED UIData.invokeOnComponent -->
<sf:commandLink id="cancel" value="Cancel" ajaxEnabled="false" action="cancelBooking"/>
</h:column>
</h:dataTable>
</div>
</ui:fragment>
</h:form>
</ui:define>

View File

@@ -17,7 +17,9 @@
<view-state id="displayMain" view="main.xhtml">
<transition on="findHotels" to="findHotels" />
<transition on="selectHotel" to="bookHotel" />
<transition on="selectHotel" to="displayHotel">
<set attribute="#{hotel}" value="#{hotels.selectedRow}" scope="flash"/>
</transition>
<transition on="cancelBooking" to="cancelBooking" />
</view-state>
@@ -26,10 +28,15 @@
<transition on="success" to="displayMain" />
</action-state>
<view-state id="displayHotel" view="hotelDetails.xhtml">
<transition on="book" to="bookHotel" />
<transition on="cancel" to="displayMain" />
</view-state>
<subflow-state id="bookHotel" flow="booking">
<attribute-mapper>
<input-mapper>
<mapping source="#{requestParameters.hotelId}" target="#{id}" from="string" to="long" />
<mapping source="#{hotels.selectedRow.id}" target="#{id}"/>
</input-mapper>
</attribute-mapper>
<transition on="bookingAuthorized" to="reloadCurrentUserBookings" />
@@ -39,7 +46,7 @@
<action-state id="cancelBooking">
<bean-action method="cancelBooking" bean="bookingService">
<method-arguments>
<argument expression="${requestParameters.bookingId}" parameter-type="long"/>
<argument expression="#{bookings.selectedRow.id}" parameter-type="long"/>
</method-arguments>
</bean-action>
<transition on="success" to="reloadCurrentUserBookings" />
@@ -50,6 +57,6 @@
<transition on="success" to="displayMain" />
</action-state>
<import resource="main-beans.xml" />
<import resource="main-beans.xml"/>
</flow>

View File

@@ -68,6 +68,11 @@
<p align="right">
<a href="spring/main">Start your hotel booking experience</a>
</p>
<p align="right">
<h:form id="testForm">
<h:commandButton action="#{testBean.fooAction}" value="Test Command Button"/>
</h:form>
</p>
</div>
</div>
</div>