Saturday, June 11, 2011

Java & Flex: Using Adobe Flex and JavaFX with JavaServer Faces 2.0

Learn how to take advantage of new features in JSF 2.0 and integrate Flex and JavaFX into your JSF applications
The JavaServer Faces (JSF) 2.0 specification builds on the success and lessons from the last six years of usage of the JSF 1.0 specification. It takes inspiration from Seam and other Web frameworks and incorporates popular agile practices, such as convention over configuration and annotation over XML. This results in a much streamlined framework. Highlights include standardized AJAX support, Facelets as the default view technology, and custom composite components, which finally make component authoring straightforward and even enjoyable. A good overview of JSF 2.0 can be found here.
This article explores how these new features can be utilized to facilitate embedding rich client applications. Adobe Flex has been a popular rich Internet application framework. JavaFX, while relatively new, builds on top of the Java platforms and has attracted much attention. There has been constant interest in integrating rich clients into Java Web applications. With JSF 2.0 and its focus on simplified development, integration has become easier than ever.
We start with a sample Flex pie chart application that displays the results of a survey about the popularity of ice cream flavors. A JSF composite component is used to encapsulate the embedding. Next, instead of hard-coding, the survey result is passed to the Flex application from a JSF managed bean. Then, we further enhance the sample by adding server round-trips that submit a user’s choice of the favorite flavor. Finally, we re-implement the chart in JavaFX and show how to embed it into the JSF application.
This application developed by the following tools:
The application is developed using Flex SKD 4.1, JSF Mojarra Implementation 2.0.2, and JavaFX SDK 1.3.1. NetBeans 6.9.1 is used as the IDE, which already bundles the latter two libraries.
Creating the Sample Flex Application

First, we create a simple pie chart application in Flex to display the popularity of ice cream flavors, as shown in Figure 1. You click a chart item. The message label then displays your choice.
Figure 1 Simple Pie Chart Application
Flex source code (SampleChartFlex.mxml):
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               styleName="plain"
               width="500" height="500">
     
   <mx:PieChart x="50" y="40" id="piechart"
       dataProvider="{getChartData()}" itemClick="onItemClick(event)" >
       <mx:series>
           <mx:PieSeries
               field="rank"
               nameField="flavor"
               labelField="flavor"
               labelPosition="callout"
           />
       </mx:series>
   </mx:PieChart>
  
   <s:Label x="64" y="448" id="message"
       text="Click to choose your favorite flavor."/>
  
   <fx:Script>
     <![CDATA[
       import mx.collections.ArrayCollection;
       import mx.charts.events.ChartItemEvent;

       private function getChartData() : ArrayCollection {
           return new ArrayCollection ([
               {flavor: "Vanilla",    rank: 60},
               {flavor: "Chocolate",  rank: 30},
               {flavor: "Strawberry", rank: 10} 
           ]);
       }
      
      private function onItemClick(event : ChartItemEvent) : void {
           var flavor : String = event.hitData.chartItem.item.flavor;
           message.text = "You chose " + flavor + "."; 
       }
      ]]>        
   </fx:Script>
</s:Application>

The Flex application consists of a pie chart and a message label. The pie chart data is provided by function getChartData(). When a user clicks a chart item, onItemClick processes the event and updates the message label. The source file is compiled into SampleChartFlex.swf using
mxmlc. The provided sample project SampleChartFlex has customized its ant build.xml file, which invokes mxmlc when you build the project.
Embedding the Flex Application

To embed the Flash object into our JSF Web application, we first add SampleChartFlex.swf into folder resources/demochart of the Web content of our JSF application project SampleWeb. We create a composite component to encapsulate the embedding.
Composite components are a new facility in JSF 2.0 that tremendously eases the development of custom components. You no longer need to worry much about encoding, decoding, tag library descriptors (TLDs), and renderers. You simply declare a Facelet composite component file and use it, similar to the acclaimed custom tag support in Grails. The following is our custom component demo:chart.
JSF composite component source code (resources/demo/chart.xhtml):
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:cc="http://java.sun.com/jsf/composite"
      xmlns:h="http://java.sun.com/jsf/html">

    <!-- INTERFACE -->
    <cc:interface>
    </cc:interface>

    <!-- IMPLEMENTATION -->
    <cc:implementation>
        <h:outputScript name="swfobject.js" library="demochart" target="head" />
        <script type="text/javascript">
            (function(){
                var swfUrl = "${facesContext.externalContext.requestContextPath}
                     + /resources/demochart/SampleChartFlex.swf";
                var replaceElementId = "${cc.clientId}:chart";
                swfobject.embedSWF(swfUrl, replaceElementId,
                    "500", "500", "10.0.0");
            })();
        </script>

        <div id="${cc.clientId}:chart" />
    </cc:implementation>
</html>

The open source
SWFObject is used to embed the Flash content. The JavaScript file, swfobject.js, can be found under folder templates\swfobject of the Flex 4 SDK installation. Copy it into folder resources\demochart of our Web content.
Effort was made to mitigate name conflicts: Our local variables are defined in an anonymous function and the div HTML element ID is prefixed with the composite component client ID.
Now that we’ve created the custom component, we can use tag demo:chart just like any other JSF tags. It is transparent that Flex is used in the implementation. Here’s an example.
JSF page source code (index.xhtml):
<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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:demo="http://java.sun.com/jsf/composite/demo">
    <h:head>
        <title>Spice up your JSF Pages</title>
    </h:head>
    <h:body>
        <f:view>
            <h1> Spice Up Your JSF Pages </h1>
            <demo:chart />
        </f:view>
    </h:body>
</html>

Passing Variables to Flex Applications

More often than not, embedded Flex applications rely on dynamic data. It turns out to be easy to pass variables into Flex applications with the help of flashVars.
This section extends our sample chart by passing the ice cream flavor survey result from a JSF managed bean.
JSF managed bean source code (IceCreamSurvey.java):
package demo.data;

import java.util.HashMap;
import java.util.Map;
import javax.faces.bean.ManagedBean;

@ManagedBean
public class IceCreamSurvey {
    public Map<String, Integer> getResult() {
        Map<String, Integer> result = new HashMap<String, Integer>();
        result.put("Vanilla",    Integer.valueOf(60));
        result.put("Chocolate",  Integer.valueOf(30));
        result.put("Strawberry", Integer.valueOf(10));
        return result;
    }
}

We use the JSF 2.0 annotation to designate a managed bean.
To feed the survey result from the managed bean to Flex, we first modify our JSF composite component chart.xhtml by adding an attribute named data to the interface section to accept the survey result and passing the survey result as flashVars into Flex.
JSF composite component source code snippet (resources/demo/chart.xhtml):
    <!-- INTERFACE -->
    <cc:interface>
        <cc:attribute name="data" />
    </cc:interface>

    <!-- IMPLEMENTATION -->
    <cc:implementation>
        <h:outputScript name="swfobject.js" library="demochart" target="head" />
        <script type="text/javascript">
           (function(){
                var swfUrl = "${facesContext.externalContext.requestContextPath}
                    + /resources/demochart/SampleChartFlex.swf";
                var replaceElementId = "${cc.clientId}:chart";
                var expressInstall = "";
                var flashVars = {data: "${cc.attrs.data}"};
                swfobject.embedSWF(swfUrl, replaceElementId,
                    "500", "500", "10.0.0", expressInstall, flashVars);
           })();
    </cc:implementation>

Now in the consuming JSF page, we just need to pass the ice cream flavor survey result to the demo:chart tag.
JSF page source code snippet (index.xhtml):
<demo:chart data="#{iceCreamSurvey.result}"/>

On the Flex application side, we need to modify function getChartData to fetch the parameter.
Flex source code snippet (SampleChartFlex.mxml):
 private function getChartData() : ArrayCollection {
      // Retrieve "data" from flashVars,
      // Formatted as Map.toString(), e.g., {Strawberry=10, Chocolate=30,
      //     Vanilla=60}
     var input : String = Application.application.parameters.data;
     var data : Array = input ? input.split(/\W+/) : [];
     var source = [];
     for (var index : int = 1; index < data.length - 1; index += 2) {
           source.push( (flavor: data[index], rank: parseInt(data[index+1])} );
     }
     return new ArrayCollection(source);       
 }

In this example, the data format is simple. We, therefore, just parse it using regular expressions. In more complicated cases, formal encoding such as JavaScript Object Notation (JSON) can be considered.
Facilitating Communication between Client and JSF Server Sessions Using JSF AJAX

In this section, we move on to a more complicated scenario: round-trip communications between Flex and JSF server sessions. This section presents a novel, yet practical, approach to integrating the best of Flex and JSF using the JSF 2.0 AJAX feature.
There are a number of ways Flex applications can communicate with servers.
LiveCyle Data Service is the data solution provided by Adobe that umbrellas several technologies, including the Java server-based BlazeDS and Action Message Format (AMF). The service would be particularly appealing if it were possible to devote both the client and server sides to a complete Adobe solution, although open sourcing of BlazeDS and AMF makes it possible to work with other technologies. Noticeably, recent development in Spring Flex and Grails Flex integration is basically built on top of BlazeDS and AMF.
Flex also provides generic data access components to communicate with servers, including HTTP and Web services. This allows Flex to interoperate with various server technologies, including JSF.
In addition, Flex has good integration with JavaScript, enabling us to integrate at the browser side, relaying to the AJAX application to communicate with servers.
Realistically, these approaches all can be used with JSF, each with its pros and cons. Exadel Fiji, for example, offers support for integration of Flex/JavaFX with JSF using all of the approaches above.
With the arrival of JSF 2.0, one interesting aspect is that the AJAX API has been standardized. In this section, we will exploit the feature to integrate Flex applications with JSF. It is in essence integration at the browser side. We’ll rely on the JSF AJAX framework to handle session and view state tracking. Because the JSF AJAX API is part of the 2.0 specification, it is guaranteed to be supported by all implementations. On the server side, it is fairly transparent that a Flex client is used. This approach is, therefore, easy to plug in to an existing JSF application.
The additional JSF AJAX layer conceivably would add performance overhead. This should not be an issue when the data exchange is small, which should be true for the majority of AJAX cases. However, if a large amount of data is exchanged, direct server access, such as with the first two approaches we mentioned above, should be considered.
We’ll modify our sample by submitting the selection when a user clicks on a flavor in the pie chart. A JSF managed bean would process the selection and reply with a message, which is in turn displayed in the Flex application. On the Flex application side, we’ll modify function onItemClick to use ExternalInterface to invoke JavaScript function demo.ajax.submit inside the embedding Web page, which we will define shortly.
Flex source code snippets (SampleChartFlex.mxml):
 private function onItemClick(event : ChartItemEvent) : void {
     var flavor : String = event.hitData.chartItem.item.flavor;
     ExternalInterface.call("demo.ajax.submit", flavor); 
 }

Add a callback function named refresh to update the message label. The function is exposed to JavaScript via
ExternalInterface.addCallback during the initialization of the Flex application, as follows:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               styleName="plain"
               width="500" height="500"
               creationComplete="init()" >
...
       private function init() : void {
           ExternalInterface.addCallback("refresh", refresh);
       }
      
       private function refresh(feedback : String): void {
           message.text = feedback;          
       }

For our JSF composite component, we’ll add one more attribute, response, in the interface section, which is mapped to the server response to our asynchronous submit.
JSF composite component source code snippets (resources/demo/chart.xhtml):
<cc:interface>
     <cc:attribute name="data" />
     <cc:attribute name="response" />
</cc:interface>

Next, inside the implementation section, define a hidden form to submit to and receive response from the server:
<h:form id="form" style="display:none">
     <h:outputText id="out" value="#{cc.attrs.response}" />
</h:form>

Add the following JavaScript to handle the asynchronous submission and reply:
    
           // namespace: demo
           if (!demo)  var demo = {};
          
           // namespace: demo.ajax
           if (!demo.ajax) demo.ajax = {};

           demo.ajax.submit =  function(arg) {
                var options = {
                    input:   arg,
                    render:  "${cc.clientId}:form:out",
                    onevent: demo.ajax.onevent
                };
                jsf.ajax.request("${cc.clientId}:form", null, options);
           };

           demo.ajax.onevent =  function(event) {
               if (event.status == "success") {
                  var node = document.getElementById("${cc.clientId}:form:out");
                  var response = node.textContent || node.innerText;
                  var chart = document.getElementById("${cc.clientId}:chart");
                  chart.refresh(response);
              }
           };

The function demo.ajax.submit is invoked by Flex function onItemClick to submit the request to the server. It uses the JSF 2.0 JavaScript function
jsf.ajax.request to submit an asynchronous request using the hidden form with the following options:
·         The payload is sent as the pass-through request parameter named input.
·         It instructs the server to render the child outputText named out inside the form.
·         The server response would be processed by event handler demo.ajax.onevent.
The demo.ajax.onevent handles the AJAX submit events. Upon success, it fetches the response from the outputText node, and calls the refresh method exposed by Flash. It works around browser differences by trying to fetch the node text in different ways.
On the JSF server side, add the following to the JSF managed bean to process the submission.
JSF managed bean source code snippets (IceCreamSurvey.java):
private String selection;

public String getSelection() {
       return selection;
}
   
public void setSelection(String selection) {
       this.selection = selection;
}


public String getSelectionResponse() {
       return reply(selection);
}

 
public String reply(String flavor) {
       return "Good choice! Many people also like " + flavor + "!";
}

In the consuming JSF page, we first add jsf.js to the page head to enable JSF JavaScript inside the page.
JSF page source code snippets (index.xhtml):
<h:outputScript library="javax.faces" name="jsf.js" target="head"/>

We need to further map the request parameter input as well as the response attribute exposed by our custom chart tag. There are several options to do this. One way is to utilize a JSF 2.0 enhancement that allows EL action binding to take variables, as follows:
<demo:chart data="#{iceCreamSurvey.result}"
     response="#{iceCreamSurvey.reply(param.input)}" />

Another way is to leverage view parameters. You can map a request parameter to an EL expression via view parameters, as follows:
<f:metadata>
   <f:viewParam name="input" value="#{iceCreamSurvey.selection}"/>
</f:metadata>
<demo:chart data="#{iceCreamSurvey.result}"
   response="#{iceCreamSurvey.selectionResponse}" />

Each approach is interesting in its own right. The first one appears straightforward and involves fewer configurations. The second one relies on the view parameter, which is an editable value holder and can, therefore, take converters and validators. For cases where complicated encoding is needed, the second approach can be more appropriate.
Integrating with JavaFX
We can similarly implement the chart application in JavaFX.

Figure 2 Implementing the Chart Application in JavaFX
JavaFX source code (demo.piechart.Main.FX):
package demo.piechart;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.chart.PieChart3D;
import  javafx.stage.AppletStageExtension;

def piechart = PieChart3D {
   layoutX: 0.0
   layoutY: 11.0
   data:    getChartData()
}

def message = javafx.scene.control.Label {
   layoutX: 189.0
   layoutY: 340.0
   text:    "Click to choose your favorite flavor"
}

function getChartData() : PieChart.Data[]  {
   // Retrieve "data" from the FX argument
   // Formatted as Map.toString(), e.g., {Strawberry=10, Chocolate=30,
   //     Vanilla=60}
   var input = FX.getArgument("data") as String;
   var data = input.split("\\W+");
   for (flavor in data where indexof flavor mod 2 == 1) {
       PieChart.Data {
           label:  flavor
           value:  Integer.parseInt(data[indexof flavor + 1])
           action: function()
                       {AppletStageExtension.eval("demo.ajax.submit('{flavor}')")
                   }
       }
   }
}

function run(): Void {
   Stage {
       scene: Scene {
           height: 500
           width: 500
           content: [piechart, message]
       }
   }
}

// Call-back function to update the message label with the server response
public function refresh (response : String) : Void {
   message.text = response;
}

The code is intentionally made similar to our Flex application. The chart is populated by a run-time argument named data. We use AppletStageExtension to invoke the container page’s JavaScript function demo.ajax.submit. For JavaFX, it is easy to expose the callback function refresh. All script-level public functions are automatically visible to JavaScript in JavaFX.
To embed the JavaFX applet, copy SampleChartFX.jar and SampleChartFX_browser.jnlp into the resources/demochart folder of our Web content. Note the generated jnlp file by NetBeans points, by default, to a local codebase. Because we will specify the jar file location in our Web page anyway, simply remove the codebase attributes from the jnlp file.
Afterwards, we just need to make minor changes to our JSF composite component to embed the JavaFX applet.
JSF composite component source code snippets (resources/demo/chart.xhtml):
<script type="text/javascript" 
    src="http://dl.javafx.com/1.3/dtfx.js" target="head"/>
<script type="text/javascript">
   javafx(
         {
            archive: "${facesContext.externalContext.requestContextPath}
                     + /resources/demochart/SampleChartFX.jar",
            draggable: true,
            width:  500,
            height: 500,
            code: "demo.piechart.Main",
            name: "${cc.clientId}:chart",
            id:   "${cc.clientId}:chart"
         },
         {
            data: "${cc.attrs.data}"
         }
     );
...

Most of the JavaScript code would continue to work for our JavaFX applet. The only change is how JavaScript calls back into JavaFX. Inside the demo.ajax.onevent function, instead of chart.refresh(response), it should be chart.script.refresh(response). To allow the code work for both situations, use the following:
chart.refresh? chart.refresh(response) : chart.script.refresh(response)

That’s it. There is no need to change the consuming JSF page. Whether JSF or JavaFX is used to provide the chart is an implementation detail and is totally transparent to the consuming page.
Conclusion
In this article, we took advantage of new features in JSF 2.0 to integrate Flex and JavaFX into our JSF applications. These new capabilities in JSF free us from the need to take care of plumbing on encoding, decoding, view state tracking, and so on. In particular, we used the composite component feature to create a custom component to encapsulate the embedding of Flex and JavaFX.
See Also:
·         NetBeans IDE download: http://netbeans.org/downloads/index.html
·         Adobe Flex download: http://www.adobe.com/products/flex/
·         Flex SDK download: http://opensource.adobe.com/wiki/display/flexsdk/Flex+SDK
·        JavaServer Faces 2.0 download: http://www.oracle.com/technetwork/java/javaee/download-139288.html


No comments :

Post a Comment