Launching Hyperlinks from JavaFX (including Mobile)
steveonjava | March 4, 2010
Creating hyperlinks in JavaFX should be in the category of things that are trivially easy, but is complicated by various factors, such as deployment mode and Java version. First I will go into detail on all the different permutations of how you can launch links in a browser and under what circumstances each will work. Next I will give you a nice packaged solution that you can use as a library (if you are impatient, just skip to The Easy Way Out now). Finally, I will show how you can do the same thing for JavaFX Mobile applications.
A Tale of 3 APIs
There are 3 different ways that you can launch hyperlinks in Java/JavaFX. Unfortunately, none of them work in all circumstances, so you need to know when to call each. Here is a quick reference table:
| AppletStageExtension | Web Start BasicService | Desktop.browse | |
|---|---|---|---|
| Works in Applet | Yes | Yes | Yes |
| Works in Web Start | No | Yes | Yes |
| Works in Application | No | No | Yes |
| Works on Java 1.5 | Yes | Yes | No |
| Can Set Target | Yes | No | No |
| Default Target | _self | _blank | _self |
AppletStageExtension
The first option is to use the JavaFX AppletStageExtension. This is only available if you are running as an Applet, but also gives you the most control over how the hyperlink is launched. In addition to a URL you can also specify a target, which can be any of the standard HTML targets including the following (excerpted from the AppletStageExtension javadocs):
| Target Argument | Description |
|---|---|
| "_self" | Show in the window and frame that contain the applet. |
| "_parent" | Show in the applet’s parent frame. If the applet’s frame has no parent frame, acts the same as “_self”. |
| "_top" | Show in the top-level frame of the applet’s window. If the applet’s frame is the top-level frame, acts the same as “_self”. |
| "_blank" | Show in a new, unnamed top-level window. |
| name | Show in the frame or window named name. If a target named name does not already exist, a new top-level window with the specified name is created, and the document is shown there. |
Web Start BasicService
The second option is to use the Web Start BasicService. This works from both JavaFX Applets and Web Start applications, but does not let you specify the HTML target. It is effectively the same as using the AppletStageExtension with a target of “_blank”.
Here is a small code excerpt showing how you would call the Web Start BasicService from your JavaFX code:
def basicService = ServiceManager.lookup("javax.jnlp.BasicService") as BasicService;
basicService.showDocument(new URL(url));
Desktop.browse
The third option is to use the new Desktop class introduced in Java 1.6. This works from Applet, Web Start applications, and Standard Execution (within a desktop Frame). Unfortunately, it did not exist in Java 1.5, so it won’t work from JavaFX without a little hacking.
The quick and dirty hack is to modify your JavaFX distribution to include the rt.jar from Java 1.6 as explained in this earlier post. The only problem with this is you also have to get all the other developers on your project to do the same (and redo this on every upgrade).
The friendlier approach is to use reflection to check and see if the Desktop class is available, and then invoke the methods dynamically. There is quite a bit more boilerplate code, but it will allow you to compile with a plain vanilla JavaFX installation, and also handle the odd case where someone is trying to run JavaFX under 1.5. (Which is unsupported on Windows/Unix, but happens to be the only option for the poor lost souls with 32 bit chips who have been abandoned by Apple).
Since the code is easier to follow without reflection, I will show that first:
Desktop.getDesktop().browse(new URI(url));
And here is the munged version with reflection:
try {
def desktopClazz = Class.forName("java.awt.Desktop");
def desktop = desktopClazz.getMethod("getDesktop").invoke(null);
def browseMethod = desktopClazz.getMethod("browse", [URI.class] as java.lang.Class[]);
browseMethod.invoke(desktop, new URI(url));
} catch (e) {
println("Upgrade to Java 6 or later to launch hyperlinks: {url}");
}
The Easy Way Out
When things are easy to do, they will get done right. To make sure that JavaFX applications do not fall prey to broken and inconsistent linking, I put together a library for JFXtras that takes care of all the plumbing for you.
There is a new JFXtras class called BrowserUtil that has a very simple API:
BrowserUtil.browse(url);
or
BrowserUtil.browse(url, target);
It is that simple… Conversion of string URLs to URL or URI objects, selection of the correct API based on your deployment mode, and failover modes based on the Java version are all included.
In addition, I created an extended Hyperlink called the XHyperlink. This behaves identically to the built-in control, with the addition of simple configuration of URL navigation (this is what hyperlinks are designed for, right?) The usage of the XHyperlink class is as follows:
XHyperlink {
text: "Oracle's Homepage", url: "http://oracle.com/"}
}
All of this functionality will be included in the JFXtras 0.6 release. If you need it now, you can build off the head of our repo. Otherwise we are working on a release, which I will announce on this blog shortly which you can follow.
What about JavaFX mobile?
None of these desktop techniques actually work on a mobile device, so this is not a 100% solution yet.
Fortunately, there is also a solution for JavaFX Mobile if you are willing to delve in to the Java ME APIs. To do this you first need to get a handle to the MIDlet like this:
def midlet = com.sun.javafx.runtime.adapter.MIDletAdapter.getMidlet();
And then you can call platformRequest to launch a browser on the mobile device:
midlet.platformRequest(url);
Note: This requires use of private APIs, so this may not work in future JavaFX releases.
It is not possible to merge this in with the desktop solution, because the JavaFX Mobile libraries do not exist on the desktop platform (and vice versa), but it is relatively easy to use this technique yourself by copying and pasting the above code sample into a helper function in your application.



















