Tutorial

Tutorial: JSF 2 and HTML5 Server Sent Events

RogerKitain
jsf-2-0

JSF co-spec lead Roger Kitain introduces us to composite components, combining JSF 2.0 with HTML5 Server Sent Events to form a powerful mix

The world of HTML5 offers many features to facilitate the creation of exciting and dynamic web sites. There are web communication specifications under the HTML5 “umbrella” that define a standard way to perform real-time communication between browser clients and servers. One such specification is the Server Sent Events specification. In this article, we will explore how we can incorporate HTML5 Server Sent Events into a JavaServer Faces (JSF) User Interface using some of the new component features available since JSF 2.0.

HTML5 Server Sent Events (SSE) is a W3C specification that defines a standard API for opening an HTTP connection for receiving push notifications (data) from the server. Operationally, it works the same as Comet. However, Comet attempts to facilitate “server push” over the existing HTTP request/response protocol through various hacks such as appending scripts to iframes. The SSE specification introduces a new EventSource HTML element that can be used with JavaScript to establish a connection to a server end point. The client also registers event callback JavaScript functions for handing “pushed” data from the server. Let’s look at a client example:

Listing 1: Client Page

 

 <!DOCTYPE html>
    <head>
       <script type='text/javascript'>
          var source = new EventSource('serverevents');
          source.onmessage = function(event) {
             var ev = document.getElementById('events');
             ev.innerHTML += event.data; 
          };
       </script>
   </head>
   <body>
      <div id=”events” />
   </body>

 

This example shows a simple page that will establish a connection to a server endpoint when the page is loaded. On line 4, we create a new EventSource object, feeding it the URL of a server endpoint. The server endpoint could be a PHP script, NodeJS implementation, or even a Java Servet. When a new EventSource instance is created, an initial GET request to the server endpoint is done to establish the initial connection. On line 5, we register a message callback that will populate the events div with the event data. We could have also registered a message handler on the EventSource instance this way:

 

source.addEventListener('message', 'msgCallback', false);

 

As data is pushed from the server, this callback will update the div area in the page. It is also possible to handle multiple event types from the server:

 

source.addEventListener('ticker', 'tickerCallback', false);
source.addEventListener('time', 'clockCallback', false);

 

The event data for these events would be pushed over the same EventSource connection. It should be mentioned that SSE (namely the EventSource API) is not yet available in all browsers. Google Chrome is one such browser that has SSE support.

Server Requirements

The SSE specification specifies requirements for the server response. The specification states that the response content type must be text/event-stream. To send a single line of data (Hello World!):

 

data: Hello World!nn

 

To send multiple lines of data:

 

data: This is the first linen
data: This is the second linenn

 

Here, a single string of data concatenated with the newline character will be sent to the client side event handler function. It is also possible to send the response as JSON data:

 

data: {n
data: "msg":"hello world",n
data: "id:"12345n
data: }nn

 

In all cases, notice that two successive newline characters end the response.

The User Interface

We’ll look at a simple “Stock View” application that will cause the streaming of three event types to the JSF user interface. This user interface consists of three areas of information. The first area is the “time” bar. The second area is the stock quote area that consists of an input field to enter stock quotes, buttons to retrieve stock quote information and reset (clear) the input area, and a table that displays the stock quote information “real-time” as it is updated on the server. The quote numbers are randomly generated on the server, but real quotes could be retrieved by tying into a quote service. The third area consists of a link to retrieve RSS news feeds for the stocks, and a scrollable area that displays the news feed. The news feed is also pushed from the server “real-time”. Corresponding to these three areas are three SSE event types: time, ticker and rss. When the user interface is first displayed, the clock will tick real time. You enter space delimited stock symbols, press the Get Quotes button and a table is dynamically displayed with streaming quotes. Pressing the Get Stock News link causes news feeds to stream from the server.

The User Interface Markup

JSF 2.0 introduced three big features that make it easier to develop dynamic user interfaces.

  • Facelets becomes the primary view technology
  • Composite components make it easier to develop complex components
  • Ajax support

Let’s see how these three features are used in this user interface. Listing 2 is the markup for the main user interface.

Listing 2: stock.xhtml Facelets View

 

 <!DOCTYPE html>
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:h5="http://java.sun.com/jsf/composite/h5"
       xmlns:ez="http://java.sun.com/jsf/composite/stockinfo">
     <h:head>
        <meta charset="UTF-8" />
   </h:head>

   <h:body style="background: #fefeef">

      <h:form>
          <h:panelGrid styleClass="title-panel">
             <h:panelGrid columns="2" cellpadding="20">
                <h:outputText value="S t o c k V i e w" styleClass="title-panel-text"/>
             </h:panelGrid>
             <h:outputText value="Powered By JavaServer Faces 2.1 and Html 5 Server Sent Events "
                styleClass="title-panel-subtext"/>
          </h:panelGrid>

           <h:outputStylesheet name="stylesheet.css" />

           <h5:sse url="/JSFStock1/sse/stockticker" events="{stock:stockHandler,time:clockHandler,rss:rssHandler}"/>

           <br/>
           <ez:clock/>
           <br/>
           <br/>
           <ez:stock/>

           <h:messages/>

       </h:form>

   <a href="doc.xhtml">Documentation</a>
   </h:body>
 </html>

 

This user interface uses four composite components. We’ll look at these in more detail later on, but I summarize them here:

  • sse: Server Sent Events component used to establish a connection to a server end point.
  • clock: Clock component displays the ticking time bar
  • stock: Stock component that produces the input field for entering stock symbols, the two buttons, and a placeholder for the dynamic table that will display stock ticker information.
  • rss: Component that produces the link Get Stock News and a placeholder for the dynamic scrollable area that will display stock news. This component is actually contained in the implementation of the stock component which is why you don’t see it in this view.

The first thing you’ll notice is that on line 1 we’re using an HTML5 document type declaration. That makes this document an HTML5 polyglot document because it has an HTML5 document type /namespace and well-formed XHTML syntax – which works great for JSF views. This would allow us to use other HTML5 features as well. Let’s take a look at the other relevant sections of this view:

Lines 3 – 7: Here we declare the namespaces used in the user interface. Lines 3 and 4 are just the standard JSF namespaces. Line 5 is the Facelets namespace. Line 6 declares the namespace that we’ll use for our SSE component. Line 7 declares the namespace that will be used for the clock, stock and rss components.

 Line 25: This is the usage of the SSE component. This component uses two attributes:

  • url: The URL of the server endpoint that we are connecting to
  • events: Specifies event type : event handler pairings. The event handler is the name of the JavaScript function that will handle the associated event type data coming from the server.

Line 28: This is the usage of the clock component.

 Line 31: This is the usage of the stock component.

It’s easy for a page author to use well defined components in a view. Now let’s see how these components are defined.


 

The Composite Components

Recall that JSF composite components consist of two areas:

  • interface section
  • implementation section

The interface section is where you define the usage contract for the component. It is where you define the attributes that a page author would use with your component. The implementation section is the code that produces the component. It may consist of HTML markup, JavaScript and/or other JSF components. Let’s take a look at the composite components that are used in this application.

The SSE Composite Component

The Server Sent Events (SSE) composite component is used to establish a connection to a server endpoint. As mentioned earlier, this component has two attributes. Let’s see how this component is implemented.

Listing 3: sse.xhtml SSE Composite Component

 

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:ui="http://java.sun.com/jsf/facelets"
      xmlns:composite="http://java.sun.com/jsf/composite">
<head>

<title>Server Sent Event Composite Component</title>

</head>

<body>

<composite:interface>
   <composite:attribute name="url" required="true"/>
   <composite:attribute name="events" required="false"/>
</composite:interface>

<composite:implementation>
   <h:outputScript name="js/jsf-sse.js" target="head" />
   <script type="text/javascript">
        JSF.sse.connect("#{cc.attrs.url}", #{cc.attrs.events});
   </script>
</composite:implementation>

</body>

</html>

 

Let’s take a look at the relevant sections of this component.

Lines 4 – 7: This is just our namespace declarations. Line 7 is declaring a composite namespace that will be used to define the relevant sections of our composite component.

Lines 16 – 19: This is where we define the interface or usage contract for our composite component. On line 17 we define a required url attribute whose value will be the server endpoint. On line 18 we define the events attribute whose value will contain event type : event handler name mappings as shown earlier.

Lines 21 – 26: This is the implementation of the composite component. JavaScript and JSF work together nicely to create dynamic components. On line 22, we are loading a small JavaScript library that contains functions for performing SSE operations for JSF. Notice that we’re using a JSF component <h:outputScript> to specify the target location of the JavaScript file. On line 24 we are using a function from the JSF SSE JavaScript library to establish a connection to the server endpoint using the url and events attribute values. 

So, the idea is that you can drop one of these components in your JSF view to connect to a server endpoint. Let’s take a look at the JavaScript library that contains the functions for performing SSE actions.

Listing 4: jsf-sse.js The JSF SSE JavaScript Library

 

1.  if (!JSF) {
2.  
3.      /**
4.       * The top level global namespace for JavaServer Faces Server Sent Event
5.       * functionality. 
6.       * @name JSF
7.       * @namespace
8.       */
9.      var JSF = {};
10.
11.     /**
12.      * The namespace for Server Sent Event functionality. 
13.      * @name JSF.sse
14.      * @namespace
15.      * @exec
16.      */
17.     JSF.sse = function() {
18.
19.         var eventSource = null;
20. 
21.         var getEventSource = function getEventSource(url) {
22.             url = 'http://' + document.location.host + url;
23.             eventSource = new EventSource(url);
24.         };
25.
26.         return {
27.
28.             /**
29.              * Connect to a server end point. 
30.              * <p><b>Usage:</b></p>
31.              * <pre><code>
32.              * JSF.sse.connect(url, {events: 'stock:stockHandler time:clockHandler'});
33.              * ... 
34.              * function stockHandler(event) {
35.              * ... 
36.              * }
37.              * </pre></code>
38.              *
39.              * @member JSF.sse
40.              * @param url The URL of a valid server end point that will deliver messages. 
41.              * @param eventOptions The set of events consisting of event name:handler name pairs
42.              *   associating an event name from the server, and the name of the handler that
43.              *   will process the event.. 
44.              */
45.             connect: function connect(url, eventOptions) {
46.                 if (url === null) {
47.                     throw new Error("Must specify URL");
48.                 }
49.                 getEventSource(url);
50.                 if (eventSource !== null) {
51.                     if (eventOptions !== null) {
52.                         for (var i in eventOptions) {
53.                            JSF.sse.addOnEvent(i, eventOptions[i]);
54.                         }
55.                     }
56.                }
57.
58.             },
59. 
60.             /**
61.              * Register a callback for error handling. 
62.              * <p><b>Usage:</b></p>
63.              * <pre><code>
64.              * JSF.sse.addOnError(handleError); 
65.              * ... 
66.              * var handleError = function handleError(data) {
67.              * ... 
68.              * }
69.              * </pre></code>
70.              *
71.              * @member JSF.sse
72.              * @param callback a reference to a function to call on an error
73.              */
74.             addOnError: function addOnError(callback) {
75.                 if (eventSource !== null) {
76.                     if (typeof callback === 'function') {
77.                         eventSource.addEventListener('error', callback, false);
78.                     }
79.                 }
80.
81.             },
82.
83.             /**
84.              * Register a callback for event handling. 
85.              * <p><b>Usage:</b></p>
86.              * <pre><code>
87.              * JSF.sse.addOnEvent('timeEvent', handleEvent); 
88.              * ... 
89.              * var handleEvent = function handleEvent(data) {
90.              * ... 
91.              * }
92.              * </pre></code>
93.              *
94.              * @member JSF.sse
95.              * @param eventName the event name associated with the message. 
96.              * @param callback a reference to a function to call for processing messages with a specific event
97.              * name. 
98.              */
99.             addOnEvent: function addOnEvent(eventName, callback) {
100.                if (eventSource !== null) {
101.                     if (typeof callback === 'function' && eventName !== null
102.                           && typeof eventName !== 'undefined') {
103.                           eventSource.addEventListener(eventName, callback, false);
104.                     }
105.                }
106.
107.            }
108.         };
109.
110.     }();
111. } 

 

Line 9: JSF namespace declaration.

Line 21: A private function that creates an EventSource instance to connect to the server endpoint specified by the provided url.

Line 45: The public connect function that is used in the SSE composite component in Error: Reference source not found. This function calls through to the private function getEventSource and registers the application callback functions using a public addOnEvent function (line 99).

Line 74: A public utility function for registering an error processing callback function. With SSE you can register a function with the error event type.

Line 99: A public utility function for registering callback functions for processing event types.

The Clock Composite Component

The clock composite component is used to display the current time.

Listing 5: clock.html The Clock Composite Component

1.  <!DOCTYPE html>
2.
3.  <html xmlns="http://www.w3.org/1999/xhtml"
4.       xmlns:h="http://java.sun.com/jsf/html"
5.       xmlns:f="http://java.sun.com/jsf/core"
6.       xmlns:ui="http://java.sun.com/jsf/facelets"
7.       xmlns:composite="http://java.sun.com/jsf/composite">
8.
9.    <composite:interface>
10.
11.   </composite:interface>
12.
13.   <composite:implementation>
14.      #{timeBean.time}
15.     <div id="clock" style="font-size: 20px; border:2px solid blue"/>
16.     <h:outputScript name="js/app.js" target="head"/>
17.
18.   </composite:implementation>
19.
20. </html>

 

Lines 4 – 7 : Namespace declarations

Lines 9 – 11 : Notice this component has no attributes – which is perfectly fine. If we wanted to make this clock component fancier we could, for example, define an attribute to indicate that our clock could display in analog or digital mode.

Lines 13 – 18: The implementation of our component. On line 14, we are using the Expression Language (EL) to invoke a method on a managed bean to start the clock ticking. The managed bean could just utilize a java.util.Timer method to compose and send the current date/time string back to the client. Line 15 is a placeholder for where we will display the time event data from the server. On Line 16, we load a JavaScript file that contains application callback functions that will process the event data returned from the server. Let’s take a look at the relevant section of code from this application JavaScript file for this clock component.

Listing 6: Partial JavaScript Listing for Stock Ticker Component

1.  /**
2.   * Clock message handling function. 
3.   */
4.  function clockHandler(event) {
5.      var eventData = event.data.toString().replace(/^s*/,"").replace(/s*$/,"");
6.      var clock = document.getElementById("clock");
7.      clock.innerHTML = eventData;
8.  }

Line 5: We obtain the event data

Line 6: We obtain our div placeholder element (created in Error: Reference source not found, line 15).

Line 7: We write the event data string (which is the current data / time)

   

The Stock Composite Component

Listing 7: stock.hmtl : Stock Ticket Composite Component

 

1.  <!DOCTYPE html>
2.
3.  <html xmlns="http://www.w3.org/1999/xhtml"
4.    xmlns:h="http://java.sun.com/jsf/html"
5.    xmlns:f="http://java.sun.com/jsf/core"
6.    xmlns:ui="http://java.sun.com/jsf/facelets"
7.    xmlns:ez="http://java.sun.com/jsf/composite/stockinfo"
8.    xmlns:composite="http://java.sun.com/jsf/composite">
9.
10.  <composite:interface>
11.
12.  </composite:interface>
13.
14.  <composite:implementation>
15.    <br/>
16.    <h:panelGrid columns="4">
17.       <h:outputText value="Space delimited symbol(s):" style="font-size: 18px;"/>
18.       <h:inputText id="symbol" value="#{stockTickerBean.symbols}"/>
19.       <h:commandButton id="submit" value="Get Quotes" action="#{stockTickerBean.getStockInfo}">
20.          <f:ajax execute="@this symbol"/>
21.       </h:commandButton>
22.       <h:commandButton id="clear" value="Reset" action="#{stockTickerBean.reset}">
23.          <f:ajax execute="@this symbol" render="symbol"/>
24.       </h:commandButton>
25.    </h:panelGrid>
26.
27.    <br/>
28.
29.    <div id="stockInfo" style="font-size: 16px;">
30.       <table id="stockTable" border="8" bordercolor="#000080" cellspacing="10" style="visibility:hidden">
31.          <tr>
32.             <th style="background-color:#b0c4de;text-align:left">Symbol</th><th style="background-color:#b0c4de;text-align:left">Open</th>
33.             <th style="background-color:#b0c4de;text-align:left">Trade</th><th style="background-color:#b0c4de;text-align:left">Chg</th>
34.          </tr>
35.        </table>
36.    </div>
37.
38.    <br/>
39.
40.    <ez:rss symbols="#{stockTickerBean.symbols}"/>
41.
42.    <h:outputScript name="js/app.js" target="head"/>
43.
44.  </composite:implementation>
45.
46. </html>

 

This composite component’s implementation uses standard JSF components.

Line 18: The input text field that will store the entered stock symbol data to a managed bean.

Lines 19 – 21: The button that, when activated, will fire a request over Ajax to invoke a method getStockInfo on a managed bean. This method could utilize a java.util.Timer method to reach out to a stock quote service periodically, and send the event data back to the client.

Lines 29 – 36: This is the placeholder area for our dynamic table to display the stock data from the server.

Line 40: This component uses the rss composite component (more about that next).

Listing 8: Partial JavaScript Listing for Stock Ticker Component

 

1.  /**
2.   * Server Sent Event message handling function dynamically builds a table
3.   * based on the message data. 
4.   */
5.  function stockHandler(event) {
6.      var eventData = event.data.toString().replace(/^s*/,"").replace(/s*$/,"");
7.      var stockTable = document.getElementById("stockTable");
8.      var rowCount = stockTable.rows.length - 1;
9.      if (eventData.length == 0) {
10.          stockTable.style.visibility="hidden";
11.          clearTableRows(stockTable, rowCount);
12.          return;
13.    }
14.    var quotes = eventData.split(" ");
15.    if (quotes.length !== rowCount || quotes.length == 0) {
16.         clearTableRows(stockTable, rowCount);
17.    }
18.    if (quotes !== null) {
19.         stockTable.style.visibility="visible";
20.         var newRow, newCell;
21.         for (var i = 0; i < quotes.length; i++) {
22.              var fields = quotes[i].split(":");
23.              if (document.getElementById(fields[0]) !== 'undefined' &&
24.                   document.getElementById(fields[0]) !== null) {
25.                   document.getElementById(fields[0]).innerHTML = fields[0];
26.                   document.getElementById(fields[0]+1).innerHTML = fields[1];
27.                   document.getElementById(fields[0]+2).innerHTML = fields[2];
28.                   if (fields[3] == "UP") {
29.                       document.getElementById(fields[0]+3).innerHTML =
30.                           "<img src='resources/up_g.gif'/>";
31.                   } else if (fields[3] == "DOWN") {
32.                       document.getElementById(fields[0]+3).innerHTML =
33.                           "<img src='resources/down_r.gif'/>";
34.                   } else {
35.                       document.getElementById(fields[0]+3).innerHTML = "";
36.                   }
37.               } else {
38.                   newRow = stockTable.insertRow(stockTable.rows.length);
39.                   newCell = newRow.insertCell(newRow.cells.length);
40.                   newCell.id = fields[0];
41.                   newCell.innerHTML = fields[0];
42.                   newCell.align = "center";
43.                   newCell.width = "20%";
44.                   newCell = newRow.insertCell(newRow.cells.length);
45.                   newCell.id = fields[0]+1;
46.                   newCell.innerHTML = fields[1];
47.                   newCell.align = "center";
48.                   newCell.width = "20%";
49.                   newCell = newRow.insertCell(newRow.cells.length);
50.                   newCell.id = fields[0]+2;
51.                   newCell.innerHTML = fields[2];
52.                   newCell.align = "center";
53.                   newCell.width = "20%";
54.                   newCell = newRow.insertCell(newRow.cells.length);
55.                   newCell.id = fields[0]+3;
56.                   if (fields[3] == "UP") {
57.                       document.getElementById(fields[0]+3).innerHTML =
58.                           "<img src='resources/up_g.gif'/>";
59.                   } else if (fields[3] == "DOWN") {
60.                       document.getElementById(fields[0]+3).innerHTML =
61.                           "<img src='resources/down_r.gif'/>";
62.                   } else {
63.                       document.getElementById(fields[0]+3).innerHTML = "";
64.                   }
65.
66.                   newCell.align = "center";
67.                   newCell.width = "20%";
68.            }
69.        }
70.    }
71.}

 

The stockHandler function receives the event data, that is the stock quote information and performs some JavaScript magic to dynamically build and display the stock table.

The RSS Composite Component

The rss composite component displays a link, that when activated, will dynamically display stock company news relevant to the stock symbols that were entered.

Listing 9: rss.xhtml : RSS Composite Component

1.  <!DOCTYPE html>
2. 
3.  <html xmlns="http://www.w3.org/1999/xhtml"
4.        xmlns:h="http://java.sun.com/jsf/html"
5.        xmlns:f="http://java.sun.com/jsf/core"
6.        xmlns:ui="http://java.sun.com/jsf/facelets"
7.        xmlns:composite="http://java.sun.com/jsf/composite">
8.
9.    <composite:interface>
10.       <composite:attribute name="symbols" required="true"/>
11.   </composite:interface>
12.
13.   <composite:implementation>
14.   <br/>
15.   <h:panelGrid columns="4">
16.       <h:commandLink id="submit" value="Get Stock News" action="#{rssBean.getStockNews}">
17.          <f:setPropertyActionListener value="#{cc.attributes.symbols}" target="#{rssBean.symbols}"/>
18.          <f:ajax execute="@this"/>
19.       </h:commandLink>
20.    </h:panelGrid>

21.   <br/>
22.
23.    <div id="rssInfo" style="font-size: 16px; border:2px solid blue; height : 150px; overflow : auto; visibility:hidden"/>
24.
25.    <h:outputScript name="js/app.js" target="head"/>
26.
27.
28.  </composite:implementation>
29.
30. </html>

 

Line 10: This component defines an attribute for accepting stock symbols. The stock symbols are used to get the rss information.

Lines 16 – 19: The link, that when activated, will propagate the stock symbols to a managed bean and invoke the method getStockNews on the managed bean over Ajax.

Line 23: A div placeholder for displaying the returned rss information from the server.

Listing 10: Partial JavaScript Listing for RSS Composite Component

1.  function rssHandler(event) {
2.      var eventData = event.data.toString().replace(/^s*/,"").replace(/s*$/,"");
3.      var rssInfo = document.getElementById("rssInfo");
4.      if (eventData.length == 0) {
5.          rssInfo.style.visibility="hidden";
6.      } else {
7.          rssInfo.style.visibility="visible";
8.      }
9.      rssInfo.innerHTML="";
10.    rssInfo.innerHTML = eventData; 
11. }

 

The rssHandler callback function writes the rss event data into the div that was created in Error: Reference source not found.

Server Implementation Considerations

In the browser that has SSE support. section, we mentioned some of the requirements for sending SSE event data to the client. Server implementations need to maintain an open connection to stream event data to the client. For Java implementations, a Servlet utilizing the Servlet 3.0 Asynchronous API is one choice. For other implementations, NodeJS or PHP could be used as well. Server implementation details are not mentioned, since the main focus of this article is the JSF user interface and components.

Conclusion

This article has been all about creating JSF composite components that make up the user interface for a stock quote application that uses HTML5 Server Sent Events. JSF 2 introduced the world to composite components, and together with JavaScript, provides a component author with a multitude of options for creating dynamic components. HTML5 Server Sent Events are the “second class citizen” to HTML5 WebSockets, but it does offer an HTTP solution to event streaming without switching to another protocol.

 

Roger Kitain is a Principal Software Engineer at Oracle where he has spent most of his time in the web technology space since 1997. Roger has been involved with the JavaServer Faces technology since its inception, and has co-led the JavaServer Faces Specification since JSF 1.2. He has presented at many conferences including JAX, JSF Summit, JavaOne, Dexoxx, Jazoon, Ajax Experience, Ajax World.  

 

This article first appeared in Java Tech Journal : Web Frameworks. For more of that issue, click here

 

Author
RogerKitain
Roger Kitain is a Principal Software Engineer at Oracle where he has spent most of his time in the web technology space since 1997. Roger has been involved with the JavaServer Faces technology since its inception, and has co-led the JavaServer Faces Specification since JSF 1.2. He has presented at many conferences including JAX, JSF Summit, JavaOne, Dexoxx, Jazoon, Ajax Experience, Ajax World.
Comments
comments powered by Disqus