1 2 3 Previous Next 33 Replies Latest reply on Oct 27, 2011 4:38 PM by Richard Bair-Oracle

    FXML suggested improvements

    zonski
      This was actually intended as a comment for Richard Bair's blog post on FXML (http://fxexperience.com/2011/10/fxml-why-it-rocks-and-the-next-phase), but that just ate all my embedded code so I'm posting here. It may be of interest to some people here anyway.

      There are some very cool features being listed here. FXML is very powerful already and its future is bright in my opinion. I have a suggested improvement that relates back to your proposed features - one I think allows the developer to do all of the design options listed above in a powerful, intuitive and flexible way.

      I would like to see an FXML file be able to declare variables that it needs to be passed. Something like:
      <?xml version="1.0" encoding="UTF-8"?>
       
      <usevar name="model" type="com.mycom.myapp.Person"/>
      <usevar name="currentUser" type="com.mycom.myapp.User"/>
      This would allow the developer to pass in one model, many models or whatever they like to the FXML. So long as the passed in object meets the specified type (which I would encourage to be defined as interfaces, but this is up to the developer) then the FXML file will be happy.

      We could optionally provide some way to specify what happens if the variable is not passed in (i.e. either fail with error, create the object or just ignore and keep it as null). Something like:
      <usevar name="model" type="com.mycom.myapp.Person" ifNotSet="create"/>
      <usevar name="currentUser" type="com.mycom.myapp.User" ifNotSet="error"/>
      Variables could be passed into the FXMLLoader like so:
      Map<String, Object> vars = new HashMap<String, Object>();
      vars.put("model", new Person());
      vars.put("currentUser", session.getCurrentUser()); // i.e. look up from somewhere - whatever
      FXMLLoader.load("myfxml.fxml", vars);
      And then used as per your nice expression language notation in your post:
      <FlowPane fx:id="root" xmlns:fx="http://javafx.com/fxml" fx:controller="fxmlapp.Sample">
      <children>
          <Label text="${currentUser.userName}" />
          <TextField fx:id="firstNameField" text="${person.phoneNumber}" />
      </children>
      </FlowPane>
      This keeps the RAD tools happy as the type of the objects are defined so they can do introspection, etc. But it also gives the developer much more control over their design - they can choose to use a model, or multiple models, or the controller, or their domain beans, or whatever - FXML is not dictating the design anymore.

      I would really (really!) love to see this idea extended to the controllers as well. Currently there is a far too intimate relationship between the FXML and the controller. The view (FXML) explicitly defines the implementation of the controller it will use. This really limits our architectural options and does not lead to clean design on our end. Why not allow the user to pass in any number of arbitrary 'controllers' as named variables that can then be used in the FXML?
      <!-- the developer decides how their controllers are structured and how they are named -->
      <usevar name="personController" type="com.mycom.myapp.PersonController"/>
      <usevar name="userController" type="com.mycom.myapp.UserController"/>
      Then attach these to events:
      <Button text="Logout" onAction="${userController.logout}"/>
      <Button text="Add User" onAction="${personController.addUser}"/>
      Much more flexible - and the really great thing about all this is the controller is not instantiated by the FXML. I can load my controller from anywhere (a Spring or JNDI context for example) and pass it into the FXML. If I want to have a controller manage three different FXML views I can pass the same instance into all three. If I want to use the same FXML file but with two different controller implementations, I can call the load twice but with different vars.

      Even better, for testing I can create a mock controller which implements my controller interface and pass this into the FXML. I can test my GUI in different error states and with different amounts and types of data in it! (Going overboard but you could even have a little scripting engine in your RAD tool that allowed you to dummy up an implementation of the variables so you could test it with data from within the RAD tool!).

      Another small plus in this is that we could completely do away with the need for the @FXML annotation.

      For fx:includes we could pass the variables to the sub-xml. Something like:
      <fx:include source="mysubfxml.fxml">
          <var name="controller" value="${controller}"/>
          <var name="model" value="${person}"/>  <!-- 'model' is the var defined in the sub FXML, person defined in the calling FXML
          <var name="someOther"> <SomeOtherBean testValue="whatever"/> </var>
      </fx:incliude>
      And why not take it the distance, we could also use this same variable approach for resources/internationalisation. If we pass in a variable of type ResourceBundle then the FXMLLoader would just know to call getString() to resolve it.
      <usevar name="myResources" type="java.util.ResourceBundle"/>
         
      <Button text="${myResources.logout}" onAction="${userController.logout}"/>
      <Button text="${myResources.addUser}" onAction="${personController.addUser}"/>
      This gives us the same nice syntax all the way through (no need for # %) and also allows the developer to do more powerful stuff with their resources. For example, I can just change the type of the resources to a bean with Observable properties for each resource and now I can dynamically change the language of my running app!

      I personally think this would add a lot of power and flexibility to the FXML space. I'm interested to hear peoples' thoughts.

      Cheers,
      zonski
        • 1. Re: FXML suggested improvements
          894969
          This is perfect and makes a FXML file way less hardwired. Being able to rewire the models the FXML works with is perfect for the cache of context based dynamically activated models described in the following link by Stardrive Engineering:

          http://fxexperience.com/2011/10/fxml-why-it-rocks-and-the-next-phase/comment-page-1/#comment-32807

          All the ideas here are excellent. What does it take to add them to FXML? What do you say Greg, is it doable?

          Edited by: 891966 on Oct 18, 2011 12:44 AM
          • 2. Re: FXML suggested improvements
            MiPa
            I fully second zonskis proposal. The details certainly have to be further examined but the overall direction is right and would solve a lot of problems.
            • 3. Re: FXML suggested improvements
              885691
              I'm not sure how much value "declaring" variables would offer. FXML isn't statically typed, nor is it compiled (and neither are most of the scripting languages you are likely to use within an FXML document).

              However, you may be able to accomplish much of what you describe already using the FXML loader's namespace:

              FXMLLoader fxmlLoader = new FXMLLoader();
              URL location = getClass().getResource("myfxml.fxml");
              fxmlLoader.getNamespace().put("model", new Person());
              fxmlLoader.getNamespace().put("currentUser", session.getCurrentUser());
              fxmlLoader.load(location.openStream());

              This should definitely work for top-level variables (i.e. variables you pass to your root document). It doesn't currently work for includes, but I have been working on a possible solution to that.

              G
              • 4. Re: FXML suggested improvements
                885691
                By the way, this wouldn't work:
                <Button text="Logout" onAction="${userController.logout}"/>
                <Button text="Add User" onAction="${personController.addUser}"/>
                The "${...}" syntax is used for expression binding. It doesn't apply to event handlers. You could do it like this, though:

                <Button text="Logout" onAction="userController.logout()"/>
                <Button text="Add User" onAction="personController.addUser()"/>
                • 5. Re: FXML suggested improvements
                  zonski
                  Greg!

                  Sometimes I think you keep these little magical API wonders from me just so when you do reveal them I am even more excited about them. You have already implemented an easy way to inject controllers! If you had told me about this earlier I would have stopped stirring weeks ago ;)

                  Just in case anyone missed it, you need to specify the scripting language (as JavaScript) in the FXML for Greg's last post to work. Here's a working example:
                  public class TestApp extends Application
                  {
                      public static void main(String[] args)
                      {
                          Application.launch(args);
                      }
                  
                      public void start(Stage stage) throws Exception
                      {
                          URL location = getClass().getResource("/test.fxml");
                          FXMLLoader fxmlLoader = new FXMLLoader();
                          fxmlLoader.getNamespace().put("controller", new Controller());
                          Parent rootNode = (Parent) fxmlLoader.load(location.openStream());
                          Scene scene = new Scene(rootNode, 800, 600);
                          stage.setScene(scene);
                          stage.show();
                      }
                  
                      //-------------------------------------------------------------------------
                  
                      public class Controller
                      {
                          public void sayHello()
                          {
                              System.out.println("Hello");
                          }
                      }
                  }
                  <?xml version="1.0" encoding="UTF-8"?>
                  
                  <?language javascript?>
                  
                  <?import javafx.scene.layout.*?>
                  <?import javafx.scene.control.*?>
                  
                  <VBox>
                      <children>
                          <Button text="Say Hello" onAction="controller.sayHello()"/>
                      </children>
                  </VBox>
                  This also solves the problem of FXML binding not working for inherited classes (http://javafx-jira.kenai.com/browse/RT-16722). This was a major bummer for me as I've had to duplicate stuff in all my controller classes that should have been in base classes - awesome!

                  It's a damn shame we haven't got bi-directional binding yet. This would all really solve the model problem too then - for now I guess we stick with the @FXML annotation and binding our controls back to control variables in the controller.

                  Greg, I will be using this completely for my controller loading now on but I can't help but feel it is a little like a 'back door'. In particular this will make RAD tool support harder as the controllers are untyped. You say:
                  FXML isn't statically typed, nor is it compiled (and neither are most of the scripting languages you are likely to use within an FXML document)
                  But the RAD tools are doing exactly that - using static typing (i.e. the statically defined controller class) and introspection to link the FXML to the code and identify errors, etc. In Richard's post he actually lists this as one of the driving factors:
                  Now of course, we want to make all this tool-able. So in a RAD tool you can load up the controller (presentation model) for an FXML document and then bind things directly in the tool. It will introspect and find all the properties exposed on the PM and offer these as bind targets and make this all nice and easy.
                  The namespace approach you described breaks this completely. This was the only reason I suggested declaring the variables.

                  I probably won't be using a RAD tool (I'm old school and stuck in my ways) but I would like my IDE to highlight problems in my FXML and help me with tab-completion. I'll also probably get some of the more junior members of my team using the RAD tool at some point (kids these days!). As such, I'm still voting for something like I suggested above - basically I want a way to inject controllers (and other things) but still keep static typing to keep IDEs and RAD tools happy.

                  Still, I'm pretty happy with the fact that I can properly inject controllers now. A day of re-writing all my code is now in order I think. :)

                  Cheers,
                  zonski
                  • 6. Re: FXML suggested improvements
                    885691
                    A RAD tool could do something similar with the namespace. Although the namespace itself isn't statically typed, a RAD tool could inspect the contents of the namespace and use reflection to determine the types/properties of the values it contains.
                    • 7. Re: FXML suggested improvements
                      zonski
                      But as I understand the usage, the namespace won't be populated when the RAD tool is being used? The namespace will be set by the code at runtime, and assuming the RAD tool is like ever other RAD tool (just how rad is it?) it will be providing some sort of sandbox for the designers to build their GUIs without needing the backing code. That's usually the point of them anyway from my experience.

                      I'm basing a lot of this on Richard's blog post though, you guys seem to be talking slightly different themes from each other - though that could easily just be me being confused (it's not uncommon).
                      • 8. Re: FXML suggested improvements
                        MiPa
                        Would it be feasible to extend FXML in such a way that, instead of specifiying a controller class for each file, you specify a name of a controller instance in the namespace?
                        • 9. Re: FXML suggested improvements
                          895253
                          A big thank you to zonski for raising this issue. I've been banging my head to the wall all day trying to figure out how to regain control of my controller while still being able to use the beautiful FXML approach.

                          The namespace solution completely fixes this for me, and your posts here sum up perfectly my view on this. Please, please, JavaFX guys, enable us to push whatever objects we want to our FXML, and don't tie us to implicit constructs like the controller in 2.0.
                          • 10. Re: FXML suggested improvements
                            885691
                            Let me try to clarify the intent of the controller in FXML. In other systems, such as Flex and WPF, markup is compiled into a class. For example, if I create a file named LoginForm.mxml, this gets compiled to a class named LoginForm, just as if I had written it in ActionScript. The same goes for XAML files in WPF.

                            There are a number of drawbacks to this approach:

                            - It creates a hard association between the UI and the controller logic. In essence, the UI element is the controller. This does not facilitate a clean separation of concerns.

                            - It precludes dynamic generation of markup; for example, an enterprise app that automatically generates forms from DB tables/configuration info specified on the server. We can do this with FXML because it is processed dynamically.

                            - It precludes the use of a shared controller. For example, if I want to write an app that targets both desktop and mobile, my controller logic might be the same, but I want to create multiple markup files for the UI that are optimized for each target device. I can do this by attaching the same controller to two or more FXML documents.

                            So, the controller approach couples the benefit of compile-time type safety for your application logic with the flexibility of dynamic UI specification for presentation. It also promotes a clean separation of concerns between UI definition and UI behavior, allowing team resources to work more independently.

                            Re: multiple controllers - my suggestion would be to take a look at your markup and see if it could be factored into includes. Since each FXML file gets its own controller and namespace, that would be the easiest way to accomplish it. You may find that there are other benefits to refactoring as well.

                            I suspect that many of these issues will be resolved when we add support for controller injection and the implicit "controller" variable in the document's namespace. I think this will introduce a lot more flexibility in what can be done with the controller.

                            Hope this helps,

                            G

                            Edited by: Greg Brown on Oct 19, 2011 8:18 AM

                            Edited by: Greg Brown on Oct 19, 2011 8:19 AM
                            • 11. Re: FXML suggested improvements
                              MiPa
                              But one question then remains - what exactly is your proposal to "add support for controller injection"?
                              • 12. Re: FXML suggested improvements
                                885691
                                Ah, sorry. :-) I have implemented the controller factory approach we discussed a while back. So you'll be able to provide an optional factory that will be consulted before the default path for controller construction is executed.
                                • 13. Re: FXML suggested improvements
                                  MiPa
                                  Great - but now I am curious when mere mortals like us will be able to get this between our fingers. Hope we don't have to wait till 2013 :-)
                                  • 14. Re: FXML suggested improvements
                                    895253
                                    My problem is this:

                                    I'm composing complex forms with JavaFX right now. I'm using a Guice-constructed controller to abstract away the database. With the namespace approach, I'm now able to push my custom made controller instance into the FXML for use there. Using FXML ${} syntax, I'm able to pull the values from the custom controller to the GUI.

                                    But my custom made controller is not able to access the GUI components, to for example get the value of a text field on screen, which it needs in order to save it to the database. @FXML annotations only work on the implicit controller specified in the FXML.

                                    So, as far as I can see, only half of the problem is actually solved by the namespace approach, given that the objects inserted into the FXML namespace don't have any access to the actual components on screen, the communication goes one way.

                                    Am I missing something obvious here?
                                    1 2 3 Previous Next