FXML Patterns / best practices
Greg Brown
greg.x.brown at oracle.com
Thu Feb 23 07:29:40 PST 2012
Here's a working example that demonstrates one way this could be implemented using FXML. It won't work on 2.1 since it uses bi-directional binding and the controller enhancements I have proposed, but you should be able to get a good idea for how it works from the source code.
The attached archive includes the following files:
- PersonModel.java - person model (complete implementation of your example PersonModel class)
- person_view.fxml - markup for person view
- PersonViewController.java - controller for person view
- PersonPresenter.java - presenter implementation that uses PersonModel and person_view.fxml
- Gender.java - enum definition for gender
- PresenterExample.java - sample launcher application
A screen shot of the running application is also attached.
Note that everything works as expected with one exception - since ChoiceBox does not define a "selectedItem" property, it was not possible to declaratively create the binding to the "controller.model.gender" property. Hopefully this will be resolved in a future release.
Let me know if you have any questions.
Greg
-------------- next part --------------
-------------- next part --------------
On Feb 23, 2012, at 3:05 AM, Daniel Zwolenski wrote:
> 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;
> }
More information about the openjfx-dev
mailing list