There have been several questions on the RichFaces User Forum regarding using push techniques to update data table cells. The default a4j:repeat sample isn't really enough for beginners to get the partial dataTable updates concepts because the default ajaxKeys feature is used (keys generated automatically for the row from where the action event comes). So I decided to create an additional sample which will be released with next richfaces-demo version and be more advanced.
Use-case:While a user is viewing a voting results page, values on the table are updating as people are voting. I will use the same base sample as the a4j:repeat demo but with the Runnable interface implementation used in the a4j:push demo in order to simulate periodic votes being placed. This will show how to use server side events to trigger the update on client side instead of just use polling which performs full form. In real life the use-case could be much more complex - user could watch for huge tables updates . Another goal of the sample is to show how to encode and update the changed cells on the client.
Let's create a simple voting form. It will show us the table with choices of the day and votes for these choices:
<h:form> <rich:dataTable value="#{choicesBean.choices}" var="choice" rowKeyVar="row" ajaxKeys="#{choicesBean.keysSet}"> <f:facet name="header"> <h:outputText value="Voting for favourite fruit" /> </f:facet> <rich:column> <h:outputText value="#{row}" /> </rich:column> <rich:column> <f:facet name="header"> <h:outputText value="choice name" /> </f:facet> <h:outputText value="#{choice.label}" id="choiceLabel" /> </rich:column> <rich:column> <f:facet name="header"> <h:outputText value="Votes Number" /> </f:facet> <h:outputText value="#{choice.votesCount}" id="choiceVotes" /> </rich:column> </rich:dataTable> </h:form>
Now we need to create two simple objects. One is Choice object which holds choice label and the votes count:
public class Choice { private String label; private int votesCount; public Choice(String label) { this.label = label; this.votesCount = 0; } //Getters and setters should be there
The second is ChoicesBean which will holds list of choices and be responsible for collecting changes information.
public class ChoicesBean { private List<Choice> choices; //current state of voting private List<Choice> lastVotes; //collected votes to be added on update private Set<Integer> keysSet; // set of rows keys updated and needed to be updated at //client side public ChoicesBean() { //Choices and lastVotes lists instantiation } //Getters and setters should be there }
Then we specify that this ChoisesBean class will implement Runnable and add the same start, stop and run methods as in a4j:push demo in order to simulate voting process. Also we will define push event producer method in similar way. The following code should be added:
... private Thread thread; private String updateInfo; private boolean enabled = false; PushEventListener listener; ... public void addListener(EventListener listener) { if (this.listener != listener) { this.listener = (PushEventListener) listener; } } public synchronized void start() { if (thread == null) { thread = new Thread(this); thread.start(); setEnabled(true); } } public synchronized void stop() { if (thread != null) { setEnabled(false); thread = null; } } public void run() { while (thread != null) { try { for (Choice choice : lastVotes) { choice.setVotesCount(DataTableScrollerBean.rand(0, 3)); } listener.onEvent(new EventObject(this)); Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }
I wanted to highlight the following points:
- After the new thread is started – it will generate new random additions to current votes every ten seconds and raise an event to push.
- Enabled and updateInfo used just in order to provide more info to sample UI (in order to keep controls enabled disabled and show last updates which was returned from server).
Now we will add the buttons to the page which will start simulation of voting:
<a4j:commandButton value="Start" action="#{choicesBean.start}" id="start" disabled="#{choicesBean.enabled}" ajaxSingle="true" reRender="push, stop, start" limitToList="true" /> <a4j:commandButton value="Stop" action="#{choicesBean.stop}" id="stop" disabled="#{!choicesBean.enabled}" ajaxSingle="true" reRender="push,start, stop" limitToList="true" />
And finally push component:
<a4j:push enabled="#{choicesBean.enabled}" interval="3000" timeout="3000" eventProducer="#{choicesBean.addListener}" id="push" limitToList="true" action="#{choicesBean.processUpdates}" reRender="choiceVotes, push, tempResults" />
Now we just need to define processUpdates method which will be called by the push component after the event generated on server side and request for updates fired:
public void processUpdates() { Set<Integer> keysForUpdate = new HashSet<Integer>(); for (Choice choice : lastVotes) { if (choice.getVotesCount() > 0) { int index = lastVotes.indexOf(choice); keysForUpdate.add(index); choices.get(index).increment(choice.getVotesCount()); } } //updatedInfo generation. keysSet = keysForUpdate; }
While checking if new votes have been generated we fill the keysSet with keys of rows which are actually changed. Now look through push page definition again:
reRender="choiceVotes... “
The Id of the h:outputText inside column component is automatically added as the reRender target. The keysSet filled in the ChoicesBean and was defined as ajaxKeys on the table. The table only encodes and passes the needed cells content to the client for reRender!
So finally after running this sample and pressing the start button you will see the periodic updates of updated cells:
Full demo code is available at 3.3.x community branch under richfaces-demo project and can be downloaded from anonymous svn.