Enterprise JavaFX: Our Experience Building a JavaFX UI for a Seam Booking Application

This article is also posted on JavaLobby.com

This is a guest post by Anton Polyakov. Anton is Senior Developer at Exadel, he describes our experience building a JavaFX front end for a Seam booking application.

JavaFX is new tool set for developing and delivering Rich Internet Applications or RIAs. JavaFX 1.0 was released in December 2008, and JavaFX 1.2 was released in June 2009. As these new releases have rolled out, the JavaFX community has been growing fast. This growth has produced a large selection of resources, articles, blog posts, books, and extension projects.

Over this time, while JavaFX has been used extensively to provide “richness� in applications, it has been mostly “missing in action� for enterprise-level Web applications that would involve greater integration of JavaFX with the server side of the application. Remember, a Rich Internet Application is delivered from a server to the the client, but, more importantly, it continues to communicate with the server. The UI runs on the client while the application logic runs on the server.

We believe that for JavaFX to continue growing and compete against Flex and Silverlight, it should be acceptable by the enterprise for use in applications that take full advantage of both sides of Rich Internet Applications (Rich Enterprise Applications). Adobe and its community has done an excellent job demonstrating that Flex can be used to build real-world enterprise applications. The same needs to happen for JavaFX.

Exadel’s Experience with JavaFX

Exadel has been working with JavaFX for more than a year now. In June of 2008, we took the popular Jboss Seam hotel booking Web application and built a JavaFX UI on it as a replacement for the standard JSF UI. Back then, we used a prerelease version of JavaFX. (There was very little interest in JavaFX at that time.)

Fast forward to today. We took the same Seam booking application and updated the JavaFX UI to JavaFX 1.2. You can find the demo running here. For your local use, you can also download the file to run or a Maven project to build this application.

Redoing the Booking Application

What we are going to do in the rest of this article is share our experience building this demo as an example of building an enterprise JavaFX applications. The task wasn’t easy (but then it wouldn’t have been as fun).

Continue reading

Enterprise JavaFX – Seam booking demo application

You can now view or download and deploy one of the first real enterprise JavaFX application. We took the popular Seam booking demo application and created JavaFX UI for it. The JavaFX side is connected to Seam via Flamingo RIA framework.

screenshot_001

View online
You can view and run the application by going to this URL: http://demo.flamingo.exadel.com/booking/. You will also see JSF/RichFaces and Flex versions of the application. All instances are connected to the same Seam back-end. Once you register, you can use the same name/password information to login using any other user interface.

Download and run
You can also download, deploy and run this application on your machine. There are two ways you can do it:

  1. Deploy (ready) booking.ear file
  2. Build the application with Maven and deploy it

Download the source and application (.ear). You will find instructions on how to deploy and build inside the zip file.

We are working on an article to tell you about our experience building an enterprise application with JavaFX. Lastly, yesterday we released new version of our JavaFX Studio plug-in for Eclipse. Get it here.

Have fun with JavaFX!

Seam hotel booking with JavaFX

More than a year ago Exadel used Flamingo RIA framework to create Flex and JavaFX interfaces for the popular Seam booking application in addition to standard JSF UI. You can still find the original applications running here. You can register once and login using any user interface.

Since we built the JavaFX interface last year, we used a preview version of JavaFX. We have started converting the JavaFX user interface to use the latest JavaFX version 1.2 and also use the new UI controls.

Once we are done, we will make the application (and sources) available for download. As this would be the first true JavaFX enterprise application, we will share our experience developing such application: what’s good, what’s not and what needs to be improved.

Screen shot of the new version:

screenshot_001

JavaFX with JBoss Seam

This is an introductory post that shows how to use JavaFX with JBoss Seam. Communication between JavaFX and server (Seam) is done via Flamingo RIA framework. Flamingo, not only provides Flex to server communications like Granite DS and Blaze DS, but also provides JavaFX to server communications.

screenshot01.png

JavaFX:

package com.exadel.flamingo.javafx;


import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.text.TextOrigin;
import javafx.ext.swing.SwingButton;
import javafx.scene.layout.VBox;
import javafx.scene.control.TextBox;

import com.exadel.flamingo.javafx.samples.helloworld.javafx.HelloworldClient;

class Hello {
    public var name:String;
    public var str:String;
}
var helloModel = new Hello();

HelloworldClient.setServerUrl(FX.getArgument('uri') as java.lang.String);

var helloText: TextBox = TextBox {
    text: bind helloModel.name with inverse
    columns: 7
    selectOnFocus:true
}

var helloLabel = Text{
    y:8
    font: Font { name:"sansserif", size: 12 }
    fill: Color.BLACK
    content: bind "Server says: {helloModel.str}"
    textOrigin: TextOrigin.TOP
}

var helloButton = SwingButton  {
    text:"Say Hello!"
    action: function(){
        helloModel.str = HelloworldClient.CLIENT.hello(helloModel.name);
    }
}

Stage {
    title: "Helloworld Sample"
    width: 200
    height: 150
    scene: Scene {
        content: VBox {
            translateX: 5
            translateY: 5
            spacing: 10
            content: [
                HBox {
                    content: helloText
                    spacing: 10
                },
                HBox {
                    content: helloButton
                    spacing: 10
            	},
                HBox {
                    content: helloLabel
                    spacing: 10
            }]

        }
    }
}

HelloAction interface:

package com.exadel.flamingo.javafx.samples;

import org.jboss.seam.annotations.remoting.WebRemote;

public interface IHelloAction {

    @WebRemote
    public String hello(String name);
}

HelloAction (Seam component):

package com.exadel.flamingo.javafx.samples;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.remoting.WebRemote;

@Scope(ScopeType.STATELESS)
@Name("helloAction")
public class HelloAction implements IHelloAction {

    @WebRemote
    public String hello(String name) {
        return "Hello, " + name;
    }
}

Helloworld client:

package com.exadel.flamingo.javafx.samples.javafx;

import com.caucho.hessian.client.HessianProxyFactory;
import com.exadel.flamingo.javafx.samples.IHelloAction;
import java.net.MalformedURLException;

public class HelloworldClient {

    public static HelloworldClient CLIENT;
    private IHelloAction _service;
    private String _url;

    private HelloworldClient(String string) {
        _url = string;
    }
    public static void setServerUrl(String url) {
        CLIENT = new HelloworldClient(url);
    }
    private IHelloAction getService() {
        if (_service == null) {
            try {
                HessianProxyFactory factory = new HessianProxyFactory();
                _service = (IHelloAction) factory.create(IHelloAction.class, _url);
            } catch (MalformedURLException ex) {
                System.out.println(ex);
            }
        }
        return _service;
    }
    public String hello(String s) {
        return getService().hello(s);
    }
}

JSP file:






Helloworld


JavaFX with Seam!

javafx( { archive: "jnlp/helloworld-client-javafx.jar", draggable: true, width: 600, height: 560, code: "com.exadel.flamingo.javafx.MainFrame", name: "Helloworld", uri:'' } );

Updating training materials

Started to update my training materials for JSF and RichFaces and shortly Seam. Right now I have slides, going to convert them to a short training booklet. Why am I doing this? Well, I have realized that training is not a presentation, so slides is not the best way to do it. Plus, it’s going to save lots of paper. I used to print 2-slides per page and all the space around them wasted. The short booklet will also allow to include more details and examples.

How to delete a row in JSF

There is more than one way to delete a row from data UI component in JSF. If you use RichFaces and Seam, there even more ways.

All the cases will use the following setup:

data1.png

JSF view:


   
      Seven New Wonders of the World
      
         Action
         
      
      
         Name
  	 
      
      
         Location
  	 
      
      
         Location
  	 
      
   

WonderService.java:

public class WonderService {

   private ArrayList  list;
   public ArrayList getList() {
	return list;
   }
   @PostConstruct
   public void init () {
		list = new ArrayList ();
		list.add(new Wonder("Chichen Itza", "Mexico", "http://upload.wikimedia.org/wikipedia/commons/thumb/7/7a/Chichen-Itza-Castillo-Seen-From-East.JPG/90px-Chichen-Itza-Castillo-Seen-From-East.JPG"));
		list.add(new Wonder("Christ the Redeemer", "Brazil", "http://upload.wikimedia.org/wikipedia/commons/thumb/5/50/CorcovadofotoRJ.jpg/90px-CorcovadofotoRJ.jpg"));
		list.add(new Wonder("Colosseum", "Italy", "http://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Colosseum_in_Rome%2C_Italy_-_April_2007.jpg/90px-Colosseum_in_Rome%2C_Italy_-_April_2007.jpg"));
		list.add(new Wonder("Great Wall of China", "China", "http://upload.wikimedia.org/wikipedia/commons/thumb/1/16/GreatWallNearBeijingWinter.jpg/90px-GreatWallNearBeijingWinter.jpg"));
		list.add(new Wonder("Machu Picchu", "Peru", "http://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Before_Machu_Picchu.jpg/90px-Before_Machu_Picchu.jpg"));
		list.add(new Wonder("Petra", "Jordan", "http://upload.wikimedia.org/wikipedia/commons/thumb/0/06/PetraMonastery.JPG/90px-PetraMonastery.JPG"));
		list.add(new Wonder("Taj Mahal", "India", "http://upload.wikimedia.org/wikipedia/commons/thumb/c/c8/Taj_Mahal_in_March_2004.jpg/90px-Taj_Mahal_in_March_2004.jpg"));
   }
   public WonderService() {}
}

Wonder.java

public class Wonder{

   private String name; // setter and getter
   private String location; // setter and getter
   private String imageUrl; // setter and getter

   public Wonder(String name, String location, String imageUrl) {
	super();
	this.name = name;
	this.location = location;
	this.imageUrl = imageUrl;
   }
}

WonderBean.java

public class WonderBean {

   private WonderService service;

   public WonderService getService() {
	return service;
   }
   public void setService(WonderService service) {
	this.service = service;
   }
   public WonderBean() {
	super();
   }
   public ArrayList getWonders() {
	return service.getList();
   }
}

All the code changes are done in WonderBean.java.

JSF

If you are only using plain JSF, one way is to bind to component instance

private UIData table; // getter and setter

   Seven New Wonders of the World
   
      Action
      
   
   ...

Let’s create delete() method:

public void delete() {
  service.getList().remove(table.getRowData());
}

Using UIData.getRowData() method we can retrieve the row data the corresponds to raw clicked in the browser. JSF is smart to wire everything for us automatically.

Instead of using getRowData(), it’s also possible to use getRowIndex() method:

public void delete() {
  service.getList().remove(table.getRowIndex());
}

We can also use f:setPropertyActionListener to help us set the row we want to delete.

private Wonder selected; // setter and getter

   Seven New Wonders of the World
    
    Action
     
        
      
    
    ...

setPropertyActionListener takes the current row object #{wonder} and assigns it to #{wonderBean.wonder}.

One more option is to use data modal class such as ListDataModal, bind it directly to the UI component and then wrap the list. Being a more exotic option, I will skip it in this post.

RichFaces

If you are using RichFaces, you get a few more options. Of course you can use everything you saw above.

With rich:dataTable you get access to row key defined by rowKeyVar attribute. We can use this attribute as the index of the row we want to delete.


   Seven New Wonders of the World
   
      Action
      
  	
      
   
   ...

private int wonderIndex; // getter and setter

public void delete() {
   service.getList().remove(wonderIndex);
}

Using rich:dataTable with ruby skin:

data2.png

Keeping everything the same in the bean, instead of using f:setPropertyActionListener, it’s possible to use a4j:actionparam:


   

a4j:actionparam works like f:param but in addition automatically assigns the value (in our case #{row}) to assignTo property (in our case #{wonderBean.wonderIndex}). One thing to remember is that a4j:actionparam renders a request parameter on to the page. If you are trying to pass in a custom object, you need to write a converter.

Seam

Seam makes deleting even simpler. We can use two annotations that will inject the selected data row into a Seam component.

@DataModel
private List wonderList;

@DataModelSelection
private Wonder wonder;
...


   Seven New Wonders of the World
   
	Action
  	
   
   ...

@DataModelSelection will inject the actual row data which was clicked. Or we can @DataModelSelectionIndex which will inject the row index:

@DataModelSelectionIndex
private Integer wonderIndex;

No changes in the page.