Roger has posted 2 posts at DZone. View Full User Profile

Back To Basics With JSF and Ajax

09.10.2008
| 16258 views |
  • submit to reddit

This past week I had the pleasure of attending (and presenting at) the Ajax Experience in San Francisco. The collection of talks were good and the "Reverse Ajax" (Comet) talks were particularly interesting. I spoke a bit about JSF and Ajax, and I'd like to share the technical details of a little "mashup" I put together for my talk.

This demo uses the Dynamic Faces library (with JSF) to illustrate a stock lookup service. To simplify things for the demo, there are only three artifacts used in the demo:

  1. One JSP page
  2. One small JavaScript file
  3. One Managed Bean

First, let's take a look at the UI:

stock-faces-ui.GIF


The UI is pretty basic. You enter one or more space delimited stock symbols in the "Symbols" text field and press the "Search" button. You can enter proxy information if you are behind a firewall. The most interesting feature of the demo is the "Streaming" option. If "Streaming" is set to "On", the client will poll the server, firing Ajax transactions every 10 seconds (or a specified time interval). The "Remote/Local" option allows you to switch the demo to use local data, if a network connection is not available. If the "Remote" option is used, the Yahoo Stock Quoting service is used to get the stock data. The table of stock data will dynamically change size depending on the number of symbols that are used in the query. Let's take a look at the artifacts used in this demo.

JSP

Here's a snippet of the JSP page for the demo, showing the relevant parts:

<f:view>
<html>
<head>
...
...
<jsfExt:scripts/>
<script type="text/javascript">
...
...
include_js('javascripts/stock-faces.js');
</script>
</head>
<body>
  <h:form id="form" prependId="false">
    <h:panelGrid styleClass="title-panel">
        <h:outputText value="Stock Query" styleClass="title-panel-text"/>
        <h:outputText value="Powered By Dynamic Faces" styleClass="title-panel-subtext"/>
    </h:panelGrid>
    <h:panelGrid border="1" columns="1" styleClass="panel-input-border">
        <h:panelGrid border="1" columns="7">
            <h:outputText value="Symbol:"/>
            <h:inputText id="symbol"/>
            <h:commandButton id="search" value="Search" 
                onclick="DynaFaces.fireAjaxTransaction(this, {});return false;"
                actionListener="#{bean.getStockInfo}" />
            <h:outputText value="Proxy Host:"/>
            <h:inputText id="proxyHost"/>
            <h:outputText value="Proxy Port:"/>
            <h:inputText id="proxyPort"/>
            <h:outputText value="Streaming:"/>
            <h:selectOneMenu id="streaming" value="Off" 
                onchange="toggleStreaming()">
                <f:selectItem itemValue="Off" itemLabel="Off"/>
                <f:selectItem itemValue="On" itemLabel="On"/>
            </h:selectOneMenu>
            <h:selectOneMenu id="connection" value="Local"> 
                <f:selectItem itemValue="Local" itemLabel="Local"/>
                <f:selectItem itemValue="Remote" itemLabel="Remote"/>
            </h:selectOneMenu>
        </h:panelGrid>
    </h:panelGrid>
          
    <h:panelGrid id="stockdata" border="1" columns="8"
        styleClass="panel-data-border" rendered="false">
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Symbol"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Name"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Open"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Last"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value=""/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Change"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Change %"/>
            </f:facet>
        </h:panelGrid>
        <h:panelGrid>
            <f:facet name="header">
                <h:outputText value="Volume"/>
            </f:facet>
        </h:panelGrid>
    </h:panelGrid>
           
  </h:form>
</body>
</html>
</f:view>


Here is an explanation of the "blue" sections in the code snippet:

  1. <jsfExt:scripts/> is the standard tag to include for Dynamic Faces applications, and it includes the Dynamic Faces JavaScript library.
  2. The include_js('javascripts/stock-faces.js'); line is just a utility function that loads the application's JavaScript file stock-faces.js (which we will discuss next).
  3. The h:commandButton tag has an onclick JavaScript event handler attached to it - DynaFaces.fireAjaxTransaction, which will send an Ajax request to the server when the button is pressed. The actionListener specified by #{bean.getStockInfo} will get executed on the server as usual. But the key is that any view or JSF component manipulation done on the server will happen over Ajax.
  4. The "streaming" option is just a h:selectOneMenu component that has an onchange JavaScript event handler.
  5. Next is the "placeholder" for our dynamic table of stock data. The attribute rendered is set to false, but this will be set to true from the application code when there is stock data to return.

Get the entire source of this file here.

Application JavaScript

stock-faces.js

var pollId;

/** Delay between requests to the server when polling. */
var pollDelay = 10000;

/** Start polling the server */
function start() {
    pollId = setInterval(poll, pollDelay);
}

/** Stop polling the server */
function stop() {
    clearInterval(pollId);
}

function poll() {
    queueEvent();
    DynaFaces.fireAjaxTransaction(null, {});
}

function queueEvent() {
    var actionEvent =
        new DynaFaces.ActionEvent("search",
        DynaFaces.PhaseId.INVOKE_APPLICATION);
    DynaFaces.queueFacesEvent(actionEvent);
    return false;
}

function toggleStreaming() {
    var menu = document.getElementById("streaming");
    var idx = menu.selectedIndex;
    var streaming = menu[idx].value;
    if (streaming == "Off") {
        stop();
    } else if (streaming == "On") {
        start();
    }
}

 

 

  1. The polling delay (or the time interval between calls to the server is 10 seconds.
  2. The start() function kicks off the server polling.
  3. The stop() function stops server polling.
  4. The poll() function does two things:
    1. It queues up a server side JSF action event.
    2. Fires an Ajax request to the server using the Dynamic Faces library.
  5. The queueEvent() function queues up a server side <>JSF action event using the Dynamic Faces library. This action event will get processed during the standard JSF lifecycle processing as the Ajax request flows through.
  6. The toggleStreaming() function just toggles the value of the "streaming" menu control.

Get the entire source for this file here.

JSF Managed Bean

Bean.java (how original...)

/**
 * This bean has methods to retrieve stock information from
 * the Yahoo quote service.
 */
public class Bean {

    private static final String SERVICE_URL =
            "http://quote.yahoo.com/d/quotes.csv";

    /**
     * Action method that is used to retrieve stock information.
     * This method uses two helper methods - one to get the
     * stock information, and the other to dynamically build
     * the "data" components for the UI.
     */
    public void getStockInfo(ActionEvent ae) {
...
...
                        stockData = getStockData(symbols);
                        buildUI(stockData);
...
    }

    /**
     * Helper method to get the stock data (remotely).
     */
    private String[] getStockData(String[] symbols)
        throws IOException, MalformedURLException {
        String[] data = new String[symbols.length];
        for (int i=0; i<symbols.length; i++) {
            StringBuffer sb = new StringBuffer(SERVICE_URL);
            sb.append("?s=");
            sb.append(symbols[i]);
            sb.append("&f=snol1cp2v=.csv");
            String url = sb.toString();
            URLConnection urlConn = null;
            InputStreamReader inputReader = null;
            BufferedReader buff = null;
            try {
                urlConn = new URL(url).openConnection();
                inputReader = new InputStreamReader(
                    urlConn.getInputStream());
                buff = new BufferedReader(inputReader);
                data[i] = buff.readLine();
                data[i] = data[i].replace( "\"", "" );
...
...
        }
        return data;
    }

    /**
     * Helper method to dynamically add JSF components to display
     * the data.
     */
    private void buildUI(String[] stockData) {
        FacesContext context = FacesContext.getCurrentInstance();
        UIForm form = (UIForm)context.getViewRoot().findComponent("form");
        UIPanel dataPanel = (UIPanel)form.findComponent("stockdata");
        String buffer = null;
        dataPanel.getChildren().clear();
        for (int i=0; i<stockData.length; i++) {
            String[] data = stockData[i].split("\\,");
            UIOutput outputComponent = null;
            UIGraphic imageComponent = null;
...
...
            // Create and add components wth data values

            // Symbol

            outputComponent = new UIOutput();
            outputComponent.setValue(data[0]);
            dataPanel.getChildren().add(outputComponent);

            // Name

            outputComponent = new UIOutput();
            outputComponent.setValue(data[1]);
            dataPanel.getChildren().add(outputComponent);

            // Open Price (if any)

            outputComponent = new UIOutput();
            try {
                openPrice = new Double(data[2]).doubleValue();
            } catch (NumberFormatException nfe) {
                openPriceAvailable = false;
            }
            outputComponent.setValue(data[2]);
            dataPanel.getChildren().add(outputComponent);
...
...
        }
        dataPanel.setRendered(true);
    }

This JSF Managed Bean has an action method getStockInfo that does two things:

  1. It uses a helper method getStockData to contact the Yahoo Stock Quote service (as defined by SERVICE_URL) to retrieve stock data for all the symbols.
  2. It uses a helper method buildUI to build JSF components (from the stock data) and it adds the JSF components to the JSF component view. After all components have been created and added, then it sets the rendered attribute to true on the "stockdata" JSF component.

The action method, getStockInfo is called on two seperate occasions:

  1. It is called when the "Search" button is pressed.
  2. It is also called as the result of an Ajax "poll" request. This is because each client poll queues an action event tied to this event handler. Refer to the queueEvent method in the stock-faces.js JavaScript file.

Get the entire source of this file here.

Summary

We've seen how we can combine the power of JSF component building with Ajax to produce dynamic applications. This application illustrated the use of two features from Dynamic Faces:

  1. fireAjaxTransaction
  2. Remote JSF event queueing from JavaScript

This application is included as one of the samples applications in the jsf-extensions project. Please refer to the jsf-extensions FAQ for getting and building the source code.

Resources

jsf-extensions
GlassFish
javaserverfaces

Published at DZone with permission of its author, Roger Kitain.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)