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]
For such a trivial example it might have been quicker to just turn the flags into a a Boolean sequence, then use a for loop to construct the HBox’s. Indeed a sequence driven loop is still preferable even with the Indicator node (what happens if you want 100 flags?) But your point is well made: deeply nested public scene graphs are usually a bad idea; much better to encapsulate key parts of the graph behind custom nodes.