Alternate View Technologies-Facelets

Chapter 12. Open Source

Topics in This Chapter

  • "Web Flow Shale" on page 572

  • "Alternate View Technologies Facelets" on page 585

  • "EJB Integration Seam" on page 596

When Sun first conceived of JSF, it counted on a vibrant open source community to help drive innovation. Although it took longer than expected, that is exactly what happened with the advent of projects such as Apache Shale, JBoss Seam, and Facelets. Those open source projects along with other projects based on JSF, such as AjaxFaces not only provide immediate benefit for JSF developers, but are also shaping JSF's future.

In this chapter, we take a look at three significant innovations:

  • Web flow

  • Alternate view technologies

  • EJB (Enterprise JavaBeans) integration

Web flow is an industrial-strength version of JSF's default navigation mechanism. With web flow you can easily define complicated user interactions.

Starting with JSF 1.2 and JSP 2.1, the differences that caused incompatibilities between the two have now been banished to the dustbin, and you should be able to use them together without difficulty. However, even with that idyllic scenario, there is still a considerable segment of the JSF developer community that would like to replace JSP altogether with a lightweight templating mechanism. Luckily, JSF was built to accomodate just such a scenario.

If you have not taken a look at EJB 3.0, you may be surprised at just how much this most-maligned of specifications has matured to become a viable solution for many developers. Alas, the EJB and JSF component models are incompatible, which opens the door for frameworks that provide a unified component model.

Next, we take a look at these three innovations through the lens of three open source frameworks that implement them: Shale, Facelets, and Seam.

Web Flow Shale

From the folks that brought you Struts comes Shale, a set of services layered on top of JSF. Shale has lots of features that make everyday JSF development much easier:

  • Web flow

  • Remote method calls for Ajax

  • Templating and Tapestry-like views

  • Client- and server-side validation with Apache Commons Validator

  • Testing framework with support for unit and integration testing

  • Spring, JNDI, and Tiles integration

  • View controllers (concrete implementation of the JSF backing bean concept)

Shale web flow lets you implement a series of interactions between a user and your application. That set of interactions is more commonly called a user conversation, dialog, or wizard. Shale uses the term dialog, so that is the term we will use here.

A Shale dialog consists of one or more states. These states have transitions that define how control is transferred from one state to another. Dialogs also have a special state, called the end state, that exits the dialog and releases dialog state. Here is an example of a Shale dialog definition:

   <dialogs>       <dialog name="Example Dialog"          start="Starting State">          ...          <view name="Starting State"             view>             <transition outcome="next"                target="The Next State"/>             <transition outcome="cancel"                target="Exit"/>          </view>          <view name="The Next State"             view>             <transition outcome="next"                target="Yet Another State"/>             <transition outcome="cancel"                target="Exit"/>          </view>          ...          <end name="Exit"/>       </dialog>    </dialogs>     

In the preceding code fragment, we defined in an XML configuration file a dialog named Example Dialog with three states: Starting State, The Next State, and Exit. The transitions define how Shale navigates from one state to another for example, in a JSP page, you can do this:

   <h:commandButton        value="#{msgs.nextButtonText}"       action="next"/>

If the preceding dialog's state is The Next State and you click the button, Shale uses that outcome next to send you to the next state, in this case named Yet Another State. Because of The Next State's second transition, clicking a button whose action is cancel will end the dialog.

That contrived example shows the basics of Shale dialogs, but Figure 12-1 illustrates a more realistic example of a bill pay wizard. The wizard lets you make an online payment and is composed of four steps: Payee Information, Payment Method, Payment Schedule, and Summary. Each of those steps is shown in Figure 12-1, from top to bottom, respectively.

Figure 12-1. The bill pay wizard


But the bill pay wizard has a twist. If you select "Wire Transfer" for your payment method and click the "Next" button, you do not go directly to the "Payment Schedule" panel, as Figure 12-1 leads you to believe. Instead, a subdialog intervenes that collects the wire transfer information.

That sequence of events is shown in Figure 12-2. Notice that the top and bottom pictures in that figure are from the surrounding dialog, whereas the middle pictures are from the wire transfer subdialog.

Figure 12-2. Wire transfer subdialog (summary panel not shown)


So there is our finished application, with two dialogs, one nested in the other. Now we take a look at the key steps in implementing those dialogs.

Dialog Configuration

By default, you define your dialogs in an XML file named /WEB-INF/dialog-config.xml. We have two dialogs, so we have chosen to use two configuration files, which we declare in the deployment descriptor:

   <!-- this is WEB-INF/web.xml -->    <web-app>       <context-param>          <param-name>org.apache.shale.dialog.CONFIGURATION</param-name>          <param-value>/WEB-INF/dialogs/payment.xml,             /WEB-INF/dialogs/wire-transfer.xml</param-value>       </context-param>       ...    </web-app>

If you use a single file, named /WEB-INF/dialog-config.xml, you do not need to declare it in your deployment descriptor.

Entering a Dialog

The next order of business is entering the dialog. In our example, we use a link, as shown in Figure 12-3.

Figure 12-3. Entering the bill payment dialog


The code for that link looks like this:

   <h:commandLink action="dialog:Payment">       <h:outputText value="#{msgs.billpayPrompt}"/>    </h:commandLink>

As you might suspect, Shale places special meaning on any action that starts with the string dialog:. The name that follows the colon represents the name of a dialog, which Shale subsequently enters when a user clicks the link.

Dialog Navigation

By default, Shale dialogs are defined in XML. Here is how the Payment dialog, referenced in the preceding code fragment, is defined:

   <?xml version="1.0" encoding="UTF-8"?>    <!DOCTYPE dialogs PUBLIC       "-//Apache Software Foundation//DTD Shale Dialog Configuration 1.0//EN"       "http://struts.apache.org/dtds/shale-dialog-config-1_0.dtd">    <dialogs>       <dialog name="Payment"          start="Setup">          ...          <action name="Setup"             method="#{dialogLauncher.setupPaymentDialog}">             <transition outcome="success"                target="Payee Information"/>          </action>          <!-- Payee Information -->          <view name="Payee Information"             view>             <transition outcome="next"                target="Payment Method"/>          </view>          ...     

When you enter a dialog, Shale loads the state specified with the dialog element's start attribute. In our case, that state is Setup, which is an action state. Action states execute a method and immediately transition to another state, depending upon the string returned from the method (the action's outcome).

When Shale enters our dialog's Setup state, it invokes the setupPaymentDialog method on a managed bean named dialogLauncher. That method stores some objects in dialog scope (see "Dialog Scope" on page 578 for more information about that method) and returns the string success.

That success outcome causes Shale to load the Payee Information state, which is a view state. View states load a view, specified with the viewId attribute, and wait for the user to click a button or a link, which generates an outcome that Shale uses to navigate to the next state.

So, to summarize, when you click the link to enter the dialog, Shale loads the Setup action state, which invokes dialogLauncher.setupPaymentDialog(). That method returns success, which causes Shale to load /billpay/payment/wizard.jsp and wait for the next outcome.

At some point, our dialog ends with the end state:

         ...          <end name="Exit" view/>       </dialog>    </dialogs>

The end state is a special view state that exits the current dialog and subsequently loads the view specified with the viewId attribute.

Dialog Scope

Throughout the JSP pages in our wizards, we have input fields wired to an object that is stored as the current dialog's data object for example:

   <h:inputText        size="30"       value="#{dialog.data.name}"       ...       style/>

When you enter a dialog, Shale puts an object, named dialog, in session scope and when you exit the dialog, Shale removes the dialog object from the session, thereby effectively creating a dialog scope.

You can store an object of your choosing in the dialog object by setting the dialog's data property. In the preceding code, we access that data object to wire a text field to the data object's name property. As is often the case, our data object is a simple collection of properties representing the fields in the wizard's panels.

This all begs a question: How does our data object get associated with the dialog's data property? Like the Payment dialog discussed in "Dialog Navigation" on page 577, the Wire Transfer dialog also has a Setup action state that Shale executes when you enter the dialog. That method stores the data object in the dialog's data property. Here is how we declare the Setup method:

   <dialogs>       <dialog name="Wire Transfer Dialog"          start="Setup">          ...          <action name="Setup"             method="#{dialogLauncher.setupWireTransferDialog}">             <transition outcome="success"                target="Bank Information"/>          </action>          ...          <end name="Exit"/>       </dialog>    </dialogs>

Shale executes dialogLauncher.setupWireTransferDialog() when you enter the Wire Transfer dialog. So we have two methods, setupPaymentDialog and setupWireTransferDialog, that Shale executes immediately after entering the Payment and Wire Transfer dialogs, respectively. Those methods look like this:

   public class DialogLauncher extends AbstractFacesBean  {        private BillpayData billpayData = null;        // Called just afer entering the payment dialog        public String setupPaymentDialog() {           billpayData = new BillpayData();           billpayData.setTransfer(new WireTransferData());           setValue("#{dialog.data}", billpayData);           return "success";        }        // Called just afer entering the wire transfer dialog        public String setupWireTransferDialog() {           setValue("#{dialog.data}", billpayData.getTransfer());           return "success";        }    }

The setupPaymentDialog method creates two objects, that combined, contain all the properties on all the panels for the Payment and Wire Transfer dialogs. Because the Wire Transfer dialog is a subdialog of the Payment dialog, we likewise store the wire transfer data object in the payment data object.

While a dialog is active, the dialog object and its associated data object, are available via JSF expressions like #{dialog.data.someProperty}. When the dialog exits, Shale removes the dialog object from "dialog" scope and it is no longer accessible via JSF expressions. It is interesting to note that the data object for a dialog could easily be a map, which effectively gives you a bona fide new scope.

Note

Notice that the DialogLauncher class in the preceding code extends Shale's AbstractFacesBean. That class has a number of handy methods, some of which are listed below, that you will find useful for JSF development in general. For example, DialogLauncher uses AbstractFacesBean.setValue(), which sets a bean property's value to a given string, which can be a value expression.


org.apache.shale.faces.AbstractFacesBean

  • Object getBean(String beanName)

    Returns a managed bean with the specified beanName, if one exists. This method delegates to the JSF variable resolver, which, by default, searches request, session, and application scope in that order for managed beans. If no managed bean is found with the given name, the variable resolver looks for a managed bean definition for the bean in question; if the definition exists, JSF creates the bean. If the bean does not exist and has no definition, this method returns null.

  • Object getValue(String expr)

    Given a value expression, this method returns the corresponding object. For example: LoginPage page = (LoginPage)getValue("#{loginPage}");

  • void setValue(String expr, Object value)

    This method is the inverse of getValue; it sets a value, given a value expression; for example: setValue("#{loginPage}", new LoginPage());


Dialog Context Sensitivity

Look closely at the tabs and buttons for the dialog panels in Figure 12-1 on page 574 and Figure 12-2 on page 575, and you will see that they are context sensitive. That sensitivity is implemented in a page object and accessed in JSF expressions. Here is how the sensitivity of the wizard buttons is controlled:

   <h:commandButton        value="#{msgs.nextButtonText}"       action="next"       style       disabled="#{not dialog.data.page.nextButtonEnabled}"/>

If the page object stored in the dialog's data returns true from isNextButtonEnabled(), JSF enables the button. Similar properties, such as previousButtonEnabled, are contained in the page object and accessed in wizardButtons.jsp. Here is how the CSS styles for the tabs at the top of each wizard panel are set:

   <h:panelGrid columns="5">       <h:outputText value="#{msgs.payeeTabPrompt}"          styleClass="#{dialog.data.page.payeeStyle}"/>    </h:panelGrid>

The page object accesses dialog state programatically. In our application, we implemented a base class that encapsulates the basics:

   public class BaseDialogPage {       protected BaseDialogPage() {           // Base class only...       }       protected Status getDialogStatus() {          Map sessionMap = FacesContext.getCurrentInstance()             .getExternalContext()             .getSessionMap();          return(Status)sessionMap.get(org.apache.shale.dialog.Globals.STATUS);       }       protected boolean stateEquals(String stateName) {          return stateName.equals(getDialogStatus().getStateName());       }       protected boolean isStateOneOf(String[] these) {          String state = getDialogStatus().getStateName();          for (int i=0; i < these.length; ++i) {             if(state.equals(these[i]))             return true;          }          return false;       }    }     

Shale stores a status object in session scope, which we retrieve in the preceding code. From that status object we can determine the current state. We subsequently put those base class methods to good use in subclasses. Here is the page class for the Payment dialog:

   public class BillpayPage extends BaseDialogPage {       // State constants       private static final String PAYEE_INFORMATION = "Payee Information";       private static final String PAYMENT_METHOD = "Payment Method";       private static final String PAYMENT_SCHEDULE = "Payment Schedule";       private static final String SUMMARY = "Summary";       public String enterPaymentDialog() {          return "dialog:Payment";       }       // View logic for panels:       public boolean isPayeeRendered() {          return stateEquals(PAYEE_INFORMATION);       }       public boolean isPaymentMethodRendered() {          return stateEquals(PAYMENT_METHOD);       }       public boolean isPaymentScheduleRendered() {          return stateEquals(PAYMENT_SCHEDULE);       }       public boolean isSummaryRendered() {          return stateEquals(SUMMARY);       }       // View logic for buttons:       public boolean isNextButtonEnabled() {          return isStateOneOf(new String[] {             PAYEE_INFORMATION, PAYMENT_METHOD, PAYMENT_SCHEDULE });       }       public boolean isPreviousButtonEnabled() {          return isStateOneOf(new String[] {             PAYMENT_METHOD, PAYMENT_SCHEDULE, SUMMARY });       }       public boolean isCancelButtonEnabled() {          return true;       }       public boolean isFinishButtonEnabled() {          return stateEquals(SUMMARY);       }       // View logic for CSS style names       public String getPayeeStyle() {          return isPayeeRendered() ?              "selectedHeading" : "unselectedHeading";       }       public String getPaymentMethodStyle() {          return isPaymentMethodRendered() ?             "selectedHeading" : "unselectedHeading";       }       public String getPaymentScheduleStyle() {          return isPaymentScheduleRendered() ?             "selectedHeading" : "unselectedHeading";       }       public String getSummaryStyle() {          return isSummaryRendered() ?             "selectedHeading" : "unselectedHeading";       }    }     

The preceding class controls the visibility of panels, enabled state of buttons, and CSS tab styles, all by programatically determining the current dialog state.

Subdialogs

In our example, the Wire Transfer dialog is a subdialog of the Payment dialog. Here is how that is defined:

   <dialogs>       <dialog name="Payment"          start="Setup">          ...          <!-- The following action navigates from the Payment              Method page depending upon the payment method              that the user selected from a drop-down list.              The action simply returns that value. -->          <action name="Navigate Based on Transfer Mechanism"             method="#{dialog.data.navigateTransfer}">             <transition outcome="Wire Transfer"                target="Wire Transfer"/>          </action>          <subdialog name="Wire Transfer"             dialogName="Wire Transfer Dialog">              <transition outcome="cancel"                target="Payment Method"/>             <transition outcome="success"                target="Payment Schedule"/>          </subdialog>          ...       </dialog>    </dialogs>     

Inside the Payment dialog we declare a Wire Transfer subdialog. We navigate to that subdialog through an action state named Navigate Based on Transfer Mechanism that returns the string selected from a drop-down list of transfer types. If that string is Wire Transfer, Shale navigates to the Wire Transfer subdialog.

Notice the transitions for that subdialog: If those outcomes (cancel and success) are not handled in the subdialog, Shale associates them with the states Payment Method and Payment Schedule, respectively.

The Wire Transfer subdialog is a dialog, defined like any other:

   <dialogs>       <dialog name="Wire Transfer Dialog"          start="Setup">          <action name="Setup"             method="#{dialogLauncher.setupWireTransferDialog}">              <transition outcome="success"                 target="Bank Information"/>          </action>          <view name="Bank Information"             view>             <transition outcome="next"                target="Account Information"/>             <transition outcome="cancel"                target="Exit"/>          </view>          <view name="Account Information"             view>             <transition outcome="previous"                target="Bank Information"/>             <transition outcome="next"                target="Account Contact"/>             <transition outcome="cancel"                target="Exit"/>          </view>          <view name="Account Contact"             view>             <transition outcome="previous"                target="Account Information"/>             <transition outcome="next"                target="Summary"/>             <transition outcome="cancel"                target="Exit"/>          </view>          <view name="Summary"             view>             <transition outcome="previous"                target="Account Contact"/>             <transition outcome="finish"                target="Finish"/>             <transition outcome="cancel"                 target="Exit"/>          </view>          <action name="Finish"             method="#{dialog.data.finish}">             <transition outcome="success"                target="Exit"/>          </action>          <end name="Exit"/>       </dialog>    </dialogs>     

Shale's dialog support is a powerful upgrade to JSF's built-in navigation capabilities. By itself, it is a compelling inducement to add Shale to your tool chest.



Core JavaServerT Faces
Core JavaServer(TM) Faces (2nd Edition)
ISBN: 0131738860
EAN: 2147483647
Year: 2004
Pages: 84

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net