Stateful components

The hotel search page is backed by the a stateful session bean named
hotelSearch
and implemented by the class
HotelSearchingAction
.
<h:inputText value="#{hotelSearch.searchString}" />
<h:commandButton value="Find Hotels"
action="#{hotelBooking.find}"
styleClass="button" />
<h:outputLabel for="pageSize">Maximum results:</h:outputLabel>
<h:selectOneMenu value="#{hotelSearch.pageSize}" id="pageSize">
<f:selectItem itemLabel="5" itemValue="5"/>
<f:selectItem itemLabel="10" itemValue="10"/>
<f:selectItem itemLabel="20" itemValue="20"/>
</h:selectOneMenu>
When the button is clicked, the form is submitted and JSF sets the value
of the text box and drop down menu onto the searchString
and
pageSize
attributes of HotelSearchingAction
before calling the find()
action listener method. We've used a
session-scope stateful bean because we want it's state (the search results) to
be held in the session between requests to the server.
@Stateful
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@LoggedIn
public class HotelSearchingAction implements HotelSearching
{
@PersistenceContext
private EntityManager em;
private String searchString;
private int pageSize = 10;
@DataModel
private List<Hotel> hotels;
@DataModelSelection
private Hotel selectedHotel;
public String find()
{
String searchPattern = searchString==null ? "%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
hotels = em.createQuery("from Hotel where lower(name) like :search or lower(city) like :search or lower(zip) like :search or lower(address) like :search")
.setParameter("search", searchPattern)
.setMaxResults(pageSize)
.getResultList();
return "main";
}
public Hotel getSelectedHotel()
{
return selectedHotel;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public String getSearchString()
{
return searchString;
}
public void setSearchString(String searchString)
{
this.searchString = searchString;
}
@Destroy @Remove
public void destroy() {}
}
The find()
method retrieves a list of hotels from the database and
initializes the hotels
field. The hotels
field is marked
with the @DataModel
annotation, so when the find()
method
returns, Seam outjects an instance of ListDataModel
to a context
variable named hotels
. So, when the search page is re-rendered, the
result list is available to the JSF dataTable
.
Each row of the data table has an associated command button or link
(see below). When the user
clicks on the button / link, the Hotel
represented by that
row is injected into the hotel
field annotated by the
@DataModelSelection
annotation. Other application components
can then access the selected hotel via the
HotelSearching.selectedHotel
property.
<h:outputText value="No Hotels Found" rendered="#{hotels != null and hotels.rowCount==0}"/>
<h:dataTable value="#{hotels}" var="hot" rendered="#{hotels.rowCount>0}">
<h:column>
<f:facet name="header">Name</f:facet>
#{hot.name}
</h:column>
<h:column>
<f:facet name="header">Address</f:facet>
#{hot.address}
</h:column>
<h:column>
<f:facet name="header">City, State</f:facet>
#{hot.city}, #{hot.state}
</h:column>
<h:column>
<f:facet name="header">Zip</f:facet>
#{hot.zip}
</h:column>
<h:column>
<f:facet name="header">Action</f:facet>
<s:link value="View Hotel" action="#{hotelBooking.selectHotel}"/>
</h:column>
</h:dataTable>
The "View Hotel" link is the above mentioned command link associated
with each row of the data table. It is implemented
using a Seam <s:Link>
, which is part of Seam's
extension of JSF controls.
This JSF control let's us call an action, and pass a request parameter, without
submitting any JSF form. The advantage of <s:link>
is that,
unlike a standard JSF <h:commandLink>
, there is no JavaScript
used, so "open link in new tab" works seamlessly.
When this link is clicked, the selectHotel()
method of the
HotelBookingAction
bean is called. It gets the selected
hotel from the HotelSearching.selectedHotel
property,
merges it to the current persistence context (in case the same
hotel has been accessed before in the same session),
and starts a Seam conversation. We will discuss Seam conversations
in the next step.
@Stateful
@Name("hotelBooking")
@LoggedIn
public class HotelBookingAction implements HotelBooking
{
... ...
@Begin
public String selectHotel()
{
hotel = em.merge( hotelSearch.getSelectedHotel() );
return "hotel";
}
}