Tuesday, February 15, 2005

Data, but not Database Data

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


  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!


12 comments:

  1. hi Tor and thanks for this article BUT do it may that dynamically adding row to table data, for example into action button method. or even delete from table data into another action button

    ReplyDelete
  2. Sorry, parse error :-) Can you restate that question?

    ReplyDelete
  3. Tor, I implemented this exactly as you described and it works perfectly. Now I do not know much about the DataTable component, can you comment on what I would need to add to your solution in order to select a row?

    ReplyDelete
  4. I'm not sure what you mean exactly by "select a row", but if you mean how to visually highlight the row, there are lots of data table ways to do that. I've blogged on some of that before. Take a look at this entry for example:
    http://blogs.sun.com/roller/page/tor?entry=creator_how_to_highlight_specific

    ReplyDelete
  5. Actually I meant get the selected row in code.
    To your example I have added a checkbox as the first column. I then have a Search button on the Page. In the searchButton_action method I have added calls to
    dataTable1.getRowIndex()
    dataTable1Model.getRowIndex()
    but these always return -1 I need the actual rowIndex. I don't know what I'm missing?

    ReplyDelete
  6. Hi Tor,
    Related to this topic,i have a question.
    In the data table, i am displaying the information which are present in my database.
    I have to give an option to the user in order to select a particular row and edit the contents of that row.
    how to acheive this functionality?
    Already i have a Row Selection(Check box) column which is bounded to the primary key of my database.
    I am really puzzled what i have to write in my Edit button action method which will make the user to edit the contents of a selected row by clicking the check box.
    Please let me know the reply ASAP.

    ReplyDelete
  7. Tor, thanks for the post. I am working with Creator 2 and have tried to follow your directions, but I have noticed that the table I added to the screen did not put a dataTableModel. It does add a DefaultTableDataProvider but I don't see any setWraper method.
    Is there now a new way to do this?
    I, like a few other people use another middleware (hibernate) and need to use a table.
    Thanks

    ReplyDelete
  8. I have a datatable in which data is displayed as follows
    List rows = new ArrayList();
    HashMap row1 = new HashMap();
    row1.put("FIRSTNAME", "Mickey");
    row1.put("LASTNAME", "Mouse");
    row1.put("FAVCOLOR", "Green");
    rows.add(row1);
    dataTable2Model.setWrappedData(rows);
    whenever i fetch data from the table it works fine when the number of rows in the data table is 4.But whenever row count is more than 4 the content from the 5th row fetched will be null.
    And also dataTable1Model RowCount property is 4 and it cannot be changed
    in the properties window.
    Is there any solution to fetch more than 4 rows?

    ReplyDelete
  9. I work with Creator2 and I would like to populate rows of a datatable with a collection of pojo.
    On your blog, I found an article about "Data, but not Database Data" http://blogs.sun.com/roller/page/tor?entry=data_but_not_database_data but I think it works well with datatable component of Creator1 only.
    How to fill a such table with a collection of bean for example ?

    ReplyDelete
  10. I work with Creator2 and I would like to populate rows of a datatable with a collection of pojo.
    On your blog, I found an article about "Data, but not Database Data" http://blogs.sun.com/roller/page/tor?entry=data_but_not_database_data but I think it works well with datatable component of Creator1 only.
    How to fill a such table with a collection of bean for example ?

    ReplyDelete
  11. In order to display data from Pojo's you could use the ObjectListDataProvider. My blog http://blogs.sun.com/roller/page/winston?entry=objectlistdataprovider_workaround has an example project on how to do this.
    If you want to display just two dimensional array of primitive type data, you can also do it. Please read my blog
    http://blogs.sun.com/roller/page/winston?entry=displaying_two_dimensional_data on this.

    ReplyDelete
  12. Tor, thanx for the post. My question is this: Since I display my data in the dataTable by iterating over a list, how can I edit the fields in the datatable and bind the values to my List (in the backing bean)? Whenever I add a button that does a submit() on the form, all original and modified data in the datatable is dropped by the refresh.... I would like to show the datatable with it's original content in input fields, permit changes and catch theses changes in the backing bean in order to save. Is this possible?

    ReplyDelete