FXML Patterns / best practices (original) (raw)
Greg Brown greg.x.brown at oracle.com
Thu Feb 23 07:32:31 PST 2012
- Previous message: FXML Patterns / best practices
- Next message: FXML Patterns / best practices
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
The mail server appears to have stripped the attachments. You can find them here:
http://dl.dropbox.com/u/49180229/src.zip http://dl.dropbox.com/u/49180229/presenter_example.png
On Feb 23, 2012, at 10:29 AM, Greg Brown wrote:
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) - personview.fxml - markup for person view - PersonViewController.java - controller for person view - PersonPresenter.java - presenter implementation that uses PersonModel and personview.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
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; ... 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() { public void handle(ActionEvent event) { presenter.delete(); } }); getChildren().add(saveButton); saveButton= new Button("Save"); saveButton.setOnAction(new EventHandler() { 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 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 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() { 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; }
- Previous message: FXML Patterns / best practices
- Next message: FXML Patterns / best practices
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]