views:

690

answers:

3

If I am using the MVP pattern with GWT, as in the GWT architecture best practices talk from Google I/O from 2009, but have spread out the information into multiple widgets, how should the value object be populated?

Say I have a EditPersonView/Presenter, a EditPetView/Presenter and an EditAddressView/Presenter and the last two are widgets as a part of a panel in the EditPersonView. With these I have the following class:

class PersonDetails {
    private PetDetails pet;
    private AddressDetails addressDetails;

    // ...
}

The PetDetails and AddressDetails instance variables are managed in their presenter counterparts. When the user clicks the "Save" button in the EditPersonView, how should the communication between the widgets be done so that the PersonDetails is filled with information from its child widgets?

+3  A: 

If you look at page 42 of the presentation by Ray Ryan from Google IO 2009 you should find the solution to your question. You use an "event bus" (shared instance of HandlerManager) and fire your custom PetDetailsChangedEvent event and listen for that event from your child widgets (page 45). Also, remember that while decoupling, etc is great and all, some coupling is not a bad thing and might actually be a better solution than trying to force everything to be loosely coupled - RR says so in that presentation himself :)

Igor Klimer
Yeah, I can see how the response from the server can be propagated out to the application through an event. However, I'm having a hard time finding a solution for the other way around, that is collecting information from the different widgets and then send a "savePerson" RPC call. The only examples I have found are pretty trivial with a getName() method on the view for example.
Arthur
You could keep the different objects in sync on the client with the same Event Bus. Have every change to the object (e.g. via a setter method) trigger a "modelChange" event, which all the involved widgets listen for. Then they update the object. By the time you have to send a "savePerson" call, they're all in sync.
Bluu
I agree with Bluu's comment. However, depending on the app, you might want to change your approach a little - you could first save the new data to the server and then from there issue a "modelChanged" type of event (for example with the help of cometd, ape-server or the push module for nginx). What you would gain is that if you have many instances of the app opened in different tabs in the browser, they all get synchronized (useful for stuff like chat, mail, etc clients - it all depends on how your users will use your application).
Igor Klimer
Keeping the objects synced with the UI during the editing process I think creates a very "brittle" dialog and I will also need to attach event handlers to all controls.What I want to do is as soon as the user clicks "Save", all the data from the dialog should be collected and then sent as a request to the service. I could have getters on the widgets but I figured the event mechanism could solve this for me (just letting new widgets attach itself to the event bus). However, conditions are hard to manage. For example, only collect information from a control if a checkbox is checked.
Arthur
The way I have solved it right now is to have a PopulatePersonDetailsEvent which components around the dialog can handle and populate with the information they have (for example the EditPetView does an event.getPersonDetails().setPetDetails(new PetDetails(...))).However, I find conditions to be quite hard to deal with with this solution. Say there is a check box at the top which enables/disables the person editing view. The value of the check box has to be passed along with the event and therefor needs to be fetched on beforehand.
Arthur
Forgot to mention that if the check box is not checked, the pet details should not be set in the person details...
Arthur
A: 

I've faced this same problem in a few different GWT applications that I've designed using Ray Ryan's approach. My preferred solution is to create a Singleton "session object" that stores the state of that part of the application. In your example, it might look like this:

interface EditPersonSession {

    void fetchPerson(PersonId id);
    PersonDetails getCurrentPersonDetails();
    void updatePersonDetail(PersonDetail<?> detail);
    void updatePetDetail(PetDetail<?> detail);
    void updateAddressDetail(AddressDetail<?> detail);
    void save();

}

All three presenters contain a reference to the session object (perhaps injected by Gin). Whenever the UI (view) is manipulated by the user, the presenter associated with that view immediately pushes the state to the shared session object. For example, inside EditAddressPresenter:

view.getStreetNameTextBox().addValueChangeHandler(new ValueChangeHandler() {

    void onValueChange(ValueChangeEvent<String> event) {
        editPersonSession.updateAddressDetail(new StreetNameAddressDetail("Short Street"));
    }

}

When it is time to save, the state object is told to save the state to the server. At this point, the session object has up-to-date representations of the data, and can save it all at once. So, in EditPersonPresenter:

view.getSaveButton().addClickHandler(new ClickHandler() {

    void onClick(ClickEvent event) {
        editPersonSession.save();
    }

}

This way, the presenters need not contain any references to each other, but can send consistent information to the server. If the presenters need to know when information that they display has been updated (either by other presenters, or by the server), the session object can notify them by firing events on the event bus (shared Singleton HandlerManager). The presenters can then pull the most current PersonDetails from the session object.

Daniel
Hmm.. Interesting solution. What I am not crazy about is the fact that I will need to register value change handlers for all elements which may not get me a snapshot representation of the dialog as if I were to get the values when doing the editPersonSession.save() method call. But I guess it will do. Thanks! :)
Arthur
No problem :) . As long as you make sure that you push the values from the presenters to the session object as soon as any value in the views change, the session object should always have an up-to-date representation of the data in the views when you call save().
Daniel
A: 

I've also come to the conclusion that I can have one model that corresponds to each presenter. So a PetWidget may create a Pet instance and a PersonWidget may create a Person instance. The PersonWidget may then contain one or more PetWidgets which in turn means that the Person class can have a list of Pet instances.

Arthur