The a:push
component simulates push data from the server. It sends (according to the interval
attribute) a very small, (and fast) non-JSF request to the server, in order to check if there are new messages in the queue and if this is true, it performs a standard JSF request.
Example code:
<a4j:push reRender="myComponent" interval="3000" eventProducer="#{messageBean.addListener}" />
In this example, it will check in every 3 seconds (3000 milliseconds) whether there is a message in the queue. If so, the component with id = "myComponent
" will be re-rendered.
The eventProducer
attribute is needed to point at a method that accepts the PushEventListener
instance and registers it, in order to use it inside the bean.
It can be very useful, for example, for asynchronous actions that need periodic page updates.
Let's see a practical use we want to do a complex (and long) job in the background, updating the user about the job status percentage; the Start job button must be disabled after the job calculation starts, and enabled again at the end.
Let's create our backing bean called PushExample
inside the book.richfaces.advcm.modules.main.examples.push
package and insert this code:
package book.richfaces.advcm.modules.main.examples.push; @Name("pushExample") @Scope(ScopeType.CONVERSATION) public class PushExample implements Runnable { private Integer completedPercentage = 0; public Integer getCompletedPercentage() { return completedPercentage; } public void sand setCompletedPercentage(Integer completedPercentage) { this.completedPercentage = completedPercentage; } public void startJob() { new Thread(this).start(); } public void run() { // This is a heavy job for (int c = 0; c < 10; c++) { try { Thread.sleep(1000); // Wait for 1 second completedPercentage += 10; // If I finished if (completedPercentage == 100) { // Reset completedPercentage = 0; } } catch (InterruptedException e) { completedPercentage = 0; // Reset on error } } } }
The component we have created has an Integer
property that contains the job status percentage value (from 0 to 100). Also, the component implements the Runnable
interface so we can run the job in background (inside a Thread instance) without blocking the frontend during the calculation.
The startJob()
method is called by a button (we'll see it soon) and returned immediately after creating a new thread that does the job in the background. As we have made our class Runnable
, we need to implement the run()
method, that is the one called after the thread creation, for doing the job. The code inside the run()
method simulates a job that updates the completedPercentage
value every second and resets it when the job is completed.
Now, we have to connect the push component to the backend, in order to be able to communicate with it when new data is ready to be pushed into the frontend.
Let's add the PushEventListener
property and a method that will be called by the push component in order to register the listener:
@Name("pushExample") apush component:PushEventListener property, adding@Scope(ScopeType.CONVERSATION) public class PushExample implements Runnable { private PushEventListener listener; public void addListener(EventListener listener) { synchronized (listener) { if (this.listener != listener) { this.listener = (PushEventListener) listener; } } } // Other code }
The addListener
method just saves the listener object that can be used to fire a push update, by calling the onEvent
method of the listener this way:
listener.onEvent(new EventObject(this));
We can also pass a customized EventObject
if we need that.
Coming back to our example, we need to modify the run()
method to make it notify the push component at every step (so, every second in our case):
public void run() { // This is a heavy job for (int c = 0; c < 10; c++) { try { Thread.sleep(1000); // Wait for 1 second completedPercentage += 10; // If I finished if (completedPercentage == 100) { // Reset completedPercentage = 0; } listener.onEvent(new EventObject(this)); } catch (InterruptedException e) { completedPercentage = 0; // Reset on error } } }
After every completed part (or in general, when we think there is important data to push), we have just to call the onEvent
method, in order to notify the push component that there is new data ready to be pushed.
The /view/examples/push/pushExample.xhtml
file is quite simple to understand:
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> apush component:/view/examples/push/pushExample.xhtml file <ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:a="http://richfaces.org/a4j" template="/layout/template.xhtml"> <ui:define name="body"> <h:form> <a:push interval="1000" eventProducer="#{pushExample.addListener}" reRender="pushUpdate"/> </h:form> <h:form id="pushUpdate"> <h:outputText value="Job status: #{pushExample.completedPercentage}%"/> <a:commandButton value="Start job" action="#{pushExample.startJob}" disabled="#{pushExample.completedPercentage>0}" reRender="pushUpdate"/> </h:form> </ui:define> </ui:composition>
You can notice we have connected the push component to the backend using the eventProducer
attribute every time there is new data, the push component will re-render the pushUpdate
form that shows the completed job percentage and the Start job button. This will be disabled when the first data is pushed and enabled again at the end of the job (when the completedPercentage
property is set again to 0).
In order to start the conversation and make the example URL simpler, we are going to add another page (in the same directory of the example) called pushExample.page.xml
that contains the configuration we need:
<?xml version="1.0" encoding="UTF-8"?> <page xmlns="http://jboss.com/products/seam/pages" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"> <begin-conversation join="true" /> <rewrite pattern="/pushExample" /> </page>
This is what we see when the job has not been started:
And this is what we see when our job is running:
3.138.122.11