Woodstock to ICEfaces Porting Guide - Part 1

Part 1 of this guide examines the steps required to port a single page in an existing Woodstock application to an ICEfaces page.  The following topics are discussed:

At the completion of this guide you will be well-positioned to begin porting your Woodstock application to ICEfaces.  Beyond this guide, you can examine issues related to porting data tables in Woodstock to ICEfaces Porting Guide - Part 2.  The completed project for this step of the guide is available here.

Installing the ICEfaces Plugins in NetBeans

Woodstock migration support is available for the first time in the ICEfaces 1.7.2SP1 release, which is aligned with the NetBeans 6.5 release.  Prior to embarking on a porting exercise you will need to upgrade to NetBeans 6.5, import your Woodstock Visual JSF project, and install the ICEfaces plugins for NetBeans. The steps required to install the ICEfaces plugins are:

  1. Registered ICEfaces community members can download the ICEfaces/NetBeans integration bundle here.  The bundle is located under IDE Tool Integrations > NetBeans, and is called ICEfaces-1.7.2-SP1-NetBeans-6.5-modules.zip.  Unpack the bundle somewhere that you can access it from NetBeans.
  2. Install the ICEfaces plugins into NetBeans from the Tools > Plugins dialog, using the Downloaded tab.  The Add Plugins button is used to select the ICEfaces plugins which are called com-icesoft-faces-vwp-ide.nbm and com-icesoft-ide-netbeans-libs-module.nbm.  Once you have selected the plugins, you should see something like this, and just need to hit the Install button.

    Add ICEfaces Plugins to NetBeans

Working Example

The working example for this guide is based on a readily available NetBeans sample project call Two Page CRUD With Table.  It is chosen for it's relative simplicity, and because it is a multi-page application with navigation.  Over the course of the migration we will add the ICEfaces framework, add ICEfaces pages, and port existing Woodstock pages to ICEfaces. We will also establish navigation between ICEfaces and Woodstock pages.  You can get your own working copy of the example using the File > New Project dialog and choosing from Samples > Java Web (Visual JSF) > Two Page CRUD With Table, as illustrated below.

New Project for Working Example

Adding The ICEfaces Framework

The first step in the porting process is to add the ICEfaces framework to the project using the following steps.

  1. Access the Project Properties Dialog by right clicking the TwoPageCrudTable project and selecting Properties from the context menu.
  2. From the Project Properties Dialog select Frameworks and click the Add button.  From the Add a Framework Dialog select the Visual Web ICEfaces frameworks as illustrated below.

    Add ICEfaces Framework


  3. Complete the dialog by selecting Synchronous Update in the ICEfaces Settings and hitting the OK button, as illustrated below.   Synchronous updates simplify the client/server communications in applications where ICEfaces Ajax Push capabilities are not being leveraged.  Synchronous updates most closely match the existing  behavior of the application.  The other setting, Concurrent DOM View, is discussed in Part 3 of this guide.  For now, it should be left unchecked

    .
    Project Properties Dialog

Adding the ICEfaces framework to the project results in a number of changes, including:

  1. Configuration in web.xml is modified to include:
  2. ICEfaces design and run time libraries are added.
  3. A template ICEfaces page called IcePage1.jsp is created.
  4. A template index.html page is created.

The project should be runnable as is, but with the introduction of the index.html, you need to either:

The ICEfaces Page Template

The ICEfaces NetBeans plugin provides JSF Visual Design capabilities similar to the Woodstock-based JSF Visual Design capabilities that your existing project is based on.  The ICEfaces page template strongly resembles the Woodstock template as illustrated below in a diff of these two pages.

Page Template Differences


As of NetBeans 6.5 the standard HTML tags  <html>, <head>, and <body> are supported in design view so ICEfaces replacements are not required for <webuijsf:html>, <webuijsf:head>, and <webuijsf:body> and the standard HTML tags are used in the ICEfaces page template.  Any page porting exercise will involve migrating the page content in the <webuijsf:form> of the Woodstock page to the <ice:form> in the replacement ICEfaces page.

Woodstock Component Bindings

Before you start porting Woodstock pages to ICEfaces pages, there is a potential problem with the application data model that you might need to address.  If your JSF Visual Project was created prior to the NetBeans 6.1 release, and you have not already refactored to eliminate the component bindings, it will be necessary to do so.  In the case of our working example, that refactoring has not occurred, so we will use it to describe the process of achieving this refactoring.

A quick review of JSF component bindings vs. value bindings is in order before proceeding.  A component binding binds the view component to a component instance in the backing bean as illustrated below:

The view component definition,
<h:outputText binding="#{myBean.someOutputText}" />
is bound to an instance of HtmlOutputText in the backing bean.
public class MyBean {
    HtmlOutputText someOutputText;
    public HtmlOutputText getSomeOutputText() {
       return someOutputText;
    }
    public void setSomeOutputText(HtmlOutputText outputText) {
       someOutputText = outputText;
    }
}

While component binds facilitate manipulation of the component directly from Java code, the view and the model become tightly coupled through the component class itself.  When you consider porting away from a particular component you are forced to port the model as well, because the component instance will no longer be present.  In contrast, value bindings bind the view component to a property in the backing bean as illustrated below:

The view component definition,
<h:outputText value="#{myBean.someText}" />
is value bound to an instance of String in the backing bean.
public class MyBean {
    String someText;
    public String getSomeText() {
       return someText;
    }
    public void setSomeText(String text) {
       someText = text;
    }
}

This completely separates the view from the model, and ensures that the backing bean can be represented as a portable POJO.  This is the essence of the refactoring that must occur prior to porting the page to ICEfaces.

Eliminating Component Bindings in the Working Example

We will work with the simplest page, CreatePage.jsp, in the working example to fully illustrate the process of factoring out the component bindings.  A quick look at the page illustrates that it contains a calendar input, a couple input texts, and a drop down menu as the input controls requiring a backing model.

CreatePage.jsp


And you can see in CreatePage.jsp, these input components currently have component bindings.

Component Bindings


We will start with the calendar, and eliminate the component bindings from CreatePage.java as follows:
  1. Replace the Calendar component import statement
            import com.sun.webui.jsf.component.Calendar;
    with a standard Java Date import statement
            import java.util.Date;

  2. Replace dateCalendar bean property code
            private Calendar dateCalendar = new Calendar();
            public Calendar getDateCalendar() {
                return dateCalendar;
            }
            public void setDateCalendar(Calendar c) {
                this.dateCalendar = c;
            }
    with date bean property code
            private Date depDate = new Date();
            public Date getDate() {
                return depDate;
            }
            public void setDate(Date d) {
                this.depDate = d;
            }

  3. In the addButton_action() listener remove the line
            java.util.Date depDate = (java.util.Date) dateCalendar.getValue();

Now we can return to the JSP code and modify the calendar component declaration
    <webuijsf:calendar binding="#{CreatePage.dateCalendar}" id="dateCalendar" ... />
with
    <webuijsf:calendar  selectedDate="#{CreatePage.date}" id="dateCalendar" ... />
At this point we have a functional CreatePage.jsp where the calendar binding has been converted from a component to a value binding.  From here we move on to the textField components with a similar set of modifications.

  1. Remove the TextField component import statement
            import com.sun.webui.jsf.component.TextField;

  2. Replace TextField bean property code
            private TextField fromCity = new TextField();
            public TextField getFromCity() {
                return fromCity;
            }
            public void setFromCity(TextField tf) {
                this.fromCity = tf;
            }

            private TextField toCity = new TextField();
            public TextField getToCity() {
                return toCity;
            }
            public void setToCity(TextField tf) {
               this.toCity = tf;
            }
    with String bean property code
            private String fromCity = "";
            public String getFromCity() {
                return fromCity;
            }
            public void setFromCity(String s) {
                this.fromCity = s;
            }

            private String toCity = "";
            public String getToCity() {
                return toCity;
            }
            public void setToCity(String s) {
                this.fromCity = s;
           }

  3. In the addButton_action() listener modify the lines
           tripDataProvider.setValue("TRIP.DEPCITY", rowKey, fromCity.getValue());
           tripDataProvider.setValue("TRIP.DESTCITY", rowKey, toCity.getValue());
    to use the String bean properties directly
           tripDataProvider.setValue("TRIP.DEPCITY", rowKey, fromCity);
           tripDataProvider.setValue("TRIP.DESTCITY", rowKey, toCity);

And back in the JSP code we modify the textField declarations
    <webuijsf:textField binding="#{CreatePage.fromCity}" id="fromCity"/>
    <webuijsf:textField binding="#{CreatePage.toCity}" id="toCity"/>
with
    <webuijsf:textField text="#{CreatePage.fromCity}" id="fromCity"/>
    <webuijsf:textField text="#{CreatePage.toCity}" id="toCity"/>

At this point we again have a functional CreatePage.jsp where four of the five component bindings have been converted to value bindings.  The last step is to modify the dropDown in a similar fashion. 

  1. Remove the DropDown component import statement
            import com.sun.webui.jsf.component.DropDown;

  2. Replace DropDown bean property code
            private DropDown tripType = new DropDown();
            public DropDown getTripType() {
                return tripType;
            }
            public void setTripType(DropDown dd) {
                this.tripType = dd;
            }
    with String bean property code
            private String tripType = "";
            public String getTripType() {
                return tripType;
            }
            public void setTripType(String s) {
                this.tripType = s;
            }

  3. In the addButton_action() listener modify the line
           tripDataProvider.setValue("TRIP.TRIPTYPEID", rowKey, tripType.getSelected());
    to use the tripType bean properties directly
           tripDataProvider.setValue("TRIP.TRIPTYPEID", rowKey, tripType);

And finally, back in the JSP code we modify the dropDown declarations
     <webuijsf:dropDown binding="#{CreatePage.tripType}" id="tripType" items=... />
with
     <webuijsf:dropDown selected="#{CreatePage.tripType}" id="tripType1" items=... />

Now we have a functionally equivalent CreatePage.java backing bean that has no Woodstock-specific component bindings and is portable enough to proceed with the page conversion to ICEfaces.

Notes:

  1. For some reason the NetBeans visual editor would not render the dropDown because the id tripType matched the selected value tripType.  This was corrected by modifying the id to eliminate the match.
  2. The IntegerConverter is no longer required on the page, but it is used in a binding from UpdatePage.jsp, so it is left in the code.

Creating a New ICEfaces Page

Before we begin the process of porting a Woodstock page to ICEfaces, we need a template ICEfaces page to port into. The context menu  Web Pages > New > ICEfaces Visual Web JSF Page... will result in a new page dialog as illustrated below.  For our working example, the page is called IceCreatePage.jsp.

Create ICEfaces Page


Page Porting Basics

There are two likely scenarios that will be encountered when embarking on a page porting exercise.

  1. If the new page design is significantly different than the existing page design and will take advantage of the extended features and components available in ICEfaces,  the recommenced approach is to build an entirely new ICEfaces page using the visual editor and not attempt to port the existing Woodstock page.  It is likely that the existing backing beans can be reused, but it may be necessary to augment those beans to support the new page functionality.

  2. If the new ICEfaces page will initially remain the same, with the intent of adding additional features only after the existing functionality has been reproduced, you will want to preserve the original page structure as much as possible, and reuse the existing backing beans.  The recommended approach is to use the JSP editor to cut the page contents from the old page, and paste it in to the new ICEfaces page.  From there you can perform component-by-component conversion using the JSP editor.  Considering our working example with CreatePage.jsp being ported to IceCreatePage.jsp, the resulting starting point for the page conversion is illustrated below.

    Woodstock Initial Paste

Component-by-component Porting

Porting away from Woodstock components to ICEfaces components must be addressed on a component by component basis. To assist in this process a Component Mapping Matrix is available that provides component to component mappings, and attribute comparisons.  We now examine the variations on component porting that you will likely encounter.

Standard JSF Component Porting

Components from the standard JSF namespaces xmlns:f="http://java.sun.com/jsf/core" and xmlns:h="http://java.sun.com/jsf/html" do not require porting as they can coexist with other ICEfaces components in the page.  In the case of the h: components, ICEfaces also provides direct replacements from the ice: namespace, so you can simply change the prefix on these components to get the extended ICEfaces versions.  The advantage to the ice: versions is they support all the ICEfaces automatic Ajax features such as partial submit.  A general rule of thumb is, if the h: component has any dynamic behavior associated with it, it is a candidate for conversion to an ice: component.  If, on the other hand, it is static element in the page, there is no real advantage to converting it.

Woodstock Component Porting

With the ICEfaces 1.7.2SP1 release all components from the Woodstock namespace xmlns:webuijsf="http://www.sun.com/webui/webuijsf" must be ported to ICEfaces components.  In subsequent ICEfaces releases a certain subset of webuijsf: components may be directly supported and will not require porting, but until then you must consider porting all of them.  During the porting process you will encounter two likely scenarios.

  1. There is a direct replacement in the ICEfaces component suite for the Woodstock component being replaced.  A simple example is <ice:outputLabel/> replacing <webuijsf:label/>.  In this case, it will be necessary to adjust the attributes list to match those available in the ICEfaces component.  Although you are dealing with a direct component replacement, it is important to understand that the components may not have the exact same feature set so adjustments may be necessary. The component porting matrix identifies attribute similarities and differences, and provides general guidance related to behavioral differences between the ICEfaces and Woodstock counterpart.

  2. There is no direct replacement in the ICEfaces component suite for the Woodstock component being replaced.  An example of this is <wiebuijsf:addRemove> that includes two lists and a mechanism to move element back and forth between the lists.  In this case, a group of ICEfaces components may have to be combined to achieve the functional equivalent.  Again, the component porting matrix will identify these components and provide guidance on how to achieve a port to ICEfaces.  As an aside, some subset of this class of components will likely be prioritized for inclusion as first-class components in subsequent releases of ICEfaces.

Porting the Working Example Page

Several different strategies can be taken when porting a page from Woodstock to ICEfaces.  You could copy the entire page and its backing beans to a new page and do your porting there, or you could build a new JSP page, and reuse the existing backing bean as is.  For the working example we will take the later approach and reuse the exising Page1.java backing bean.  To complete the page port we need to:

  1. Port the masthead.jspf
  2. Port the main panelGrid in CreatePage.jsp

Porting the masthead

The first step in porting the page is to port the Masthead which is included at the top of each page.  To achieve this we need to copy the existing Masthead.jspf to iceMasthead.jspf and ported to ICEfaces by simply replacing the webuijsf: components with corresponding ice: components, as illustrated in the diff below.

Masthead Differences


The specific changes in the port include:
  1. Modification of the namespace from
    <div style="height: 155px; width: 760px; -rave-layout: grid" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html" xmlns:webuijsf="http://www.sun.com/webui/webuijsf">
    to
    <div style="height: 155px; width: 760px; -rave-layout: grid" xmlns:f="http://java.sun.com/jsf/core" xmlns:h="http://java.sun.com/jsf/html"
    xmlns:ice="http://www.icesoft.com/icefaces/component">

  2.  Change <webuijdf:staticText/> declarations in the title panelGrid from
    <webuijsf:staticText id="appName" style="color: rgb(255, 255, 255); font-family: Georgia,'Times New Roman',times,serif; font-size: 24px; font-weight: bold" text="Two Page CRUD With Table"/>
    <webuijsf:staticText id="poweredBy" style="color: #ffffff; font-family: font-family: Georgia,'Times New Roman',times,serif; font-size: 10px" text="Empowered by NetBeans 6"/>
    to
    <ice:outputText id="appName" style="color: rgb(255, 255, 255); font-family: Georgia,'Times New Roman',times,serif; font-size: 24px; font-weight: bold" value="Two Page CRUD With Table"/>
    <ice:outputText id="poweredBy" style="color: #ffffff; font-family: font-family: Georgia,'Times New Roman',times,serif; font-size: 10px"                value="Empowered by NetBeans 6"/>

  3. Change <webuijsf:imageHyperlink/> declarations in the navPanel panelGrid from
    <webuijsf:imageHyperlink id="home" imageURL="/resources/home.gif" target="_blank" text="Home" url="http://visualweb.netbeans.org/"/>
    <webuijsf:imageHyperlink id="help" imageURL="/resources/help.gif" target="_blank" text="Help" url="readme.html"/>
    to
    <ice:outputLink id="home" target="_blank" value="http://visualweb.netbeans.org/">
           <ice:graphicImage id="graphic1" value="./resources/home.gif"/>
           <ice:outputText id="out1" value="home"/>
    </ice:outputLink>
    <ice:outputLink id="help" target="_blank" value="readme.html">
           <ice:graphicImage id="graphic2" value="./resources/help.gif"/>
           <ice:outputText id="out2" value="help"/>
    </ice:outputLink>
    In this case we use multiple ICEfaces component to replace a single Woodstock component.

Porting the page body

The following diff illustrates the changes required to port the content in the main panel grid.

Main Panel Difference


The resulting JSP code looks like:

<ice:form id="form1">
    <div style="position: absolute; left: 0px; top: 0px">
        <jsp:directive.include file="iceMasthead.jspf"/>
    </div>
    <h:panelGrid id="mainPanel" style="margin: 5px; padding: 5px; left: 0px; top: 160px; position: absolute; width: 760px">
        <h:panelGrid columns="2" id="tripPanel" style="">
            <ice:outputLabel id="label1" value="Date"/>
            <ice:selectInputDate value="#{CreatePage.date}" id="dateCalendar" renderAsPopup="true">
                <f:convertDateTime timeZone="MST"/>
            </ice:selectInputDate>
            <ice:outputLabel id="label2" value="From"/>
            <ice:inputText value="#{CreatePage.fromCity}" id="fromCity" partialSubmit="true" required="true"/>
            <ice:outputLabel id="label3" value="To"/>
            <ice:inputText value="#{CreatePage.toCity}" id="toCity" partialSubmit="true" required="true"/>
            <ice:outputLabel id="label4" value="Trip Type"/>
            <ice:selectOneMenu value="#{CreatePage.tripType}" id="tripType1">
                <f:selectItems id="tripSelect" value="#{CreatePage.triptypeDataProvider.options['TRIPTYPE.TRIPTYPEID,TRIPTYPE.NAME']}"/>
            </ice:selectOneMenu>
        </h:panelGrid>
        <h:panelGrid columns="2" id="buttonPanel" style="">
            <ice:commandButton action="#{CreatePage.addButton_action}" id="addButton" value="Add"/>
            <ice:commandButton action="#{CreatePage.cancel_action}" id="cancel" value="Cancel" immediate="true"/>
        </h:panelGrid>
        <ice:messages id="messageGroup1" layout="table" style="color:red;"/>
    </h:panelGrid>
</ice:form>

The specific changes in the port include:
  1. The jsp include directive now specifies iceMasthead.jspf
  2. <webuijsf:label/> is replaced with <ice:outputLabel/>, and value binding attribute name is changed from text to value.
  3.  <webuijdf:calendar/> is replaced with <ice:selectInputDate/>.  In this case there is no direct replacement for the maximum and minimum date values, so it would be necessary to add a validator to the ICEfaces component to match the functionality in the Woodstock component. This is an exercise left to the reader.  One other issue related to selectInputDate is managing the timezone properly.  A lengthy discussion is beyond the scope of this guide, so for simplicity we use a standard f:convertDataTime and specify a fixed timezone that match the timezone the server is running in.  For accurate results modify this to your timezone.
  4.  <webuijdf:textField/> is replaced with <ice:inputText/>, and value binding attribute name is changed from text to value.. 
  5. <webuijsf:dropDown/> is replaces with <ice:selectOneMenu/> containing a child <f:selectItems/> for the list value binding.
  6. <webuijsf:button/> is replaced with <ice:commandButon/>, and value binding attribute name is changed from text to value.  The actionExpression attribute is also changed to an action.
  7. <webuijsf:messageGroup/> is replaced with <ice:messages/>.  Layout and style attributes have been added to <ice:messages/> to closely mimic <webuijsf:messageGroup/>.
  8. One minor change to the style of the panelGrid id="mainPanelGrid" is required.  For the page to render properly height:100% must be removed from the style attribute.

We now have a fully function ICEfaces replacement page for CreatePage.jsp.  The next step is to modify the navigation rules to go to and from IceCreatePage.jsp.

Page Navigation

With Woodstock the request path is defined by the Servlet mapping  /faces/* through the standard Faces Servlet.  With ICEfaces the request path is defined by the Servlet mapping*.iface through the ICEfaces Persistent Faces Servlet.  Because different Servlets are used for these request paths, it is necessary to use redirects when navigating to and from ICEfaces pages.  The changes required to modify the navigation rules for the working example are illustrated below.

Navigation Rules Differences


The resulting navigation rules are:

    <navigation-rule>
        <from-view-id>/Page1.jsp</from-view-id>
        <navigation-case>
            <from-outcome>createCase</from-outcome>
            <to-view-id>/IceCreatePage.iface</to-view-id>
            <redirect/>
        </navigation-case>
        <navigation-case>
            <from-outcome>updateCase</from-outcome>
            <to-view-id>/UpdatePage.jsp</to-view-id>
        </navigation-case>
    </navigation-rule>
    <navigation-rule>
        <from-view-id>/IceCreatePage.jsp</from-view-id>
        <navigation-case>
            <from-outcome>created</from-outcome>
            <to-view-id>/faces/Page1.jsp</to-view-id>
            <redirect/>
        </navigation-case>
        <navigation-case>
            <from-outcome>canceled</from-outcome>
            <to-view-id>/faces/Page1.jsp</to-view-id>
            <redirect/>
        </navigation-case>
    </navigation-rule>

As you can see, Page1.jsp now navigates to IceCreatePage.iface using a redirect as the outcome of the Create button, and IceCreatePage.jsp navigates back to Page1.jsp using a redirect as the outcome of the Create and Cancel buttons.

Note: You cannot use the visual page flow designer to make the necessary changes to incorporate ICEfaces navigation rules.

Automatic Ajax 

With the functional ICEfaces page now in place you can begin to explore some of the Automatic Ajax features that the framework delivers - without you altering a single line of code.  Intelligent form processing is one such feature, where the application can react to the user on a field by field basis instead of processing the entire form when it is submitted.  As an example, simple type some gibberish into the date field and then tab out of that field to the next.  You will see that validation on the date field runs immediately and informs you that an invalid date has been entered, as illustrated below.

Date Validation


A capability called partial submit is responsible for this behavior.  Basically, a partial submit occurs as focus leaves the date field, which causes the JSF lifecycle to run.  The submit is partial in that it will not validate the entire form, so you don't see validation messages for fields that have not been visited yet.  Because the lifecycle runs it is possible to add business logic that analyzes the users input and adjust the form accordingly.  In our case we have simply allowed the validation to run on the date input. 

We will now extend the intelligent form processing by requiring that the From and To city fields are filled in prior to submitting the form.  This is achieved by turning on the partialSubmit and required attributes in the inputText fields, as illustrated below.

    <ice:inputText value="#{CreatePage.fromCity}" id="fromCity" partialSubmit="true" required="true"/>
    <ice:inputText value="#{CreatePage.toCity}" id="toCity" partialSubmit="true" required="true"/>

If you rerun the application now, enter a valid date and then tab through the From City inputText, you will see validation occurring immediately as illustrated below.

City Validation


So you can see a more interactive form can be presented to the user with very little effort using ICEfaces.  Of course you could add more sophisticated processing like checking the city against a database of valid destinations, and disabling the Add button until a valid form has been completed.  Have a look at the ICEfaces Address Form Demo as another example of intelligent form processing.

Finished Part 1

You have now completed Part 1 of the Woodstock to ICEfaces Porting Guide.  The complete project up to this point can be downloaded here.  At this point you can go back to the table of contents, or move on to Part 2 of the guide, which focuses on porting a data table from Woodstock to ICEfaces.