Stateful components
The hotel search page is backed by the a stateful session bean named
hotelSearch
and implemented by the class
HotelSearchingAction
.
<h:inputText id="searchString" value="#{hotelSearch.searchString}" >
<a:support event="onkeyup" actionListener="#{hotelSearch.find}"
reRender="searchResults"/>
</h:inputText>
<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. The <a:support>
tax specfies that after a keypress, the contents of the <a:outputPanel>
whose
id is searchResults
should be rerendererd. This is done through an AJAX-style
call back to the server with no additional code required of the application.
@Stateful
@Name("hotelSearch")
@Scope(ScopeType.SESSION)
@LoggedIn
public class HotelSearchingAction implements HotelSearching
{
@PersistenceContext
private EntityManager em;
private String searchString;
private int pageSize = 10;
private int page;
@DataModel
private List<Hotel> hotels;
public void find()
{
page = 0;
queryHotels();
}
public void nextPage()
{
page++;
queryHotels();
}
private void queryHotels()
{
hotels = em.createQuery("select h from Hotel h where lower(h.name) " +
"like #{pattern} or lower(h.city) like #{pattern} " +
"or lower(h.zip) like #{pattern} or " +
"lower(h.address) like #{pattern}")
.setMaxResults(pageSize)
.setFirstResult( page * pageSize )
.getResultList();
}
public boolean isNextPageAvailable()
{
return hotels!=null && hotels.size()==pageSize;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
@Factory(value="pattern", scope=ScopeType.EVENT)
public String getSearchPattern()
{
return searchString==null ?
"%" : '%' + searchString.toLowerCase().replace('*', '%') + '%';
}
public String getSearchString()
{
return searchString;
}
public void setSearchString(String searchString)
{
this.searchString = searchString;
}
@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).
<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(hot)}"/>
</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 with the hot
parameter that is specified in the query. The parameter values are evaluated
at invocation time, not when the link is generated, so the <s:link>
tag adds a dataModelSelection parameter that indicates the value of the
hot
loop variable for the given row.
The selectHotel()
method merges the selected hotel into
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 void selectHotel(Hotel selectedHotel)
{
hotel = em.merge(selectedHotel);
}
}