December 2008: Experiments With JavaFX Script

Experiments With JavaFX Script

By Weiqi Gao, OCI Principal Software Engineer

December 2008


Introduction

JavaFX was unveiled at JavaOne 2007 as a new initiative for consumer-oriented Java applications. The strategy includes a new programming language called JavaFX Script that is geared towards developing Java-based GUIs, as well as libraries and tools for developing desktop, browser and mobile applications.

The rest of the year 2007 saw a JavaFX Script compiler being developed as the openjfx-compiler project on java.net. Meanwhile, technologies supporting JavaFX applications were developed by Sun. By December 2007, Scenario, a scene graph library that makes 2D programming easy in JavaFX, was made open source. At JavaOne 2008 in May, JavaFX became the central theme of multiple keynotes and JavaFX demos were headline grabbers. Also announced at JavaOne 2008 was a license deal with ON2 to support multimedia codecs on the Java platform.

The first preview release of JavaFX SDK with support for desktop applications, called the desktop profile, was released in July 31, 2008. Version 1.0 of JavaFX Desktop is scheduled for the end of 2008 while JavaFX Mobile is scheduled for Spring 2009.

For the rest of this article I will abbreviate JavaFX Script into JavaFX when doing so causes no confusion.

As I write this article in mid November 2008, my frame of reference consists of two software bundles: the JavaFX Preview SDK from the end of July, 2008, and the open source openjfx-compiler project at java.net. The former includes the GUI and video bits, and the latter contains just the compiler. Since the language has evolved a bit in recent months, I will write non-GUI examples to the latest openjfx-compiler Subversion head, and GUI examples to the JavaFX Preview SDK. Please be advised that some of the examples (the GUI examples more than the non-GUI examples) in this article may be made obsolete when JavaFX Desktop SDK 1.0 is released.

The Place of JavaFX in the Universe

Around the same time frame when JavaFX is being developed, several other technologies in roughly the same category are also being released, among them are Flex and AIR from Adobe and Silverlight from Microsoft. Both offerings are impressive and would be good technology choices where appropriate.

However, JavaFX promises to offer something unique that makes it a compelling choice:

Many Java GUI developers use Swing to build their GUIs and are therefore worried what JavaFX will do to Swing development. While it is true that JavaFX will be an alternative way to build Java GUI applications, the fact that JavaFX, at least in the desktop profile, depends on both Swing and the JVM also means that Swing will continue to be improved along with JavaFX. JavaFX is also the driving force behind the recently released Java 6 Update 10, which includes many improvements for client side Java that will be enjoyed by both Swing and JavaFX applications.

Getting The JavaFX Preview SDK

The JavaFX Preview SDK can be downloaded from the official JavaFX website. Simply click on the "Get JavaFX Preview SDK" button to go to the download page, where you can download the JavaFX Preview SDK either with or without NetBeans IDE. For this article, the one without NetBeans IDE suffices.

The JavaFX Preview SDK is supported on Windows and Mac OS X only. However, for Linux or Solaris users, if you download the zip file version for Mac OS X, most of the SDK will work. Only parts that depends on native libraries will not work.

After adding the bin directory of the JavaFX Preview SDK to your $PATH environment variable, you will be able to use following three commands from your command shell:

These commands are similar to the javacjava and javadoc commands in the JDK.

Getting the Openjfx-compiler

The openjfx-compiler open source project is hosted on java.net at https://openjfx-compiler.dev.java.net.

This project has been active since mid-2007 and has an active mailing list, an issue tracker, and a Subversion repository.

You can grab the latest successful build artifacts from the continuous build server or build from the Subversion head.

Running "ant all" in the openjfx-compiler source directory builds the compiler. The result lives in a dist subdirectory, which in turn contains the usual bindoclib and man subdirectories. You will find the javafxcjavafx, and javafxdoc commands in the bin subdirectory.

Since both the JavaFX Preview SDK and the openjfx-compiler offer the same commands, you can't use both at the same time without resorting to using full paths. For the purpose of this article, it is better to write a couple of shell scripts to manipulate the $PATH to contain either the JavaFX Preview SDK's bin directory or openjfx-compiler/dist's bin directory.

Hello JavaFX World

With the JavaFX Preview SDK or the openjfx-compiler installed, we can get started with our experiments.

First, the obligatory "hello, world" example. Create a file named hello.fx that contains the following line:

        println("hello, world.")

Compile it with the command:

       [weiqi@gao]$ javafxc hello.fx

Then run it with:

       [weiqi@gao]$ javafx hello
       hello, world.

As you can see from this example, the flow of developing an JavaFX application is very similar to the flow of developing a Java application.

However, the JavaFX source is simpler than Java. In JavaFX there is no need to declare a class and write a main method to do a simple "hello, world" program. In this regard, JavaFX is similar to other scripting languages like Perl, Python, Ruby and Groovy.

In JavaFX, a *.fx file is called a script. A script may contain any number of class definitions, function definitions, import declarations and loose statements. Our hello.fx contains only one statement, the call to the println built-in function. Notice that there is no semicolon at the end of that statement. In JavaFX, like in Java, the semicolon is a statement terminator. However in JavaFX the semicolon is optional in many places. As a result, the JavaFX compiler appears to be more lenient towards missing semicolons.

JavaFX Compiles JavaFX Sources to Java Class Files

In this experiment, we compile the following JavaFX source file:

// func.fx
function sumOfSquares(x, y) {
  x * x + y * y
}
println(sumOfSquares(3, 4));

The JavaFX compiler compiles func.fx into two Java class files: func.class and func$Intf.class. The former has a JavaFX compiler generated main method, which eventually calls into another generated method that executes the loose statements in func.fx. The latter is never directly used when we run JavaFX Scripts, and can be considered an implementation detail.

The javafx command is a wrapper of the java command that prepends the JavaFX runtime jars into the bootclasspath before running the class specified on the command line.

The func.fx script defines a simple function that calculates the sum of squares of its two arguments. The function keyword is used to define a function. Notice how in JavaFX you are not required to specify the argument types or the return type when you define a function. The compiler can determine them based on the functions body using a facility called type inference. In our case, the compiler will treat our function as if we have defined it with the following data types:

function sumOfSquares(x: Number, y: Number): Number {
  x * x + y * y
}

Number is a JavaFX built-in type, it represents double precision floating-point numbers. In JavaFX the type specifier follows the entity it applies to and is separated from it by a colon. This makes it easy to omit the type specifier.

Boolean, Integer, Number, String, Duration

Besides Number, JavaFX supports four other built-in types. They are: BooleanIntegerString and DurationInteger represents 32-bit integer values. Duration plays an important role in animations, which we'll see a bit later.

They are called primitive types in JavaFX. But unlike primitive types in Java, they have classes and you can call methods on them. The BooleanInteger and String types are backed by Java classes java.lang.Booleanjava.lang.Integer and java.lang.String. The Number type is backed by the Java class java.lang.Double. The Duration class is backed by javafx.lang.Duration.

The BooleanInteger and Number types support Java-like literal expressions, such as truefalse1024 and 3.14.

Like in other scripting languages, the JavaFX String type supports embedded JavaFX expressions using braces. JavaFX further supports formatting of embedded JavaFX expressions in strings through the use of format specifiers. JavaFX does not support multi-line strings as some other scripting languages do. However, to ease the representation of overly long strings and multi line strings, JavaFX supports adjacent String expressions concatenation. Here are some examples:

var str1 = "Hello";                    // double quotes
var str2 = 'Goodbye';                  // single quotes
var str3 = "1 + 2 = {1 + 2}";          // => "1 + 2 = 3"
var str3 = '3 + 4 = {3 + 4}';          // => "3 + 4 = 7"
var str4 = "1024 in hex is {%x 1024}"; // => "1024 in hex is 400"
var str5 = "Hello, "
           "World.";                   // => "Hello, World."

For the Duration type, JavaFX supports time literals, which are self evident in form and meaning. Here are some examples:

var oneHour = 1h;
var oneMinute = 1m;
var oneSecond = 1s;
var oneMillisecond = 1ms;
println(oneHour);                    // => 3600000.0ms
println(oneMinute + 15 * oneSecond); // => 75000.0ms
println(oneMillisecond);             // => 1.0ms

Constants, Variables, Classes

In the last few experiments, we have seen function definitions, variable declarations and statements in JavaFX. There are only two more kinds of major constructs that can appear at the top level of a JavaFX script: constant declarations and class definitions.

Constants are introduced by def. Constants must be initialized when they are declared. The type specifier of the constant is optional and is usually omitted. For example:

       def PI = 3.14159265;
       PI = 2.71828;        // Error, PI is a constant

Variables are introduced by var. Variables can be assigned after they are declared. The type specifier is optional. For example:

       var name: String;
       name = "John";
 

Functions are introduced by function. The function name is followed by the parameter list. The type specifiers for each parameter as well as the return type specifier are optional.

Classes are introduced by class. Classes can contain constants, variables, and functions and a couple of other things which we will cover in later sections. Variables in classes are also called member variables. Functions in classes are also called member functions. Here's a simple class:

<pre style="margin-left:3em" xml:space="preserve">class Point {
  var x: Number;
  var y: Number;
  function print() {
    println("Point({x}, {y})");
  }
}</pre>

As language constructs go, these are the major ones in JavaFX. They are constants, variables, functions, classes, and statements. Strictly speaking, constant and variable declarations are also statements. And there are many other kinds of statements in JavaFX.

Types in JavaFX

JavaFX is a statically-typed language. We have seen the five primitive types. Each user-defined class defines a type. Thus the Point class we defined earlier gives us a Point type.

One of the differences between primitive types and user-defined types is that variables of primitive types always have a value. If you don't specify an initializer when defining a variable of a primitive type, they get the default value. Here are some examples:

var b: Boolean;
var i: Integer;
var n: Number;
var str: String;
var dur: Duration;
println("b = {b}");         // => b = false
println("i = {i}");         // => i = 0
println("n = {n}");         // => n = 0.0
println("str = \"{str}\""); // => str = ""
println("dur = {dur}");     // => dur = 0ms

Note that the default values for primitive types are never null. The default value for String is "".

On the other hand, uninitialized variables of user-defined types have the value null:

var point: Point;
println(point);  // => null

Aside from primitive types and class types, JavaFX supports two other kinds of types: function types and sequence types, which we experiment with in the next two sections.

Function Types

Unlike in Java, but like in other scripting languages, functions are first class entities in JavaFX. Functions have types, called function types. The sumOfSquares function that we defined earlier would have the type function (:Number, :Number): Number. You can declare constants and variables of function types. You can pass functions as parameters to other functions. You can return functions from functions. Here is the addN example that is usually found in Lisp books:

function addN(n: Number): function(:Number): Number {
  function(x: Number): Number { x + n }
}
var addTen = addN(10);
println(addTen(3));    // => 13.0

The function addN takes one parameter of type Number and returns a function of type function(:Number): Number. The function that is returned by addN(10) is assigned to the variable addTen. The variable addTen is of a function type and is called just like any other function.

Function types are a little hard to specify, especially when functions are passed in as parameters or returned. Fortunately, JavaFX's type inference engine is pretty powerful so we don't always have to explicitly specify the types. The addN function could just as well be defined as follows:

function addN(n) {
  function(x) { x + n }
}

In JavaFX, the last expression in the function body is the return value of the function. You can, but are not required to, use the return keyword in this situation. You do, however, need the return keyword if you want to return from a place other than the last expression.

Notice also how the return value of addN is a function without a name. This is called an anonymous function expression. It evaluates to a closure, which is like a function that remembers all the variables that are in scope when the anonymous function expression is defined.

Sequence Types

Primitive types, class types and function types can also be used to form sequence types. Sequence types are formed by adding [] to one of these types. Here are some examples:

class Point {
  var x: Number;
  var y: Number;
}
var nums: Number[];  // sequence of Number
var strs: String[];  // sequence of String
var points: Point[]; // sequence of Point
var funcs: (function(:Number):Number)[] = [function(x){x}];
                     // sequence of function()

Only non-sequence types may be used to form sequence types. In other words, you cannot form a type that is a sequence of sequences. You will see why a little bit later. The current version of the compiler also has some limitations when it comes to sequences of functions. You must specify a non-empty sequence as an initializer for any variables of sequence of function type.

All JavaFX types have an element specifier and a cardinality. Non-sequence types consist of an explicit element specifier and an implied cardinality. Primitive types have the cardinality required, which means that a variable of primitive type contains exactly one value and cannot be null. Sequence types have the cardinality sequence, which means a variable of sequence type may contain zero or more elements. All other types (class types and function types) have the cardinality of optional, which means a variable of these types may have a value or be null.

To construct a variable of sequence type, you use an explicit sequence expression or a range expression:

var oneTwoThree = [1, 2, 3];   // => [ 1 2 3 ]
var oneToTen = [1..10];        // => [ 1 2 3 4 5 6 7 8 9 10 ]
var oneToNine = [1..<10];      // => [ 1 2 3 4 5 6 7 8 9 ]
var odds = [1..10 step 2];     // => [ 1 3 5 7 9 ]
var reversed = [5..1 step -1]; // => [ 5 4 3 2 1 ]

The first line uses an explicit sequence expression. It works for any element type. The rest use range expressions. They work only for numeric types. The thing to look out for when using range expressions is that you must specify a negative step value when constructing a decreasing sequence. Failure to do so will result in an empty sequence.

An uninitialized sequence variable has the value of an empty sequence, [].

Object Literals

In the last few experiments, we learned the four kinds of data types in JavaFX: primitive types, class types, functions types and sequence types. We also learned ways to initialize variables of three of the four kinds of types: literals for primitive types, function definitions or anonymous function expressions for function types, and explicit sequence expressions and range expressions for sequence types.

To initialize a variable of a class type in JavaFX, we use an object literal expression. Here are a couple of Point variables:

// Reuse the Point class define earlier
var pointA = Point { x: 3, y: 4 };
var pointB = Point {
               x: 5
               y: 12
             };

An object literal starts with the class name followed by a pair of braces that contains initializers for the member variables. The member variable initializers may be separated by commas for when they are put in one line as we did for pointA, or spaces for when they are put on separate lines as we did for pointB. You can also use semicolons, but that is necessary only when separating member variable initializers from other constructs in the object literal.

One benefit of the JavaFX object literal syntax is that you can understand the object being constructed at the site of the object literal without looking at the class definition. You can see exactly which member variables are initialized to what values.

Another benefit is that object literals nest well. If a member variable is of class type, its initializer can be another object literal nested inside the enclosing object literal.

A third benefit is that object literals and explicit sequence expressions mix well. If a member variable is of sequence type, its initializer can be a explicit sequence expression nested inside the enclosing object literal.

Declarative GUI Construction

These conveniences of the object literal expression, when applied to GUI construction, yield a style of programming that is declarative in nature. Here's an example of a JavaFX GUI:

JavaFX GUI
// hello.fx
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.text.*;
 
Stage {
  title: "Example"
  scene: Scene {
    width: 500
    height: 300
    content: Text {
      x: 100, y: 126
      content: "Hello, World."
      font: Font {
        size: 48
      }
    }
  }
}

In this example we constructed a javafx.stage.Stage object whose scene member variable is initialized to a newly constructed javafx.scene.Scene object. The Scene object's contents member variable is initialized to a javafx.scene.text.Text. And finally the Text object's font member variable is initialized to a javafx.scene.text.Font.

You need the JavaFX Preview SDK to run this program. The JavaFX Preview SDK includes the JavaFX GUI runtime which provides the necessary StageSceneText and Font classes.

Operators, Expressions, Statements

Like Java and other scripting languages, JavaFX supports operators, expressions and statements. All executable code in JavaFX are expressions. Many of them are similar to their Java counterparts. We'll show examples of some of them in this section. A few are quite different from their Java counterparts. And finally, some are unique to JavaFX. We will cover them in more detail in later sections.

Here is some JavaFX code that uses a few of the JavaFX language constructs:

// fib.fx
import java.lang.*;
 
function fib(n: Number): Number {
  if (n < 0)
    throw new IllegalArgumentException(
      "fib: parameter must be >= 0.");
  if (n == 0) {
    return 0;
  } else if (n == 1) {
    return 1;
  } else {
    var a = fib(n - 1);
    var b = fib(n - 2);
    return a + b;
  }
}

The import facility works as in Java.

The exceptions facility works as in Java but checked exceptions are not enforced by the JavaFX compiler. Of course you have to use JavaFX style type specification syntax in catch clauses.

The arithmetic operators (+, -, *, /, mod), increment operators (++ and --, both pre and post), assignment operators (=, +=, -=, *=, /=), relational operators (<, , >, >=), equality operators (==and !=), boolean operators (and, or, not) and the instanceof operator all work as expected.

JavaFX does not use %, &&, || and ! operators as in Java. The spelled out form mod, and, or and notare used instead.

You can even use the new operator to construct objects of Java classes.

println(new java.util.Date()) // => Fri Nov 14 08:12:47 CST 2008

Values Of Expressions

A few expressions, such as the while loop, do not have a value and are considered to be of theVoid type. Most JavaFX expressions have a value and a type. They are called value expressions.

You can use value expressions as the right-hand side of variable declarations, member variable initializations in object literals and assignments. When a variable is declared without an explicit type specifier, its type is the type of the right hand side of the declaration.

Non-value expressions correspond to what are called statements in Java. I will use the term statement in this article although that is not standard JavaFX terminology. Standalone value expressions are also statements. The semicolon is used to separate a value expression statement from the next statement.

The block, a number of statements surrounded by a pair of braces, is an expression in JavaFX. Its value and type are those of its last statement.

var x = {
  var y = 10;
  y * y
}
// x is of type Integer with a value of 100

The if construct, which is a statement in Java, does have a value. Therefore it has been turned into an expression in JavaFX, with several variants of syntax.

var b: Boolean; // initialize to true or false
var x: Number;
if (b) { x = 3; } else { x = 4;} // traditional form
x = if (b) then 3 else 4;        // expression form, like b?3:4 in Java
x = if (b) 3 else 4;             // 'then' is optional

The for loop, which has two forms in Java—the traditional for loop and the so-called for each loop—has a new face in JavaFX that resembles the Java for each loop but with a few twists. It is an expression in JavaFX whose value is most often a sequence whose elements are the values of the block introduced by the for expression, once for each iterand. A for expression is of Void type if its block is of Void type.

var input = [1, 3, 5, 7, 9];
var output = for (x in input) { x + 1 } // => [ 2, 4, 6, 8, 10 ]
 

Since sequences play such an central role in JavaFX, and since the for expression works closely with JavaFX sequences, there is a lot more that the for expression can do than is demonstrated above. We will reveal these details later when we experiment more with sequences.

Data Binding

Now that we have gone through the familiar statements and expressions, it's time to tackle the unfamiliar ones, expressions that are unique to JavaFX.

JavaFX supports data binding at the language level with the bind construct. In its simplest form, it makes a variable change its value automatically when its binding changes its value:

var x = 10;
var y = bind x;
println("x = {x}, y = {y}"); // => x = 10, y = 10
x = 20;
println("x = {x}, y = {y}"); // => x = 20, y = 20

You can make the binding a two way binding by adding with inverse at the end of the binding:

var x = 10;
var y = bind x with inverse;
y = 20;
println("x = {x}"); // => x = 20

The right-hand side of a binding can be an arbitrary expression, in which case an automatic update of the left-hand variable is triggered if any of the variables mentioned in the right-hand side expression is changed:

var x = 1;
var y = 2;
var z = bind x*x+y*y;            // z is updated when x or y change
var w = bind sumOfSquares(x, y); // w is updated when x or y change

Of course when you bind to an expression that is not a single variable it does not make sense to make it a two way binding.

When binding a variable to a function, there is a way to let the automatic update happen more often than when the function arguments change. You can make it so that the variable is updated when anything that the body of the function depends on changes. To achieve this, you define the function with the bound modifier:

var a = 10;
bound function f(n) {
  n + a
}
var x = 20;
var y = bind f(x);    // y is updated when x or a changes

JavaFX data binding achieves with very concise syntax what requires custom listeners and wiring code in Java.

Working With Sequences

JavaFX has an extensive set of facilities for working with sequences. We have seen some of them already. In the next few experiments we learn more about sequence manipulation.

Perhaps the most non-intuitive aspect of JavaFX sequences is that they do not nest. When you try to create a sequence whose elements are themselves sequences, JavaFX flattens the result:

println([1, [2, 3]]) // => [ 1, 2, 3 ]

You can assign a value of a non-sequence type to a variable of the corresponding sequence type. In this context, JavaFX will promote the value into a sequence of one element. Assigning null to a sequence variable will make the variable's value an empty sequence.

var numbers: Number[];
println(numbers);      // => [ ]
numbers = 3.1415926;
println(numbers);      // => [ 3.1415926 ]
numbers = null;
println(numbers);      // => [ ]

The sizeof operator can be used to find the size of a sequence:

println(sizeof [1, 2, 3]); // => 3

Sequences cannot hold null elements. Attempting to put a null into a sequence will be ignored:

var numbers: Number[];
numbers = [ 1, null, 3 ];
println(numbers);         // => [ 1.0, 3.0 ]

Insert and delete statements can be used to manipulate the content of sequences. There are three variants of insert statements: insert into, insert before, and insert after.

var nums = [1, 4, 2, 8, 5, 7];
insert 10 into nums;           // [ 1, 4, 2, 8, 5, 7, 10 ]
insert 20 before nums[2];      // [ 1, 4, 20, 2, 8, 5, 7, 10 ]
insert 30 after nums[5];       // [ 1, 4, 20, 2, 8, 5, 30, 7, 10 ]
delete 8 from nums;            // [ 1, 4, 20, 2, 5, 30, 7, 10 ]

You can also delete a specific element or slice from a sequence:

delete nums[3]; // [ 1, 4, 20, 5, 30, 7, 10 ]
delete nums[1..4]; // [ 1, 7, 10 ]

JavaFX provides a reverse operator to reverse sequences:

<pre style="margin-left:3em" xml:space="preserve">var seq = [1 2 3];
var qes = reverse seq;
println(seq);          // => [ 1, 2, 3 ]
println(qes);          // => [ 3, 2, 1 ]</pre>

Sequence Comprehension

I use the term sequence comprehension to mean syntactic constructs that build up new sequences out of existing sequences. JavaFX provides multiple sequence comprehensions.

var nums = [ 1, 4, 2, 8, 5, 7 ];
var fifth = nums[4];              // 5
var slice = nums[0..2];           // [ 1, 4, 2 ]
var slice2 = nums[0..<2];          // [ 1, 4 ]
var evens = nums[x|x mod 2 == 0]; // [ 4, 2, 8 ]

The last expression is supposed to mimic the mathematical notation for defining a subset with a constraint on its members:

{x ∈ N | x ≡ 0 mod 2}.

In this form of sequence comprehension, called sequence selection, the x on the left of the vertical bar is an selection variable. And the expression on the right is an Boolean expression, called the constraint. An element is selected if and only if the constraint is true.

We can achieve the same result with a for expression:

var evens = for (x in nums) {
  if (x mod 2 == 0) then x else null
}

Unlike iteration variables in Java's for each loops, you can query the position of iteration variables in the original sequence with the indexof operator:

var evenIndexed = nums[x|indexof x mod 2 == 0];
var evenIndexed2 = for (x in nums) {
  if (indexof x mod 2 == 0) then x else null
}
println(evenIndexed);  // => [ 1, 2, 5 ]
println(evenIndexed2); // => [ 1, 2, 5 ]

The For Loop Is A Query Language

So far in our experiments, we have used the for expression with only one iteration variable iterating over one sequence. JavaFX allows more than one iteration variables in its forexpressions iterating over more than one sequences. Taking a cue from SQL, these iteration specifications are called in clauses. Here is a for expression with two in clauses:

var rs = ["A", "B"];  // rows
var cs = ["1", "2"];  // columns
var xs =                   // cross product
  for (r in rs, c in cs) {
   "{r}{c}"
  };
println(xs);               // [ A1, A2, B1, B2 ]

Also inspired by SQL, the in clauses in a for expression can have where clauses that constrain the selection. The expressions in the where clauses are Boolean expressions and can refer only to iteration variables already introduced. In other words, if the first in clause's iteration variable is x, and the second in clause's iteration variable is y, then the where clause attached to the first in clause may refer only to x, not y, while the where clause attached to the second in clause may refer to both x and y.

Here is a small program that finds all integral points in first quadrant that lie within the circle centered at the origin with radius 2:

class Point {
  var x: Integer;
  var y: Integer;
  override function toString() {
    "P({x},{y})"
  }
}
var xs = [0..2];
var ys = [0..2];
var ps = for (x in xs, y in ys where x*x+y*y <= 4) {
  Point { x: x, y: y}
};
println(ps);
// => [ P(0,0), P(0,1), P(0,2), P(1,0), P(1,1), P(2,0) ]

A few side notes are in order. First, the override modifier must be used in the definition of thetoString() method because we are overriding the toString() method of java.lang.Object.

Second, the object literal in the body of the for expression, with initializers that look like x: x andy: y seems a little unsettling. But don't worry, the object literal is perfectly fine, because the firstx refers to the member variable of Point and the second x refers to a variable x in the surrounding scope, in our case the iteration variable x.

Triggers

JavaFX variables may optionally be fitted with a trigger in the form of an on replace clause. The on replace clause introduces a block that is executed every time the value of the variable is modified.

There are several forms of the on replace clause. Here is a simple one:

var x = 50 on replace {
  if (x < 0) {
    x = 0;
  } else if (x > 100) {
    x = 100;
  }
};
x = -3;
println(x); // => 0
x = 120;
println(x); // => 100

Triggers of this form are similar to setters in Java.

For non-sequence type variables you can have access to the old value of the variable:

var x = 50 on replace oldValue {
  println("on replace: oldValue={oldValue}, newValue={x}");
};      // => on replace: oldValue=0, newValue=50
x = 80; // => on replace: oldValue=50, newValue=80

Notice that the initialization of the variable x to the value 50 counts as a change to the value of the variable, a change from the default value of 0. The trigger is executed for this initialization as well as subsequent modifications to the variable.

Sequence typed variables may be modified in more complicated ways. The corresponding triggers accommodate this complexity by providing more information to the on replace clause.

var xs = [1, 4, 2, 8, 5, 7] on replace oldValue[lo..hi] = newSlice {
  println("on replace: oldValue={oldValue}");
  println("            lo={lo}, hi={hi}");
  println("            newSlice={newSlice}");
};
// => on replace: oldValue=
//                lo=0, hi=-1
//                newSlice=142857</pre>

In the above example, the on replace trigger is fired when xs is initialized to the six element sequence. During this change the oldValue is the empty sequence; the lo index of the change is 0; the hi index is -1, one less than the lo index, indicating an insertion; and the newSlice is the six element sequence.

(The oldValue and newSlice variables do not print properly as sequences. The elements are squeezed together.)

In the next example, we replace a subsequence with new values, delete a subsequence, and insert an element into the sequence. And the output from trigger shows the effect of our actions:

xs[2..3] = [3, 9];
// => on replace: oldValue=142857
//                lo=2, hi=3
//                newSlice=39
delete xs[1..2];
// => on replace: oldValue=143957
//                lo=1, hi=2
//                newSlice=
insert 2 into xs;
// => on replace: oldValue=1957
//                lo=4, hi=3
//                newSlice=2

The syntax of the on replace clause is not the most intuitive part of JavaFX. It take some practice to get used to. The key thing to understand is that the variables you specify in the on replace clause are passed in from the JavaFX runtime.

The JavaFX runtime can supply three pieces of information: the old value of the variable, the low and high indices of the slice of the sequence that has changed, and what it has changed to, i.e., the new slice.

In the code above, I used oldValue[lo..hi], and newSlice in the on replace clause to receive these three pieces of information. But any other variable names may be used. These variables are read-only variables.

The form of the on replace clause:

var seq = [] on replace oldValue[lo..hi]=newSlice {
  // ...
}

serves two purposes: It reminds the programmer of the role of each variable by resembling the slice modification syntax; It also allows the programmer to omit some or all of the pieces of information by not specifying the corresponding name.

Assignment to the observed variable in the on replace clause is allowed, but it will cause the trigger to be fired again if the assignment changes the value of the variable. Care must be taken to not cause the on replace trigger to be fired an infinite number of times.

JavaFX Executes On The EDT

We have gone through a lot of experiments so far. The good news is that we are almost done looking at the core features of the JavaFX Script language. We haven't covered organizational features like packages, access modifiers and class hierarchies. The only big features left are object initialization, and animations.

But before we get to them, I want to talk about the single threaded nature of JavaFX Script. By design, all user code written in JavaFX Script is executed in one thread. For desktop applications that thread is the AWT event dispatching thread, or the EDT.

Under the JavaFX Preview SDK, the following experiment will convince you that your code is being executed on the EDT:

println(java.lang.Thread.currentThread().getName());
// => AWT-EventQueue-0

If you run the above line of code with the openjfx-compiler, you will see "Thread-0". That is because the openjfx-compiler is not hooked up to any GUI runtime.

One implication of this design choice is that you cannot write long running code in JavaFX or you risk an unresponsive GUI.

JavaFX does not offer any threading or synchronization primitives of its own. You can create threads using java.lang.Thread. However those threads should not touch the internal states of JavaFX.

JavaFX does provide a way to asynchronously communicate with remote resources through the use of a set of classes in the javafx.async package. I will talk more about this in a later section.

Init Blocks In Classes

Since JavaFX classes are initialized with object literal expressions, there are no constructors in JavaFX classes. Instead, two specially named blocks of code, the init block and the postinit block, may be declared in a JavaFX class that fulfil some of the functionalities of constructors in Java.

The init block is run during the construction of a JavaFX object at a moment after the member variable values that are specified in the object literal expression have taken effect:

import java.lang.Math;
class Point {
  var x: Number;
  var y: Number;
  var d: Number;
 
  init {
    d = Math.sqrt(x*x+y*y);
  }
}
var p = Point { x: 3, y: 4 };
println(p.d);                 // => 5.0

The postinit block is executed after the object has been completely initialized. For a stand alone class, the postinit block is run right after the init block. The difference between init and postinit becomes apparent during the construction of an object of a derived class in a class hierarchy. An example will make the difference clearer:

class Base {
  var x: Number;
  init { println("Base.init"); }
  postinit { println("Base.postinit"); }
}
class Derived extends Base {
  var y: Number
  init { println("Derived.init") }
  postinit { println("Derived.postinit") }
}
var o = Derived {x: 3, y: 4};
// => Base.init
//    Derived.init
//    Base.postinit
//    Derived.postinit

While the init and postinit blocks execute in the order from base class to derived class, by the time the postinit block of the base class is executed, the init block of the most derived class has already been executed.

Access Modifiers And Code Organization

So far in this article, we have been writing loose code—essentially statements in a file, with the occasional class to illustrate a point or two.

JavaFX supports most of the organizational features found in Java. JavaFX programs can be organized into packages. Packages may contain subpackages, classes, and non-class JavaFX files. It is advisable to organize JavaFX source files into a directory hierarchy that reflects their package hierarchy. Just like for javac, the -d  command line switch for javafxc will put the compiled class files into the  directory under the correct directory hierarchy.

JavaFX supports the concept of modifiers for its classes, functions, variables, member functions and member variables. However, the set of modifiers it supports is radically different from those in Java.

First, the good news: there are no static members in JavaFX. You can use script level variables or functions instead:

// com/ociweb/jnb/Point.fx
package com.ociweb.jnb;
public class Point {
  var x: Integer;
  var y: Integer;
  public override function toString() {
    "P({x}, {y})"
  }
}
public function gridPoints(n: Integer): Point[] {
  for (i in [0..n], j in [0..n]) {
    Point { x: i, y: j }
  }
}
// main.fx
import com.ociweb.jnb.Point;
println(Point.gridPoints(1));
// => [ P(0, 0), P(0, 1), P(1, 0), P(1, 1) ]

There is also no private modifier in JavaFX. You specify no modifiers instead. This gives you the default access in JavaFX, which is script private. We have been using this access level in this article all along.

The public modifier makes entities it modifies accessible from anywhere in the application. It can be applied to entities at the script level, or to members of classes.

The package modifier makes entities it modifies accessible from anywhere in the same package.

The protected modifier is more complicated in JavaFX than in Java. When applied to member variables and member functions, it has the same meaning as in Java, i.e., accessible from subclasses and from the same package. When applied to script level variables and functions, it makes them accessible from the same package. It does not apply to classes.

JavaFX provides two additional access modifiers, public-read and public-init for member variables and script level variables. When public-read is applied to a variable, it can be read from anywhere in the application. When public-init is applied to a variable, it can be initialized in a object literal anywhere in the application.

The public-read and public-init modifiers are invented to make variables readable or initializable by the application but writable only by the implementation unit—either the script (if used alone) or the package (if used in conjunction with package) where the variables are declared.

// com/ociweb/jnb/AdServer.fx
package com.ociweb.jnb;
public class AdServer {
  public-init var count: Integer;
  public-read var serverName = "My Ad Server";
  def ads = ["Buy Food", "Buy Clothes",
             "Buy Bike", "Buy Ticket"];
  public function getAds() {
    return ads[0..count-1];
  }
}
// main.fx
import com.ociweb.jnb.AdServer;
var adServer = AdServer { count: 2 };
 
println(adServer.getAds());   // => [ Buy Food, Buy Clothes ]
println(adServer.serverName); // => My Ad Server
println(adServer.count);      // => 2

Notice that public-init implies public-read but public-read does not imply public-init. And under normal circumstances, public-init or public-read variables are not writable by the outside world. We will get compiler errors if we do the following in main.fx:

adServer.count = 3;                      // No write access
adServer.serverName = "X";               // No write access
adServer = AdServer { serverName: "Y" }; // No init access

Object-Oriented Programming

The JavaFX class is the central concept of the object-oriented programming facilities in the language.

JavaFX does not support interfaces as Java does. It supports abstract classes. Abstract classes are declared with the abstract modifier. An abstract class may contain zero or more abstract member functions, also declared with the abstract modifier. Abstract classes cannot be instantiated.

public abstract class Shape {
  public abstract function show(): Void;
}

JavaFX supports multiple inheritance of JavaFX classes. Subclasses can override members of superclasses. It must do so with the override modifier. Both member functions and member variables can be overridden. The only reason you may want to override a member variable is to give it a different initializer or to augment its on replace trigger in a subclass.

public class Base {
  public var a: Number = 0 on replace {
    println("Base.a={a}");
  }
  public function f() {
    println("Base.f()");
  }
}
 
public class Derived extends Base {
  public override var a = 10 on replace {
    println("Derived.a={a}");
  }
  public var b: String = "hi" on replace {
    println("Derived.b={b}");
  }
  public override function f() {
    println("Derived.f()");
  }
  public function g() {
    println("Derived.g()");
  }
}
 
function run() {
  var base = Base { a: 20 }; // => Base.a=20.0
  base.f();                  // => Base.f()
  var derived = Derived {
    a: 30
    b: "bye"
  };                         // => Base.a=30.0
                             //    Derived.a=30.0
                             //    Derived.b=bye
  derived.f();               // => Derived.f()
  derived.g();               // => Derived.g()
}

When you override a variable in a derived class, you cannot specify the variable's type. The variable will get its type from the base class. Notice also that when derived is constructed, triggers for the overridden variable a from both the base class and the derived class are fired.

All member functions are virtual.

The keyword this can be used within the class definition to refer to the current object, just like in Java.

JavaFX classes can inherit from Java classes or interfaces. At most one Java class can be inherited, directly or indirectly, through at most one lineage, by a JavaFX class.

What Is A Module?

The way JavaFX code may be organized into files is more flexible (or complex, depending on your point of view) than Java because it allows variables, functions, and even loose statements at the script level.

There are two implications of this flexibility. First, in JavaFX the distinction between a library author and a library consumer is more profound. The library author, like a traditional Java developer, is mainly concerned with writing classes. The library consumer would focus on using the library classes and write mostly loose code.

Secondly, there are more ways to organize JavaFX code into script files. The traditional "one public class per file whose name matches the class name" organization is still supported, and is recommended for library authors of substantial classes. An alternative organization, in which several related smaller classes and functions are grouped into a single file, whose name does not necessarily match any of the public classes it contains, is also possible.

Such a JavaFX script file is called a module. Here is a simple example:

// com/ociweb/jnb/shapes.fx
package com.ociweb.jnb;
 
public class Point {
  public var x: Number;
  public var y: Number;
}
 
public class Circle {
  public var center: Point;
  public var radius: Number;
}
 
public class Triangle {
  public var a: Point;
  public var b: Point;
  public var c: Point;
}
 
public function area(circle: Circle) {
  var r = circle.radius;
  java.lang.Math.PI * r * r
}
 
public function area(triangle: Triangle) {
  var x1 = triangle.a.x;
  var y1 = triangle.a.y;
  var x2 = triangle.b.x;
  var y2 = triangle.b.y;
  var x3 = triangle.c.x;
  var y3 = triangle.c.y;
  (x1*y2 + x2*y3 + x3*y1 - x2*y1 - x3*y2 - x1*y3)/2
}
 
// main.fx
import com.ociweb.jnb.shapes.*;
 
var triangle = Triangle {
  a: Point {x: 1, y: 0}
  b: Point {x: 2, y: 2}
  c: Point {x: 0, y: 1}
};
 
println(area(triangle)); // => 1.5

As you can see from the above example, a module looks just like an ordinary script file. The difference lies in the fact that a module exports some or all of its members to the outside world by using a more open access modifier, usually public, than the default script private access. Incidentally, once a script file has an exported member, be it a public class or a public-read script level variable, JavaFX no longer allows any loose statements in the script file. And the module class itself cannot be run with javafx any more unless you create a function called run() in the module file.

Notice also how the import statement cooperates with modules. In the above example, we used the wild card character * to import all members of the module com.ociweb.jnb.shapes into the current scope. Alternatively, you can import each member individually:

// main2.fx
import com.ociweb.jnb.shapes.Point;
import com.ociweb.jnb.shapes.Triangle;
import com.ociweb.jnb.shapes.area;
 
var triangle = Triangle {
  a: Point {x: 1, y: 0}
  b: Point {x: 2, y: 2}
  c: Point {x: 0, y: 1}
};
 
println(area(triangle)); // => 3.5 (but see notes below)

Or you can import the module itself, and access its members with the dot notation:

 
// main3.fx
import com.ociweb.jnb.shapes;
 
var triangle = shapes.Triangle {
  a: shapes.Point {x: 0, y: 0}
  b: shapes.Point {x: 1, y: 0}
  c: shapes.Point {x: 0, y: 1}
};
 
println(shapes.area(shapes.triangle)); // => 0.5

(Please note that a compiler bug prevents the second example main2.fx from compiling.)

Animations

One of the attractions of JavaFX is its animation features. It makes the GUI look pretty. However, if you think it through, an animation is nothing more than changing some GUI properties from one value to a sequence of updated values over time. For example, to animate a little box moving from the left side of a window to the right side of a window, you change its x position from 0 to the width of the window through a sequence of intermediate values over time. To achieve a fade in effect, you change the opacity of an object from 0 to 1 through a sequence of intermediate values over time.

That is why JavaFX can put its animation support in the language rather than in the GUI runtime. The animation support is achieved through a library-based approach with some syntactic help.

Let's look at a very simple example, in which we animate a numeric variable and monitor the effects of the animation through a trigger:

import javafx.animation.*;
import java.lang.System;
 
var x = 0 on replace {
  println("At {System.currentTimeMillis() mod 1000000}, x={x}");
};
var t = Timeline {
  keyFrames: [ at(5s) { x => 10 }]
};
t.play();

It generated the following output on one run:

At 617610, x=0
At 618553, x=1
At 619065, x=2
At 619561, x=3
At 620061, x=4
At 620558, x=5
At 621053, x=6
At 621565, x=7
At 622061, x=8
At 622557, x=9
At 623053, x=10

The javafx.animation.Timeline class is the controller of animations. In the example, we instantiate a Timeline object by setting its keyFrames member variable to a sequence of onejavafx.animation.KeyFrame object. When we call the play() method on the Timeline object, the animation is performed.

Can you guess what the KeyFrame object is saying? If your guess is "at the 5 second mark, x should be 10" then you are spot on. Given this KeyFrame, the Timeline object will figure out the intermediate values using an javafx.animation.Interpolator. Since we did not specify an interpolator, the default one, which is Interpolator.LINEAR is used.

To specify a different interpolator, we use the following syntax:

var t = Timeline {
  keyFrames: [ at(5s) { x => 10 tween Interpolator.DISCRETE } ]
};

The DISCRETE interpolator is one that jumps from the start value to the end value in one step. The modified animation generated the following output on one run:

At 291118, x=0
At 296986, x=10

There is much more that the JavaFX animation framework can do to make your GUI application more alive. We have just scratched the surface of it.

Let me close this section by animating our earlier GUI application. We first declared a script level variable x. We then bind the x member variable of the Text element to the script level variable x. Finally, we animate the value of the script level variable x.

import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.animation.*;
 
var x = 50;
 
Stage {
  title: "Example"
  scene: Scene {
    width: 500
    height: 300
    content: Text {
      x: bind x, y: 125
      content: "Hello, World."
      font: Font {
        size: 48
      }
    }
  }
};
 
var timeline = Timeline {
  repeatCount: Timeline.INDEFINITE
  autoReverse: true
  keyFrames: [ at(1s) { x => 150 tween Interpolator.EASEBOTH
  } ]
};
 
timeline.play();

Internationalization

JavaFX's internationalization support is based on Java's internationalization facility. However, JavaFX introduces a syntactic tool and some new rules to make internationalization easier than in Java.

To signal to the JavaFX compiler that a String is a candidate for internationalization, we prefix the String with a double hash sign ## followed optionally by a key enclosed in brackets. If no key is specified, the String itself is considered the key if it does not contain any embedded JavaFX expressions. If the String contains embedded JavaFX expressions, the key is the String with the embedded expressions converted to the java.util.Formatter style.

// i18n.fx
var i = 1024;
println(##[greeting]"Hello, World.");
println(##[value]"The value of i is {%d i}.");

When this program is compiled and run, the JavaFX runtime will search the classpath for a *.fxproperties file whose name is derived from the *.fx file name and the locale. For our example, if we run it in the US English locale, the files being searched are:

i18n_en_US.fxproperties
i18n_en.fxproperties

If a fxproperties file is found, the property value for each key is retrieved from the fxproperties file and used in place of the original String. If a matching fxproperties file is not found, the original String in the source file is used. When the original String contain format specifiers, the property value for the corresponding key should also contain the appropriate format specifiers in thejava.util.Formatter style.

A localization file for the German locales might look like the following:

// i18n_de.fxproperties
"greeting"="Hallo, Welt."
"value"="Der Wert von i ist %1$d."

Notice how the embedded JavaFX expression "{%d i}" is converted to the java.util.Formatter style "%1$d". The position rather than the variable name is used in the localization files to minimize the burden on the translator who may be confused on whether to translate the variable name to the local language.

Odds And Ends

With that, I believe we have covered most of the language features of JavaFX. There are a few small things that I left out. Let me just mention them briefly.

In JavaFX you can use continue and break in while loops.

In JavaFX, Void can be used as the return type for functions to indicate that the function does not return a value.

In JavaFX, casting is done with the as operator:

var point: Point = Point3D { x: 3, y: 4, z: 5 };
var point3d = point as Point3D;

Although a constant introduced with def cannot be assigned to later, it is legal to initialize it with a bind expression. In that case, the constant is not a true constant. Its value will be updated when its dependencies change.

In an object literal expression, in addition to specifying member variable initializers, you can also declare variables and define functions.

To receive command line arguments, the run() function in a JavaFX script file can be defined to take a String[] argument:

// run.fx
function run(args: String[]) {
  for (arg in args) {
    print(arg);
    if (indexof arg < sizeof args - 1) {
      print(", ");
    } else {
      println("");
    }
  }
}
$ javafxc run.fx
$ javafx run 1 2 3 4 5
1, 2, 3, 4, 5

With JavaFX language features out of the way, in the remaining sections we cover a few topics from the JavaFX Preview SDK that may be of interest to you. There are many more features that I do not have space to cover in this article. You are encouraged to browse the JavaFX API documentation.

Enhanced Multimedia Support

One area where Java has been lacking is robust multimedia support. JavaFX addresses this issue with the introduction of the Java Media Components (JMC). The JMC is actually a Java-based framework that handles media playback on the Java platform. JavaFX contains a set of JMC wrappers in the javafx.scene.media package that make media playback in JavaFX very simple.

The framework's central classes are Media, MediaPlayer and MediaView.

The Media class encapsulates multimedia resources like video files. Its member variables includesource, duration and other information about the resource such as the width and the height of the video clip, and any other metadata that might be embedded in the resource file. You supply thesource as a URL when you construct a Media object.

The MediaPlayer is responsible for the actual playing of the Media clip. It has two member functions,play() and pause(). It has member variables that controls the MediaPlayer itself, like volume, fader,balance and mute. And it has member variables that control how the media is played, like autoPlay,repeatCount, etc. And of course it has a media member variable that controls what it plays.

The MediaView is a visual Node that is responsible for the on screen presentation of the media player. It has a mediaPlayer member variable. Its other member variables control how it may be manipulated.

A simple video player can be written in just a few lines of JavaFX code:

// player.fx
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.media.*;
 
function run(args: String[]) {
  if (sizeof args < 1) {
    println("Usage: javafx player <media-url>");
    return 0;
  }
 
  var media = Media { source: args[0] };
 
  var mediaPlayer = MediaPlayer {
    autoPlay: true
    media: media
  }
 
  Stage {
    title: "Video Player"
    scene: Scene {
      width: media.width
      height: media.height
      content: MediaView {
        mediaPlayer: mediaPlayer
      }
    }
  }
}

Video support in the JavaFX Preview SDK is not complete. You can play native video formats on the supported platforms: On Windows, you can play anything that the Windows Media Player can play. On Mac OS X, you can play anything that CoreAudio and CoreVideo can play.

Cross-platform multimedia support using ON2 technologies is coming with the JavaFX Desktop 1.0 release at the end of 2008.

Communication With The Outside World

Since JavaFX code runs on the AWT event dispatch thread (EDT), a natural question developers ask is: "What about long running operations?"

JavaFX's answer to this question is a mechanism that is similar to XMLHttpRequest in AJAX applications. Although the JavaFX wrapper classes did not make it into the JavaFX Preview SDK, the Java classes that constitute the async operations framework can be found in the openjfx-compiler project.

Here's a possible reconstructed scenario from an earlier conversation on the openjfx-compiler mailing list:

// remote.fx
import javafx.async.*;
 
var rtd = RemoteTextDocument {
  url: "http://example.com/index.html"
  method: "GET"
};
 
var text = bind if (rtd.done) rtd.document else "Loading...";

After construction, the RemoteTextDocument object would start downloading the document pointed to by the url member variable in a background thread. It would update its done member variable to true and its document member variable to the content of the document when it has obtained the document.

By binding the text variable to the if-expression, its value will be updated by the JavaFX runtime, on the EDT thread, when the document is completely downloaded.

You can monitor the RemoteTextDocument object for progress of the download. You can also attach various callback functions to it to have more control over the handling of the downloaded document.

Look for something similar to the above in the JavaFX Desktop 1.0 release.

Rethinking Deployment

Since JavaFX compiles its source files into Java class files, JavaFX application deployment scenarios are essentially the same as Java deployment scenarios.

You can deploy your JavaFX applications as regular Java applications that are invoked on the command line, as we have done in this article.

You can package your JavaFX applications into jar files and deliver them with Java Web Start. With the JavaFX Preview SDK, you have to include the JavaFX runtime jars in your application. However, with the JavaFX Desktop 1.0 release, Sun will offer the JavaFX runtime jars as a JNLP extension at a standard URL. At that time, you can simply reference the standard JavaFX JNLP extension in the JNLP file of your JavaFX application. The hope is that the standard JavaFX runtime will be downloaded to the user's JRE cache the first time any JavaFX application is run, and subsequent JavaFX applications won't have to make another download.

The JavaFX Preview SDK supports the writing of JavaFX applets. However, Java applets haven't been a popular deployment mechanism in recent years. I don't see traditional style applets to be a popular mechanism for JavaFX application deployment.

With the new Java 6 Update 10 JRE release, however, the boundary between Java Web Start and Java applets is blurred. You can deploy your JavaFX application as a JNLP based applet that can be dragged on to the desktop to become a JNLP based application.

Look for JavaFX classes and NetBeans IDE wizards that support this deployment mechanism in the JavaFX Desktop 1.0 release.

Summary

JavaFX Script is a declarative, statically-typed, object-oriented and functional domain-specific language designed for GUI development. The JavaFX SDK provide powerful libraries that make creating media-rich and compelling GUI applications much easier.

The ubiquity of the JRE in all popular operating systems and hand-held devices gives the JavaFX developer a wide range of targets for their applications.

I would like to thank Mark Volkmann, Lance Finney and Mario Aquino for their help in reviewing this article. I would also like to thank Naoto Sato, Richard Bair and Dean Iverson for their corrections and suggestions for improvements.

References

 

The Software Engineering Tech Trends is a monthly publication featuring emerging trends in software engineering.

Subscribe

© Copyright Object Computing, Inc. 1993, 2018. All rights reserved

secret