dynamic datatable + filter comboboxes?
I'm working on a web page that has a datatable that's driven by a DB query. This DB table changes frequently, so I need to refresh the data model every time the page is reloaded. I have a new requirement to provide a filter combobox on the web page that restricts the amount of data shown on the table -- ideally to reduce stress on the DB, since this pulls from a large table.
I'm running into a problem implementing this. The datatable is refreshed early in the JSF lifecycle, before the combobox changes have been applied to the model. So when I try to retrieve the data from the DB, the combobox value hasn't been updated.
My bean code looks like:
publicclass BackingBean{
private List<DataRow> dataBeans;// beans for the dataTable
private String filterValue;// contains selected value for filter combobox
public List getDataBeans(){
if (dataBeans ==null){
dataBeans = dbUtil.getData(getFilterValue());
}
return dataBeans;
}
// other getters/setters removed
}
(Everything is currently request scope.)
I've considered pushing off the table data refresh until after the combobox value has been updated. However, I believe that I will have to implement row selections soon, and I don't see how I could apply the combobox value, refresh the table data, and then apply the row selections.
Help?
Message was edited by:
pallen
[1971 byte] By [
pallena] at [2007-11-27 8:28:15]

# 1
What I've done is have getDataBeans() not set dataBeans until it's called while in the render response phase. To figure out if I'm in the render response phase, I created a phase listener with a static method.
public List getDataBeans() {
if ( ! RenderResponsePhaseListener.isRenderResponse() ) { return null; }
if (dataBeans == null) {
dataBeans = dbUtil.getData(getFilterValue());
}
return dataBeans;
}
If there's a better way, I'd like to know, too.
# 2
1. You should create a "ViewController" or managed bean that holds all filter information associated with your filter. Its scope has to be better than request scope so either put it in Session or find an alternative (whole different topic) to persist this bean across requests.
2. I would always get the results of the database query through the controller. It knows the filter parameters and it can execute the appropriate query to the database.
3. If you are going to implement row selections make sure you have a way to get to a uniquely identifying id. JSF will always return you the DataRow selected and then it is up to you to figure out what to do with it.
This is really not too hard. Remember that you want to let JSF return the selection row (as object), go through your Controller so that you can manage the data. Most mistakes are binding the table directly to the data backing bean, don't do it. Go through a mediator.
# 3
I will expand my answer because this example (I forgot this part) shows why it is so difficult. JSF assumes that the data model between the request and response will remain constant. If you back your table with a "live" database query then you will find that if the database content changes your JSF table will break, especially if you are trying to select objects.
What I did not say in my last response is that I make it the job of my ViewController to hold the state of the UI between the response and request. Many people don't like to use Session to do this but frankly it is the only way. Typically you are only displaying a select number of rows so you would be saving only 25-50 rows out of the bean call.
The author above does have a good solution in that he is lookiing at the phase to determine if he would get the Old or Refreshed data set. Too much trouble, comit the information to Session scope.
The other way to do it is to use a Tomahawk saveState tag and store the databeans that way. It has the advantage that it is "cleaned" up when the view id changes.
# 4
> What I've done is have getDataBeans() not set
> dataBeans until it's called while in the render
> response phase. To figure out if I'm in the render
> response phase, I created a phase listener with a
> static method.
>
> [code]public List getDataBeans() {
> if ( !
> RenderResponsePhaseListener.isRenderResponse() ) {
> return null; }
> if (dataBeans == null) {
>dataBeans = dbUtil.getData(getFilterValue());
>}
>return dataBeans;
> /code]
>
> If there's a better way, I'd like to know, too.
Using a static method on PhaseListener sounds extremely dangerous. What is going to happen when your application is handling multiple requests simultaneously? There are ways to get this correct but you can more easily get it wrong.
# 5
The static method RenderResponsePhaseListener.isRenderResponse() doesn't reference any static variable of RenderResponsePhaseListener, it checks for a request-scoped attribute.
public void beforePhase( PhaseEvent event ) {
event.getFacesContext().getExternalContext().getRequestMap().put( INDICATOR, Boolean.TRUE );
}
public static boolean checkIsRenderResponse() {
return FacesContext.getCurrentInstance().getExternalContext().getRequestMap().containsKey( INDICATOR );
}
The RenderResponseListener class was just a handy spot to put the code... it could have gone anywhere.
# 6
I realize this doesn't handle the case where you want to put inputs on the rows as it's not something I've had to do.
The first thing I'd do is move the bean into session scope and then just have a Refresh button to refresh the results. I assume you'll have an Apply button to perform whatever action on the rows. This would be less than ideal because users would have to push the Refresh button (or Apply button) to get the latest results.
To get rid of the Refresh button, maybe you could have a render response phase listener notify your session-scoped bean at the beginning of the render response phase so that it can re-load the data. This isn't something I've tried.
pseudo-code:
class DataBean implements RenderResponseListener.Listener {
DataBean() {
RenderResponseListener.addListener( this );
}
beginRenderResponse() {
dataModel.setData( null );
}
getDataModel() {
...
}
apply() {
...
}
...
}
class RenderResponseListener {
addListener() {
add listener to a session-scoped attr
}
beforePhase() {
for each listener l in session-scoped attr { l.beginRenderResponse(); }
}
}
I've got to admit I don't understand the "ViewController" described above.
# 7
I thought about it some more and I think I understand what smurray_erie was referring to with "ViewController" (I thought it had something to do with JSF views).
When I code up this kind of pattern (I guess you could call it a pattern), I call the "ViewController" the Options bean. I'd have 2 managed beans: BackingBean and BackingBeanOptions, a third class BackingBeanQuery knows how to do the query. BackingBeanOptions contains the proposed filterValue. I put an Apply button on the filter options to set the BackingBean's filterValue to the proposedFilterValue.
Obviously pseudo-code:
class BackingBeanQuery {
public static class FilterOptions {
String filterValue;
FilterOptions() {
filterValue = "";
}
FilterOptions( other ) {
filterValue = other.filterValue;
}
}
BackingBeanQuery( FilterOptions options ) {
// do the query.
}
}
class BackingBeanOptions {
private BBQ.FilterOptions fo = new BBQ.FilterOptions();
private BackingBean bb;
BBQ.FilterOptions getFilterOptions() { return fo; }
public setBackingBean( BackingBean bb ) {
this.bb = bb;
apply();
}
apply() {
bb.setFilterOptions( new BBQ.FilterOptions( fo ) );
}
}
class BackingBean {
private BBQ.FilterOptions fo;
setFilterOptions() { this.fo = fo; }
getResults() {
if ( results == null ) {
BBQ q = new BBQ( fo );
results = q.getResults()
}
return results;
}
}
In faces-config.xml you'd have backingBeanOption's backingBean managed property initialized ot #{backingBean}.
I hope that makes some sense because I left out several details.
# 8
> The static method
> RenderResponsePhaseListener.isRenderResponse()
> doesn't reference any static variable of
> RenderResponsePhaseListener, it checks for a
> request-scoped attribute.
>
I was hoping you were going to say something like this.
# 9
Thanks, all.
I tried a combination of suggestions here, and found one that seemed to work well for us. I ended up changing the backing bean to session scope (which also fixed the problem of the reload button clearing out the filter information). I then only refresh the table data during the render response lifecycle phase.
Now our filters are being applied correctly, and we're not running into problems with table data persistence anymore.
Row selection was a little trickier, but it looks like we have that working also. Before refreshing the table data, our code iterates through the old data and stores of a unique ID for each selected row. We then refresh the data, then iterate through the new data set and set the appropriate rows to selected.
Thanks everyone for your help!