A photograph of a road and rail line Between Hapuku and Mangamaunu, South Island, New Zealand

Java 8 has introduced a bunch of great features and one of the gems amongst those new features is Nashorn. Nashorn is the replacement for the Rhino Javascript engine and could turn out to be a serious competitor for Google's V8. A lot of you are aware of JSR-233 and the ability to embed Javascript (and many other languages) in the back end using Java and the JVM. Well this has been the case since Java 6, so although Nashorn makes many, many improvements here, that is not the subject of this guide.

In this article, I'll introduce JavaScript in Java, known as jjs. jjs is a small wrapper around javax.script.ScriptEngine and provides a REPL and library for scripting in Javascript. For an in-depth example of building a plugin framework using the ScriptEngine, please see my former article Java Plugin Scripting Architecture.

An image depicting a crudely drawn Nashorn tank and GUI mockup for an artcle about scriped user interfaces with Nashorn and JavaFX


Adding the -fx switch to the jjs command allows you to execute fully blown JavaFX UI applications written in Javascipt providing you with a pretty powerful UI scripting toolbox. Maturity aside, you could probably draw a pretty close analogy with wxPython, but in this case, everything you need is already bundled in the Java 8 distribution.

Alrighty, let's build a little toy app to introduce a few concepts. This should be enough to get you away and hacking with Nashorn and JavaFX.


TL;DR

Just give me the code: GitHub

Nuts and Bolts example

Let’s jump straight in with a complete little UI application. For those familiar with JavaFX from Java, this example will be easy to follow, and for those that aren’t, here is a great place to get started with JavaFX in Java1

slideshow.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
load("fx:base.js");
load("fx:controls.js");
load("fx:graphics.js");

var File = Java.type("java.io.File");

function start(primaryStage) {
  primaryStage.title = "Slideshow";

  var images = new java.util.ArrayList();
  var captions = new java.util.ArrayList();

  var root = new StackPane();
  var gridpane = new GridPane();
  var heading = new Label("My Aussie Slideshow");
  heading.setStyle("-fx-font-size: 30pt;");

  gridpane.add(heading, 0, 0); // c, r

  var imageView = new ImageView();
  imageView.setImage(new Image(fileToURL("Footy.png")));
  images.add(imageView);
  captions.add("Footy");
  imageView = new ImageView();
  imageView.setImage(new Image(fileToURL("Warnie.png")));
  images.add(imageView);
  captions.add("Warnie");
  imageView = new ImageView();
  imageView.setImage(new Image(fileToURL("Servo.png")));
  images.add(imageView);
  captions.add("Servo");

  var cur = 0;

  var borderpane = new BorderPane(images[cur]);
  BorderPane.setAlignment(images[cur], Pos.CENTER);
  gridpane.add(borderpane, 0, 1); // c, r
  GridPane.setHalignment(borderpane, HPos.CENTER)

  var col = new ColumnConstraints();
  col.setPercentWidth(100);
  col.setHalignment(HPos.CENTER);
  gridpane.getColumnConstraints().addAll(col);

  var caption = new Label(captions[cur]);
  caption.setStyle("-fx-font-size: 30pt;");
  gridpane.add(caption, 0, 2); // c, r

  var hbox = new HBox();
  var separator = new Separator();
  separator.maxWidth(12);
  separator.setStyle("visibility: hidden;");

  var prevButton = new Button();
  prevButton.text = "<- Back";
  prevButton.onAction = function(){
    if(cur > 0)
      cur--;
    else
      cur=images.size() -1;
    borderpane.setCenter(images[cur]);
    caption.text = captions[cur];
  }

  var nextButton = new Button();
  nextButton.text = "Next ->";
  nextButton.onAction = function(){
    if(cur < images.size() -1)
      cur++;
    else
      cur=0;
    borderpane.setCenter(images[cur]);
    caption.text = captions[cur];
  }

  hbox.getChildren().addAll(prevButton, separator, nextButton);

  var buttongrid = new GridPane();
  buttongrid.setAlignment(Pos.CENTER);
  buttongrid.add(hbox, 0, 0);
  gridpane.add(buttongrid, 0, 3); // c, r
  GridPane.setHalignment(hbox, HPos.CENTER)

  root.children.add(gridpane);
  primaryStage.scene = new Scene(root, 800, 600);
  primaryStage.show();
}

function fileToURL(file) {
    return new File(file).toURI().toURL().toExternalForm();
}

(oh, don’t forget to add your own images and captions, or else get this entire project from GitHub)

Our little app is done. To execute, just do this in the shell:

1
jjs -fx slideshow.js


Noteworthy:

  • line 1, 2 and 3 are predefined includes for JavaFX saving us from importing and/or defining a lot of individual JavaFX classes. For a listing of all files included by these imports, please refer to the Oracle documentation.

  • line 5 shows the regular way to import and alias a class or package.

  • JavaFX CSS styling works almost as you would expect. There are some inconsistencies with some attributes preprended with fx- whilst some others are not which can lead to a little guess work. The full CSS reference documentation can be found here.

Scoped imports still work as they did under Rhino and are still quite handy.

1
2
3
4
5
6
7
8
9
10
11
12
 // Scoped imports
  var JavaFileIO = new JavaImporter(
    java.io.BufferedReader,
    java.io.FileReader,
    java.io.File
  );

  //do some coding and stuff...
  with(JavaFileIO){
    var reader = new BufferedReader(new FileReader(file));
    //...
  }


Old school class imports (e.g. importClass(...) ) under Rhino no longer work natively with Nashorn. If you do have legacy code writen for Rhino, you can however load a compatibility library like so…

1
2
3
4
5
6
7
// not required for Java versions < Java 8
load("nashorn:mozilla_compat.js");

// imports
importClass(java.lang.Thread);
importClass(java.lang.Runnable);
importPackage(java.util.concurrent.locks);

Which brings to an end this modest little introduction to UI scripted interfaces with Nashorn and JavaFX. If you have any questions please leave them in the comments below and I’ll be happy to reply.

Notes

  1. All the available documentation from Oracle for getting started with JavaFX are specifically aimed at Java, howver the concepts map pretty much one-to-one with Nashorn. 





Related Articles

Java Plugin Scripting Architecture
Multithreaded Javascript using Nashorn