The common thread running through my hobby software activities lately, is the porting of my hour registration applet to a JavaFX desktop, mobile and web version. Yesterday I picked up work on the favorites; those projects who are currently most used.
In the original applet the favorites (as seen on the right in the image above) are implemented as a list and share the same screen space as the full tree of projects. Switching between either is done using an accordion; a layout similar to a tabbed pane, showing a header and per header a content. The content of one tab is shown at a time, and switching is done by clicking on the tab header. The main difference is that the headers are intermixed with the content, and move up or down when other content is revealed. Not unlike an accordion has ridges that move apart to show the space in between. I wanted to have the same visualisation in the JavaFX version, I could have settled on a tabbed pane, but what is the fun in that?
Now, JavaFX 8 already has an accordion control, but it behaves differently from what I want. In my usage there is only one tab visible, while the default accordion can (un)fold multiple tabs, and even have them all closed. It’s a minor UI/UX difference, but not what I want, so I decided to write it my version.
It turned out that writing that behavior as part of the screen was fairly simple; you simply stack a number of Buttons (tab headers) and Nodes (tab panes) in a VBox, and have a little loop making the correct Nodes hidden/unmanaged when the buttons are pressed. Done. The core part of the accordion logic is in this small sniplet:
// add the tab button Button lTabButton = new Button(tab.getName()); vbox.add(lTabButton, new VBox.C().maxWidth(Double.MAX_VALUE)); // add the tab pane BorderPane lTabPane = new BorderPane(tab.getNode()); vbox.add(lTabPane, new VBox.C().maxWidth(Double.MAX_VALUE).vgrow(Priority.ALWAYS)); // since only one TabPane is visible at any given time, all TabPanes can be ALWAYS // on click of the button, make the corresponding pane visible (and hide any visible ones) lTabButton.onActionProperty().set(event -> { show(lTabPane.getCenter()); }); ... // scan all TabPanes, first to last for (int i = 1; i < vbox.getChildren().size(); i+=2) { // the panes are all the odd nodes (1, 3, 5, ...) Node lNode = vbox.getChildren().get(i); // if the tabpane holds the node that needs to be shown, make it visible, otherwise hide it lTabPane.setVisible(toBeShownNode == lNode); lTabPane.setManaged(lNode.isVisible()); }
Maybe 30 minutes of work to get that going, and it works perfectly in the screen. The behavior of this screen is then tested using a UI test with maybe Cucumber; switch to the favorites tab, select a project, enter hours, save, and see if totals on the screen are updated correctly. Below a screenshot of the JavaFX version, running under Gluon.
All good and well, and then you realize that more people may want to have such an accordion control, so you decide to pull it out and create the AccordionPane. And then things quickly become a lot of work. I tweeted:
It always amazes me how much time is needed to polish a simple control into a production ready one. Added an accordion to #jfxtras #javafx
The thing is, when that accordion was just on my screen, it usage was known and limited. But as soon as you make it a control, the “what ifs” come to mind.
- What if someone adds a tab when the control is already shown?
- What is someone removes a tab?
- What if someone removes the currently visible tab?
- What if someone wants to change the visible tab programatically?
- What if someone wants an icon on the header?
- What if someone wants to change the text or icon on the header while it is rendered?
- …
So even though the core logic (those roughly 10 lines of code) remain the same, you start refactoring the hell out of the accordion. A simple inner class with two fields becomes one with JavaFX properties. You need observable collections, visible tab properties, etc. And then there also is the testing. Because now it no longer is only part of a screen, but an independent control, it needs unit tests on its own. Testing it only in the testscript of that single usage, that single screen, is not cover enough. Does it render correctly with zero, one, two, more tabs? Does it respond correctly to clicking? Does it respond correctly to programmatic changes? Adding, removing, every sensible scenario you can think of.
And those 30 minutes suddenly become a full day’s works, and the number of lines increase more than ten-fold. Enough work to write a blog about 🙂
And I haven’t even started on animation and styling yet… People sometimes don’t realize how much work goes into getting even such a simple control with just a 10 line core production ready.
Pingback: Java desktop links of the week, January 30 « Jonathan Giles