Building Rich Clients with JacpFX and JavaFX2
JacpFX
The JacpFX (Java Asynchronous Client Platform) Project is a Framework to create Rich Clients (Desktop and/or Web) in MVC style with JavaFX 2, Spring and an Actor like component approach. It provides a simple API to create a workspace, perspectives, and components; to communicate with all parts and to compose your Client application easily.
What does JacpFX offer to you?
- Implement scaling and responsive Rich Clients against a small and simple API (~120Kb) in Java
- Fully integrated with Spring and JavaFX 2
- Add/move/remove defined Components at run-time in your UI
- Create your basic layout in Perspectives and define “placeholders” for your Component UI
- Handle Components outside the FX application thread
- “handle” methods are executed in a worker thread and a “posthandle” method is executed by the FX application thread
- Stateful and stateless callback non-UI components
- Outsource long running processes and computation
- Handle asynchronous processes easily
- No need for explicit threading or things like Runtime.invoke()
- Communicate through Asynchronous Messages
- No shared data between Components
- Immutable Messages
The following article will give you a short introduction on JacpFX and how to create JavaFX 2 based Rich Clients with it. The example client shown here, is a (pseudo) contact manager that you can try out here: http://developer.ahcp.de/demo/JACPFX2Demo.html ; The complete source code can you download here: http://code.google.com/p/jacp/downloads/list
Pre-requirements :
A valid JavaFX2 run-time must be installed to run the demo application (currently available on windows only). To compile the attached demo code, a JavaFX2 SDK (also available for Mac and Linux) and Apache Maven is assumed to be installed. See the detailed instructions at the end of this article:
Application – Structure
A JacpFX application is composed of an application-launcher, a workbench, minimum one perspective and at least one component. The example client uses three UI-Components and three non-UI (Callback) Components to create the data and to simulate heavy data access.
The Application – Launcher (AFX2SpringLauncher)
The application – launcher is the main class of your application where you define the Spring context. A JacpFX – application uses xml declaration to define the hierarchy of the application and all the meta-data, like “component-id” and “execution-target”. The Spring main.xml is located in the resources directory and will be declared in the launchers constructor:
public class ContactMain extends AFX2SpringLauncher { public ContactMain() { super("main.xml"); } public static void main(String[] args) { Application.launch(args); } @Override public void postInit(Stage stage) { // define your css and other config stuff here } }
Listing 1: application launcher
The Application – Workbench (AFX2Workbench)
The workbench is the UI-root node of a JacpFX application. Here you configure the application, set the resolution, define tool – bars, menus and have reference to the JavaFX stage.
public class ContactWorkbench extends AFX2Workbench { @Override public void handleInitialLayout(IAction<Event, Object> action, IWorkbenchLayout<Node> layout, Stage stage) { layout.setWorkbenchXYSize(1024, 768); layout.registerToolBar(ToolbarPosition.NORTH); layout.setMenuEnabled(true); } @Override public void postHandle(FX2ComponentLayout layout) { final MenuBar menu = layout.getMenu(); final Menu menuFile = new Menu("File"); final MenuItem itemHelp = new MenuItem("Help"); /// add the event listener and show an option-pane with some help text menuFile.getItems().add(itemHelp); menu.getMenus().addAll(menuFile); } }
Listing 2: Workbench
The Perspective (AFX2Perspective)
The task of a perspective is to provide the layout of the current view and to register the root and all leaf nodes of the view. The leaf nodes are the “execution-target” (container) for all components associated (injected) to this perspective. The demo perspective basically defines two SplitPanes and registers them as “execution-target” to display the content of the ContactTreeView on the left; the ContactTableView at top right and the ContactChartView at bottom right.
Image 2: Three targets defined by the perspective in the demo |
public class ContactPerspective extends AFX2Perspective { @Override public void onStartPerspective(FX2ComponentLayout layout) { // create button in toolbar; button should switch top and bottom id's ToolBar north = layout .getRegisteredToolBar(ToolbarPosition.NORTH); ... north.getItems().add(new Button("switch view");) ; } @Override public void onTearDownPerspective(FX2ComponentLayout layout) { } @Override public void handlePerspective(IAction<Event, Object> action, FX2PerspectiveLayout perspectiveLayout) { if (action.getLastMessage().equals(MessageUtil.INIT)) { createPerspectiveLayout(perspectiveLayout); } } private void createPerspectiveLayout(FX2PerspectiveLayout perspectiveLayout) { //// define your UI layout ... // Register root component perspectiveLayout.registerRootComponent(mainLayout); // register left menu perspectiveLayout.registerTargetLayoutComponent("PleftMenu", leftMenu); // register main content Top perspectiveLayout.registerTargetLayoutComponent(“PmainContentTop”, mainContentTop); // register main content Bottom perspectiveLayout.registerTargetLayoutComponent("PmainContentBottom", mainContentBottom); } }
Listing 3: Perspective
The UI-Components (AFX2Component)
AFX2Components are the actual UI-Components in a JacpFX application that are rendered as JavaFX components. The demo defines a left (ContactTreeView), main-top (ContactTableView) and a main-bottom (ContactDemoChartView) AFX2Component. A JacpFX component has four methods to implement; “onStartComponent” and “onTearDownComponent” as well as “handleAction” and “postHandleAction”. While the “handleAction” is executed in a worker thread the “postHandle” runs in the FX application thread.
Image 3: Component lifesycle of an AFX2Component |
You can use lazy initialization of components and shutdown or restart components if you want.
public class ContactTreeView extends AFX2Component { private ObservableList<Contact> contactList; ... @Override public Node handleAction(final IAction<Event, Object> action) { if (action.getLastMessage().equals(MessageUtil.INIT)) { this.pane = new ScrollPane(); ... return this.pane; } return null; } @Override public Node postHandleAction(final Node node, final IAction<Event, Object> action) { if (action.getLastMessage() instanceof Contact) { this.contactList.addAll((Contact) action.getLastMessage()); } return this.pane; } @Override public void onStartComponent(final FX2ComponentLayout layout) { final ToolBar north = layout .getRegisteredToolBar(ToolbarPosition.NORTH); … north.add(new Button("add category")); } @Override public void onTearDownComponent(final FX2ComponentLayout layout) { } ... }
Listing 4: ContactTreeView (the left view)
The “handleAction” method in Listing 4 is used to initialize the component UI. In the “postHandle” action of the demo new contacts were added; the contactList is bound to the existing TreeView, so you can’t update it outside the FX application thread and must therefore use the “postHandle” method.
The Callback Components
JacpFX callback-components are non-UI/service components. Similar to an AFX2Component, they have a method called “handle” that is executed in a worker thread. The result can be any type of object and will be automatically delivered back to the calling component or redirected to any other component.
In these types of components you execute long running tasks, invoke service calls or simply retrieve data from the storage. JacpFX provides two types of callback components, an “AStatelessCallbackComponent” and a (stateful) “ACallbackComponent”. The demo client uses an “ACallbackComponent” component to generate the random chart data for a selected contact a table.
To generate the large amount of contacts two “AStatelessCallbackComponents” are involved. One component divides the total amount into chunks and the second one just creates contacts. The result will be sent to the UI component directly and added to the table.
Messaging
Messaging is the essences of the JacpFX Framework. JacpFX uses asynchronous messaging to notify components and perspectives of your application.
The message flow to create the initial amount of data for a category in the demo application looks like:
Image 4 : Message Flow to create initial amount of data in the demo application |
The state of a JacpFX component is always changed by messages. If a task should be handled in a background thread you simply send a message to a component and process the work in the “handle” method. The result can be sent back to the caller component or be processed by the FX application thread in the “postHandle” method (in case of UI Components). You should always avoid execution of long running tasks on the FX application thread; instead call your service- or DB-operation inside the “handle” method.
JacpFX differs between two message types, a local and a global message.
Local messages
To trigger a local message simply get a listener
IActionListener<EventHandler<Event>, Event, Object> listener = this.getActionListener(“message”);
with only one argument – the message itself. This listener can be assigned to any JavaFX eventHandler (like onMouseEvent, etc.) or it can be fired by invoking
listener.performAction(event);
Global messages
With global messages you communicate with other registered components. Callback components respond to a message by default so you don’t need to create a respond message explicitly – also you could. Messaging is the prefered way to exchange data between components and to trigger Tasks. Similar to local messages you create a listener instance but with an explicit target id:
IActionListener<EventHandler<Event>, Event, Object> listener = this.getActionListener(“id“,“message”);
Build the demo application from source
Register JavaFX in your local Maven repository
All JacpFX-Projects are Maven projects and require JavaFX 2 (jfxrt.jar). Some include JavaFX 2 as system dependency but we preferred to register the jfxrt.jar in the local repository. To create deployable files (jnlp and html) you additionally need to register the JavaFX Ant-Tasks (ant-javafx.jar). To do so change to your ${SDK-home}/rt/lib and type:
mvn install:install-file -Dfile=jfxrt.jar -DgroupId=com.oracle -DartifactId=javafx-runtime -Dpackaging=jar -Dversion=2.0
Then copy the “bin” directory (on linux i386) to your .m2\repository\com\oracle\javafx-runtime folder. Next change to your ${SDK-home}/tools directory and type:
mvn install:install-file -Dfile=ant-javafx.jar -DgroupId=com.oracle -DartifactId=ant-javafx -Dpackaging=jar -Dversion=2.0
Build the project
To build the project, simply unzip the project folder and type mvn package. The jar, jnlp and the html file is created in the ${projectHome}/target/deploy. To create an Eclipse project simply type mvn eclipse:eclipse.
What’s coming next?
JacpFX is currently in Version 1.0; after many years of prototyping with Echo3 and Swing, JacpFX is the first stable release based on JavaFX 2 and a defined release plan. You can find a detailed documentation on the projects Wiki page (http://code.google.com/p/jacp/wiki/Documentation). Our release plan (also located at the Wiki) defines Version 1.1 in June this year. Major changes will be annotation support and an official FXML support (also you can already use FXML). Feedback is always welcome, so feel free to contact us.
Reference: Building Rich Clients with JacpFX and JavaFX2 from our W4G partner Andy Moncsek.