0

I have an application that takes in an FXML to load. The FXML is external to the application (not made by the developer).

The application has a server connection to get telemetry data.

I need to update nodes based on these telemetry data. In that regard I have made a NodeData user data object that designers of FXML can add to each Node in the FXML

FXML userData showed inline for demonstration

<AnchorPane fx:id="rootPane" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1">
    <Label fx:id="label1">
        <userData>
            <NodeData>
                <queries>
                    <FXCollections fx:factory="observableArrayList">
                        <String fx:value="select name from TABLE_1" />
                        <String fx:value="select title from TABLE_2 />
                    </FXCollections>
                </queries>
            </NodeData>
        </userData>
    </Label>
</AnchorPane>


The Java user data objects

/** Node DAO */
public class NodeData {

    private ObservableList<NodeQuery> queries;

    public ObservableList<NodeQuery> getQueries() {
        return queries;
    }

    public void setQueries(ObservableList<NodeQuery> queries) {
        this.queries = queries;
    }

}

public class NodeQuery {

    private String query;

    public String getQuery() {
        return query;
    }

    public void setQuery(String query) {
        this.queries = queries;
    }

}

I planned in having NodeData sending the queries and receiving the response.

When the response is received I wanted to update the Node. I am not quite sure how I would do it. There is no Observable on user data back to the node I can listen to.

One solution:
Perhaps I could have the controller do the work. In @FXML initialize I could iterate through the rootPane I have access to, find all nodes, retrieve its NodeData, send the queries. When received update the node. However that seems a little messy.

@FXML
private void initialize() {
    Stack<Node> nodes = new Stack<>();
    nodes.addAll(rootPane.getChildren());

    while (!nodes.empty()) {
        Node node = nodes.pop();

        Object userData = node.getUserData();
        if (userData instanceof NodeData) {
            NodeData nodeData = (NodeData) userData;
            // Do work on nodeData.
        }

        if (node instanceof Pane) {
            Pane pane = (Pane) node;
            nodes.addAll(pane.getChildren());
        } else if (node instanceof TitledPane) {
            TitledPane titledPane = (TitledPane) node;
            Node content = titledPane.getContent();
            if (content instanceof Pane) {
                Pane pane = (Pane) content;
                nodes.addAll(pane.getChildren());
            }
        }
    }
}


I would like an opinion on this design strategy, if there perhaps is a better solution here?

DJViking
  • 832
  • 1
  • 12
  • 29
  • Why don't you just define `fx:id` on the elements in the FXML in the usual way? It's not at all clear why you appear to want to define SQL statements in the FXML (in the *view*); surely this doesn't belong here at all. Normally, you have a DAO class which is referenced by the controller (or by the model, and the model referenced by the controller); the DAO encapsulates the SQL statements. Then the controller just calls methods on the DAO and updates the nodes with the result. – James_D May 09 '18 at 12:55
  • I have fx:id on all nodes in the FXML. Was missing from my little example. – DJViking May 09 '18 at 13:47
  • I set the user data in FXML because I have no control over the FXML. A user can load an FXML he has built with SceneBuilder, but the application need to populate its Nodes with data. I needed to set that Node with id label1 had one or more queries which will be used to update that node. – DJViking May 09 '18 at 13:49
  • Need to specify that the application knows nothing about the FXML structure and layout. All the controller knows about is the rootPane top node. After loading the FXML all nodes subscribe to telemetry. This telemetry subscription must be defined somewhere outside of the application, and I have tried with user data defined in the FXML. – DJViking May 09 '18 at 13:57
  • I used DAO and SQL as an example, but we are using Google Protobuf to query for telemetry from server. For this we have an API. The NodeData uses this API to subscribe to telemetry and listens to respons. – DJViking May 09 '18 at 14:02
  • Sounds like you are trying to write a framework, rather than looking for design advice... At any rate, you're really trying to extend the purpose of FXML, which is intended for *layout*; not for defining controller behavior for individual nodes. – James_D May 09 '18 at 14:15
  • Off the top of my head, I would at least get the [namespace](https://docs.oracle.com/javase/9/docs/api/javafx/fxml/FXMLLoader.html#getNamespace--) from the `FXMLLoader` after loading the FXML, and use it to iterate through the elements defined in the FXML, instead of the scene graph navigation you're trying to do. I'd *probably* define the mapping between the `fx:id` and the data you're putting in the FXML in the `userData` in a separate file (format of your choosing). Then read and parse that and use the FXML namespace to process all the information. – James_D May 09 '18 at 14:21
  • Putting the data in a seperate file is also one solution. As you say I would need to map between fx:id and the data. That solution is perhaps best as it doesn't require the designer to manually edit the FXML. Thanks for the namespace suggestion, I was not aware of that one. Much better than scene graph navigation. – DJViking May 09 '18 at 14:45

0 Answers0