Managing the DataScroller's Page in a Master/Detail scenario

A typical master/detail scenario:

A table displays a Collection of objects. You want be able to click on an 'edit' link or button and be brought to the detail screen for editing the record. See ExecutingMethodsFromLinkButtonParameters for this part of the functionality. When you view the table again, you want to see the same section of the list, avoiding making the user scroll through the data again. In this article, we'll introduce some strategies for handling this scenario.

Note: I will use for my examples the environment of !MyFaces/Tomahawk in JSP, and assume the dataTable is being manipulated by a t:dataScroller. However, the basic strategy for tracking the "first" element on the dataTable is tied directly neither to Tomahawk nor JSP, so there may be broader applications for these concepts.

Creating a managed bean to back the dataTable

DataTableBacking.java

public class DataTableBacking {
    
    private final static int DEFAULT_ROWS = 5;
    public final static int NO_ROW_SELECTED = -1;
    
    private List records;
    private int selectedRowIndex;
    private int rowIndex;
    private int rowsPerPage = DEFAULT_ROWS;
    
    public DataTableBacking() {
        records = new ArrayList();
    }

    public DataTableBacking(List records){
        setRecords(records);
    }

    public List getRecords() {
        return this.records;
    }

    public void setRecords(List records) {
        setRecords(records, DEFAULT_ROWS);
    }
    
    public void setRecords(List records, int rowsPerPage) {
        this.records = records;
        this.rowsPerPage = rowsPerPage;
    }

    public void setRowsPerPage(int rowsPerPage) {
        this.rowsPerPage = rowsPerPage;
    }

    public int getRowsPerPage() {
        return rowsPerPage;
    }
       
    public int getSelectedRowIndex() {
        return selectedRowIndex;
    }

    public void setSelectedRowIndex(int selectedRowIndex) {
        this.selectedRowIndex = selectedRowIndex;
        this.setRowIndex(selectedRowIndex);
    }

    public int getRowIndex() {
        return rowIndex;
    }

    public void setRowIndex(int rowIndex) {
        this.rowIndex = roundDownRowIndex(rowIndex);
    }

    private int roundDownRowIndex(int rowIndex) {
        int page = rowIndex / rowsPerPage;
        int roundedIndex = page * rowsPerPage;
        return roundedIndex;
    }
} 

Let me explain what this code is for.

records is simply the data for the dataTable. This doesn't have to be part of this class, though it does clean up the dataTable declaration a bit (needing to reference only one backing bean).

rowsPerPage is important for managing what page we are on - it's used later in roundDownIndex().

rowIndex is the key property for setting what page we are on. The dataTable will point to this as its "first" property. When this attribute is set (with the index of the selected row), we automatically round down the value to a multiple of rowsPerPage. This is important to make sure that pagination is consistent.

selectedRowIndex exists solely to allow a neat css trick on the dataTable that highlights (or otherwise changes the layout/appearance depending on your css) the selected row. Thanks to Daniel Lefevre on the mailing list for this tip. You can also set the selectedRowIndex to NO_ROW_SELECTED when you don't want any row highlighted. (The first page will be displayed)

Setup of the dataTable in JSP

We create a dataTable and set it to use an instance of our backing bean:

<t:dataTable id="TheDataTable"
    styleClass="tableBorder"
    headerClass="tableHeader"
    rowClasses="oddRow,evenRow"
    rowStyleClass="#{dataTableBacking.selectedRowIndex == rowIndex ? 'highlightRow' : null}"
    columnClasses="standardTable_Column"
    value="#{dataTableBacking.records}"
    var="data"
    preserveDataModel="false"
    rows="#{dataTableBacking.rowsPerPage}"
    rowIndexVar="rowIndex"
    first="#{dataTableBacking.rowIndex}"
    > 

But wait, where's the rest? I left out the columns and the dataScroller because their content and settings are largely unimportant to our page persistence strategy. With the execption of the ideas below for commandLinks in your columns, the nature of your columns and dataScrollers are up to you. You can add one or more dataScrollers to manipulate this table, and have the table page managed.

Options for tracking the table page we are on

1) Tracking selections by row index

If you're not reloading your list between requests, or if the ordering is such that the indexes won't change, you can use a neat little trick in your dataTable columns. Whichever column(s) have a commandLink to drill down to the detail page, add an extra t:updateActionListener:

<t:updateActionListener property="#{dataTableBacking.selectedRowIndex}" value="#{rowIndex}"/>

Where #{rowIndex} references the "rowIndexVar" we defined in our dataTable.

So if your original dataTable column looked like:

<h:column>
  <f:facet name="header"><h:outputText value="ID:"/></f:facet>
  <h:commandLink action="#{navigation.drillDown}" immediate="true">
    <h:outputText value="#{data.id}"/>
    <t:updateActionListener property="#{detailsBean.id}" value="#{data.id}"/>
  </h:commandLink>
</h:column> 

Now it will look like:

<h:column>
  <f:facet name="header"><h:outputText value="ID:"/></f:facet>
  <h:commandLink action="#{navigation.drillDown}" immediate="true">
    <h:outputText value="#{data.id}"/>
    <t:updateActionListener property="#{detailsBean.id}" value="#{data.id}"/>
    <t:updateActionListener property="#{dataTableBacking.selectedRowIndex}" value="#{rowIndex}"/>
  </h:commandLink>
</h:column> 

2) Tracking selections by unique IDs

If the data is being reloaded between requests, and the indexes thus may change, you will need to rely on some unique key in your data. Remembering the key of the selected item from the last request, you iterate through the new list until you find it, and then manually set the selectedIndex on the dataTable backing bean.

You may also devise some other schemes to manually set the appropriate rowIndex from your code.

ManagingDataScrollerPage (last edited 2009-09-20 23:00:55 by localhost)