WARNING: This blog entry was imported from my old blog on blogs.sun.com (which used different blogging software), so formatting and links may not be correct.
Some customers I met last week said that even though automatic databinding
is handy (this is when rowsets dropped on visual components are automatically
bound to these components),
they don't want a tight coupling to the database
in this way. They have their own middle tier, and they want to use it.
How can they use the Data Table component, with its paging features
etc., and display arbitrary data as opposed to data fetched automagically from
a databound rowset?
It's actually pretty straightforward. There is just one key concept
you need to know: the JSF Data Table interacts with its data
through the DataModel interface. You can supply your own data model,
and the Data Table will happily display your own data.
When you drop a Data Table component in Creator, you automatically
get a Data Model associated with the Data Table created. For
dataTable1
you get dataTable1Model
. This
model is an instanceof javax.faces.model.ListDataModel
,
which wraps a simple java.util.List
. Each row in the
data table will come from a single item in this list.
How the row object is accessed depends on what you put in the table columns.
You may have noticed that when you drop a database table on the Data Table,
you by default get Output Text components with value bindings like these:
value="#{currentRow['FIRSTNAME']}
This lets each column in the table logically bind to different columns
of the row object. To achieve the same effect, put a Map in each
row of the data model, where the keys are keys to be used in the
value binding expressions (like "FIRSTNAME" above), and the values are
the actual values you want assigned as the value for the bound output
texts.
Let's get specific. You've dropped a JSF Data Table component on your
canvas, and you ended up with dataTable1
, and its data model,
dataTable1Model
. Go to your page bean (double click on the
canvas somewhere outside the Data Table), and modify the end of the
constructor to
- Create a List object
- Populate the List with Hashmaps, one for each row, where each
hashmap contains key/value pairs for all columns in this row
- When done, set this list as the wrapped data for the data model
- Return to the Data Table in the design view, and modify the
value bindings for the columns such that they point to the
new keys
For example, your code might look something like this:
(Creator-managed Component Initialization here)
// Additional user provided initialization code
List rows = new ArrayList();
// You would probably have a loop here to fetch your
// data from your real storage or middle tier
HashMap row1 = new HashMap();
row1.put("FIRSTNAME", "Mickey");
row1.put("LASTNAME", "Mouse");
row1.put("FAVCOLOR", "Green");
HashMap row2 = new HashMap();
row2.put("FIRSTNAME", "Donald");
row2.put("LASTNAME", "Duck");
row2.put("FAVCOLOR", "Blue");
rows.add(row1);
rows.add(row2);
dataTable2Model.setWrappedData(rows);
}
Now return to the Design view, right click on the Data Table,
bring up the Table Layout customizer. Get rid of all the existing
columns that are there (using the << button), then use the New button
to create your own columns. In my example, we'll want three columns.
Choose the heading names, like "First", "Last", and "Favorite Color".
Choose the component types - for example, Output Texts. And finally,
set the value binding expressions. For example, for the first column,
you should set the Value to #{currentRow['FIRSTNAME']}
.
Voila! When you run you should see your own data displayed in the
data table. If you enable Paging in the Table Layout customizer,
that should work too.
Using ArrayList and HashMap (and one for every row at that) may strike
you as wildly inefficient. But notice how you're supplying the List
and Map objects! You don't have to use the default implementations;
you can write your own class implementing java.util.List
which maps directly into your own data storage for the data to be
displayed, and similarly, the Map lookup from column name to column
value can be done through smart computation rather than pre-storing
all the row values in a series of HashMaps for the rows!
One final note - if the data being displayed can change as the page
is displayed repeatedly (for example due to failed validation or because
the user is interacting with other components on the page),
you probably don't want to construct your wrapped list
data in the constructor. Instead, make the List object a class member,
such as
private ArrayList rows;
and add a method which first calls
rows.clear()
and then
updates the list to contain the current correct set of rows.
Then call this update method both from the constructor as well
as from
beforeRenderResponse()
(which is a method
you should add to your page bean; its parent class has an empty
implementation which is called.)
This (the need to call the update method from both places) is a bit ugly
and is something we'll fix. Happy coding!