As you may have guessed, the MVP framework consists of code that goes in the Model, code that goes in the View, and code that goes in the Presenter. What we created last time was basically the Model part (Contact, ContactLight, ContactService, etc).
Groundwork
Now it is time to create the Presenter and View parts. To keep things nice and tidy, let's create two new packages where we will place the presenters and views respectively:- gaej.example.contacts.client.presenter
- gaej.example.contacts.client.view
Let's create a Presenter interface in the new presenter package:
package gaej.example.contacts.client.presenter;
import com.google.gwt.user.client.ui.HasWidgets;
public interface Presenter {
public void go(final HasWidgets container);
}
ContactsPresenter
With our interface ready, we can go ahead and create our first presenter class that will list all the contacts we have, and do things if we click the 'Add Contact' button and the 'Delete Contact' button, or when we click a particular contact (for editing). Let's call this presenter the ContactsPresenter:package gaej.example.contacts.client.presenter;
import gaej.example.contacts.client.ContactsServiceAsync;
import java.util.List;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
public class ContactsPresenter implements Presenter {
public interface Display {
HasClickHandlers getAddButton();
HasClickHandlers getDeleteButton();
HasClickHandlers getList();
int getClickedRow(ClickEvent event);
List<integer> getSelectedRows();
void setData(List<String> data);
Widget asWidget();
}
private final HandlerManager eventBus;
private final ContactsServiceAsync rpcService;
private final Display display;
public ContactsPresenter(HandlerManager eventBus, ContactsServiceAsync rpcService, Display display) {
this.eventBus = eventBus;
this.rpcService = rpcService;
this.display = display;
}
@Override
public void go(HasWidgets container) {
container.clear();
container.add(display.asWidget());
}
}
So what do we have here? First, we defined an interface for all the widgets that our screen will have. This interface will be used by our future View class (which we will probably call ContactsView - but more on that later).
The important widgets we will draw on the view are an add button, a delete button, and a contact list. These widgets will all raise an event if clicked, so hence the three HasClickHandlers methods. Besides these, we also want to generate an event when we click a contact in the list. Once clicked, we will need to know which contact was clicked, and that's why we have getClickedRow(ClickEvent event) there. For the delete function we wanted to be able to select multiple contacts via a check box, and then delete them in one go. To identify all the contacts that we selected, we have the method getSelectedRows(). The setData(List<String> data) method is to get the list of contacts that we want to display on the screen. Finally, we will use the asWidget() method to return the future view to us as a Widget object.
Next we have the constructor for our ContactsPresenter object. The constructor takes three parameters: the eventBus, the rpcService, and the display. The eventBus handles all the events that take place on our screens. When a user clicks on a widget, the event will be stored in the eventBus. There, listeners will be able to see the event, and perform actions against it. An example action can be the deletion of a contact. That is where the rpcService comes into play. We will use this service to create, retrieve, update, and delete contacts. The last parameter is the display, and this one will be the concrete object representing the contacts view. Take note that this object must implement our new Display interface!
The display is "painted" on our screen when the go(HasWidgets container) method is called. This method takes a HasWdigets container as parameter, so that we can add our ContactsPresenter to any container that we want. Since our application is simple, we will not have many containers, and what we will pass in will probably be the RootPanel. In any case, the first line of this method clears the panel, and the second line adds the presenter's display to it.
So what does this display look like? Let's create our ContactsView...
ContactsView
Our ContactsView will look like follows:package gaej.example.contacts.client.view;
import java.util.List;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DecoratorPanel;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Widget;
import gaej.example.contacts.client.presenter.ContactsPresenter;
public class ContactsView extends Composite implements ContactsPresenter.Display {
private final Button addButton;
private final Button deleteButton;
private FlexTable contactsList;
private final FlexTable contentTable;
private static final int CHECKBOX_POSITION = 0;
private static final int DATA_POSITION = 1;
public ContactsView() {
DecoratorPanel contentTableDecorator = new DecoratorPanel();
initWidget(contentTableDecorator);
contentTableDecorator.setWidth("100%");
contentTableDecorator.setWidth("18em");
contentTable = new FlexTable();
contentTable.setWidth("100%");
contentTable.getCellFormatter().setStyleName(0, 0, "contacts-ListContainer");
contentTable.getCellFormatter().setWidth(0, 0, "100%");
contentTable.getFlexCellFormatter().setVerticalAlignment(0, 0, DockPanel.ALIGN_TOP);
// Create the menu
HorizontalPanel menuPanel = new HorizontalPanel();
menuPanel.setBorderWidth(0);
menuPanel.setSpacing(0);
menuPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
addButton = new Button("Add");
menuPanel.add(addButton);
deleteButton = new Button("Delete");
menuPanel.add(deleteButton);
contentTable.getCellFormatter().addStyleName(0, 0, "contacts-ListMenu");
contentTable.setWidget(0, 0, menuPanel);
// Create the Contacts List
contactsList = new FlexTable();
contactsList.setCellSpacing(0);
contactsList.setCellPadding(0);
contactsList.setWidth("100%");
contactsList.addStyleName("contacts-ListContacts");
contactsList.getColumnFormatter().setWidth(0, "15px");
contentTable.setWidget(1, 0, contactsList);
contentTableDecorator.add(contentTable);
}
@Override
public Widget asWidget() {
return this;
}
@Override
public HasClickHandlers getAddButton() {
return addButton;
}
@Override
public int getClickedRow(ClickEvent event) {
// TODO Auto-generated method stub
return 0;
}
@Override
public HasClickHandlers getDeleteButton() {
return deleteButton;
}
@Override
public HasClickHandlers getList() {
return contactsList;
}
@Override
public List<Integer> getSelectedRows() {
// TODO Auto-generated method stub
return null;
}
@Override
public void setData(List<String> data) {
contactsList.removeAllRows();
int i = 0;
for (String line: data) {
contactsList.setWidget(i, CHECKBOX_POSITION, new CheckBox());
contactsList.setText(i, DATA_POSITION, line);
i++;
}
}
}
Don't worry, its much simpler than it looks. What we have here is a ContactsView that extends the GWT Composite class. This is a type of GWT widget that can wrap another widget, hiding the wrapped widget's methods. When added to a panel, a composite behaves exactly as the widget it wraps, in this case our ContactsView.
The ContactsView is built up using a DocoratorPanel. This is a type of SimplePanel that wraps its contents in stylized boxes, which can be used to add rounded corners. We set the width of the panel and then add a FlexTable to it. To the FlexTable we will add our menu bar containing the Add and Delete buttons. Likewise, we also and our contacts list to it. That's it, we have drawn our view.
The getter methods are there so that the ContactsPresenter can manipulate our view. Most methods simply return the view's properties, except for getSelectedRows() and setData(List<String> data). The getSelectedRows() we will just leave stubbed for now (we will get back to that one later). The setData(List<String> data) method is there to display the records of contacts on the view. For each line in our list, we will draw a CheckBox, and present the data.
That is the ContactsView for now. Let's get back to the AppController we briefly mentioned a while back.
AppController
The AppController is in essence the central Presenter that will tie all our other presenters together. For now we only have a ContactsPresenter, so that will be the only one we will add to it. This is what our initial ContactsPresenter looks like:package gaej.example.contacts.client;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.ui.HasWidgets;
import gaej.example.contacts.client.presenter.Presenter;
import gaej.example.contacts.client.ContactsServiceAsync;
import gaej.example.contacts.client.presenter.ContactsPresenter;
import gaej.example.contacts.client.view.ContactsView;
public class AppController implements Presenter {
private final HandlerManager eventBus;
private final ContactsServiceAsync rpcService;
private HasWidgets container;
private ContactsPresenter contactsPresenter;
AppController(HandlerManager eventBus, ContactsServiceAsync rpcService) {
this.eventBus = eventBus;
this.rpcService = rpcService;
bind();
}
private void bind() {
contactsPresenter = new ContactsPresenter(eventBus, rpcService, new ContactsView());
}
@Override
public void go(HasWidgets container) {
this.container = container;
contactsPresenter.go(this.container);
}
}
As you can see, this class also implements the Presenter interface. We defined a constructor that takes two parameters: the eventBus, and the rpcService. The constructor calls the private method bind() which creates an instance of our ContactsPresenter. When creating this instance, we pass to it the eventBus, the rpcService, and a newly created ContactsView instance.
Since the AppController is basically a Presenter, it also overrides the go(HasWidgets container) method. This method basically passes on the container that was received onwards to the ContactsPresenter#go(HasWidgets container) method. As we already saw in the previous section, this method basically adds the ContactsView display to the container, so that it gets painted on the screen.
Alright, so we have seen that the AppController creates the ContactsPresenter, it's associated ContactsView, and displays it on screen via its go(HasWidgets container) method. So who creates the eventBus and the rpcService that its constructor takes as parameters? Well, the EntryPoint does!
The EntryPoint
The EntryPoint is an interface that acts much like the static main() method. Any class that implements it should have an onModuleLoad() method, which is the GWT entry point. In other words, it all starts from here when the GWT module loads. In our case, that is the samplecontactsapp module (see SampleContactsApp.gwt.xml).Since the class that implements the EntryPoint interface is SampleContactsApp, let's add the necessary code there:
package gaej.example.contacts.client;
import gaej.example.contacts.client.AppController;
import gaej.example.contacts.client.ContactsService;
import gaej.example.contacts.client.ContactsServiceAsync;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.ui.RootPanel;
/**
* Entry point classes define onModuleLoad().
*/
public class SampleContactsApp implements EntryPoint {
@Override
public void onModuleLoad() {
ContactsServiceAsync rpcService = GWT.create(ContactsService.class);
HandlerManager eventBus = new HandlerManager(null);
AppController appViewer = new AppController(eventBus, rpcService);
appViewer.go(RootPanel.get());
}
}
So here we create our rpcService, our eventBus, and our AppController. Notice also that we pass in the RootPanel to our AppController. In other words, the RootPanel goes to the AppController, and the AppController will pass it on to the ContactsPresenter. The ContactsPresenter will then clear it, and add the ContactsView display to it.
Simple no? Fire up the application and navigate to it. Let's see what it looks like so far:
What?? All this work for just that? Well yeah, but later you will see how useful the MVP framework becomes. For now, though, let's focus on putting our RPC Service to work so we get to display our mock data.


No comments:
Post a Comment