Java-JavaScript interoperability incepted in Java SE 6 and rode Mozilla Rhino until Java SE 7. Oracle Nashorn is the script engine shipped with the latest version of Java SE 8. Observe how the bin folder of your Java SDK getts fatter with each new introduction of tools. This time it is JJS. This tool represents the Nashorn script engine and can be used to run JavaScript from the command line. The command line is the basic way to interact with the engine, or we can embed it inside a Java application. This coupling of Java with JavaScript can be extended to use JavaFX API as well. The integration is seamless although the languages have a very different inherent nature. One is strictly typed and another being dynamic and un-typed. The article shall explore the simplicity of integration between two very different types of languages, especially from the point of view of a JavaFX application.
A JavaFX App vis-à-vis Scripting App
The life-cycle of a JavaFX application is managed by the init(), start(), and stop() methods. In a similar manner, a Nashorn script can be managed. However, it is not compulsory to have these methods in Nashorn. If no start() method is supplied in a script, the entire code written in the global scope is considered to be the start() method. Let us create a simple JavaFX application to draw a pie chart on some hypothetical data on the sales of food items on a restaurant. The application code is as follows:
package application; import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.chart.PieChart; import javafx.scene.layout.BorderPane; import javafx.scene.layout.GridPane; import javafx.stage.Stage; public class Main extends Application { private GridPane grid = new GridPane(); @Override public void start(Stage primaryStage) { BorderPane root = new BorderPane(); root.setTop(grid); Scene scene = new Scene(root, 400, 400); grid.addRow(0, createPie()); primaryStage.setScene(scene); primaryStage.sizeToScene(); primaryStage.show(); } public PieChart createPie(){ PieChart pie=new PieChart(); pie.getData().clear(); ObservableList<PieChart.Data> pieData=FXCollections.observableArrayList(); pieData.add(new PieChart.Data("Sandwiches", 150)); pieData.add(new PieChart.Data("Salad", 90)); pieData.add(new PieChart.Data("Soup", 155)); pieData.add(new PieChart.Data("Beverages", 210)); pieData.add(new PieChart.Data("Desserts", 400)); pie.setData(pieData); pie.setAnimated(true); pie.setTitle("Lunch Sales"); return pie; } public static void main(String[] args) { launch(args); } }
It is a simple JavaFX application where we create a PieChart on the basis of given ObservableArrayList of PieChart.Data. As we run the program, it gives an output as follows.
Figure 1: The rendered pie chart
We almost entirely can write the preceding JavaFX application in JavaScript with the help of Nashorn. A replica of the same application in JavaScript is as follows.
load("fx:base.js") load("fx:controls.js") load("fx:graphics.js") function start(primaryStage){ grid=new GridPane() root = new BorderPane(); root.setTop(grid); grid.addRow(0, createPie()); primaryStage.setScene(new Scene(root, 400, 400)); primaryStage.sizeToScene(); primaryStage.show(); } function createPie(){ pie=new PieChart(); pie.getData().clear(); pieData=FXCollections.observableArrayList(); pieData.add(new PieChart.Data("Sandwiches", 150)); pieData.add(new PieChart.Data("Salad", 90)); pieData.add(new PieChart.Data("Soup", 155)); pieData.add(new PieChart.Data("Beverages", 210)); pieData.add(new PieChart.Data("Desserts", 400)); pie.setData(pieData); pie.setAnimated(true); pie.setTitle("Lunch Sales"); return pie; }
Observe how we have imported classes and packages into the JavaScript file with the load method. We also can use the complete classified name of the JavaFX classes or import them by using the Java.type() function.
The code snippet for using a complete classified name is:
var pie=new javafx.scene.chart.PieChart();
or, we may use a Java.type() function as follows:
var PieChart=Java.type("javafx.scene.chart.PieChart"); var pie=new PieChart();
However, it is not a very convenient way to write code in this manner, especially as lines of code increase. Fortunately, JavaFX contains several script files that we can load within the script. These files define the JavaFX types as their simple names. We simply can use the load method to import these script files.
The list of some common Nashorn script files we may want to load for creating JavaFX objects are shown in the following table. As a rule of thumb, fx:base.js, fx:graphics.js, and fx:controls.js are the most common.
Nashorn script files | JavaFX classes and packages |
fx:base.js | javafx.stage.Stage javafx.scene.Scene javafx.scene.Group javafx/beans javafx/collections javafx/events javafx/util |
fx:graphics.js | javafx.stage.Stage javafx.scene.Scene javafx.scene.Group javafx/beans javafx/collections javafx/events javafx/util |
fx:controls.js | javafx/scene/chart javafx/scene/control |
fx:web.js | javafx/scene/web |
fx:media.js | javafx/scene/media |
Executing a Script with the JJS Tool
To run the application, we need to pass the -fx flag to the JJS command-line tool along with the JavaScript file name. This flag introduces to Nashorn that the script is using JavaFX APIs and automatically initiates program setup without the need to write explicit boiler-plate code.
Thus, to run the previous code file, say, with the name script1.js, we may write this line of code.
Figure 2: Writing the JJS script
About the Global $STAGE Object
Try creating an empty JavaScript file (say, test.js) and run the file with JJS command-line tool as follows:
C:>jjs -fx test.js
Observe that an empty window is displayed as an output even if not a single line of code is written in test.js. This interesting aspect is due to Nashorn’s global object name $STAGE, which is created automatically by the engine and references the primary stage. As a result, we easily can refer to this global object and modify the previous JavaScript application as follows:
load("fx:base.js") load("fx:controls.js") load("fx:graphics.js") grid = new GridPane() root = new BorderPane(); root.setTop(grid); grid.addRow(0, createPie()); $STAGE.setScene(new Scene(root, 400, 400)); $STAGE.sizeToScene(); $STAGE.show(); function createPie() { pie = new PieChart(); pie.getData().clear(); pieData = FXCollections.observableArrayList(); pieData.add(new PieChart.Data("Sandwiches", 150)); pieData.add(new PieChart.Data("Salad", 90)); pieData.add(new PieChart.Data("Soup", 155)); pieData.add(new PieChart.Data("Beverages", 210)); pieData.add(new PieChart.Data("Desserts", 400)); pie.setData(pieData); pie.setAnimated(true); pie.setTitle("Lunch Sales"); return pie; }
Conclusion
Scripting and JavaFX can leverage productivity if they can merge not only the code but also each other’s advantage as well. Scripts are evaluated at runtime and can be compiled to Java byte code. The Scripting API allows us to execute scripts written in any scripting language. These features make integration seamless even with major language differences between the two. The advantage of scripting, if fully utilized, especially in JavaFX, is that we may able to say Java is partly (tinge of) un-typed and dynamic as well.