Oracle JDeveloper Tip


Generic Approach for Back-Button-Friendly Web Rowset Paging

Author: Steve Muench, ADF Development Team
Date: July 26, 2004



You can download the workspace containing the example project that illustrates this feature in use. You'll need a JDeveloper database connection named scott defined for the familiar SCOTT schema

Web Rowset Paging and the Browser Back Button

Over on this thread in the OTN JDeveloper Forum, a user asks about alternatives to ADF's PreviousSet and NextSet built-in actions for paging through a data collection. One of his issues with the way that the PreviousSet and NextSet actions work is that they are relative to the current starting row in the range, which can sometimes surprise users if they go backwards through the data by using the back button.

For example, suppose we're showing users 35 rows of query results, 10 at a time. This means that we've created an ADF iterator binding in our browse page's binding container with Range Size of 10, and a range binding to support rendering the data as a table, as well as filtering and ordering that way that the columns appear. If we use the default PreviousSet and NextSet actions, and the user performs the following sequence of steps, then they may be surprised at the final step due to their using the browser back button in between.

Imagine that the user:

  1. Starts by looking at rows 1-10
  2. Presses (NextSet)
  3. Sees rows 11-20
  4. Presses the browser back button.
  5. Sees rows 1-10 again (from local browser page cache)
  6. Presses the (NextSet) button to go forward again.

After step 6, having clicked the (NextSet) button while visually seeing the cached browser page showing rows 1-10, the user will see one of two results, either of which is likely to surprise them:

  • If the page contains the ADF bindings state token, and the developer has left the default setting of True for the Enable Token Validation property on their binding container, the user will see:

    JBO-33035: Row currency has changed since the user interface was rendered.
               The expected row key was oracle.jbo.Key[NNNNN]
  • If the page does not contain the ADF bindings state token, or if the developer has set the Enable Token Validation property on their binding container to False at design time, then the user will see rows 21-30.

Both "surprises" occur because at step 6 above, the user pressed the browser back button to see the previous page full of rows, instead of pressing the (PreviousSet) button. When they press the browser back button they see the previous page from the browser's local page cache. However, since this involved no interaction with the web server the ADF iterator binding's current notion of the first row in the range — as well as the current row in the rowset — remains unchanged.

Of course, if the user had used the (PreviousSet) button to go backward instead of the browser back button, everything would have worked as expected.

The ADF Binding Token

The binding token is added to your page by the ADF data control palette during drop operations involving an HTML form. For example, dropping an Input Form , or a Button with Form the ADF design time will add the following field into your form:

<input type="hidden"
       name="<c:out value='${bindings.statetokenid}'/>"
       value="<c:out value='${bindings.statetoken}'/>"/>

It's name and value are provided by EL expressions that access statetokenid and statetoken properties on the current binding container. The binding token validation can be controlled by the Enable Token Validation property on any binding container, whose True or False value you can set in the Property Inspector after clicking on the binding container name in the UI Model tab of the Structure Window .

The binding token is used to prevent duplicate form submission and to validate that the state of the iterators in the binding container are what your submitting web form expects them to be. It is validated — if the token exists in the submitted HTTP request and the Enable Token Validation property is set to True (the default) — during the prepareModel() phase of the ADF page lifecyle.

An Example Solution

A solution is to keep track of the current page number in a browser hidden field, or in a URL parameter, and then use this current page number — along with an appropriate event like NextPage, PreviousPage, or GotoPage — to calculate which page to scroll to next. This approach sets the next page the user sees relative to the current page that they see — irrespective of whether they got to that page via the browser back button, or the (PreviousSet) button — instead of scrolling the page relative to the ADF iterator's current notion of the starting row in the range.

I cobbled together a sample application workspace that illustrates this approach.

Some interesting points to note about the sample code:

  • The PagingDataForwardAction class in the test.controller package extends the base DataForwardAction for ADF data pages with generic support for handing events named NextPage, PreviousPage, GotoPage, and UpdateRangeSize. As expected from the Understanding ADF DataAction Event Handling section of the ADF Data Binding Primer and ADF/Struts Overview whitepaper, it accomplishes this by having onNextPage(), onPreviousPage(), onGotoPage(), and onUpdateRangeSize() methods with the expected signature.

    Notice that the core routine that accomplishes the paging uses the ADF RowSetIterator API called scrollToRangePage(int n) to scroll the display to the n th page.

    If the changeCurrentRowToNewPage() method returns true it will also move the current row to be on the current page. By default, scrolling the visible range of rows that the user can see is like sliding the scrollbar of an Excel spreadsheet. Your eyes see different rows, but the current row is where it was before.

    If the retrieveOnlyCurrentPageFromDatabase() method returns true it will use the ADF view object feature called Range Paging to fetch and cache only the current page worth of rows from the database.

  • The PagingTable.jsp page illustrates a possible implementation of a "generic table paging widget" that can be reused in any JSP page that has an ADF range binding.
  • The browseDepts.jsp and browseEmps.jsp pages illustrate two examples of reusing this generic PagingTable.jsp paging "widget", passing two parameters that are needed: the name of the range binding in the page's binding container to use for the paging behavior, and the name of the current page to use for the post-back URL.
  • The corresponding browseDepts and browseEmps Struts data action mappings are setup to use the test.controller.PagingDataForwardAction class instead of the default DataForwardAction class.

Hopefully this small example application will help you think about how you'd like to tackle web-based paging of database query results in your own ADF-based applications.