FXML Patterns / best practices
Greg Brown
greg.x.brown at oracle.com
Thu Feb 23 07:32:31 PST 2012
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)
> - 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
>
>
>
>
>
> 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