One of the interesting features of JavaFX is its ability to leverage existing Java APIs. Accessing pure Java APIs in JavaFX applications is almost as simple as it would be in a pure Java application. This means that your JavaFX application can not only access all of the standard JavaSE APIs, but any Java API as well. In this article, I'll show you how to leverage an existing Java API to build a complex web services user-interface with JavaFX. As always, I'll point out how some of the more interesting parts of the application work.

Google Analytics Dashboard

For those of you not familiar with Google Analytics, it’s a service that allows you to analyze just about anything you might want to track on your web site. The service provides a number of different pre-built reports and graphs that allow you to quickly examine how your web site is meeting your goals.

Capture of Analytics Dashboard
Webstart.small2

I often find myself wanting to quickly view three things about sites: how many people are visiting, where they’re coming from and which pages are the most popular. It’s possible to get this information from Google Analytics, but it requires a few clicks every time. So, when Google recently announced it had released a GA API, there was a new opportunity to make a dashboard that showed exactly what I needed on one screen. Naturally, I created the dashboard using JavaFX. The demo application provides a real login prompt to the Google Analytics API. Try it out yourself! If you have more than one site attached to your google account, you’ll have the chance to pick a site before viewing your dashboard. If you don’t have a GA account, no sweat — there’s a “fake login” button that produces random data to give you an idea of what the application does.

If you’d like to check out the source code of the application, I’ve released it under the MIT open source license here on GitHub.

Implementing The Dashboard

The application is divided into two major pieces: the Analytics API client is written in Java and the user-interface in JavaFX. The API client takes care of all the plumbing required by the GData client APIs to provide a simple facade for the the user-interface to pull the information it needs. The JavaFX user-interface is responsible for displaying the data graphically, coordinating events and applying effects.

Java API for Analytics Client Logic

There are a number of approaches you can use to integrate Java API calls into your JavaFX application. In this application, I chose to design a Java facade to encapsulate the necessary API calls. This approach is useful for two reasons: it helps cleanly separate the middleware of the application from the user-interface and it allows us to show that you can mix Java API wrappers in the same project as the rest of your JavaFX code. Below is the interface that’s used by JavaFX. As far as our UI is concerned, this is about as simple an interface to our GA data as we might hope for.

	public interface AnalyticsClient {
		/** Authenticate a user */
	    void login(String user, String pass);

        /** After logging in, provide the list of sites to which the
            user is subscribed. */
	    List<Site> getSites();

        /** Set a valid GA profileID obtained from one of the sites in getSites() */
	    void setProfileId(String profileId);

		/** provide a list of hits for a given date range */
	    List<TimeEntry> hits(String start, String end);


		/** list pages ordered by # of visitors for date range */
	    List<Pair<String, Long>> topPages(String start, String end);
	
        /** list referrers ordered by # visits for date range */
	    List<Pair<String, Long>> topReferrers(String start, String end);
	}
 

There are two implementations of this interface: GdataAnalyticsClient and FakeAnalyticsClient. The fake client merely produces random data in a form expected by the UI in the event that a GA login is not available. The GData client authenticates users using their google accounts and issues the appropriate GData queries required to produce the dashboard. Accessing a given implementation is pretty easy. In this application, a LoginController is responsible for choosing which implementation of AnalyticsClient the application should use depending on which button the user clicks on. Here’s the code:

	 // ...
     loginAction: function(username, password) {
         client = GdataAnalyticsClient{ };
         // .. Gdata Specific logic
     }
     fakeAction: function(username, password) {
         client = FakeAnalyticsClient{ };
         // 
     }
     // ...	
 

With an appropriate instance of AnalyticsClient, our JavaFX program is free to call any of the methods in its interface using familiar dot notation to invoke them. For instance to see which pages were our top pages for a given period we’d invoke the following method:

  // hits will refer to a List<TimeEntry> instance
  var hits = client.topPages(start, end);	
 

JavaFX Script for User Interface

Much like many of the other examples I’ve produced so far, the majority of the user-interface was built using a graphics suite (this time I used InkScape). The interface was then converted to native JavaFX using the Production Suite. Unlike the other Production Suite examples I’ve shown in the past, this application uses JavaFX code directly to draw some things: the chart’s graph and the login form.

The JavaFX code to produce the chart is remarkably simple. The main portion that draw’s the graph’s outline is below:

     Path {
         elements: [
             // start with the first point
             MoveTo {
                 x: minX
                 y: minY
             },
             // Draw a line to each subsequent point
             for (i in [1..values.size() - 1]) {
                 LineTo {
                     x: parentBounds.minX + padding + ((values[i].x - 1) * xIncrement)
                     y: (parentBounds.minY + (parentBounds.height - values[i].y * yIncrement))
                 }
             },
             // close out the path so that it fills the bottom of the chart's container
             LineTo {
                 x: maxX
                 y: parentBounds.maxY
             },
             LineTo {
                 x: minX
                 y: parentBounds.maxY
             },
             LineTo {
                 x: minX
                 y: minY
             } 
         ]
         stroke: null
         fill: fill,
     }	
 

This application also uses a few UI controls and Swing Components. In the main screen, I used InkScape to decide where these controls should be placed using placeholder rectangles. After the application starts up, I overwrite the placeholder rectangles with the real controls. Here’s the code that replaces the refresh button in the design with a real Swing Button. The overwrite() and align() functions are used to place the date range text fields and the login button as well.

	// ...
    public-read var refreshButton = SwingButton {
        text: “Refresh”
        action: function() {
            refresh(startDate.text, endDate.text);
        }
    }
    // ...	
	init {
		//...
		// ui.refreshButton is our place-holder node
        overwrite(ui.refreshButton, refreshButton);
        //...
    }

function overwrite(dest: Node, target: SwingComponent) { var d = dest.boundsInScene; target.width = d.width; target.height = d.height; align(dest, target); } function align(dest: Node, target: Node) { var d = dest.boundsInScene; var t = target.boundsInScene; var leftX = d.minX + dest.translateX; var topY = d.minY + dest.translateY; target.translateX = leftX – t.minX; target.translateY = topY – t.minY; }

Unlike the main screen in the application, the login form used by the application was created in pure JavaFX without any design tools. If you follow Jim Weaver’s excellent blog, you might recognize much of the code that follows. I used his Mig Layout example as the basis of the login form.

 public class LoginForm extends CustomNode {
    var username: SwingTextField = SwingTextField {
        action: function() {
            password.requestFocus();
        }
    };
    // there is currently no Swing Password Field, so we use
    // a pure swing component instead.
    var password = new JPasswordField() on replace {
        password.setAction(RequestFocus {
            node: loginButton
        });
    }

    var loginButton = SwingButton {
        text: "Login"
        action: function() {
            if (action != null) {
                action(username.text, new String(password.getPassword()));
            }
        }
    };	
	
   // ... snip

   public override function create(): Node {
       return Group {
           content: [
		   // ... snip
		     MigLayout {
		         width: bind width
		         height: bind height
		         layout: "fill, wrap"
		         rows: "push[]8px[][][]4px[]push"
		         columns: "[][]"

		         migContent: [
		             MigNode {
		                 node: Text {
		                     textAlignment: TextAlignment.LEFT
		                     content: "Analytics Login"
		                     fill: Color.WHITE
		                     font: Font.font("Georgia", FontWeight.BOLD, 24)
		                 }
		                 constraints: "span"
		             },
		             MigNode {
		                 node: SwingLabel {
		                     foreground: Color.RED
		                     text: bind errorMessage
		                     visible: bind (errorMessage != null and errorMessage.length() > 0)
		                     font: Font.font("Georgia", FontWeight.BOLD, 20)
		                 }
		                 constraints: "span"
		             },
		             MigNode {
		                 node: SwingLabel {
		                     foreground: Color.WHITE
		                     text: "Email"
		                 }
		                 constraints: "ax right"
		             },
		             MigNode {
		                 node: username
		                 constraints: "growx"
		             },
		             MigNode {
		                 node: SwingLabel {
		                     foreground: Color.WHITE
		                     text: "Password"
		                 }
		                 constraints: "ax right"
		             },
		             MigNode {
		                 node: SwingComponent.wrap(password)
		                 constraints: "growx"
		             },
		             MigNode {
		                 node: loginButton
		                 constraints: "ax right"
		             },
		             MigNode {
		                 node: fakeButton
		                 constraints: "ax left"
		             },
		         ]
		     }	
		 ]
      // ... snip
 

The Power of Java Integration

The Analytics Dashboard is just a hint of the power available to JavaFX/Java hybrid applications. There are a number of enhancements we could apply to the dashboard to make it even more useful. We could add a Feed Burner client to track RSS subscriptions, or an interface into Google’s web master tools. Each of these enhancements is fairly trivial because robust Java APIs already exist for this purpose.

Many tutorials and articles you’ll find about JavaFX have focused on how easy it is to create user-interfaces in JavaFX. If you’ve read them, you’ve seen that it is indeed pretty easy! I believe making user-interfaces easier to build is necessary for JavaFX’s success, but not sufficient. JavaFX’s integration with existing Java APIs will ultimately help it
emerge as a premier graphical application development tool. More and more applications are accessing data through existing services and many of these services already provide robust Java clients to access their data. With the creation of more powerful JavaFX tools for producing UIs, JavaFX’s case will become even stronger.

After writing my article on creating a JavaFX game, many people sent excellent questions to me via email. Please don’t hesitate to leave a comment below with any question you might have.

comments powered by Disqus