“I’m still at the beginning of my career. It’s all a little new, and I’m still learning as I go.”
—Orlando Bloom
JavaFX is partially a declarative language. Using a declarative language, a developer describes what needs to be done, then lets the system get it done. Olof Torgersson, Program Director for the Chalmers University of Technology Master’s program in Interaction Design and Associate Professor at Göteborg University, has been researching declarative programming for over 10 years. From his analysis of declarative programming approaches, we find this definition:
“From a programmer’s point of view, the basic property is that programming is lifted to a higher level of abstraction. At this higher level of abstraction the programmer can concentrate on stating what is to be computed, not necessarily how it is to be computed”1
1. Torgersson, Olof. “A Note on Declarative Programming Paradigms and the Future of Definitional Programming,” Chalmers University of Technology and Göteborg University, Göteborg, Sweden. http://www.cs.chalmers.se/~oloft/Papers/wm96/wm96.html.
JavaFX Script blends declarative programming concepts with object orientation. This provides a highly productive, yet flexible and robust, foundation for applications. However, with this flexibility comes responsibility from the developer. JavaFX Script is a forgiving language and being declarative, it assumes inherent rules that may obscure a programming fault. The most obvious of these is that null objects are handled by the runtime engine and seldom cause a Java Null Pointer exception. As a result, the program will continue when a null is encountered within an expression, and will produce a valid result. However, the result may not have been what you expected. Therefore, the developer needs to be extra vigilant when writing code and more thorough when testing it. At first, this may seem alarming; however, this is offset by the ease of use and greater productivity of JavaFX and by the fact that JavaFX tries to mitigate the user from experiencing a crash.
One of the benefits of JavaFX being a declarative language is that much of the “plumbing” to make objects interact is already provided within the language. This allows the developer to be able to concentrate more on what needs to display, and less on how to do it. The next sections provide an overview of the JavaFX Script language including syntax, operators, and other features.
As we already mentioned, JavaFX Script is a declarative scripting language with object-oriented support. If you are already acquainted with other languages such as Java, JavaScript, Groovy, Adobe ActionScript, or JRuby, JavaFX Script will look familiar, but there are significant differences. While supporting traditional pure scripting, it also supports the encapsulation and reuse capabilities afforded by object orientation. This allows the developer to use JavaFX to produce and maintain small- to large-scale applications. Another key feature is that JavaFX Script seamlessly integrates with Java.
Conceptually, JavaFX Script is broken down into two main levels, script and class. At the script level, variables and functions may be defined. These may be shared with other classes defined within the script, or if they have wider access rights, they may be shared with other scripts and classes. In addition, expressions called loose expressions may be created. These are all expressions declared outside of a class definition. When the script is evaluated, all loose expressions are evaluated.
A very simple script to display Hello World to the console is
println("Hello World");
Another example, showing how to do a factorial of 3, is shown in Listing 3.1.
Listing 3.1 Factorial of 3
Apart from the script level, a class defines instance variables and functions and must first be instantiated into an object before being used. Class functions or variables may access script level functions or variables within the same script file, or from other script files if the appropriate access rights are assigned. On the other hand, script level functions can only access class variables and functions if the class is created into an object and then only if the class provides the appropriate access rights. Access rights are defined in more detail later in this chapter.
To declare a class in JavaFX, use the class
keyword.
The public
keyword is called an access modifier and means that this class can be used by any other class or script, even if that class is declared in another script file. If the class does not have a modifier, it is only accessible within the script file where it is declared. For example, the class Point
in Listing 3.2 does not have a visibility modifier, so it is only has script visibility and can only be used within the ArtWork
script.
Listing 3.2 Artwork.fx
To extend a class, use the extends
keyword followed by the more generalized class name. JavaFX classes can extend at most one Java or JavaFX class. If you extend a Java class, that class must have a default (no-args) constructor.
JavaFX may extend multiple JavaFX mixin
classes or Java interfaces. Mixin
classes are discussed in the next section.
An application may contain many classes, so it is helpful to organize them in a coherent way called packages. To declare that your class or script should belong to a package, include a package declaration at the beginning of the script file. The following example means that the Title
class belongs to the com.mycompany.components
package. The full name of the Title
class is now com.mycompany.components.Title
. Whenever the Title
class is referenced, it must be resolved to this full name.
To make this resolution easier, you can include an import statement at the top of your source file. For example:
Now, wherever Title
is referenced within that script file, it will resolve to com.mycompany.components.Title
. You can also use a wildcard import declaration:
import com.mycompany.components.*;
With the wildcard form of import, whenever you refer to any class in the com.mycompany.components
package, it will resolve to its full name. The following code example shows how the class names are resolved, showing the fully qualified class name in comments.
A class can have package visibility by using the package
keyword instead of public
. This means the class can only be accessed from classes within the same package.
A class may also be declared abstract
, meaning that this class cannot be instantiated directly, but can only be instantiated using one of its subclasses. Abstract classes are not intended to stand on their own, but encapsulate a portion of shared state and functions that several classes may use. Only a subclass of an abstract class can be instantiated, and typically the subclass has to fill in those unique states or behavior not addressed in the abstract class.
If a class declares an abstract
function, it must be declared abstract
.
JavaFX supports a form of inheritance called mixin inheritance. To support this, JavaFX includes a special type of class called a mixin
. A mixin
class is a class that provides certain functionality to be inherited by subclasses. They cannot be instantiated on their own. A mixin
class is different from a Java interface in that the mixin may provide default implementations for its functions and also may declare and initialize its own variables.
To declare a mixin
class in JavaFX, you need to include the mixin
keyword in the class declaration. The following code shows this.
public mixin class Positioner {
A mixin
class may contain any number of function declarations. If the function declaration has a function body, then this is the default implementation for the function. For example, the following listing shows a mixin
class declaration for a class that positions one node within another.
Subclasses that want to implement their own version of the mixin
function must use the override
keyword when declaring the function. For instance, the following code shows a subclass that implements its own version of the centerX()
function from the Positioner mixin
class.
If the mixin
function does not have a default implementation, it must be declared abstract and the subclass must override this function to provide an implementation. For instance, the following code shows an abstract
function added to the Positioner mixin
class.
The subclass must implement this function using the override
keyword, as shown in the following listing.
If two mixins have the same function signature or variable name, the system resolves to the function or variable based on which mixin is declared first in the extends
clause. To specify a specific function or variable, use the mixin
class name with the function or variable name. This is shown in the following code.
Mixins may also define variables, with or without default values and triggers. The subclass either inherits these variables or must override the variable declaration. The following listing demonstrates this.
If a class extends a JavaFX class and one or more mixin
s, the JavaFX class takes precedence over the mixin
classes for variable initialization. If the variable is declared in a superclass, the default value specified in the superclass is used; if no default value is specified in the superclass, the “default value” for the type of that variable is used. For the mixin
classes, precedence is based on the order they are defined in the extends
clause. If a variable declared in a mixin
has a default value, and the variable is overridden without a default value in the main class, the initial value specified in the mixin
is used.
Mixins may also have init
and postinit
blocks. Mixin init
and postinit
blocks are run after the super class’s init
and postinit
blocks and before the subclass’s init
and postinit
blocks. Init
and postinit
blocks from the mixin
classes are run in the order they are declared in the extends
clause for the subclass.
In JavaFX, objects are instantiated using object literals. This is a declarative syntax using the name of the class that you want to create, followed by a list of initializers and definitions for this specific instance. In Listing 3.3, an object of class Title
is created with the text “JavaFX is cool” at the screen position 10, 50. When the mouse is clicked, the provided function will be called.
Listing 3.3 Object Literal
When declaring an object literal, the instance variables may be separated by commas or whitespace, as well as the semi-colon.
You can also override abstract functions within the object literal declaration. The following object literal, shown in Listing 3.4, creates an object for the java.awt.event.ActionListener
interface and overrides the abstract java method void actionPerformed(ActionEvent e)
method.
Listing 3.4 Object Literal – Override Abstract Function
JavaFX supports two kinds of variables: instance and script. Script variables hold state for the entire script, whereas instance variables hold state for specific instantiations of a class declared within the script file.
There are basically two flavors of variables: unassignable and changeable. Unassignable variables are declared using the def
keyword and must be assigned a default value that never changes.
public def PI = 3.14;
These variables cannot be assigned to, overridden, or initialized in object literals. In a sense, these can be viewed as constants; however, they are not “pure” constants and can participate in binding. (For more information on binding, see Chapter 4, Synchronize Data Models—Binding and Triggers.)
Consider the following example of defining an unassignable variable that contains an object. The object instance cannot change, but that does not mean the state of that instance will not.
The actual Point
object assigned to centerPoint
remains unchanged, but the state of that object instance, the actual x and y values, may change. When used in binding though, centerPoint
is constant; if the state of centerPoint
changes, the bound context will be notified of the change.
Changeable instance variables are declared using the var
keyword with an optional default value. If the default value is omitted, a reasonable default is used; basically, Number
s default to zero, Boolean
defaults to false
, String
s default to the empty string, Sequence
s default to the Empty Sequence, and everything else defaults to null
.
Script variables are declared outside of any class declaration, whereas instance variables are declared within a class declaration. If a script variable is declared with one of the access modifiers—public
, protected
, or package
—it may be used from outside of the script file, by referring to its fully qualified name. This fully qualified name is the combination of package name, script name, and the variable name. The following is the fully qualified name to a public script variable from the javafx.scene.Cursor
class for the crosshair cursor.
javafx.scene.Cursor.CROSSHAIR;
Instance variables are declared within a class declaration and come into being when the object is created. Listing 3.5 illustrates several examples of script and instance variables.
Listing 3.5 Script and Instance Variables
You may have noticed that some of the declarations contain a type and some don’t. When a type is not declared, the type is inferred from the first assigned value. String
, Number
, Integer
, and Boolean
are built-in types, everything else is either a JavaFX or a Java class. (There is a special syntax for easily declaring Duration
and KeyFrame
class instances that will be discussed in Chapter 7, Add Motion with JavaFX Animation.)
Table 3.1 lists the access modifiers for variables and their meaning and restrictions. You will notice reference to initialization, which refers to object literal declarations. Also, you will notice variables being bound. This is a key feature of JavaFX and is discussed in depth in Chapter 4.
You can also declare change triggers on a variable. Change triggers are blocks of JavaFX script that are called whenever the value of a variable changes. To declare a change trigger, use the on replace
syntax:
Change triggers are discussed in more depth in Chapter 4.
Sequences are ordered lists of objects. Because ordered lists are used so often in programming, JavaFX supports sequence as a first class feature. There is built-in support in the language for declaring sequences, inserting, deleting, and modifying items in the sequence. There is also powerful support for retrieving items from the sequence.
To declare a sequence, use square brackets with each item separated by a comma. For example:
This sequence is a sequence of String
s, because the elements within the brackets are String
s. This could have also been declared as
public def monthNames: String[] = [ "January", .....];
To assign an empty sequence, just use square brackets, []
. This is also the default value for a sequence. For example, the following two statements both equal the empty sequence.
When the sequence changes, you can assign a trigger function to process the change. This is discussed in depth in the next chapter.
A shorthand for declaring a sequence of Integer
s and Number
s uses a range, a start integer or number with an end. So, [1..9]
is the sequence of the integers from 1 thru 9, inclusive; the exclusive form is [1..<9]
—that is, 1 through 8. You can also use a step function, so if, for example, you want even positive integers, use [2..100 step 2]
. For numbers, you can use decimal fractions, [0.1..1.0 step 0.1]
. Without the step, a step of 1 or 1.0 is implicit.
Ranges may also go in decreasing order. To do this, the first number must be higher than the second. However, without a negative step function, you always end up with an empty sequence. This is because the default step is always positive 1.
To build sequences that include the elements from other sequences, just include the source sequences within the square brackets.
var negativePlusEven = [ negativeNumbers, evenNumbers ];
Also, you can use another sequence to create a sequence by using the Boolean operator. Another sequence is used as the source, and a Boolean operator is applied to each element in the source sequence, and the elements from the source that evaluate to true are returned in the new sequence. In the following example, n
represents each item in the sequence of positive integers and n mod 2 == 0
is the evaluation.
var evenIntegers = positiveIntegers[n | n mod 2 == 0];
One can also allocate a sequence from a for
loop. Each object “returned” from the iteration of the for
loop is added to the sequence:
To get the current size of a sequence use the sizeof
operator.
var numEvenNumbers = sizeof evenNumbers;
To access an individual element, use the numeric index of the element within square brackets:
var firstMonth = monthNames[0];
You can also take slices of sequence by providing a range. Both of the next two sequences are equal.
The following two sequences are also equal. The second example uses a syntax for range to indicate start at an index and return all elements after that index.
To iterate over a sequence, use the for
loop:
To replace an element in a sequence, just assign a new value to that indexed location in the index.
To insert an element into the sequence, use the insert
statement:
To delete an element, use the delete
statement:
Native array is a feature that allows you to create Java arrays. This feature is mainly used to handle the transfer of arrays back and forth from JavaFX and Java. An example of creating a Java int[]
array is shown in the following code.
Native arrays are not the same as sequences, though they appear similar. You cannot use the sequence operators, such as insert
and delete
, or slices. However, you can do assignments to the elements of the array as shown in the following code:
ints[2] = 4;
However, if you assign outside of the current bounds of the array, you will get an ArrayIndexOutOfBounds
Exception.
You can also use the for
operator to iterate over the elements in the native array. The following code shows an example of this.
Functions define behavior. They encapsulate statements that operate on inputs, function arguments, and may produce a result, a returned expression. Like variables, functions are either script functions or instance functions. Script functions operate at the script level and have access to variables and other functions defined at the script level. Instance functions define the behavior of an object and have access to the other instance variables and functions contained within the function’s declaring class. Furthermore, an instance function may access any script-level variables and functions contained within its own script file.
To declare a function, use an optional access modifier, public
, protected
, or package
, followed by the keyword function
and the function name. If no access modifier is provided, the function is private to the script file. Any function arguments are contained within parentheses. You may then specify a function return type. If the return type is omitted, the function return type is inferred from the last expression in the function expression block. The special return type of Void
may be used to indicate that the function returns nothing.
In the following example, both function declarations are equal. The first function infers a return type of Glow
, because the last expression in the function block is an object literal for a Glow
object. The second function explicitly declares a return type of Glow
, and uses the return
keyword.
The return
keyword is optional when used as the last expression in a function block. However, if you want to return immediately out of an if
/else
or loop, you must use an explicit return.
In JavaFX, functions are objects in and of themselves and may be assigned to variables. For example, to declare a function variable, assign a function to that variable, and then invoke the function through the variable.
Functions definitions can also be anonymous. For example, for a function variable:
Or, within an object literal declaration:
Use override
to override a function from a superclass.
String literals can be specified using either double ("
) or single ('
) quotes. The main reason to use one over the other is to avoid character escapes within the string literal—for example, if the string literal actually contains double quotes. By enclosing the string in single quotes, you do not have to escape the embedded double quotes. Consider the following two examples, which are both valid:
Expressions can be embedded within the string literal by using curly braces:
The embedded expression must be a valid JavaFX or Java expression that returns an object. This object will be converted to a string using its toString()
method. For instance:
Also, a string literal may be split across lines:
In this example, the strings from both lines are concatenated into one string. Only the string literals within the quotes are used and any white space outside of the quotes is ignored.
Unicode characters can be entered within the string literal using u +
the four digit unicode.
var thanks = "danku00eb"; // dankë
Embedded expressions within string literals may contain a formatting code that specifies how the embedded expression should be presented. Consider the following:
var totalCountMessage = "The total count is {total}";
Now if total
is an integer, the resulting string will show the decimal number; but if total
is a Number, the resulting string will show the number formatted according to the local locale.
var total = 1000.0;
produces:
The total count is 1000.0
To format an expression, you need a format code within the embedded expression. This is a percent (%
) followed by the format codes. The format code is defined in the java.util.Formatter
class. Please refer to its JavaDoc page for more details (http://java.sun.com/javase/6/docs/api/index.html).
To internationalize a string, you must use the “Translate Key” syntax within the string declaration. To create a translate key, the String assignment starts with ##
(sharp, sharp) combination to indicate that the string is to be translated to the host locale. The ##
combination is before the leading double or single quote. Optionally, a key may be specified within square brackets ([]
). If a key is not specified, the string itself becomes the key into the locale properties file. For example:
In the preceding example, using the first form, the key is "Zip Code: "
, whereas for the second form, the key is "postal"
. So how does this work?
By default, the localizer searches for a property file for each unique script name. This is the package name plus script filename with a locale and a file type of .fxproperties
. So, if your script name is com.mycompany.MyClass
, the localizer code would look for a property file named com/mycompany/MyClass_xx.fxproperties
on the classpath, where xx
is the locale. For example, for English in the United Kingdom, the properties filename would be com/mycompany/MyClass_en_GB.fxproperties
, whereas French Canadian would be com/mycompany/MyClass_fr_CA.fxproperties
. If your default locale is just English, the properties file would be MyClass_en.fxproperties
. The more specific file is searched first, then the least specific file is consulted. For instance, MyClass_en_GB.fxproperties
is searched for the key and if it is not found, then MyClass_en.fxproperties
would be searched. If the key cannot be found at all, the string itself is used as the default. Here are some examples:
When you use a string with an embedded expression, the literal key contains a %s
, where the expression is located within the string. For example:
println(##"Hello, my name is {firstname}");
In this case, the key is "Hello, my name is %s"
. Likewise, if you use more than one expression, the key contains a "%s"
for each expression:
println(##"Hello, my name is {firstname} {lastname}");
Now, the key is "Hello, my name is %s %s"
.
This parameter substitution is also used in the translated strings. For example:
French – MyClass_fr.fxproperties:
"Hello, my name is %s %s" = "Bonjour, je m'appelle %s %s"
Lastly, you can associate another Properties file to the script. This is done using the javafx.util.StringLocalizer
class. For example:
StringLocalizer.associate("com.mycompany.resources.MyResources", "com.mycompany");
Now, all translation lookups for scripts in the com.mycompany
package will look for the properties file com/mycompany/resources/MyResources_ xx.fxproperties
, instead of using the default that uses the script name. Again, xx
is replaced with the locale abbreviation codes.
A block expression is a list of statements that may include variable declarations or other expressions within curly braces. If the last statement is an expression, the value of a block expression is the value of that last expression; otherwise, the block expression does not represent a value. Listing 3.6 shows two block expressions. The first expression evaluates to a number represented by the subtotal value. The second block expression does not evaluate to any value as the last expression is a println()
function that is declared as a Void
.
Listing 3.6 Block Expressions
The throw
statement is the same as Java and can only throw a class that extends java.lang.Throwable
.
The try
/catch
/finally
expression is the same as Java, but uses the JavaFX syntax:
Table 3.2 contains a list of the operators used in JavaFX. The priority column indicates the operator evaluation precedence, with higher precedence operators in the first rows. Operators with the same precedence level are evaluated equally. Assignment operators are evaluated right to left, whereas all others are evaluated left to right. Parentheses may be used to alter this default evaluation order.
Table 3.2 Operators
if
is similar to if
as defined in other languages. First, a condition is evaluated and if true, the expression block is evaluated. Otherwise, if an else
expression block is provided, that expression block is evaluated.
One important feature of if
/else
is that each expression block may evaluate to an expression that may be assigned to a variable:
Also the expression blocks can be more complex than simple expressions. Listing 3.7 shows a complex assignment using an if
/else
statement to assign the value to outOfDateMessage
.
Listing 3.7 Complex Assignment Using if/else Expression
In the previous example, the last expression in the block, the error message string literal, is the object that is assigned to the variable. This can be any JavaFX Object, including numbers.
Because the if
/else
is an expression block, it can be used with another if
/else
statement. For example:
for
loops are used with sequences and allow you to iterate over the members of a sequence.
To be similar with traditional for
loops that iterate over a count, use an integer sequence range defined within square brackets.
for( i in [0..100]} {
The for
expression can also return a new sequence. For each iteration, if the expression block executed evaluates to an Object
, that Object
is inserted into a new sequence returned by the for
expression. For example, in the following for
expression, a new Text
node is created with each iteration of the day of the week. The overall for
expression returns a new sequence containing Text
graphical elements, one for each day of the week.
Another feature of the for
expression is that it can do nested loops. Listing 3.8 shows an example of using nested loops.
Listing 3.8 Nested For Loop
There may be zero or more secondary loops and they are separated from the previous ones by a comma, and may reference any element from the previous loops.
You can also include a where
clause on the sequence to limit the iteration to only those elements where the where
clause evaluates to true:
var evenNumbers = for( i in [0..1000] where i mod 2 == 0 ) i;
The while
loop works similar to the while
loop as seen in other languages:
Note that unlike the JavaFX for
loop, the while
loop does not return any expression, so it cannot be used to create a sequence.
break
and continue
control loop iterations. break
is used to quit the loop altogether. It causes all the looping to stop from that point. On the other hand, continue
just causes the current iteration to stop, and the loop resumes with the next iteration. Listing 3.9 demonstrates how these are used.
Listing 3.9 Break/Continue
The instanceof
operator allows you to test the class type of an object, whereas the as
operator allows you to cast an object to another class. One way this is useful is to cast a generalized object to a more specific class in order to perform a function from that more specialized class. Of course, the object must inherently be that kind of class, and that is where the instanceof
operator is useful to test if the object is indeed that kind of class. If you try to cast an object to a class that that object does not inherit from, you will get an exception.
In the following listing, the printLower()
function will translate a string to lowercase, but for other types of objects, it will just print it as is. First, the generic object is tested to see if it is a String
. If it is, the object is cast to a String
using the as
operator, and then the String
’s toLowerCase()
method is used to convert the output to all lowercase. Listing 3.10 illustrates the use of the instanceof
and as
operators.
Listing 3.10 Type Operators
For a pure script that does not declare exported classes, variables, or functions, the command-line arguments can be retrieved using the javafx.lang.FX.getArguments():String[]
function. This returns a Sequence
of String
s that contains the arguments passed to the script when it started. There is a another version of this for use in other invocations, such as applets, where the arguments are passed using name value pairs, javafx.lang.FX.getArguments(key:String):String[]
. Similarly, there is a function to get system properties, javafx.lang.FX.getProperty(key:String):String[]
.
If the script contains any exported classes, variables, or functions, arguments are obtained by defining a special run
function at the script level.
There are a set of functions that are automatically available to all JavaFX scripts. These functions are defined in javafx.lang.Builtins
.
You have already seen one of these, println()
. Println()
takes an object argument and prints it out to the console, one line at a time. It is similar to the Java method, System.out.println()
. Its companion function is print()
. Print()
prints out its argument but without a new line. The argument’s toString()
method is invoked to print out a string.
Another function from javafx.lang.Builtins
is isInitialized()
. This method takes a JavaFX object and indicates whether the object has been completely initialized. It is useful in variable triggers to determine the current state of the object during initialization. There may be times that you want to execute some functionality only after the object has passed the initialization stage. For example, Listing 3.11 shows the built-in, isInitialized()
being used in an on replace
trigger.
Listing 3.11 isInitialized()
In this example, when the class, Test
, is first instantiated, the instance variable, status
, first takes on the default value of 0.0, and then the on replace
expression block is evaluated. However, this leaves the status in the uninitialized
state. Only when a value is assigned to status
, will the state change to initialized
. Consider the following:
In this case when Test
is created using the object literal, Test{}
, status takes on the default value of 0.0; however, it is not initialized
, so commenceTest
will not be invoked during object creation. Now when we assign a value to status
, the state changes to initialized
, so commenceTest
is now invoked. Please note that if we had assigned a default value to status
, even if that value is 0, then status
immediately is set to initialized
. The following example demonstrates this.
The last built-in function is isSameObject()
. isSameObject()
indicates if the two arguments actually are the same instance. This is opposed to the ==
operator. In JavaFX, the ==
operator determines whether two objects are considered equal, but that does not mean they are the same instance. The ==
operator is similar to the Java function isEquals()
, whereas JavaFX isSameObject
is similar to the Java ==
operator. A little confusing if your background is Java!
The built-in variables are __DIR__
and __FILE__
. __FILE__
holds the resource URL string for the containing JavaFX class. __DIR__
holds the resource URL string for directory that contains the current class. For example,
The following examples show the output from a directory based classpath versus using a JAR-based class path.
This chapter covered key concepts in the JavaFX Scripting language. You were shown what constitutes a script and what constitutes a class. You were shown how to declare script and instance variables, how to create and modify sequences, and how to control logic flow.
You now have a basic understanding of the JavaFX Script language syntax and operators. Now, it is time to put this to use. In the following chapters, we will drill down into the key features of JavaFX and show how to leverage the JavaFX Script language to take advantage of those features. In the next chapter, we start our exploration of JavaFX by discussing the data synchronization support in the JavaFX runtime.