Generic Approach for Back-Button-Friendly Web Rowset PagingAuthor: Steve Muench, ADF Development Team
You can download the
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
NextSet built-in actions for paging through a data collection. One of his issues with the way that the
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
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:
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]
Falseat 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:
value are provided by EL expressions that access
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
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
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
PagingActionExample.zip sample application workspace that illustrates this approach.
Some interesting points to note about the sample code:
PagingDataForwardAction class in the
test.controller package extends the base
DataForwardAction for ADF data pages with generic support for handing events named
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
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.
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.
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.
PagingTable.jsppage illustrates a possible implementation of a "generic table paging widget" that can be reused in any JSP page that has an ADF range binding.
browseEmps.jsppages illustrate two examples of reusing this generic
PagingTable.jsppaging "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.
test.controller.PagingDataForwardActionclass instead of the default
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.