FXML Patterns / best practices

Dr. Michael Paus mp at jugs.org
Thu Feb 23 05:58:23 PST 2012


Hi Dan,

concerning your pseudo-code example I would like to know if you really 
insist
on the view class to be a Node (VBox in your example) or whether it could
equally well be a regular class which just contains a node attribute? When
you hook up your view to your apps scene graph it should not matter in
my opinion whether you add myView directly or myView.getViewNode().

Your PersonView class could then look like this:
(I shortened it a bit but you get the idea.)

public class PersonView {

     @Inject private PersonModel model;
     @Inject private PersonPresenter presenter;

     private VBox root;

     private TextField firstNameField;
     private TextField lastNameField;
     private ChoiceBox genderField;
     private Button deleteButton;
     private Button saveButton;

     @PostConstruct void init() {

         try {
             root = FXMLLoader.load(getClass().getResource("myview.fxml"));

             firstNameField = (TextField)root.lookup("#firstNameField");
             
firstNameField.textProperty().bindBidirectional(model.firstName);

             ...

             deleteButton = (Button)root.lookup("#deleteButton");
             deleteButton.setOnAction(new EventHandler<ActionEvent>() {
                 public void handle(ActionEvent event) {
                     presenter.delete();
                 }
             });

             ...

         } catch (IOException e) {
             e.printStackTrace();
         }
     }

     public Node getRoot() {
         return root;
     }

}

FXML will provide you with the whole visual part of your views scene graph
and in the init() method you just do the wiring. When the view itself is not
a node you should also have no problem with injection.

What do you think?

Michael

Am 23.02.2012 09:05, schrieb Daniel Zwolenski:
> I'm spawning off a new thread for this, so Greg's discussion on the
> Controller base class can continue uninterrupted.
>
> I just wrote a huge email outlining all the pros/cons of clean MVP and
> how/where I think FXML is causing me to compromise, etc. It was too long
> and too complicated, so back to basics. Below I define a very pure MVP
> implementation (in psuedo code) of a Person Component using pure Java. I'll
> avoid defining interfaces at this point for ease, but ideally I would do
> this as well (so I can use mock objects when testing). I've used a
> standard, common, popular pattern that I believe a lot of people would like
> to use if they could.
>
> What I'd like to know is how people intend to implement something like the
> below using FXML. I'm looking for either the closest approximation of the
> below using FXML, or if you think you have a better pattern, then how you
> would implement this. Based on that I'd then like to look at what
> advantages/disadvantages your proposed design has over the 'pure' MVP
> approach below (or tell me what you'd do differently if you don't think the
> below is pure MVP!).
>
> The key benefits that I see from the below architecture are:
>
> 1. *Seperation of concerns*: the relevant implementation details are very
> well contained within each of the correct elements, view in View,
> presentation logic in Presenter, model stuff in Model.
> 2. *Unit Testability*: the well-defined units are easily decoupled from
> each other and can be tested in isolation allowing for clean unit tests
> 3. *Reusability*: each element of the MVP bundle is reusable, so you could
> attach a different View (e.g. a mobile specific one) to the Presenter, etc
>
> Can we retain these benefits in an FXML-based architecture, or do we have
> to give them up?
>
>
> *Model*
>
> class PersonModel {
>
>      private LongProperty personId;
>      private TextProperty firstName;
>      private TextProperty lastName;
>      private ObjectProperty<Gender>  gender;
>
>      ... constructor, getters, setters ...
> }
>
> *View*
>
> class PersonView extends VBox {
>
>      @Inject private PersonModel model;
>      @Inject private PersonPresenter presenter;
>
>      private TextField firstNameField;
>      private TextField lastNameField;
>      private ChoiceBox genderField;
>      private Button deleteButton;
>      private Button saveButton;
>
>      @PostConstruct void init() {
>
>          firstNameField = new TextField();
>
> firstNameField.textProperty().bindBidirectional(model.firstNameProperty());
>          getChildren().add(firstNameField);
>
>          lastNameField = new TextField();
>
> lastNameField.textProperty().bindBidirectional(model.lastNameProperty());
>          getChildren().add(lastNameField);
>
>          genderField= new ChoiceBox(Gender.values());
>
> genderField.selectedValueProperty().bindBidirectional(model.genderField());
>          getChildren().add(genderIcon);
>
>          deleteButton= new Button("Delete");
>          deleteButton.setOnAction(new EventHandler<ActionEvent>() {
>              public void handle(ActionEvent event) {
>                  presenter.delete();
>              }
>          });
>          getChildren().add(saveButton);
>
>          saveButton= new Button("Save");
>          saveButton.setOnAction(new EventHandler<ActionEvent>() {
>              public void handle(ActionEvent event) {
>                  presenter.save();
>              }
>          });
>          getChildren().add(saveButton);
>      }
> }
>
> *Presenter*
>
> class PersonPresenter {
>
>      @Inject private PersonModel model;
>      @Inject private PersonView view;
>      @Inject private PersonService personService; // e.g. an RMI-like
> handler onto a server
>
>      // this is the external method called by whatever wants to show a person
>      void showPerson(final long personId) {
>          new Thread(new Task<PersonDTO>() {
>              PersonDTO call() {
>                  return personService.getPersonDetails(personId);
>              }
>
>              void onSuccess(PersonDTO details) {
>                  model.setPersonId(details.getPersonId());
>                  model.setFirstName(details.getFirstName());
>                  model.setLastName(details.getLastName());
>                  model.setGender(details.getGender());
>              }
>          }).run();
>      }
>
>      void delete() {
>          final long personId = model.getPersonId();
>          new Thread(new Task<Void>() {
>              void call() {
>                  personService.deletePerson(personId);
>                  return null;
>              }
>
>              void onSuccess(Void result) {
>                  // probably navigate away from the current view
>              }
>          }).run();
>      }
>
>      void save() {
>          final PersonDTO details = new PersonDTO(
>                  model.getPersonId(),
>                  model.getFirstName(),
>                  model.getLastName()
>                  model.getGender()
>          );
>
>          new Thread(new Task<PersonDTO>() {
>              void call() {
>                  return personService.updatePerson(details);
>              }
>
>              void onSuccess(Void result) {
>                  // probably show a nice confirmation message
>              }
>          }).run();
>      }
> }
>
>
> The above assumes the existence of some sort of factory method that wires
> everything up. This would ideally be Spring or Guice but could just as
> easily be done by hand like so:
>
> public PersonPresenter createPersonPresenter() {
>
>      PersonModel model = new PersonModel();
>      PersonView view = new PersonView();
>      PersonPresenter presenter = new PersonPresenter();
>
>      view.setModel(model);
>      view.setPresenter(presenter);
>
>      presenter.setModel(model);
>      presenter.setView(view);
>      presenter.setPersonService(getPersonService());
>
>      return presenter;
> }


-- 
--------------------------------------------------------------------------------------
Dr. Michael Paus, Chairman of the Java User Group Stuttgart e.V. (JUGS).
For more information visit www.jugs.de.



More information about the openjfx-dev mailing list