|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 5 of 5
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
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:
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")
}
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:
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.
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:
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:
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"]:
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.
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; // [ ]
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:
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.
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 ]
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:
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?)
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:
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.
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:
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
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:
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.
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.
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:
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))
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:
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 ]
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:
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:
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.
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:
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:
// 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 ()
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:
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:
// 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:
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.
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
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:
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>
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:
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:
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
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:
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:
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:
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:
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:
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:
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:
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:
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
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.
Read more about Core Java in JavaWorld's Core Java section.