Sun Java Solaris Communities My SDN Account Join SDN
 
Article

Learning Curve Journal, Part 3: JavaFX Script Functions and Operations

 
By John O'Conner, September 2007  

Articles Index

In the last Learning Curve article, I implemented a simple user interface (UI) and declared my UI experiment a success. The resulting JavaFX Script representation at least appears similar to the original Java programming language UI that I had constructed for the Image Search application that searches images on Flickr. Figure 1 shows the basic frame that resulted.

Figure 1. The JavaFX Image Search application duplicates the original UI in JavaFX Script.
 

Using declarative JavaFX Script syntax, the resulting code is a reasonable start to porting a Java language UI. However, the dormant frame, unresponsive search field, inactive progress bars, blank list box, and empty image label need to do something. Nothing is happening here, and it's no wonder either. So far, none of the UI elements is attached to a live data model, nor do they respond to user interactions. The Search text field, for example, will quietly accept and display your typed characters, but it does not do anything constructive with them -- not yet anyway.

This skeletal UI needs to do something. For that, we need functions and operations, some action in our otherwise inactive application. The JavaFX Script programming language allows you to create both functions and operations, and the difference between the two is significant but not entirely clear at first glance. Learning which to use for my application is now my first priority.

Functions

JavaFX Script function bodies can contain only variable declarations and a return statement. No loops or conditional operations are allowed. Functions have limited side effects and are intended to transform one or more values to another. For example, here are several valid functions:

function z(a,b) {
    var x = a + b;
    var y = a - b;
    return sq(x) / sq(y);
}

function sq(n) {return n * n;}

function main() {
    return z(5, 10);
}
 

You don't have to declare the return value type or even the parameter types. However, using types is more familiar to me as a Java language programmer, so I tend to use them wherever possible. My awkward feelings about not declaring types may just be my initial response to using a different language, and maybe I'll eventually get used to it. For now, declaring parameter types helps make things clearer. So I'd probably rewrite the z function as this:

function z(a: Number, b: Number): Number {
    var x: Number = a + b;
    var y: Number = a - b;
    return sq(x) / sq(y);
}
 

A function starts with the function keyword. The function name and parameter list follow. In JavaFX Script, types follow the variable, function, or operation name. For example, the b: Number parameter means that an argument named b has the Number type. Finally, because the function returns a Number, I can declare that after the parameter list. The body of the function has brackets around it, a familiar way for Java language programmers to enclose method bodies.

Functions are interesting because they reevaluate their return value whenever their parameters or any other referenced variable changes. This is a useful feature when you want to bind an object to a specific value that might frequently update. I'll tell you more about binding later.

Operations

JavaFX Script operations most resemble Java methods. Like functions, operations can have parameters and return values. One big difference from functions is that operations can also contain if-then, while-loops, for-loops, and other conditional statements. You declare an operation much the way you declare a function, but use the keyword operation instead. Here's an example of an operation defined for a Friends class:

import java.lang.System;

class Friends {
    attribute knownNames: String*;
    operation sayHello(name: String): String;
}

operation Friends.sayHello(name: String): String {
    if (name in knownNames) {
        return "Hello, {name}!";
    } else {
        return "Sorry, I can't talk to strangers.";
    }
}

var buddies = Friends {
    knownNames: ["John", "Robyn", "Jack", "Nick", "Matthew",
    "Tressa", "Ruby"]
};

var greeting = buddies.sayHello("Bob");
System.out.println(greeting);
 

This small program says "Hello" to you if you provide a known name to the sayHello method. Otherwise, it tells you that it "can't talk to strangers." The Friends class contains one attribute and an operation that uses that attribute to return a message.

Notice that the sayHello definition is not embedded within the Friends class. Only its declaration is in the class. Define functions the same way by first declaring them inside the class and then defining them outside.

Responsive UI Elements

UI elements -- or widgets, as they're called in the JavaFX Script libraries -- can respond to user interactions such as key presses or mouse clicks. Widgets have action, onMouseClicked, onKeyTyped, and other event-oriented attributes. You can associate an operation with those attributes. For example, if you associate an operation with a TextField's action attribute, that operation will execute when you press Enter within the field. The same action attribute on a Button widget will activate whenever the user clicks on it as well.

Knowing that I need to associate operations with the JavaFX Image Search application, I decided to experiment with creating event handlers for UI elements. The following application creates two buttons and a label. Pressing the Bigger button increases the label's font size and changes the text. Pressing the Smaller button decreases the label's font size and changes the text.

import javafx.ui.BorderPanel;
import javafx.ui.FlowPanel;
import javafx.ui.Button;
import javafx.ui.Font;
import javafx.ui.Label;

class FontDataModel {
    attribute text: String;
    attribute font: Font;
    operation increaseFontSize();
    operation decreaseFontSize();
}

operation FontDataModel.increaseFontSize() {
    if (font.size < 36) {
        font.size++;
        text = "Font Test ({font.size})";
    }
}

operation FontDataModel.decreaseFontSize() {
    if (font.size > 8) {
        font.size--;
        text = "Font Test ({font.size})";
    }
}

var myFont = FontDataModel {
    text: "Font Test (18)"
    font: Font {size:18}
};

BorderPanel {
    top: FlowPanel {
        alignment: LEADING
        content: [
        Button {
            text: "Bigger"
            action: operation() {
                myFont.increaseFontSize();
            }
        },
        Button {
            text: "Smaller"
            action: operation( ){
                myFont.decreaseFontSize();
            }

        }]}
    center:
    Label {
        width: 200
        text: myFont.text
        font: myFont.font
    }
}
 

You can cut and paste this code directly into the JavaFXPad application to see the results shown in Figure 2. JavaFX Pad is a lightweight tool that allows you to interactively create graphical elements using the JavaFX Script programming language.

Figure 2. The JavaFX Pad application allows interactive experimentation with the JavaFX Script language.
 

This Bigger-Smaller font application creates a FontDataModel with two attributes: a text string and a font. The application creates a FontDataModel instance and initializes both the text and font attributes. The FontDataModel also has two operations: increaseFontSize and decreaseFontSize. These operations change both attributes.

Each button has an action attribute with an associated operation. For example, the button labeled Bigger will call the myFont variable's increaseFontSize operation:

Button {
    text: "Bigger"
    action: operation() {
        myFont.increaseFontSize();
    }
}
 

In the current version of this simple application, you can see the font size change each time you press a button. Figure 3 shows the results of pushing the Bigger button twice between each image.

Figure 3. Text size changes when you click the buttons.
 

Clearly the increaseFontSize method is changing the font. You can see that the text Font Test (18) becomes bigger in each progressive image from left to right. However, the method is supposed to change not only the font size but also the text content. Figure 3 does not show the change in text content. Why not? The text content does not change because the view is not tracking the model's text attribute correctly.

Binding Views to Models

JavaFX Script has a bind operator that allows one attribute to track changes in another attribute. Binding an attribute to another means that the bound attribute will always be aware of changes in the target attribute. In this Bigger-Smaller font application, I want the view text to know that the model text has changed. I can use the bind operator to accomplish this. The original definition of the label is here:

Label {
    width: 200
    text: myFont.text
    font: myFont.font
}
 

Although the Label is tracking the font change, it is not tracking changes to the text. You can add the bind operator to make the Label's text update whenever the model's text updates.

The revised Label declaration is here:

Label {
    width: 200
    text: bind myFont.text
    font: myFont.font
}
 

At first, I was surprised that the label tracked the model font changes but did not track the text change without the bind operator. After thinking about this for several minutes, I think I now understand why. The font attribute is an object that can change. Because the label's font attribute actually holds the same font attribute in the FontDataModel instance, changes are automatically transferred to the view. However, String objects are immutable, so when the model's text attribute changes, it gets a completely new String instance. No changes occur to the original text instance -- the model simply discards it and replaces its reference with a new String instance. The label continues to hold a reference to the old String instance that is no longer part of the model.

Placing the bind operator on the label's text attribute makes sure that all changes to the model's text attribute get propagated. Now both the font size and the text content show each time I click the UI buttons. The UI text changes each time to show the font's point size. Figure 4 shows the working view.

Figure 4. The bind operator allows the UI to track changes to the model
 

The bind operator works well with functions too. Because functions incrementally update their results whenever either their arguments or referenced variables change, binding to a function works just as well as binding to a single attribute. In fact, functions really are designed to be used with bindings. You can use them to refactor bindings into reusable subroutines that automatically track all their dependencies, including both their parameters and referenced variables in their body.

Consider the code snippets in Table 1. Both snippets are equivalent.

Table 1. Using Bind With and Without a Function
 
Bind Without a Function
Bind With a Function
import java.lang.System;

class Data {
    attribute foo: Number;
    attribute baz: Number;
}

var data = Data {
    foo: 4
    baz: 7
};

var zoo = bind data.foo +
        data.baz + 10;
System.out.println(zoo);
data.baz = 12;
System.out.println(zoo); 
import java.lang.System;

class Data {
    attribute foo: Number;
    attribute baz: Number;
    function add(x): Number;
}

function Data.add(x): Number {
    return foo + baz + x;
}

var data = Data {
    foo: 4
    baz: 7
};

var zoo = bind data.add(10);
System.out.println(zoo);
data.baz = 12;
System.out.println(zoo);
Output:
21
26
Output:
21
26
 

Comparing functions and operations in terms of how they work with the bind operator is also interesting. In the preceding snippet, change the add(x) function to an operation. The output changes to this:

21
22
 

An operation does not reevaluate its parameters and referenced variables when they change. So when baz changes, the zoo variable does not get a new value. The operation was evaluated once during zoo's first assignment but not again when the variable baz changed.

Summary

Use functions and operations to add behavior to JavaFX technology-based applications. UI components have attributes that map to operations. You can define those operations to handle UI events such as action, onMouseClicked, and onKeyTyped. Functions have the additional property that they reevaluate their parameters and referenced variables within their function body.

You can use the bind operator to link one attribute to another. This is particularly useful when you want a UI widget to track a model attribute. Binding a view attribute to a model attribute means that the model and view will always be synchronized with the same data.

Although I have not yet added functionality to the JavaFX Image Search application's UI, I have learned that functions and operations will be needed. I'll almost certainly use an operation to retrieve search text. Maybe that same operation will access the Flickr site in the background to retrieve images. Additionally, I'm positive that I'll use the bind operator to link the view with an underlying model.

For the next Learning Curve installment, I finally have the knowledge to connect the UI to some underlying actions and operations. Additionally, I'll be able to use the bind operator to link the UI with an underlying model.

For More Information
Rate and Review
Tell us what you think of the content of this page.
Excellent   Good   Fair   Poor  
Comments:
Your email address (no reply is possible without an address):
Sun Privacy Policy

Note: We are not able to respond to all submitted comments.