« Previous day (Feb 14, 2005) | Main | Next day (Feb 16, 2005) »

20050215 Tuesday February 15, 2005

Data, but not Database Data

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

  1. Create a List object
  2. Populate the List with Hashmaps, one for each row, where each hashmap contains key/value pairs for all columns in this row
  3. When done, set this list as the wrapped data for the data model
  4. 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!

(2005-02-15 23:48:02.0) Permalink Comments [13]