Jump into JavaFX, Part 2: JavaFX Script

how-to
Dec 4, 200839 mins

Scripting in JavaFX: From language fundamentals to data binding

Before you can create rich internet applications in JavaFX, you need to get to know the JavaFX Script language. So, fire up your NetBeans IDE and take Jeff Friesen’s scripted tour of JavaFX Script’s language features. Learn how JavaFX Script handles basic constructs like variable declarations, fundamental types, and expressions; then jump into advanced features such as sequences, replace triggers, and data binding. Level: Beginner to Intermediate

The first part of this six-part series introducing JavaFX presented a Main.fx script that describes a “hello world” type of application. I didn’t bother to describe the script’s code, at the time, because it wouldn’t make much sense without understanding the JavaFX Script language and associated APIs. In this article you’ll be introduced to JavaFX Script, which should help you better understand Main.fx.

After a quick overview of JavaFX Script’s background, we’ll embark on a script-driven tour of its basic language features, including variable declarations, fundamental types, and expressions. Once you’ve got the basics under your sombrero, we’ll advance to the next level of JavaFX Scripting, with a series of small programs that reveal how the language handles sequences, classes, and object literals. Finally, we’ll discuss two advanced topics that could be challenging to Java beginners, and should be of interest to developers already using JavaFX Script; namely replace triggers and data binding.

JavaFX Preview SDK

The discussion in this article is based on the version of JavaFX Script found in JavaFX Preview SDK, which will differ somewhat from the version found in JavaFX SDK 1.0. What you learn here will serve as background for Jeff’s first look at JavaFX SDK 1.0, in the final two articles of this series.

While not definitive, this guide to JavaFX Script does fill some holes in the current documentation, and should be code-driven enough to keep you engaged and learning from start to finish. Note that you need not know a thing about JavaFX Script to learn from this guide; you should, however, be familiar with the Java language and object-oriented programming concepts. Note, also, that what you learn in this article about JavaFX Script’s syntax (primarily) will be enriched in Part 3, when we look at the JavaFX APIs.

From F3 to JavaFX Script

Just a little more than two years ago, Sun developer Chris Oliver introduced a project called F3 (Form Follows Function) via his blog. The project consisted of the F3 language and several APIs for simplifying GUI programming.

According to Oliver, F3 was “a declarative Java scripting language with static typing,” which he said would provide IDE support and compile-time error reporting — two features not found in JavaScript. The language also offered type-inference, declarative syntax, and automatic data-binding, with, as Oliver said “full support for 2D graphics and standard Swing components as well as declarative animation.” Perhaps the biggest upside of using F3, according to its creator, was the ease with which it would allow developers to “import Java classes, create new Java objects, call their methods, and implement Java interfaces.”

Hear this: Unwrapping JavaFX 1.0

In this JavaWorld podcast, Andrew Glover interviews Param Singh and JavaFX Architect John Burkey for a developer’s perspective on what you’ll be able to do with the newly released JavaFX 1.0.

Chris Oliver’s brainchild, F3, is the basis of JavaFX Script, which Sun announced at the JavaOne conference in May 2007. We’ll spend a lot of time in this article looking at how declarative syntax manifests in JavaFX Script. At the end of the article I’ll show you some examples of the language’s sophisticated data binding functionality. For now, though, just consider how these two features set JavaFX Script apart from many other languages:

  • Declarative syntax lets you specify what you want to achieve instead of how you want to achieve it. The result is a simpler syntax than the equivalent Java method call-based syntax used to create traditional Swing GUIs.
  • Data binding enables you to automatically update one or more GUI components whenever the state of another GUI component changes, without having to go to the trouble of registering various event listeners and providing code to perform these updates.

Another nice feature of F3 was declarative animation, which is also known as keyframe animation in JavaFX Script. Although declarative animation uses some syntactic sugar for ease of use, this feature is mostly API-based, so I won’t discuss it until Part 3.

In the next section I’ll introduce some of the fundamentals of JavaFX Script. As in the previous article in this series, you’ll learn by doing, so you should start up your NetBeans IDE now. (See “Jump into JavaFX, Part 1” to get started with NetBeans 6.1 with JavaFX.)

JavaFX Script fundamentals

Start NetBeans and use the New Project wizard to introduce a new LanguageDemo project with languagedemo.Main as the main file. As we explore JavaFX Script, we’ll insert a series of scripts into Main.fx, replacing the previous script with a new one.

The first thing you’ll do is replace the skeletal Main.fx‘s “// place your code here” line with the following script — as you enter this script, you’ll probably encounter some of the IDE’s editor features, including syntax highlighting, code completion, and small red octagon-shaped icons with exclamation marks that signify potential errors until the code is completely entered:

Listing 1. A first script

import java.lang.System;

var name1: String = "JavaFX";
var name2 = "JavaFX Script";
output ();

function output ()
{
    System.out.println (name1); // Output: JavaFX
    System.out.println ("name2 = {name2}") // Output: name2 = JavaFX Script
}

This script reveals a few interesting things about JavaFX Script. You’ll notice right away that JavaFX Script supports the same single-line comment style as Java. It also supports the same multi-line and documentation comment styles. More importantly, you can include Java method calls in JavaFX Script code, but you’ll want to import the appropriate Java packages to save keystrokes. For example, because JavaFX Script doesn’t automatically import java.lang, you’ll need to import this package, or prepend java.lang to each System.out.println() method call.

Now consider what else Listing 1 tells us about JavaFX Script.

Variable declaration

Variable declaration in JavaFX Script begins with the var keyword, continues with a valid name, is optionally followed by a type specification, and ends with an optional initializer for assigning an initial value to the variable. If a type isn’t specified, the type is inferred from the initializer — the type is unknown if there’s no type and no initializer.

The name must follow Java’s rules for what constitutes a valid identifier (it must not start with a digit, for example). Furthermore, the name must not be a reserved identifier (a literal such as null, true, or false; or a keyword used to name an operator, a statement-oriented expression, or something else).

Reserved identifiers

JavaFX Script reserves 67 (and probably additional) identifiers:

abstract, after, and, as, assert, at, attribute, before, bind, bound, break, catch, class, continue, delete, else, exclusive, extends, false, finally, first, for, from, function, if, import, in, indexof, init, insert, instanceof, into, inverse, last, lazy, let, new, not, null, on, or, override, package, postinit, private, protected, public, readonly, replace, return, reverse, sizeof, static, step, super, then, this, throw, trigger, true, try, tween, typeof, var, where, while, and with.

If you need to use a reserved identifier to name a variable or function (although you really shouldn’t), or if you need to invoke an external Java method whose name matches a reserved identifier, you’ll have to surround the reserved identifier with doubled angle brackets. Example: var <<var>> = 10; System.out.println (<<var>>).

Types, default values, and literals

Each variable has a type, whether or not the type is explicitly specified. This type can be a Java type, a JavaFX user-defined type, or one of the String, Number (double-precision float), Integer, and Boolean fundamental types — you might argue that Void is also a fundamental type. These types are fundamental because they don’t need to be imported.

The fact that the fundamental types are Java classes means that you can invoke their Java methods via variables or (except for Integer) values. For example, you can specify "abc".length (), 37.3.parseDouble ("23"), var i: Integer = i.parseInt ("23"); (but not 26.parseInt ("23")), and true.toString ().

If a variable isn’t explicitly initialized, its type determines its default value: empty string for String, 0.0 for Number, 0 for Integer, false for Boolean, and null for reference types other than javafx.lang.Duration (which is 0.0 milliseconds). If there’s no type, the variable’s value defaults to null.

If the initializer is a literal, it must be compatible with the type (if present). Valid literals include sequences of characters between single quotes or double quotes (string literals), numeric values with decimal points (number literals), numeric values without decimal points (integer literals), true and false (Boolean literals), null, object literals, and time literals.

Functions

JavaFX Script’s use of functions for specifying reusable blocks of code is also interesting. Although functions must have names (unless they’re anonymous), return types don’t need to be specified. If this type isn’t present, and if no value is returned, the return type defaults to Void. To explictly identify this return type, specify it after the parameter list, as in function output (): Void {}.

A function’s body is specified via a block. The last item of executable code within a block doesn’t require a semicolon. In the previous script, the last item is a System.out.println() method call, which demonstrates yet another interesting feature: one or more expressions can be embedded within a string literal by surrounding each expression with brace characters ({}).

Finally, the script reveals that you can declare variables and functions outside of a class context, which is in sharp contrast to Java, where field variables and methods must be declared within classes. A variable declared in this manner is accessible to a function’s code, although it can be hidden by declaring a same-named local variable within the function.

Running the script

Just one script has told us quite a bit about how JavaFX Script differs from the Java language, and also leverages it. After entering the script in Listing 1, compile and run the code by clicking the green triangle (Run Main Project) button on the toolbar (or press F6). As revealed in Figure 1, the output window displays the compilation results, and also all output sent to the standard output via System.out.println() method calls.

If compilation succeeds, the screen displays 'JavaFX' on one line, followed by 'name2 = JavaFX Script'.
Figure 1. Results of a successful compilation (click to enlarge)

Now that we’ve reviewed some language fundamentals, we can begin to look at expressions, which are the building blocks of JavaFX programs. We’ll study some examples of simple expressions and review the operators for combining simple expressions into more complex ones. After that, I’ll show you how JavaFX Script uses expressions to handle familiar Java constructs such as conditionals, loops, ranges, and exception handling, as well as expressions based on function pointers.

An expression language

According to Sun’s JavaFX Script Programming Language Reference (currently in draft), JavaFX Script is an expression language. Apart from package and import statements, and variable declarations, everything else is an expression with zero or more inputs, and zero or one output. The script in Listing 2 demonstrates some simple expressions:

Listing 2. Simple expressions in JavaFX Script

var name = "JavaFX";
java.lang.System.out.println (name+" Script"); // Output: JavaFX Script
java.lang.System.out.println (name.equalsIgnoreCase ("JAVAFX")); // Output: true
java.lang.System.out.println (8 mod 3); // Output: 2
var circleArea = java.lang.Math.PI*10*10;
java.lang.System.out.println (circleArea); // Output: 314.1592653589793
var area = circleArea as Integer; // cast from Number type to Integer type
java.lang.System.out.println (area); // Output: 314
// The following expression demonstrates an alternative Number-to-Integer cast.
java.lang.System.out.println (circleArea.intValue ()); // Output: 314
java.lang.System.out.println (37.5.intValue ()); // Output: 37
// The following method call outputs: Hex equivalent of 1234: 04D2 
java.lang.System.out.println ("Hex equivalent of 1234: {%04X 1234}");
var millis = java.lang.System.currentTimeMillis ();
// The following method call outputs a date/time combination such as:
// Current date/time: Fri Sep 10 20:53:09 CDT 2008
java.lang.System.out.println ("Current date/time: {%tc millis}")

You might be curious about the {%04X 1234} expression in "Hex equivalent of 1234: {%04X 1234}", and the {%tc millis} expression in "Current date/time: {%tc millis}". These expressions reveal a language convenience for converting numbers and dates into formatted character sequences, via a java.util.Formatter formatting prefix followed by the item being formatted.

Operators in JavaFX Script

Many expressions consist of operands that are manipulated by operators. JavaFX Script offers operators found also in Java plus a few operators that are unique to the newer language. Table 1 presents the list of operators found in the JavaFX Script language reference document (draft) — note that operators are grouped by precedence with the highest precedence group at the top.

Table 1. Operator precedence

PriorityOperatorDescription
1

function()

()

new

class name

{ attribute initializers }

JavaFX function

Expression in brackets

Instantiate a new object

Instantiate and initialize a new object via an object literal

2

++ (suffixed)

--

(suffixed)

Post-increment assign

Post-decrement assign

3

++ (prefixed)

-- (prefixed)

not

sizeof

reverse

indexof

-->

Pre-increment assign

Pre-decrement assign

Logical negation

Size of a sequence

Reverse a sequence

Index of a sequence element

Tween

4

*

/

mod

Multiplication

Division

Remainder — you can also use

%

in the Preview SDK, but shouldn’t

5

+

-

Addition

Subtraction

6

==

!=

<

<=

>

>=

Equality

Inequality

Less than

Less than or equal to

Greater than

Greater than or equal to

7

instanceof

as

Type checking

Cast

8orLogical OR
9andLogical AND
10

+=

-=

*=

/=

%=

Add and assign

Subtract and assign

Multiply and assign

Divide and assign

Remainder and assign — this operator won’t be present in SDK 1.0

11=Assignment

Although not appearing in the table, I also consider insert and delete to be operators.

Conditional expressions

JavaFX Script lets you create some interesting expressions. For example, you can take advantage of the if-based conditional expression to assign one of two values to a variable during its declaration. This makes the conditional expression similar to Java’s (a < b) ? a : b construct, as demonstrated in the following script:

Listing 3. If-then-else expression

var age = 65;
var receivingPension = if (age >= 65) then true else false; // then is optional
java.lang.System.out.println (receivingPension) // Output: true

In this example, a conditional expression determines if an individual is eligible to receive pension based on a retirement age of 65. Of course, a person cannot receive a pension if they’re dead. Therefore, to make the previous script somewhat more accurate, the following script uses a Boolean expression to make sure that the person receives pension if they’re 65 or older, and not also dead:

Listing 4. Boolean expression

var age = 65;
var dead = true;
var receivingPension = if (age >= 65 and not dead) true else false;
java.lang.System.out.println (receivingPension) // Output: false

Note that JavaFX Script replaces Java’s &&, ||, and ! operators with its more readable and, or, and not equivalents. This makes scripts less confusing to those who are not familiar with Java. JavaFX Script also makes other operator-oriented changes, which are described in the aforementioned language reference document.

If the conditional expression doesn’t return a value, it cannot be assigned to a variable. For example, var a = 1; var b = if (a == 1) then System.out.println ("a == 1") else System.out.println ("a != 1"); doesn’t compile because System.out.println() doesn’t return a value that can be assigned to b.

There’s something else you need to know about conditional expressions: The expression following the conditional expression’s then-part or else-part must be surrounded with braces (it must be a block) if the expression begins with a keyword. For example, braces are required in var a = 1; if (a == 1) then { var b = 2 } because the expression following the then-part begins with keyword var.

Loop expressions

For iteration-based logic, JavaFX Script supports while-based and for-based loop expressions. The former loop expression is practically identical to its equivalent Java statement, except for JavaFX Script requiring this expression’s body to always be delimited with brace characters. The following script demonstrates a simple while-based loop expression:

Listing 5. While-based loop expression

var i = 0;
while (i < 10) { java.lang.System.out.println (i++) }

The for-based loop expression is similar to the enhanced for loop introduced by J2SE 5.0. This loop expression iterates over the elements from JavaFX Script’s only data structure, the array-like sequence (an ordered list of objects). Although I discuss sequences in more detail later, the following script illustrates a sequence being accessed via this expression:

Listing 6. For-based loop expression

var nums = [5, 7, 3, 9];
var avg = 
{
     var sum = 0;
     for (a in nums) sum += a;
     sum / sizeof nums
}

java.lang.System.out.println ("Average is {avg} ") // Output: Average is 6

This script calculates and outputs the average of an integers sequence, which is specified as a comma-delimited list of integers surrounded by square brackets. It demonstrates the JavaFX Script capability for assigning a block of variable declarations and expressions — a block expression — to a variable. The value of this block expression’s final expression, sum /= sizeof nums, is assigned to avg.

The sizeof operator

The sizeof operator returns the number of elements in its sequence operand. If the operand is null, sizeof returns 0.

Loop control expressions

To break out of a loop, or to continue with the next loop iteration, you can use JavaFX Script’s break and continue expressions. Although they support the same break and continue keywords as their Java counterparts, these expressions do not support labels. The following script demonstrates the break expression (the braces surrounding break are required):

Listing 7. Break expression

var x = [1..10];
for (a in x)
{
    java.lang.System.out.println (a);
    if (a == 5) { break }
}

Range expressions

Instead of using a comma-delimited list of integers, the previous script declares a sequence via a range expression. This expression uses two integers separated by .. notation to identify a list of values forming an arithmetic series. Although the default interval between successive values is 1, this interval can be changed by specifying the step keyword followed by a different interval value:

Listing 8. A pair of range expressions

java.lang.System.out.println ([1..10 step 2]);  // a sequence of all the odd integers from 1 through 9
java.lang.System.out.println ([10..1 step -2]) // a sequence of all the even integers from 10 through 2

Return expressions

To return a value from a function, you can use JavaFX Script’s return expression, which uses the same return keyword as its Java counterpart. However, this keyword isn’t required; you can specify an expression by itself, and its value will return from the function. The following script demonstrates this capability:

Listing 9. A return expression

function cube (x)
{
    x*x*x // return x*x*x is also valid
}

var i = 4;
java.lang.System.out.println ("Cube of {i} is {cube (i)}") // Output: Cube of 4 is 64.0

Exception handling

JavaFX Script supports throwing exceptions via the throw expression, and handling exceptions via try, catch, and finally expressions. JavaFX Script’s exception handling support is nearly the same as Java’s, except that multiple catch expressions can appear in any order — subclass catch clauses must appear first in Java. Also, a catch expression uses JavaFX’s variable-declaration syntax, as demonstrated in the following script:

Listing 10. A catch expression with variable declaration

function area (a: Number, b: Number)
{
    if (a <= 0 or b <= 0)
    {
        throw new java.lang.IllegalArgumentException (if (a <= 0) then 
                                                      "a <= 0" else
                                                      "b <= 0")
    }                                                  
                                                      
    return a*b
}

try
{
    var a = area (if (java.lang.Math.random () < 0.5) -2.0 else 2.0,
                  if (java.lang.Math.random () < 0.5) -3.0 else 3.0);
    java.lang.System.out.println (a)
}
catch (x: java.lang.IllegalArgumentException)
{
    java.lang.System.out.println (x)
}
finally
{
    java.lang.System.out.println ("Performing cleanup")
}

Function pointers

Finally, JavaFX Script supports expressions consisting of functions assigned to variables. The resulting variables are known as function pointers. For example, the following script assigns a pair of anonymous functions to two different variables, and then passes either variable (based on a random choice) to a third function during that function’s invocation, which invokes the chosen anonymous function via its pointer:

Listing 11. Anonymous function invoked via its pointer

var eat = function (): Void
{
    java.lang.System.out.println ("eating")
}

var sleep = function (): Void
{
    java.lang.System.out.println ("sleeping")
}

doSomething (if (java.lang.Math.random () < 0.5) eat else sleep);

function doSomething (activity: function (): Void)
{
    activity ()
}

You’ve learned a lot about the basics of JavaFX Script, with special attention to how it handles familiar Java constructs differently. As mentioned at the beginning of this section, JavaFX Script is an expression language, an advantage you will continue to see in the more advanced discussion that follows, where you’ll learn how JavaFX Script handles sequences, classes and object literals, replace triggers and, finally, data binding.

Sequences

In the previous section I briefly mentioned JavaFX Script’s sequence data structure, for storing an ordered list of objects that share a common type. This data structure is comparable to Java’s array data structure — you can even access individual sequence elements via the same [] subscripting notation, which the following script demonstrates:

Listing 12. Accessing sequence elements

var ages = [20, 30, 17, 19, 47, 63];
java.lang.System.out.println (ages [2]); // Output: 17
ages [2] = 86;
java.lang.System.out.println (ages [2]) // Output: 86

You can then create an empty sequence by appending [] to a variable declaration’s type. You can insert elements into this sequence by using the insert operator in the context of the insert x into seq, insert x before seq [index], and insert x after seq [index] expressions, as demonstrated below:

Listing 13. Inserting elements into a sequence

var weekdays: String [];
insert "Monday" into weekdays; // [ Monday ]
insert "Wednesday" after weekdays [0]; // [ Monday, Wednesday ]
insert "Tuesday" before weekdays [1]; // [ Monday, Tuesday, Wednesday ]

To access or modify portions of a sequence, you’ll need to use slices, which are range-like expressions that produce sequences. For example, the following script uses slices to retrieve and modify portions of the sequence ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]:

Listing 14. Using slices to access and modify portions of a sequence

var months = ["January", "February", "March", "April", "May", "June",
              "July", "August", "September", "October", "November", "December"];

// Output the elements in locations 0 through 3.
java.lang.System.out.println (months [0..3]);
// Output: [ January, February, March, April]

// Output the elements in locations 0 through 2.
java.lang.System.out.println (months [0..<3]);
// Output: [ January, February, March ]

// Output the elements in locations 9 through 11.
java.lang.System.out.println (months [9..]);
// Output: [ October, November, December ]

// Output the elements in locations 9 through 10.
java.lang.System.out.println (months [9..<]);
// Output: [ October, November ]

months [0..2] = ["Jan", "Feb", "Mar"];
java.lang.System.out.println (months [0..3])
// Output: [ Jan, Feb, Mar, April ]

You can remove an element from a sequence by using the delete operator in the context of the delete x from seq, delete seq [index], or delete seq [a..b] (and all other slice forms) expressions. However, if you want to delete the entire sequence, specify delete seq. The following script demonstrates.

Listing 15. Deleting elements from a sequence, and deleting an entire sequence

var weekdays = [ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" ];
delete "Wed" from weekdays; // [ Mon, Tue, Thu, Fri, Sat, Sun ]
delete weekdays [1]; // [ Mon, Thu, Fri, Sat, Sun ]
delete weekdays [3..< sizeof weekdays]; // [ Mon, Thu, Fri ]
delete weekdays; // [ ]

Predicates

Along with range expressions, JavaFX Script uses predicates to simplify sequence creation. This language feature has the form sequence [variableName | booleanExp] — create a new sequence from an existing sequence by iterating over that sequence, choosing only those elements that satisfy a Boolean expression. For example, the script below creates a sequence containing only multiple-of-5 integers:

Listing 16. Predicates simplify sequence creation

var multiplesOfFive = [0..100][n | n mod 5 == 0]

// For each element n in [0..100], place this element in the 
// multiplesOfFive sequence if the element is a multiple of 5.

A predicate can be used to select a sequence’s elements based on their positions instead of their values by using the indexof operator. For example, java.lang.System.out.println (["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] [m | indexof m == 6 or indexof m == 7]) outputs the abbreviations for the months of July and August.

Reversing a sequence

You can easily reverse a sequence by using the reverse operator. Example: java.lang.System.out.println (reverse [1..5]) // Output: [ 5, 4, 3, 2, 1 ]

Sequence comprehensions

In addition to sequence iteration, for-loop expressions are useful for creating sequence comprehensions, a construct that specifies one or more input sequences, a filter (the where keyword followed by a Boolean expression), and an expression; and which creates a new sequence by applying the expression to the combination of all source sequence elements that satisfy the filter. Consider the following script:

Listing 17. Using a sequence comprehension to create a sequence of failing grades

var grades = [ 87, 72, 75, 94, 56, 43, 65, 50, 88, 49, 97];
var failingGrades = for (grade in grades where grade < 51) grade;
java.lang.System.out.println (failingGrades) // Output: [ 43, 50, 49 ]

This script demonstrates a simple sequence comprehension. The comprehension takes a single input sequence, grades, specifies grade < 51 as its filter, and specifies grade as the expression. The resulting new sequence, assigned to failingGrades, stores all grades that are less than 51. (What happens if you replace the grade expression with grade+2?)

Classes and object literals

JavaFX Script lets you declare classes by specifying the class keyword, followed by a valid name identifier, followed by a class body. Within this body, object state is specified via attributes, and object behaviors are specified via functions. The script in Listing 18 declares a Circle class with one attribute and two functions:

Listing 18. Class declaration in JavaFX Script

class Circle
{
    attribute radius: Number = 1.0;
    
    function area ()
    {
        java.lang.Math.PI*radius*radius
    }
    
    function circumference ()
    {
        java.lang.Math.PI*radius*2
    }
}

An attribute describes a property of the class. It begins with the attribute keyword, followed by a name, optionally followed by a type, optionally followed by an initializer. If the initializer isn’t specified, the attribute defaults to “”, 0.0, 0, false, null, or 0.0 milliseconds (depending on its type). Whether or not an initializer is specified, the attribute’s value can be overridden when an object is created.

Class and member access

To access a class from outside its containing script, insert the public keyword in front of the class keyword — multiple classes within the same script can be marked public. To specify an attribute’s or function’s access level, prefix it with the public, private, or protected keyword. Interestingly, private attributes and functions are still accessible to subclasses.

Unlike object instantiation in Java, the new keyword isn’t used with a constructor to instantiate a JavaFX Script class. Instead, JavaFX Script relies on object literals (a declarative syntax consisting of the name of the class followed by a body of attribute initializers). The following script demonstrates a pair of object literals for the aforementioned Circle class:

Listing 19. Object literals

var circle1 = Circle
              {
                   // keep default radius 1.0
              }
java.lang.System.out.println ("circle1: radius {circle1.radius}, "+
                              "area {circle1.area ()}, "+
                              "circumference {circle1.circumference ()}");
// Output: circle1: radius 1.0, area 3.141592653589793, circumference 6.283185307179586

var circle2 = Circle
              {
                  radius: 5.0
              }
java.lang.System.out.println ("circle2: radius {circle2.radius}, "+
                              "area {circle2.area ()}, "+
                              "circumference {circle2.circumference ()}")
// Output: circle2: radius 5.0, area 78.53981633974483, circumference 31.41592653589793

Declaring a local variable

It’s sometimes convenient to declare a local variable within an object literal (a semicolon isn’t required at the end of the variable’s declaration). This variable stores a reference to an object created via another object literal, and is used to access the object’s attributes at later points within the overall literal. The script in Listing 20 provides a demonstration via a moonRef local variable:

Listing 20. Declaring a local variable within an object literal

class Moon
{
    attribute name: String
}

class Planet
{
    attribute name: String;
    attribute desc: String;
    attribute moons: Moon[];
}

var earth = Planet
            {
                name: "Earth"
                var moonRef: Moon
                moons: [
                         moonRef = Moon
                         {
                             name: "Luna (unofficial name)"
                         }
                       ]
                desc: "third planet from the Sun with one moon, {moonRef.name}"
            }

java.lang.System.out.println ("{earth.name} is {earth.desc}")
// Output: Earth is third planet from the Sun with one moon, Luna (unofficial name)

What happens if you move desc: "third planet from the Sun with one moon, {moonRef.name}" before or after var moonRef: Moon? The script still compiles, but the moon’s name doesn’t output with the rest of the text. This is due to moonRef containing null until assigned a Moon reference, and moonRef.name not evaluating when moonRef contains null.

The instanceof operator

JavaFX Script supports Java’s instanceof operator for determining if an object is an instance of a specific class. For example, earth instanceof Planet returns true. If improperly used, however, the compiler reports an error. For example, you cannot use instanceof to check if an object is of type Void.

Class-oriented behaviors

Sometimes, it’s necessary to introduce class-oriented behaviors rather than object-oriented behaviors into a class — Java’s Math class provides many examples of such behaviors. JavaFX Script supports this capability by allowing you to prepend the static keyword to a function specification. These static functions can be accessed via the class name or an object instance, as the following script demonstrates:

Listing 21. Using a static function to introduce a Conversions-oriented behavior

class Conversions
{
    static function c2f (celsius: Number): Number
    {
        return 9.0/5.0*celsius+32.0 // convert Celsius to Fahrenheit
    }
}

java.lang.System.out.println (Conversions.c2f (37.0));

var c = Conversions {}
java.lang.System.out.println (c.c2f (37.0))

Object literals and one-element sequences

If a class declares an attribute of a non-Boolean, non-Integer, and non-Number sequence type, the [] delimiters can be omitted when the sequence consists of a single element. For example, the following script assigns a []-less sequence consisting only of "Canada" to the names attribute of the Countries class:

Listing 22. A []-less sequence

class Countries
{
    attribute names: String []
}

var countries = Countries
                {
                    names: ["United States", "France", "Germany"]
                }
java.lang.System.out.println (countries.names); // Output: [ United States, France, Germany ]

countries = Countries
            {
                names: "Canada"
            }
java.lang.System.out.println (countries.names) // Output: [ Canada ]

Inheritance and multiple inheritance

When you need to introduce classes that inherit attributes and/or behaviors from other classes, you can use the extends keyword to accomplish this task. Along with this keyword, JavaFX Script provides the means to properly initialize the hierarchy via init-based and postinit-based blocks. The following script demonstrates these features:

Listing 23. Inheritance and hierarchical initialization

class Superclass
{
    init
    {
        java.lang.System.out.println ("Superclass init")
    }
    
    postinit
    {
        java.lang.System.out.println ("Superclass postinit")
    }
}

class Subclass extends Superclass
{
    init
    {
        java.lang.System.out.println ("Subclass init")
    }
    
    postinit
    {
        java.lang.System.out.println ("Subclass postinit")
    }
}

var subclass = Subclass
               {
               }

In response to object creation via the Subclass {} object literal, JavaFX Script initializes the class layers by invoking Superclass‘s init block followed by Subclass‘s init block. It then performs post-initialization by invoking Superclass‘s postinit block followed by Subclass‘s postinit block.

Unlike Java, JavaFX Script supports multiple inheritance, where a class can subclass multiple superclasses. To take advantage of multiple inheritance, specify a comma-delimited list of superclass names after the subclass’s extends keyword. The following script demonstrates multiple inheritance via an extension to the previous script:

Listing 24. Multiple inheritance

class Superclass1
{
    init
    {
        java.lang.System.out.println ("Superclass1 init")
    }
    
    postinit
    {
        java.lang.System.out.println ("Superclass1 postinit")
    }
}

class Superclass2
{
    init
    {
        java.lang.System.out.println ("Superclass2 init")
    }
    
    postinit
    {
        java.lang.System.out.println ("Superclass2 postinit")
    }
}

class Subclass extends Superclass1, Superclass2
{
    init
    {
        java.lang.System.out.println ("Subclass init")
    }
    
    postinit
    {
        java.lang.System.out.println ("Subclass postinit")
    }
}

var subclass = Subclass
               {
               }

// Output: Superclass1 init
//         Superclass2 init
//         Subclass init
//         Superclass1 postinit
//         Superclass2 postinit
//         Subclass postinit

Multiple inheritance introduces the potential for ambiguity. For example, if the same attribute is declared in multiple superclasses, which attribute is accessible to the subclass? JavaFX Script’s compiler solves this problem by reporting an error. Also, if the same function is declared in two or more superclasses, the compiler reports an error, but only if a subclass instance attempts to invoke the function.

Creating Java objects

Although object literals are used to create JavaFX Script objects, this syntax cannot be used to create Java objects. Instead, JavaFX Script supports Java’s new keyword and constructor call syntax to create Java objects. For example, the following script shows you how to create a java.util.Date object in JavaFX Script:

Listing 25. Creating a java.util.Date object

var date = new java.util.Date ();
java.lang.System.out.println (date)

Finally, to create an object based on a Java interface, you employ slightly different syntax than when you’re creating an object from a Java class. For example, the following script creates an object that’s based on the Runnable interface — notice the absence of the new keyword and constructor call syntax — and then passes this instance to a Thread instance’s constructor:

Listing 26. Creating a Java object from an interface

// The following expression indicates that the script is running on the AWT's
// event-dispatching thread -- I received Thread[AWT-EventQueue-0,6,main] as 
// the output.

java.lang.System.out.println (java.lang.Thread.currentThread ());

var r = java.lang.Runnable
{
    public function run ()
    {
        // The following expression indicates that the script is running in a 
        // different thread at this point -- I received Thread[Thread-2,6,main]
        // as the output.
        
        java.lang.System.out.println (java.lang.Thread.currentThread ());
    }
}

var t = new java.lang.Thread (r);
t.start ()

Replace triggers

JavaFX Script includes two language features that are more advanced than what you’ve seen so far. The first of these features, the replace trigger, lets you attach code to a variable, and have this code execute each time the variable is modified. For example, the script in Listing 27 outputs n‘s value each time a new value is assigned to this variable:

Listing 27. A simple replace trigger

var n: Number on replace
{
    java.lang.System.out.println (n)
}
n = 1

When you run this script, you’ll notice that it first outputs 0.0. This output proves that the replace trigger’s block is executed when a variable is initialized to its default value. You’ll next observe that the script outputs 1.0, which results from the replace trigger’s block executing after 1 is assigned to n.

The purpose of a replace trigger is to protect a variable from being assigned an illegal value. Although the notification takes place immediately after the assignment, the replace trigger can reset the variable to a more suitable value, although this action results in a recursive invocation of the replace trigger, as demonstrated by the following script:

Listing 28. Recursive invocation of a replace trigger

// i is only supposed to contain 0 or a positive value

var i: Integer on replace
{
    java.lang.System.out.println (" i = {i}");
    
    if (i < 0)
        i = 0 // Restore i to its default when a negative value is assigned.
}
i = 3;
i = -1

When you run this script, it first outputs i = 0 to signify default initialization. It next outputs i = 3, to signify this assignment. The attempt to assign -1 to i results in i = -1 being output. However, because this value is illegal, the replace trigger immediately resets i to its default, resulting in i = 0 appearing in the output.

You might want to restore the variable to its most recent legal value, and perhaps also log the illegal assignment or throw an exception. To help you discover the variable’s previous value, JavaFX Script lets you specify any valid identifier (not a keyword, for example) after the replace keyword. When the replace trigger is invoked, this identifier is assigned the previous value, as demonstrated below:

Listing 29. oldValue holds the previous value

var i: Integer on replace oldValue
{
    java.lang.System.out.println ("old value = {oldValue}");
    java.lang.System.out.println (" i = {i}")
}
i = 3;
i = 4

I’ve chosen oldValue to hold the previous value, but I could have specified some other identifier — this variable is nothing more than a formal parameter to the block. When you invoke this script, you’ll discover the following output, which proves that oldValue is assigned the previous value when the replace trigger is invoked:

old value = 0
 i = 0
old value = 0
 i = 3
old value = 3
 i = 4

In regard to throwing an exception, you might want to throw IllegalArgumentException or a similar unchecked exception. Because JavaFX Script doesn’t detect unchecked exceptions when thrown from a replace trigger block, however, you’ll need to throw a checked exception (an instance of Exception or a subclass outside the RuntimeException hierarchy), which the script in Listing 30 demonstrates.

Listing 30. Throwing a checked exception from a replace trigger block

var i: Integer on replace previous = newValue
{
    if (i < 0)
    {
        i = previous;
        throw new java.lang.Exception ("cannot assign {newValue} to i")
    }
}
try
{
    i = 2;
    i = -1
}
catch (e: java.lang.Exception)
{
    java.lang.System.out.println (e.getMessage ());
    java.lang.System.out.println ("i restored to {i}")
}

This script demonstrates another replace trigger feature — a variable’s value can be accessed via a parameter identifier that follows the = operator, as an alternative to accessing this value via the variable itself. Because i is reset (overwriting its illegal value) prior to throwing the exception, the script can only obtain the illegal value via parameter newValue, which then appears in the output, as shown here:

cannot assign -1 to i
i restored to 2

Sequence triggers

Replace triggers are also designed to work with sequences, where they are often referred to as sequence triggers. The trigger’s old value and new value parameters contain slices, and that portion of the sequence that has changed (a slice) is identified via low index and high index parameters. All four parameters are demonstrated by the following script:

Listing 31. A sequence trigger

var grades = [79, 53, 82, 91, 76] on replace oldSeq [lo..hi] = newSeq
{
    java.lang.System.out.println ("oldSeq = "+oldSeq);
    java.lang.System.out.println ("lo index = {lo}");
    java.lang.System.out.println ("hi index = {hi}");
    java.lang.System.out.println ("newSeq = "+newSeq);
    java.lang.System.out.println ()
}
delete grades [1..2];
insert 50 after grades [0];
java.lang.System.out.println (grades)

/*

Output:

oldSeq = [ ]
lo index = 0
hi index = -1
newSeq = [ 79, 53, 82, 91, 76 ]

oldSeq = [ 79, 53, 82, 91, 76 ]
lo index = 1
hi index = 2
newSeq = [ ]

oldSeq = [ 79, 91, 76 ]
lo index = 1
hi index = 0
newSeq = [ 50 ]

[ 79, 50, 91, 76 ]

*/

The output reveals that the high index parameter’s value is less than the low index parameter’s value when a sequence is first created or a slice is being inserted into a sequence. It also reveals that the new sequence parameter is empty when a slice is being deleted. Your scripts can take advantage of these items to determine which kind of operation is being performed./p>

Data binding

The other advanced language feature in JavaFX Script is data binding, for synchronizing the state of multiple objects (not necessarily class instances). When two objects are bound to each other, the dependent object’s value automatically changes in response to a change to the independent object’s value. Data binding is commonly used to synchronize GUI component attributes with a model’s attributes.

According to the following syntax, JavaFX Script employs data binding to automatically update the value of a target variable (the dependent object) with the value of a remote expression (the independent object) whenever this expression changes — the remote expression is said to be bound to the target variable:

Listing 32. A remote expression bound to a target variable

let targetVariableName = bind remoteExpression

Keyword let introduces a dependent variable. (Interestingly, the var keyword also works in this context.) Because of bind, this variable automatically receives the outcome of the bound and independent expression, whenever this expression changes. The following script demonstrates this relationship:

Listing 33. A simple binding demonstration

var name1 = "Java";
let name2 = bind name1;
java.lang.System.out.println (name2); // Output: Java
name1 = "JavaFX Script";
java.lang.System.out.println (name2) // Output: JavaFX Script

let versus var

The let and var keywords appear to be synonyms for each other. For example, you can specify var name2 = bind name1; and let age = 65;, and the compiler allows this. However, I recommend using var only for variable declarations, and using let only in binding contexts. Otherwise, you might need to modify your source code if JavaFX SDK 1.0 doesn’t let you swap keywords.

A com.sun.javafx.runtime.AssignToBoundException exception is thrown if you attempt to modify the target variable (name2 = "JRuby";, for example). However, this restriction can be overcome by suffixing the remote expression with the keyword sequence with inverse. The data binding now becomes bidirectional, as demonstrated by the following script:

Listing 34. Bidirectional data binding

var name1 = "Java";
let name2 = bind name1 with inverse;
java.lang.System.out.println (name2); // Output: Java
name1 = "JavaFX Script";
java.lang.System.out.println (name2); // Output: JavaFX Script
name2 = "JRuby";
java.lang.System.out.println (name1) // Output: JRuby

JavaFX Script always performs a minimal recalculation of remoteExpression — it doesn’t recalculate invariant parts of the expression because this language remembers and returns invariant values. This minimal recalculation is demonstrated in the following script, which binds an invariant function plus a variable to a target variable:

Listing 35. A demonstration of minimal recalculation

function func (): Integer
{
    java.lang.System.out.println ("func() called");
    return 1
}
var x = 0;
let y = bind func ()+x; // Output: func() called
java.lang.System.out.println (y);  // Output: 1
x = 1; // func() isn't called this time
java.lang.System.out.println (y) // Output: 2

Data binding can be used with block expressions via the syntax let x = bind { var a = expr1; var b = expr2; var c = expr3; ... exprN } — only variable declarations and references to externally-declared variables may appear within the block. The final exprN references the other expressions, and is assigned to the bound variable, which the script below demonstrates:

Listing 36. Data binding and block expressions

var radius = 1.0;
let circumference = bind { var diameter = 2*radius; java.lang.Math.PI*diameter }
java.lang.System.out.println (circumference); // Output: 6.283185307179586
radius = 2;
java.lang.System.out.println (circumference) // Output: 12.566370614359172

Data binding can be used with conditional expressions via the syntax let x = bind if (BooleanExpr1) expr1 else if (BooleanExpr2) expr2 ... else exprN — one of the exprs binds to the target variable. The script below demonstrates data binding via a conditional expression to automatically calculate commission based on number of sales topping 100:

Listing 37. Data binding and conditional expressions

var numSales = 130;
let commission = bind if (numSales > 100) numSales*1.5 else 0.0;
java.lang.System.out.println (commission); // Output: 195.0
numSales = 20;
java.lang.System.out.println (commission); // Output: 0.0
numSales = 101;
java.lang.System.out.println (commission) // Output: 151.5

Note: Changing a BooleanExpr may determine which branch of the conditional expression is evaluated. However, changing one of the exprs doesn’t cause any of the other exprs to be recalculated.

Data binding can be used with sequence comprehensions via the syntax let newSeq = bind for (elem in seq where filter) expr — if seq changes, elements in newSeq that previously corresponded to elements still in seq aren’t recalculated. The following script demonstrates data binding via a sequence comprehension to automatically calculate an integer’s factors:

Listing 38. Data binding and sequence comprehensions

var num = 10;
let values = bind for (n in [1..num/2] where num mod n == 0) n;
java.lang.System.out.println (values); // Output: [ 1, 2, 5 ]
num = 17;
java.lang.System.out.println (values); // Output: [ 1 ]
num = 100;
java.lang.System.out.println (values) // Output: [ 1, 2, 4, 5, 10, 20, 25, 50 ]

Note: According to the language reference document, “If an element is inserted into seq, the results of applying expr to that element are inserted into newSeq at the corresponding positions and the other elements are not recalculated.”

Data binding can be used with object literals. Whenever one of the literal’s attribute values changes, a new object is created, which is the expected behavior for immutable objects. The following script proves this immutability. It also proves that attempting to access the target variable prior to its declaration results in a null reference because binding hasn’t yet taken place:

Listing 39. Data binding and object literals

class Rectangle
{
    attribute width: Number;
    attribute length: Number
}

var l = 10;
var w = 15;
java.lang.System.out.println (rect); // Output: null
let rect = bind Rectangle { length: l width: w }
// Output: rect length=10.0, width=15.0
java.lang.System.out.println ("rect length={rect.length}, width={rect.width}");
java.lang.System.out.println (rect); // Output: language.Main$Rectangle@13a328f
l = 20;
java.lang.System.out.println (rect); // Output: language.Main$Rectangle@1cd8669
w = 25;
java.lang.System.out.println (rect); // Output: language.Main$Rectangle@337838
// Output: rect length=20.0, width=25.0
java.lang.System.out.println ("rect length={rect.length}, width={rect.width}")

To avoid creating new objects, prefix attribute values with bind, as in let rect = bind Rectangle { length: bind l width: bind w }. With this change, only one Rectangle object will be created. Because the initial bind keyword is now redundant, you might as well remove it, yielding the slightly more compact let rect = Rectangle { length: bind l width: bind w } result.

Finally, data binding can be used with functions, where the function is invoked when one of its parameters changes. If the function is not prefixed with the bound keyword (the function is known as an unbound function), changes to any dependencies beyond the function’s input parameters don’t result in the function being reinvoked. These concepts are demonstrated in the following script:

Listing 40. Data binding with an unbound function

var offsetX = 0;
var offsetY = 0;

function drawBox (cols: Integer, rows: Integer)
{
    for (offY in [1..offsetY])
         java.lang.System.out.println ();
    
    for (row in [1..rows])
    {
         for (offX in [1..offsetX])
              java.lang.System.out.print (" ");

         for (col in [1..cols])
              java.lang.System.out.print ("*");
              
         java.lang.System.out.println ()
    }
    
    java.lang.System.out.println ();
    return null
}

var cols = 10;
var rows = 5;
let x = bind drawBox (cols, rows);

cols = 2;
rows = 4;

offsetX = 3;
offsetY = 2

This script’s drawBox() function draws a filled box of asterisks to the standard output, in response to a change to the cols or rows variable. This unbound function is first invoked in response to the let expression, and must return a value (such as a null reference) before it can be bound to its arguments.

In response to the let expression, drawBox() is invoked, drawing a ten-column by five-row box of asterisks. This function is reinvoked following cols = 2 (drawing a two-column by five-row box), and rows = 4 (drawing a two-column by four-row box). Although this function depends on offsetX and offset, changes to these variables don’t cause it to be reinvoked.

To also reinvoke drawBox() whenever offsetX or offsetY changes, prefix this function with the bound keyword. However, you’ll also have to refactor the function because a bound function can only contain local variable declarations and a final expression returning the function’s result. The previous script with these changes appears below:

Listing 41. Data binding with a bound function

var offsetX = 0;
var offsetY = 0;

bound function drawBox (cols: Integer, rows: Integer)
{
    drawBoxHelper (offsetX, offsetY, cols, rows)    
}

function drawBoxHelper (offX: Integer, offY: Integer, cols: Integer, rows: Integer)
{
    for (oY in [1..offY])
         java.lang.System.out.println ();
    
    for (row in [1..rows])
    {
         for (oX in [1..offX])
              java.lang.System.out.print (" ");

         for (col in [1..cols])
              java.lang.System.out.print ("*");
              
         java.lang.System.out.println ()       
    }
    
    java.lang.System.out.println ();
    return null
}

var cols = 10;
var rows = 5;
let x = bind drawBox (cols, rows);

cols = 2;
rows = 4;

// The following change causes a 2-column by 4-row box to be drawn, 
// after leaving 3 columns of empty space on the left.

offsetX = 3;

// The following change causes a 2-column by 4-row box to be drawn,
// after leaving 3 columns of empty space on the left, and 2 rows 
// of empty space above.

offsetY = 2

In conclusion

JavaFX Script isn’t a difficult language to master, and has the potential to be very rewarding to use. With what you’ve learned in this article you should be able to better understand what’s going on with the Main.fx script presented in “Jump into JavaFX, Part 1.” You may also be able to write your own simple programs. Still, you’ll need to be familiar with the various JavaFX APIs in order to fully understand the Main.fx script, and also to write more interesting programs. We’ll embark on that journey, exploring the JavaFX APIs, in the next article in this series.

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out his javajeff.mb.ca website to discover all of his published Java articles and more. His book Beginning Java SE 6 Platform: From Novice to Professional (Apress, October 2007) explores most of the new features introduced in Java SE 6.