JavaData & JavaLeverage a JavaFX Application with Nashorn Script

Leverage a JavaFX Application with Nashorn Script

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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.

Java1
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.

Java2
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.

References

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories