Woodstock to ICEfaces Porting Guide - Part 2
Part 2 of this guide builds on
the work completed in Part 1.
At the completion of Part 1, the Woodstock CreatePage was replaced with the
ICEfaces IceCreatePage.
This part of the porting guide focuses on porting data tables, and is
based on Page1 in the working
example. The following topics are
discussed:
At the completion of this guide you will understand how to port data
tables in a Woodstock application to ICEfaces. Beyond this
guide, you can examine issues related to creating Single Page
Interfaces with ICEfaces in Woodstock
to ICEfaces Porting Guide -
Part 3. The completed project for this step of the guide is
available here.
Eliminating Component
Bindings in the Working Example
As with the previous page port, we begin by eliminating the component
binding in our working example. In the case of Page1.jsp there are two component
bindings that need to be addressed, one in the dropDown, and one in the tableRowGroup.
To begin with there are a bunch of com.sun.webui.jsf import statements
that can be eliminated. All of the following can be removed.
import
com.sun.webui.jsf.component.Body;
import
com.sun.webui.jsf.component.Button;
import
com.sun.webui.jsf.component.DropDown;
import
com.sun.webui.jsf.component.Form;
import
com.sun.webui.jsf.component.Head;
import
com.sun.webui.jsf.component.Html;
import
com.sun.webui.jsf.component.Label;
import
com.sun.webui.jsf.component.Link;
import
com.sun.webui.jsf.component.MessageGroup;
import
com.sun.webui.jsf.component.Page;
import
com.sun.webui.jsf.component.StaticText;
import
com.sun.webui.jsf.component.Table;
import
com.sun.webui.jsf.component.TableColumn;
import
com.sun.webui.jsf.component.TableRowGroup;
import
com.sun.webui.jsf.model.DefaultTableDataProvider;
|
Most of the remaining code changes occur inside the Managed Component
Definition fold. For the dropDown
we eliminate
the component bindings in Page1.java
as follows.
- Replace DropDown bean property code
private
DropDown personDropDown = new DropDown();
public DropDown getPersonDropDown() {
return personDropDown;
}
public void setPersonDropDown(DropDown dd) {
this.personDropDown = dd;
}
with String bean
property code
private Integer
currentPersonId;
public Integer getCurrentPersonId() {
return currentPersonId;
}
public void setCurrentPersonId(Integer
currentPersonId) {
this.currentPersonId =
currentPersonId;
}
- Remove
the unneeded personDropDownConverter
private
IntegerConverter personDropDownConverter = new IntegerConverter();
public
IntegerConverter getPersonDropDownConverter() {
return personDropDownConverter;
}
public void
setPersonDropDownConverter(IntegerConverter ic) {
this.personDropDownConverter = ic;
}
- In the init() function modify the logic
for restoring the current person and trip by replacing
personDropDown.setSelected(pid);
with
setCurrentPersonId(pid);
- In personDropDown_processValueChange()
replace
Integer
newPersonId = (Integer) personDropDown.getSelected();
with
Integer newPersonId =
(Integer)(event.getNewValue());
And finally, back in the JSP
code we modify the dropDown
declarations
<webuijsf:dropDown binding="#{Page1.personDropDown}"
converter="#{Page1.personDropDownConverter}" id=.../>
with
<webuijsf:dropDown selected="#{Page1.currentPersonId}"
id=.../>
The tableRowGroup component
binding is used to get the current RowKey so it gets replaced as
follows..
- Replace
private
TableRowGroup tableRowGroup1 = new TableRowGroup();
public TableRowGroup getTableRowGroup1() {
return tableRowGroup1;
}
public void setTableRowGroup1(TableRowGroup trg) {
this.tableRowGroup1 = trg;
}
with
private RowKey
currentRowKey;
public RowKey
getCurrentRowKey() {
return currentRowKey;
}
public void
setCuurentRowKey(RowKey currentRowKey) {
this.currentRowKey = currentRowKey;
}
- In the updateButton_action(), and deleteButton_action() replace
RowKey
rowKey = tableRowGroup1.getRowKey();
with
RowKey rowKey =
getCurrentRowKey();
- In the createButton_action()
replace
getSessionBean1().setCurrentPersonId((Integer)
personDropDown.getValue());
with
getSessionBean1().setCurrentPersonId(currentPersonId);
Next, we make the necessary changes in Page1.jsp as follows.
- Remove component binding in tableRowGroup
<webuijsf:tableRowGroup binding="#{Page1.tableRowGroup1}"
id="tableRowGroup1" .../>
becomes
<webuijsf:tableRowGroup id="tableRowGroup1" .../>
- Set the current row associated with the Update and Delete buttons using a <f:setPropertyActionListener/>.
<webuijsf:button
actionExpression="#{Page1.updateButton_action}" id="updateButton"
text="Update">
<f:setPropertyActionListener target="#{Page1.currentRowKey}"
value="#{currentRow.tableRow}" />
</webuijsf:button>
<webuijsf:button
actionExpression="#{Page1.deleteButton_action}" id="deleteButton"
text="Delete">
<f:setPropertyActionListener target="#{Page1.currentRowKey}"
value="#{currentRow.tableRow}" />
</webuijsf:button>
That completes the refactoring from component to value bindings.
You can run the project to prove that everything still works as
expected.
We can now move on to porting the page to ICEfaces.
Building An ICEfaces
Data Table
Because the JSP markup and Java data model for an ICEfaces dataTable is significantly
different than a Woodstock dataTable,
doing a manual port in the JSP and Java code is not the most effective
way to proceed. Instead, we will use the NetBeans Visual Design
capabilities to build a replacement ICEfaces dataTable, and port the rest of the
page around that. The following steps are taken to create the new
ICEfaces dataTable.
- Start with the existing IcePage1.jsp
that was created when the ICEfaces framework was added to the
project. Open it in Visual Design mode, and delete the inputTextArea that was created as
part of the default page template.
- From the ICEfaces palette drag a dataTable onto the page.
- Right click the dataTable
and from the context menu select Bind
to Database.... This will bring up a dialog showing the
existing RowSets that can be bound to. Select tripRowSet as illustrated below.

This will auto-generate all of the columns available in the RowSet, as
illustrated below.

- Right click the dataTable
and from the context menu select Add
Sort Header...,
which will add sortable header capabilities. You won't see any
visual change in the designer, but the data model for the table will be
adjusted. More on the sortable
header later. Note:
It is important to do this prior to any other modification of the data
table, because doing it after will undo those modifications.
- Right click the dataTable
and from the context menu select Table
Layout..., which will bring up a dialog for manipulating the
columns to be displayed in the table. From here you can remove
columns 1,2,6 and 7. Next, modify the Header text for the
remaining columns to "Date", "From", "To", and "Type". Finally,
add a new column for the buttons, and clear it's header text. The
completed dialog is illustrated below.

Once the dialog is completed, the design view looks like.

- We now have superfluous outputText
components in the column facet headers that can be removed to clean up
the header titles.
- Drag 2 ICEfaces commandButtons
into the last column of the dataTable,
and modify the button names to Update and Delete. The resulting
design view is illustrated below.

- From the dataTable
context menu select Add Header...,
which will create a panelGrid
in a facet to the dataTable
called "header". Into this panel drag an ICEfaces outputText component and change its
value to "Trips". The completed table design is illustrated below.

We will now examine the JSP code that has been created for the dataTable. The table declaration is:
<ice:dataTable
id="dataTable1"
sortAscending="#{IcePage1.dataTable1SortableDataModel.ascending}"
sortColumn="#{IcePage1.dataTable1SortableDataModel.sort}" style="left:
14px; top: 14px; position: absolute"
value="#{IcePage1.dataTable1SortableDataModel}" var="currentRow">
The sortAscending and sortColumn attributes are related
to the sortable header which is discussed later. The style
attribute defines absolute positioning, and can be removed once the
table is place in a panelGrid
in the complete page. The value is bound to a sortable data model
of type com.icesoft.faces.component.jsfcl.data.CachedRowSetSortableDataModel,
and the iteration variable is defined as "currentRow".
The basic structure for the data columns is:
<ice:column
id="column3">
<f:facet name="header">
<ice:commandSortHeader
arrow="true" columnName="TRIP.DEPDATE" id="commandSortHeader3"
value="Date"/>
</f:facet>
<ice:outputText id="outputText6"
value="#{currentRow['TRIP.DEPDATE']}"/>
</ice:column>
Each column contains a header
facet containing a commandSortHeader
used for sorting the column, and a header value for column title. Each
cell in the column contains an outputText
that is bound back into the trip data provider.
The column containing the buttons looks slightly different.
<ice:column
id="column1">
<f:facet name="header">
<ice:outputText id="outputText2" value=""/>
</f:facet>
<ice:commandButton id="button1" value="Update"/>
<ice:commandButton id="button2" value="Delete"/>
</ice:column>
In this case, the header facet contains only a title, as it is not a
sortable column. The table cells contain Update and Delete
buttons that require action bindings and should be modified to look
like:
<ice:commandButton id="button1" value="Update"
action="#{IcePage1.updateButtonAction}"/>
<ice:commandButton id="button2" value="Delete" action="#{IcePage1.deleteButtonAction}"/>
Completing The Working
Example
With the newly created ICEfaces dataTable
in place, we can now complete the port from Page1.jsp to IcePage1.jsp. The work
required includes:
- Port the rest of the Page1.jsp
around the ICEfaces dataTable
- Modify the backing bean to
support the page changes
- Modify the page navigation
- Add the necessary sortable
header logic
Porting the
remainder of the page
The basic page structure from Page1.jsp
can be cut and pasted around the new ICEfaces dataTable and ported to ICEfaces in
the same manner as was done in Part 1.
After the cut and paste exercise the basic page structure looks like:

In order to maintain a consistent layout between IcePage1, and IceCreatePage, the style for
the body, and panelGrids are modified to
match those in IceCreatePage,
so we have:
<body id="outputBody1" style="-rave-layout: grid">
<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">
The following steps are needed to complete the port.
- The panelGrid containing the dropDown
is modified to look like:
<h:panelGrid
columns="4" id="personPanel" style="">
<ice:outputLabel id="label1" value="Person"/>
<ice:selectOneMenu
value="#{IcePage1.currentPersonId}" id="personDropDown"
partialSubmit="true"
valueChangeListener="#{IcePage1.personDropDownProcessValueChange}">
<f:converter converterId="javax.faces.Integer"/>
<f:selectItems id="personSelect"
value="#{IcePage1.personDataProvider.options['PERSON.PERSONID,PERSON.NAME']}"/>
</ice:selectOneMenu>
<ice:outputLabel id="label2" value="Job
Title"/>
<ice:outputText id="jobTitleText"/>
</h:panelGrid>
The selectOneMenu has partialSubmit="true" which will
cause the form to submit as the user selects a name from the
list. It also has a valueChangeListener
that will be used to update the data displayed in the table when the
name changes.
- The panelGrid
containing the dataTable is
modified to look like:
<h:panelGrid
id="tablePanel" style="">
<ice:dataTable>...</ice:dataTable>
<ice:commandButton action="#{IcePage1.createButton_action}"
id="createButton" value="Create"/>
<ice:messages id="messageGroup1" layout="table"
style="color:red;"/>
</h:panelGrid>
The commandButton is bound to an action
listener in the bean that will be used to cause the navigation to IceCreatePage.jsp. In this
case we will be reusing the logic in Page1.java.
- In the dataTable itself, the style attribute containing the
absolute positioning can be removed.
Modifying the backing bean
We now turn our attention to the Java code in the backing bean IcePage1.java. During the
visual design process for the dataTable,
the backing bean code was automatically created, but only includes the
data model for the dataTable
itself. The rest of the now-portable data model for the page
exists in Page1.java after
the refactoring to remove
the component bindings. With the
motivation to reuse this code as much as possible, we will modify IcePage1 to extend Page1, and inherit the existing
data model and logic, so
public class
IcePage1 extends AbstractPageBean {
becomes
public class
IcePage1 extends Page1 {
With this modification preprocess(),
prerender(), and destroy() methods in IcePage1 should be modified to call
the superclass counterparts. Next, we will add the necessary action and
value changed listeners with the following steps.
- The value change listener associated with the selectOneMenu is implemented as
follows.
public void
personDropDownProcessValueChange(ValueChangeEvent event) {
super.personDropDown_processValueChange(event);
dataTable1SortableDataModel.setWrappedData(getSessionBean1().getTripRowSet());
}
Basically, this listener relies on the logic from the Page1 superclass, except that we
need to refresh the data model in our table based on the name selected
by calling setWrappedData()
on our sortable data model.
- The action listener for the Update button is implemented as
follows.
public String
updateButtonAction() {
TreeMap map = (TreeMap) dataTable1SortableDataModel.getRowData();
Integer tripID = (Integer) map.get("TRIP.TRIPID");
setCurrentRowKey(getTripDataProvider().findFirst("TRIP.TRIPID",
tripID));
return(super.updateButton_action());
}
Because of the sortable nature of our data table, we cannot assume the
row number in the table matches the row number in our data provider, so
it is necessary to find it based on the trip id. Once the currentRowSet is established, we
can rely on the Page1
superclass logic to handle the action. Because this action will
ultimately cause a page navigation, there is no need to update the dataTable model.
- The action listener for the Delete button is implemented as
follows.
public String
deleteButtonAction() {
TreeMap map = (TreeMap) dataTable1SortableDataModel.getRowData();
Integer tripID = (Integer) map.get("TRIP.TRIPID");
setCurrentRowKey(getTripDataProvider().findFirst("TRIP.TRIPID",
tripID));
String returnVal = super.deleteButton_action();
dataTable1SortableDataModel.setWrappedData(getSessionBean1().getTripRowSet());
return returnVal;
}
The currentRowKey is
established as it was for the Update button, and then the Page1 superclass handles the
action. In the case of delete, it is necessary to call setWrappedData() in order to update
the
contents of the dataTable to
reflect the changes.
- The action listener for the Create button can simply rely on the
logic in the Page1 superclass
and is bound directly to createButton_action()
in that class.
<ice:commandButton action="#{IcePage1.createButton_action}"
id="createButton" value="Create"/>
- While functionally unnecessary, for Design View to work properly,
the following getters and setters are required in the base class IcePage1.
public
CachedRowSetDataProvider getPersonDataProvider() {
return super.getPersonDataProvider();
}
public void
setPersonDataProvider(CachedRowSetDataProvider crsdp) {
super.setPersonDataProvider(crsdp);
}
public Integer
getCurrentPersonId() {
return super.getCurrentPersonId();
}
public void
setCurrentPersonId(Integer currentPersonId) {
super.setCurrentPersonId(currentPersonId);
}
- Fix the imports as necessary
import java.util.TreeMap;
import javax.faces.FacesException;
import javax.faces.event.ValueChangeEvent;
With
all these changes in place, it is now possible to view the page in
Design View again, as illustrated below.

Now we can try running it. From the project context menu select properties, and then modify the Run properties as illustrated.

When you run the project, the rendered page should now look like:

You will noticed a marked improvement in the rendering of this page
compared with the original Woodstock page. As you select
different people for the selectOneMenu,
the dataTable updates
smoothly with only the minimum re-rendering required, unlike the
Woodstock page that re-renders most everything. We can now move
on
to cleaning up the navigation rules.
Modifying the Navigation
With our primary page now an ICEfaces page, we need to modify the
navigation rules as follows.
- IcePage1 to IceCreatePage is now ICEfaces to
ICEfaces, so redirect is not required. The same applies in the
reverse direction.
- IcePage1 to UpdatePage is now ICEfaces to
Woodstock, so redirect is required. The same applies in the
reverse direction.
The modified navigation rules are:
<navigation-rule>
<from-view-id>/IcePage1.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>/faces/UpdatePage.jsp</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>/IceCreatePage.jsp</from-view-id>
<navigation-case>
<from-outcome>created</from-outcome>
<to-view-id>/IcePage1.iface</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>canceled</from-outcome>
<to-view-id>/IcePage1.iface</to-view-id>
</navigation-case>
</navigation-rule>
<navigation-rule>
<from-view-id>/UpdatePage.jsp</from-view-id>
<navigation-case>
<from-outcome>updated</from-outcome>
<to-view-id>/IcePage1.iface</to-view-id>
<redirect/>
</navigation-case>
<navigation-case>
<from-outcome>noUpdate</from-outcome>
<to-view-id>/IcePage1.iface</to-view-id>
<redirect/>
</navigation-case>
</navigation-rule>
With the navigation rules in place, we once again have a fully
functional version
of the application, with 2 of the 3 Woodstock pages ported to
ICEfaces. There is one task left to
get the sort headers working in the dataTable.
DataTable sortable header
Re-examining the declaration of the dataTable shows 2 attributes sortAssending, and sortColumn are bound into the
dataTableSortableDataModel, as
shown below.
<ice:dataTable id="dataTable1" sortAscending="#{IcePage1.dataTable1SortableDataModel.ascending}"
sortColumn="#{IcePage1.dataTable1SortableDataModel.sort}"
value="#{IcePage1.dataTable1SortableDataModel}" var="currentRow">
As you would expect, these properties identify what column the sort
occurs on, and
if that sort is ascending or descending. Because these bindings
are into a request scoped bean, the current sort header state will be
lost between requests. Since the working example is currently
using standard request scope, it is necessary to maintain this state
in session scope for it to exist across requests. It is important to note, that when we
examine single page interfaces in Part
3 of this guide, we will be
moving to ICEfaces extended request scope, and will be looking to move
session scoped state back into request scope, thereby allowing us to
build a multi-document application, but for now session scope
will have to do. The necessary steps to implement the
sortable header are:
- Add the needed state to SessionBean1.java
private boolean
sortAscending;
public boolean
isSortAscending() {
return sortAscending;
}
public void
setSortAscending(boolean sortAscending) {
this.sortAscending = sortAscending;
}
private String
sortColumn;
public String
getSortColumn() {
return sortColumn;
}
public void
setSortColumn (String sortColumn) {
this.sortColumn = sortColumn;
}
- Add the following getters and setters to IcePage1.java
public boolean
isSortAscending() {
return
getSessionBean1().isSortAscending();
}
public void setSortAscending(boolean ascending) {
getSessionBean1().setSortAscending(ascending);
if (ascending !=
dataTable1SortableDataModel.isAscending()) {
dataTable1SortableDataModel.setAscending(ascending);
}
if
(getSessionBean1().getSortColumn() != null) {
dataTable1SortableDataModel.setSort(getSessionBean1().getSortColumn());
}
}
public String getSortColumn() {
return
getSessionBean1().getSortColumn();
}
public void setSortColumn(String sortColumn) {
getSessionBean1().setSortColumn(sortColumn);
}
- Modify the bindings in the dataTable
declaration in IcePage1.jsp.
<ice:dataTable id="dataTable1" sortAscending="#{IcePage1.sortAscending}"
sortColumn="#{IcePage1.sortColumn}"
value="#{IcePage1.dataTable1SortableDataModel}" var="currentRow">
You can now rerun the example and test the table sorting. You
will notice that the page updates very smoothly as you apply different
sorting, and that the sort state persists across page navigations.
Finished Part 2
You have now completed Part 2 of the Woodstock to ICEfaces Porting
Guide. The completed project for this step of the guide is
available here.
At
this point you can go back to the table of contents or Part 1 of the guide, or
move on to Part 3 of the guide,
which focuses on aspects of creating a Single Page Interface using
ICEfaces.