4

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); } }