FX Experience Has Gone Read-Only

I've been maintaining FX Experience for a really long time now, and I love hearing from people who enjoy my weekly links roundup. One thing I've noticed recently is that maintaining two sites (FX Experience and JonathanGiles.net) takes more time than ideal, and splits the audience up. Therefore, FX Experience will become read-only for new blog posts, but weekly posts will continue to be published on JonathanGiles.net. If you follow @FXExperience on Twitter, I suggest you also follow @JonathanGiles. This is not the end - just a consolidation of my online presence to make my life a little easier!

tl;dr: Follow me on Twitter and check for the latest news on JonathanGiles.net.

One of the gratifying things about being involved in building a new platform on top of a new language is discovering new language idioms. As an industry we’d managed to put together quite a long list of patterns and best practices for Java, but since JavaFX introduces some new concepts (such as object literal notation) and makes other things really easy to do (binding, closures) it creates an environment where we need to discover the best practices and patterns that make for effective programming in JavaFX.

One such idiom has to do with encapsulation. I was presented with the following problem in a recent email. The developer wanted to create a chunk of scenegraph which looked differently depending on some flag. There would be 10 such flags with 10 such chunks of scenegraph. In essence here is what the raw code would look like:

[jfx]
def imgPending = Image { url: "…" }
def imgDone = Image { url: "…" }

var flag1 = false;
var flag2 = false;
var flag3 = false;
// … and so on

var group = Group {
content: [
HBox {
content: [
Text { content: "Item 1" }
ImageView { image: bind if (flag1) then imgPending else imgDone }
]
}
HBox {
content: [
Text { content: "Item 2" }
ImageView { image: bind if (flag2) then imgPending else imgDone }
]
}
HBox {
content: [
Text { content: "Item 3" }
ImageView { image: bind if (flag3) then imgPending else imgDone }
]
}
// … and so on
]
}
[/jfx]

So for each flag we’re creating a different HBox with a Label and ImageView. The ImageView shows a different image based on the flag. Clearly the code as written is simple enough but really verbose. It is ripe for copy/paste errors and makes maintenance painful. What can we do about it?

In JavaFX, the best way to encapsulate a chunk of scenegraph such as this is to create a CustomNode. In JavaFX 1.3 you also have the option of extending directly from Parent. For the sake of this article, I’ll stick with the 1.2 approach (which still works in 1.3) and extend from CustomNode.

Unlike a Group, CustomNode is a Parent node who’s content is private. This gives you a nice way to encapsulate some chunk of scenegraph and provide a nice clean API to it without having to expose to everybody the actual details of that private chunk of scenegraph. The other benefit to this form of encapsulation is that it helps to clean up your source code by breaking it up into logical chunks.

Here is the reworked example using proper encapsulation:

[jfx]
// in Indicator.fx
def imgPending = Image { url: "…" }
def imgDone = Image { url: "…" }

public class Indicator extends CustomNode {
public var text:String;
public var done:Boolean;

override function create() {
HBox {
content: [
Label { content: bind text }
ImageView { image: bind if(not done) then imgPending else imgDone }
]
}
}
}
[/jfx]

[jfx]
// in Main.fx
var flag1 = false;
var flag2 = false;
var flag3 = false;
// … and so on

var group = Group {
content: [
Indicator { text: "Item 1", done: bind flag1 }
Indicator { text: "Item 2", done: bind flag2 }
Indicator { text: "Item 3", done: bind flag3 }
// … and so on
]
}
[/jfx]