Blog

GWT

  • GWT: Working with Google Calendar using gwt-gdata by Filip Kujikis
    in GWT
    2 Aug 2011  | 8 Comments

    A recent showcase for one of our products included basic integration with Google Calendar through GWT. Here we aim to present a simple step by step guide to achieving basic interaction with a Google Calendar through GWT. 

    Google allows access to Calenader via the Data API, available as a library for .NET, Java, JavaScript, PHP and Python clients. We will use the gwt-gdata API, which is a GWT wrapper for the JavaScript Data API. 

    Our demo application

    Users of our application will be able to:

    • authenticaticate to a calendar
    • query the events in a calendar
    • create, delete and edit calendar events
    • view a read only (public) version of the calendar

    1. Setting up

    1.1 Calendar

    We will need to create a calendar to work with against your google account. If you do not have an account, you can create one here (https://www.google.com/accounts/NewAccount?service=mail).

    Creating a new Calendar:

    • Sign in to calendar (calendar.google.com) using your google account.
    • Click on Add under your list of available calendars.

    • Give your calendar a name - GWTCalendar for example.
    • Select "Make this calendar public".
    • Optionally under Share with specific people enter the google accounts of additional users that should have authenticated access to the calendar. Set their permissions to Make changes AND manage sharing. This step configures the users that will be able to create and edit events in the calendar. If no additional google accounts are added, only the google account that owns the calendar will be able to create/edit events.

    • Finally click Create Calendar.

    1.2 Development Environment

    This tutorial assumes you have Eclipse & the GWT plugin installed and ready to create GWT projects.

    • create a new GWT project for the sample Calendar application
    • download gwt-gdata-2.2.1.zip from http://code.google.com/p/gwt-gdata/
    • extract gwt-gdata.jar from the archive and add it to the project build path
    • make sure your project inherits the module com.google.gwt.gdata.GData
    • append the following line to your .gwt.xml file:

    <inherits name='com.google.gwt.gdata.GData' />

    2. Getting a view of our calendar

    To start with, let's get a read only view of the calendar displaying in our application. Whenever we make changes to the calendar we can refresh this view and see the result of our work immediately.

    • Under "My calendars" in the main home page of your calendar, click on the dropdown next to our newly created "GWTCalendar"
    • Select the "Calendar Settings" options
    • Under "Embed This Calendar" copy the URL in the "src" attribute from the iframe snippet. This is the URL to the public view of our calendar. You can paste it in a browser window to verify.
    • We will display a view of the calendar in an iframe - create a Frame with the calendar URL as source.

    Add the following code to your EntryPoint class:

    // Your calendar URL here!!
    private static final String CAL_PUBLIC_URL = "https://www.google.com/calendar/embed?src=phihl3hmbh48lq5ml3ek9cj19o%40group.calendar.google.com&ctz=Australia/Brisbane";
    private Frame googleCalendar = new Frame(CAL_PUBLIC_URL);

    Let's display that calendar:

    public void onModuleLoad() {
    googleCalendar.setWidth("500px");
    googleCalendar.setHeight("500px");
    RootPanel.get().add(googleCalendar);
    }

    If you run the project you should be able to see the calendar we just created:

    3. Setting up our UI

    Let's proceed by setting up the UI controls we will need for interacting with the calendar.
    We will be creating a simple event that has a title, start date and end date. So let's create a vertical panel with a text box for the title, and two date pickers specifying the start and end dates for our event. We'll also need a button which when clicked creates our event. Add the following code to your EntryPoint class:


    private TextBox eventTitle = new TextBox();
    private DatePicker startDate = new DatePicker();
    private DatePicker endDate = new DatePicker();
    private Widget createNewEventPanel(){
    VerticalPanel createPanel = new VerticalPanel();
    createPanel.add(new Label("Title:"));
    createPanel.add(eventTitle);
    createPanel.add(new Label("Event Start Date:"));
    createPanel.add(startDate);
    createPanel.add(new Label("Event End Date:"));
    createPanel.add(endDate);
    createPanel.add(new Button("Create Event",new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    //TODO create the event with the given title and duration
    }
    }));
    return createPanel;
    }

    When editing our event, we will be able to change the title or remove the event entirely. To do that we will need a textbox to specify the URL of the event we would like to edit (more on this later), a textbox for the new title, and two buttons - Update and Remove. The following code creates a simple panel consisting of those controls. Add it to your EntryPoint class.

    private TextBox newTitle = new TextBox();
    private TextBox eventUrl = new TextBox();
    private Widget createEventEditPanel(){
    VerticalPanel editPanel = new VerticalPanel();
    editPanel.add(new Label("Event URL:"));
    editPanel.add(eventUrl);
    editPanel.add(new Label("New Event Title:"));
    editPanel.add(newTitle);
    editPanel.add(new Button("Update", new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    //TODO update the event given by the URL
    }
    }));
    editPanel.add(new Button("Delete", new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    // TODO delete the event given by the URL
    }
    }));
    return editPanel;
    }


    Let's pull it all together and display our UI controls. Modify the onModuleLoad() method as follows:

    public void onModuleLoad() {
    HorizontalPanel controlsPanel = new HorizontalPanel(); 
    googleCalendar.setWidth("500px");
    googleCalendar.setHeight("500px");
    controlsPanel.add(googleCalendar);
    controlsPanel.add(createNewEventPanel());
    controlsPanel.add(createEventEditPanel());
    controlsPanel.setSpacing(10);
    RootPanel.get().add(controlsPanel);
    }

    If we run the project again our application should now look something like this:

    4. Authenticating to Google Calendar

    We are ready to start interacting with our calendar. The first step in our application should always ensure that we have the GData Calendar API loaded. If the API is not loaded we load it. This requires a slight refactoring. Firstly, we extract the contents of our onModuleLoad() method to a new method - loadModule():


    public void loadModule(){
    HorizontalPanel controlsPanel = new HorizontalPanel(); 
    googleCalendar.setWidth("500px");
    googleCalendar.setHeight("500px");
    controlsPanel.add(googleCalendar);
    controlsPanel.add(createNewEventPanel());
    controlsPanel.add(createEventEditPanel());
    controlsPanel.setSpacing(10);
    RootPanel.get().add(controlsPanel);
    }


    Next, we rewrite onModuleLoad() so that it checks for the presence of the calendar API. If it is absent we load the API by a call to GData.loadGDataApi(). At this stage we need to provide an API key from google (available freely from http://code.google.com/apis/gdata/signup.html). For the purposes of the demo, you could use the key supplied, but you should get your own for a real application. Note the asynchronous nature - we need a Runnable which will complete at some time in the future. Only once the Runnable has completed can we proceed loading our application as in the code below:

    public final static String GDATA_API_KEY = "ABQIAAAABGWvCfqj7yJHpNUWKnPf8xS33zGBuY57s7EfWCbD5ZXtDEt-shSPCo3EL0Dtuj-0TG3CmT93zHHI9Q";
    public void onModuleLoad() {    
    if (!GData.isLoaded(GDataSystemPackage.CALENDAR)) { 
    GData.loadGDataApi(GDATA_API_KEY, new Runnable() {
    public void run() {          
    loadModule(); // Load application          
    }        
    }, GDataSystemPackage.CALENDAR);    
    } else {      
    loadModule(); // Load application    
    }
    }


    Before interacting with our calendar, we must authenticate to it (either as the google account that owns the calendar, or one of the users we decided to share it with in step 1.1. To this end, the GData API uses AuthSub authentication, made available to us via the User class. When we execute the login() method of the User class, our user is taken to a standard Google login page where they can authenticate with their Google account. The user is then prompted to allow our application permission to use data (the calendar) associated with their Google account. Permission must be granted if we are to proceed successfully. We wrap the authentication step in a simple method:


    private void doAuthSubLogin() {
    AuthSubStatus status = User.getStatus(calendarUrl);
    if (status != AuthSubStatus.LOGGED_IN) {
    User.login(calendarUrl);
    }
    }

    We'll also slightly tweak loadModule() to ensure that the user has logged in to the calendar service before proceeding:


    public void loadModule(){
    doAuthSubLogin();
    // Page refreshed while logging in - no need to initialize the page further    
    if (User.getStatus() == AuthSubStatus.LOGGING_IN) return;  
    HorizontalPanel controlsPanel = new HorizontalPanel(); 
    googleCalendar.setWidth("500px");
    googleCalendar.setHeight("500px");
    controlsPanel.add(googleCalendar);
    controlsPanel.add(createNewEventPanel());
    controlsPanel.add(createEventEditPanel());
    controlsPanel.setSpacing(10);
    RootPanel.get().add(controlsPanel);
    }

    5. Accessing our calendar

    We're finally ready to start playing with our calendar. But how do we access it? Under the hood, the GData API interacts with the calendar by sending/receiving Atom or JSON-C feed  messages. Fortunately we don't need to worry about that and the GWT API abstracts the details away from us. However, because the calendar is in effect an Atom/JSON feed, it and its events are accessed through an identifying URL. A calendar can be accessed via a URL such as the one below. For more info on the calendar URL structure see [4].

    http://www.google.com/calendar/feeds/CALENDAR_ID/private/full

    ...where CALENDAR_ID identifies the calendar of interest. You can find your CALENDAR_ID in "Calendar settings" for each calendar (see the screenshot in step 2 of this post). Let's put that URL somewhere where we can reuse it (in the EntryPoint class):

    // Use YOUR OWN calendar ID in here!
    private static String calendarUrl = "http://www.google.com/calendar/feeds/phihl3hmbh48lq5ml3ek9cj19o@group.calendar.google.com/private/full";

    5.1 Inserting calendar events

    Next, we'll create a class which in essence will wrap (think Facade pattern) some of capability of the GData CalendarService class for the purposes of accessing a calendar with a specific URL. I've decided to go with the somewhat generic name of GoogleCalendarUtils. Our class will have only two member fields - the calendar URL, and an instance of CalendarService which contains all calendar capabilities. The calendar URL will be passed in, to identify the calendar we are working with, and we'll create our own instance of the CalendarService. Create a new class called GoogleCalendarUtils, and add the following code:

    private String calendarUrl;
    private CalendarService calendarService;
    public GoogleCalendarUtils(String calendarUrl){
    this.calendarUrl = calendarUrl;
    this.calendarService = CalendarService.newInstance("GWTGoogleCalendarDemoApplication");
    }

    We'll add a simple method for creating calendar events with just a title, start and end dates by creating the createCalendarEventEntry() method as below:

    private CalendarEventEntry createCalendarEventEntry(String title, Date startDate, Date endDate){
    CalendarEventEntry eventEntry = CalendarEventEntry.newInstance();
    eventEntry.setTitle(Text.newInstance());    
    eventEntry.getTitle().setText(title);    
    When when = When.newInstance();
    when.setStartTime(DateTime.newInstance(startDate));
    when.setEndTime(DateTime.newInstance(endDate));
    eventEntry.addTime(when);
    return eventEntry;
    }

    Note that you can create events that are much more complex and detailed, by examining the composition of the CalendarEventEntry class (you can additionally add event locations, participants, recurrence, etc - in essence everything you can do with a google calendar). However, for simplicity we'll stick to a date range and a title. No doubt you will have noticed this method uses the GData types like Text and When to create the event object. These are simply GWT wrapper types that map to XML Atom/JSON elements which are used under the hood.

    Let's proceed by creating a way to insert new events into our calendar. This is simple: we call createCalendarEventEntry(), and pass the resulting event to the calendar via the CalendarService instance we have (in GoogleCalendarUtils). We expose this operation via the insertCalendarEvent() method, which provides a callback that we will use to update the UI.

    public void insertCalendarEvent(String title, Date startDate, Date endDate, CalendarEventEntryCallback callback){
    CalendarEventEntry eventEntry = createCalendarEventEntry(title, startDate, endDate)
    calendarService.insertEntry(calendarUrl, eventEntry, callback);
    }

    Next, we should update our UI code to read those fancy input controls we created earlier and create a calendar entry for us. Let's revisit the createNewEventPanel() method. We created a button for new events, however left the event handler empty. Now it's time to actually do something in there. Update your createNewEventPanel() method so it now looks like this:

    private Widget createNewEventPanel(){
    VerticalPanel createPanel = new VerticalPanel();
    createPanel.add(new Label("Title:"));
    createPanel.add(eventTitle);
    createPanel.add(new Label("Event Start Date:"));
    createPanel.add(startDate);
    createPanel.add(new Label("Event End Date:"));
    createPanel.add(endDate);
    createPanel.add(new Button("Create Event",new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    String title = eventTitle.getText();
    Date start = startDate.getValue();
    Date end = endDate.getValue();
    // Use an instance of our CalendarService wrapper for our calendar.
    GoogleCalendarUtils ourCalendar = new GoogleCalendarUtils(calendarUrl);
    // Call the insert method we defined, and pass a callback to handle the response
    ourCalendar.insertCalendarEvent(title, start, end, new CalendarEventEntryCallback() {
    @Override
    public void onSuccess(CalendarEventEntry result) {
    Window.alert("The event " + result.getTitle().getText() + " was added to the calendar");
    // Refresh the static view of the calendar if we are successful
    googleCalendar.setUrl(googleCalendar.getUrl());
    }
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed to insert an event in the calendar..");
    }
    });
    }
    }));
    return createPanel;

    Try running the project now. Enter a title and select a start and end date for the event. Click Create Event. You will have run into a strange exception, something like:

    com.google.gwt.core.client.JavaScriptException: (Error): An image of the same domain is required on this page for authenticated reads and all writes.

    For some mysterious reason, the fix is exactly as given in the error message. Our HTML page must have an image displayed originating from our domain for the GData Calendar API to work. All I know about this is that there is some workaround in browser behaviour that Google utilizes to call out to their services. If anyone knows more I would love to learn the exact details as to the why and how, so please leave a comment below. Anyway, let's make this error go away. Find and add any image to your project, and link to it in your GWT project HTML document. In tribute to Google greatness and mystery I will include the Google Logo (http://www.google.com/images/logo_sm.gif). Practically speaking this can be any image, even a 1x1px dot hiding in some corner. Remember, we must serve up the image from our domain and not just link to it. So we download it, add it to our project, and link that way. I am lazy, so I will put mine in the /war directory, right next to the html document hosting our app. We then display the image in the HTML document:

    .
    .
    .
    <img src="logo_sm.gif"/>
    </body>
    </html>


    Try running the project now. Create a new event by selecting a start and end date, and entering a title in the boxes. Click Create Event to add the event to the calendar.

    Everything works! If you are still in disbelief about our image trick, go back to the HTML doc and comment out the <img> tag, rerun the project and see it crash and burn.

    5.2 Accessing the events in our calendar

    Since all operations with the calendar are done via a URL, it would be good to display the URLs for each of our events. Let's add a method to GoogleCalendar utils to do this:

    public void getEvents(CalendarEventFeedCallback callback){
    // Simply forward the caller's callback to our CalendarService instance
    calendarService.getEventsFeed(calendarUrl, callback);
    }

    Let us also add a way to display our calendar events as a list in our application. We'll use the method below any time we want to update our list of calendar events. We'll add it to our EntryPoint class:

    private FlexTable eventsDisplayTable = new FlexTable();
    private void displayCalendarEntries(CalendarEventEntry[] entries){
    eventsDisplayTable.clear();
    eventsDisplayTable.setText(0, 0, "Event Title");
    eventsDisplayTable.setText(0, 1, "Event URL");
    for(int row = 0; row < entries.length; row++){
    // Display the event's title
    eventsDisplayTable.setText(row+1, 0, entries[row].getTitle().getText());
    // Display the event's URL
    eventsDisplayTable.setText(row+1, 1, entries[row].getEditLink().getHref());
    }
    }

    And we will add a way to query our events via:

    private void getAllEvents(){
    GoogleCalendarUtils calendar = new GoogleCalendarUtils(calendarUrl);
    calendar.getEvents(new CalendarEventFeedCallback() {
    @Override
    public void onSuccess(CalendarEventFeed result) {
    displayCalendarEntries((CalendarEventEntry[])result.getEntries());
    }
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed getting calendar events..");
    }
    });
    }

    Further, let's provide a single method that updates both our calendar view and the list of events all at once. We add updateCalendarView() to our EntryPoint class:


    private void updateCalendarView(){
    // Refresh the iframe displaying our calendar
    googleCalendar.setUrl(googleCalendar.getUrl());
    getAllEvents();
    }


    Once more we modify our loadModule() method, this time to add a vertical layout panel in order to place the list of events at the bottom of our page. Change loadModule() in EntryPoint so it is now:


    public void loadModule(){
    doAuthSubLogin();
    // Page refreshed while logging in - no need to initialize the page further    
    if (User.getStatus() == AuthSubStatus.LOGGING_IN) return;
    HorizontalPanel controlsPanel = new HorizontalPanel(); 
    googleCalendar.setWidth("500px");
    googleCalendar.setHeight("500px");
    controlsPanel.add(googleCalendar);
    controlsPanel.add(createNewEventPanel());
    controlsPanel.add(createEventEditPanel());
    controlsPanel.setSpacing(10);
    // Add a vertical layout to our page - events list at bottom
    VerticalPanel vp = new VerticalPanel();
    vp.add(controlsPanel);
    vp.add(eventsDisplayTable);
    // Get all events that exist when the application loads
    updateCalendarView();
    RootPanel.get().add(vp);
    }

    If we run our project now, a table of event names and URLs appears below our view of the calendar.

    5.3 Editing a calendar event

    Next, we would like to be able to edit calendar events. We can achieve this in a three step process:

    • firstly we need to obtain the event via its URL 
    • then we apply our changes 
    • and lastly we update it back to the calendar 

    In our simplified scenario, we will simply rename the event using the following method which we will add to GoogleCalendarUtils:  

    public void renameEvent(final String eventUrl, final String newTitle, final Callback<CalendarEntry> callback){
    // Get the event via its URL 
    calendarService.getCalendarEntry(eventUrl, new CalendarEntryCallback(){
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed to access calendar service.");
    }
    @Override
    public void onSuccess(CalendarEntry result) {
    // Once we have the entry, change it
    Text title = Text.newInstance();
    title.setText(newTitle);
    result.setTitle(title);
    // Update the calendar with our changes
    calendarService.updateEntry(eventUrl, result, callback);
    }
    });
    }

    What remains is to hook up the inputs in our UI that we created earlier (remember the New Event Title textobx and the Update button). We will update the createEventEditPanel() method in our EntryPoint class to read in the new event title, and add an event handler to the Update button which will call GoogleCalendarUtils.renameEvent() with the new name. As a quick and dirty solution, we will identify the event that we want to modify by copying the event URL from the display table below our calendar view and pasting it into the Event URL text box. With the URL in there, we can type in the new event title we would like to use - I used Updated Event, and upon pressing the Update button our request will be pushed to our calendar. The updated version of createEventEditPanel() is:

    private Widget createEventEditPanel(){
    VerticalPanel editPanel = new VerticalPanel();
    editPanel.add(new Label("Event URL:"));
    editPanel.add(eventUrl);
    editPanel.add(new Label("New Event Title:"));
    editPanel.add(newTitle);
    editPanel.add(new Button("Update", new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    GoogleCalendarUtils ourCalendar = new GoogleCalendarUtils(calendarUrl);
    // Rename the event and handle the result
    ourCalendar.renameEvent(eventUrl.getText(), newTitle.getText(), new Callback<CalendarEntry>() {
    @Override
    public void onSuccess(CalendarEntry result) {
    Window.alert("Event update success!");
    // When we are done, update our view
    updateCalendarView();
    }
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed updating calendar event..");
    }
    });
    }
    }));
    editPanel.add(new Button("Delete", new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    // TODO delete the event given by the URL
    }
    }));
    return editPanel;
    }



    5.4 Deleting a calendar event

    The last operation we set out to achieve was the ability to delete a calendar entry. This follows a very similar process to editing an event:

    • we need to obtain the event via its URL 
    • we then delete it through CalendarService  

    We'll add a deleteEvent() method to GoogleCalendarUtils, which performs the above operations for us. The callback enables us to notify our caller of the outcome resulting from the delete operation.

    public void deleteEvent(String eventUrl, final Callback<CalendarEntry> callback){
    // Obtain the calendar event of interest
    calendarService.getCalendarEntry(eventUrl, new CalendarEntryCallback(){
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed to access calendar service.");
    }
    @Override
    public void onSuccess(CalendarEntry result) {
    // and delete it
    result.deleteEntry(callback);
    }
    });

    As before we need to wire in the delete operation in our UI. Again we'll have to paste the URL of the calendar we want to delete into the Event URL textbox, and we'll set up the Delete button so that it calls out to GoogleCalendarUtils.deleteEvent() when it is clicked. As with edit previously, we achive this by adding the click handler for the Delete button in the createEventEditPanel() method in our EntryPoint class:

    private Widget createEventEditPanel(){
    VerticalPanel editPanel = new VerticalPanel();
    editPanel.add(new Label("Event URL:"));
    editPanel.add(eventUrl);
    editPanel.add(new Label("New Event Title:"));
    editPanel.add(newTitle);
    editPanel.add(new Button("Update", new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    GoogleCalendarUtils ourCalendar = new GoogleCalendarUtils(calendarUrl);
    // Rename the event and handle the result ourCalendar.renameEvent(eventUrl.getText(), newTitle.getText(), new Callback<CalendarEntry>() {
    @Override
    public void onSuccess(CalendarEntry result) {
    Window.alert("Event update success!");
    // When we are done, update our view
    updateCalendarView();
    }
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed updating calendar event..");
    }
    });
    }
    }));
    editPanel.add(new Button("Delete", new ClickHandler(){
    @Override
    public void onClick(ClickEvent event) {
    GoogleCalendarUtils ourCalendar = new GoogleCalendarUtils(calendarUrl);
    // Rename the event and handle the result
    ourCalendar.deleteEvent(eventUrl.getText(), new Callback<CalendarEntry>(){
    @Override
    public void onSuccess(CalendarEntry result) {
    Window.alert("Event delete success!");
    // When we are done, update our view
    updateCalendarView();
    }
    @Override
    public void onFailure(CallErrorException caught) {
    Window.alert("Failed deleting calendar event..");
    }
    });
    }
    }));
    return editPanel;
    }


    If we now run the application, copy the URL from Updated Event into the Event URL box, and click Delete the event should be deleted. 

    6. More on Google Calendar

    • If you would like a closer look into the gwt-gdata API, I suggest going through the sample project included in the zip file we downloaded in step 1.2.
    • You could also look at the documentation for the Java/JavaScript APIs to discover more of the capability.
    • If you are interested in a front end calendar implementation check out gwt-cal (http://code.google.com/p/gwt-cal/). You could potentially use this with the GData APIs to build your own front end for Google Calendar.
    • Look through the reference list below for more resources.
    • Use Google and experiment!

    7. References

    [1] Calendar GData API - http://code.google.com/apis/calendar/data/2.0/developers_guide.html

    [2] GWT wrapper for GData API: gwt-gdata - http://code.google.com/p/gwt-gdata/

    [3] More on google calendar authentication: AuthSub - http://code.google.com/apis/accounts/docs/AuthSub.html

    [4] More on calendar feeds and URL structure: Data API Atom Reference - http://code.google.com/apis/calendar/data/2.0/reference.html

    8. Source

    You can download my version of the EntryPoint class GoogleCalendarUtils in the zip file below:

  • Using GWT MVP with multiple views on the page by Josh Trotter
    in GWT
    20 Jul 2011  | 8 Comments

    The Issue:

    When developing a GWT app with the MVP approach it is common to make use of Activities and Places as described in the developer guide

    Under this approach an ActivityManager makes use of an ActivityMapper which you define in order to respond to PlaceChangeEvents by running the relevant Activity. The Activity is passed a reference to an AcceptsOneWidget panel which is typically a container for the entire application. The Activity is responsible for constructing the application view and setting it into this container.

    The potential pitfall of this approach is that every time a new activity is run we clear out the entire application view and reconstruct everything from scratch. If your application is composed of multiple distinct regions, for example a Menu and a Content area, and the view for each region can change independently of the other, then you will be forcing the browser to do extra unnecessary work to rebuild the DOM with the Menu region, even though only the Content region has changed.

    A Possible Solution:

    The solution is presented in two stages

    1) Examine a simple approach that will allow for multiple regions on a page to be managed and changed independently of one another.

    2) Explore a way to dynamically remove regions that are not required in a given context (e.g. removing the menu region when on the login screen)

    We will use this simple example application layout

    Login Screen Main Screen

     

    We will first define our application layout giving each region it's own AcceptsOneWidget panel.

    appContainer = new DockLayoutPanel(Unit.PX);
    mainContent = new ScrollPanel();
    leftMenu = new ScrollPanel();
    banner = new SimplePanel();

    appContainer.addNorth(banner, 50);
    appContainer.addWest(leftMenu, 300);
    appContainer.add(mainContent);

    For each region that we want to be able to update the view independently of the other regions (the menu and the content region) we will define a new ActivityMapper and ActivityManager

    ActivityMapper contentActivityMapper = new ContentActivityMapper(clientFactory);
    ActivityManager contentActivityManager = new ActivityManager(contentActivityMapper, eventBus);       
    contentActivityManager.setDisplay(mainContent);
                   
    ActivityMapper menuActivityMapper = new MenuActivityMapper(clientFactory);
    ActivityManager menuActivityManager = new ActivityManager(menuActivityMapper, eventBus);
    menuActivityManager.setDisplay(leftMenu);

    Note that a different panel is set into the display of each ActivityManager.

    Because both managers share an EventBus, both ActivityMappers will be called whenever a PlaceChangeEvent is fired. Since each manager should only run a new activity in response to a subset of the possible Places in your app, we will make some changes to the mapper so that it only responds to the Places that affect the given region. By having the mapper hold a reference to its currently executing Activity, it can just return this same activity whenever a Place change it is not interested in gets fired. This will have no affect as the currently executing Activity will not be run again.

    public class MenuActivityMapper implements ActivityMapper {

        private Activity currentActivity;
       
        @Override
        public Activity getActivity(Place place) {
            if (place instanceof MainMenuPlace)
            {
                return new MainMenuActivity();
            } else if (place instanceof SubMenuPlace) {

                return new SubMenuActivity();

            } else {

                //Some other content Place that we aren't interested in
                return currentActivity;

            } 

    }  

    That completes the first stage. Now say we want to allow the menu region to be hidden when the user is at the login screen and just use the content region to display the login view.

    Create a new panel HideableScrollPanel which extends ScrollPanel. Set the menu to use the new panel instead of ScrollPanel. HideableScrollPanel will take as input the parent DockLayoutPanel container and the expected size of the panel when it is displayed. Override the setWidget method so that it will minimize the panel if null is set.

    public void setWidget(IsWidget activityWidget) {
            Widget widget = Widget.asWidgetOrNull(activityWidget);                       
            if (widget == null)
            {                   
                parent.setWidgetSize(this, 0);
            } else {
                parent.setWidgetSize(this, size);
            }
            super.setWidget(widget);
        }

    Now you can define a HideActivity which when run simply sets the AcceptOneWidget panel it is passed a value of null. If the AcceptOneWidget panel is an instance of your HideableScrollPanel then the panel will be removed from the screen. When a LoginPlace change is received the MenuActivityMapper should return a HideActivity.

  • How to build and package a GWT app to a WAR file using Ant by Josh Trotter
    in GWT
    23 Apr 2011  | 12 Comments

    This article is intended as a step by step guide for automating the construction of a WAR file from your GWT application using an Ant script. It covers the following steps

    • Establishing the project structure
    • Compiling the source - Java & Javascript
    • Packaging and accessing static resources - Images, CSS, property files
    • Packaging the deployment archives - JAR & WAR files

    Establishing the project structure

    Create a new GWT Web Application project and setup the directory structure as per the following screenshot

    com.rubiconred.buildtutorial: This is where the .gwt.xml module file lives and we have added a public directory. The public directory is where we will place all of our static resources (e.g. Image files) that are accessed directly from within our GWT source code (see further below).

    com.rubiconred.buildtutorial.client: This is where we place our GWT source code that is not referenced anywhere in our server code. This code will be compiled to Javascript but not to .class files. Note that you should not be putting RemoteService classes in here as these interfaces are implemented by server-side RemoteServiceServlet classes.

    com.rubiconred.buildtutorial.shared: This is where we place our GWT source code that is referenced by both client and server code. This code will be compiled to both Javascript and to .class files. This is the place to put RemoteService classes.

    com.rubiconred.buildtutorial.server: This is where we have access to the full Java APIs and will be the location for our RemoteServiceServlet classes. This code will not be compiled to Javascript but only to .class files.

    Note: To ensure the client & shared packages will both be compiled to Javascript the following must be present in your .gwt.xml module file (generated by default if using Eclipse plugin)

    <!-- Specify the paths for translatable code -->
      <source path='client'/>
      <source path='shared'/>

    war/client_resources: We will use this directory to store our static resources that are accessed directly from the BuildTutorial.html or BuildTutorial.css files. Remember static resources accessed from within the GWT code should be placed in the public directory as described above.

    war/server_resources: We will use this directory to store static resources used by our server code, such as properties or XML files.

    build.xml: Create a new file build.xml and place it on the project root directory. Following is the skeleton of this build script which we will flesh out over the next few steps to automate the build and packaging process for us.

    <?xml version="1.0"?>
    <project name="BuildTutorial" basedir="." default="war">
     
        <target name="prepare"/>
        <target name="clean"/>
        <!-- Compile the java source code using javac -->
        <target name="compile" depends="prepare/">       
        <!-- Invoke the GWT compiler to create the Javascript for us -->
        <target name="gwt-compile" depends="compile"/>
        <!-- Package the compiled Java source into a JAR file -->
        <target name="jar" depends="compile"/>   
        <!-- Copy the static server resources into the required
        directory ready for packaging -->   
        <target name="copy-resources"/>   
        <!-- Package the JAR file, Javascript, static resources
        and external libraries into a WAR file -->
        <target name="war" depends="gwt-compile, jar, copy-resources"/>
        <!-- Deploy the WAR file (optional) -->
        <target name="deploy" depends="war"/>
       
    </project>

    Compiling the project source

    We will begin by establishing the Ant tasks in our build script that will compile our source code. For a GWT app this includes the additional task of invoking the GWT compiler to build our client and shared code to Javascript.

    First we should establish the properties that we will reference throughout the build script. Add the following to the top of build.xml and update the property values to match your own project and domain names.

        <property name="gwt.module.name" value="com.rubiconred.buildtutorial.BuildTutorial"/>
        <property name="server.resources.name" value="server_resources"/>
        <property name="jar.name" value="buildtutorial.jar"/>
        <property name="war.name" value="buildtutorial.war"/>
        <property name="src.dir" location="src"/>
        <property name="server.resources.dir" location="war/${server.resources.name}"/>
        <property name="build.dir" location="build"/>   
        <property name="build.server.resources.dir" location="war/WEB-INF/classes/server_resources"/>       
        <property name="lib.dir" location="war/WEB-INF/lib"/>
        <property name="gwt.client.dir" location="com/rubiconred/soauiext/client"/>

    Next add the following to establish the classpath that we will reference when compiling. Any third party JARs you are using in your project should be available in the war/WEB-INF/lib directory.       
        <path id="project.classpath">       
            <fileset dir="${lib.dir}">
                <include name="**/*.jar"/>
            </fileset>
        </path>

    Setup Ant tasks to create and delete the build directory. The build directory is used as a temporary staging area for the compiled source code during the build process.

        <target name="prepare">
            <mkdir dir="${build.dir}"/>
        </target>
       
        <target name="clean">
            <delete dir="${build.dir}"/>
        </target>

    Next is the Ant task to invoke the Java compiler. Note that while we are compiling everything in the src dir (client, shared, server) later when we package this into the JAR we will exclude the client classes. The client classes are only included here as they are required by the GWT compiler in the next task.

        <target name="compile" depends="prepare">       
            <javac srcdir="${src.dir}" destdir="${build.dir}">
                <classpath refid="project.classpath"/>
            </javac>       
        </target>

    Now we are ready to invoke the GWT compiler. In order for the GWT compiler to work the following JAR files must be on the classpath

    • gwt-dev.jar
    • gwt-user.jar

    Add these files to the war/WEB-INF/lib directory. You should find them in your Eclipse installation directory (GWT 2.2) ECLIPSE_HOME\plugins\com.google.gwt.eclipse.sdkbundle.2.2.0_2.2.0.v201102111811\gwt-2.2.0

        <target name="gwt-compile" depends="compile">
            <java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
                <classpath>
                    <!-- src dir is added to ensure the module.xml file(s) are on the classpath -->
                    <pathelement location="${src.dir}"/>               
                    <pathelement location="${build.dir}"/>
                    <path refid="project.classpath"/>
                </classpath>
                <jvmarg value="-Xmx256M"/>
                <arg value="${gwt.module.name}"/>
             </java>
         </target>

    Packaging and accessing static resources

    One of the trickiest parts of packaging and deploying a GWT app is figuring out where to place the various types of static resource files and how to access them in your code so that they can be located after you have deployed to an application server just as they are in your development environment. The following list shows a strategy that I have found effective

    Resource Type
    Client resources accessed directly within your GWT code
    e.g. Image files used by an Image widget

    Access Method
    Image image = new Image(GWT.getModuleBaseURL() + "/images/myImage.png");

    Resource Type
    Client resources accessed from the HTML or CSS files
        

    Access Method
    background-image
    : url(client_resources/images/light_blue_vert.png);

    Resource Type
    Server resources accessed from server side code
    e.g. Properties file

     

    Access Method
     InputStream in = this.getClass().getClassLoader().getResourceAsStream("server_resources/serverProps.properties");
    props = new Properties();
    props.load(in);

     

    As part of the build process we need to copy the server_resources into the WEB-INF/classes directory so that they will be properly packaged into the WAR file and can be accessed when we deploy. The following Ant task will take care of this. It may also be necessary to invoke this target during development to keep the resource directories in sync so that they can be accessed when running in development mode too.

       <target name="copy-resources">
            <copy todir="${build.server.resources.dir}" preservelastmodified="true">
                <fileset dir="${server.resources.dir}"/>           
            </copy>
        </target>

    Packaging the deployment archives

    The first step here is to package the compiled Java source into a JAR file that will then be packaged into the WAR file in the WEB-INF/lib directory as part of the next step. Because we don't want to deploy any unnecessary code to the server we exclude the client classes from the package.

        <target name="jar" depends="compile">       
            <jar jarfile="${lib.dir}/${jar.name}" basedir="${build.dir}/">
                <!-- Don't wrap any of the client only code into the JAR -->
                <exclude name="${gwt.client.dir}/**/*.class"/>
            </jar>   
        </target>

    Now we are ready to build the WAR file. The following task packages the JAR, Javascript, static resources and third party JAR files (excluding gwt-dev.jar & gwt-user.jar) that we established during the previous build targets into the deployable WAR file that we require.

        <target name="war" depends="gwt-compile, jar, copy-resources">
            <war basedir="war" destfile="${war.name}" webxml="war/WEB-INF/web.xml">
                <exclude name="WEB-INF/**" />
                <exclude name="${server.resources.name}/**"/>
                <webinf dir="war/WEB-INF/">
                    <include name="classes/${server.resources.name}/**" />
                    <include name="**/*.jar" />
                    <exclude name="**/gwt-dev.jar" />
                    <exclude name="**/gwt-user.jar" />
                </webinf>
            </war>
        </target>

    Final Steps

    Run the default task by right clicking on the build.xml file and selecting Run As > Ant Build. If you refresh the project directory in Eclipse you will notice a new build directory containing your compiled source has been added. You can remove this using the Ant target clean if you wish. You will also find buildtutorial.war has been added to the root directory of your project. If you copy this WAR file over to your server and deploy it onto the application server you will be able to run your GWT app in the deployed environment. It is also possible to automate the deployment step with Ant but that is beyond the scope of this article.

    And here is the final build.xml contents

    <?xml version="1.0"?>
    <project name="BuildTutorial" basedir="." default="war">

        <property name="gwt.module.name" value="com.rubiconred.buildtutorial.BuildTutorial"/>
        <property name="server.resources.name" value="server_resources"/>
        <property name="jar.name" value="buildtutorial.jar"/>
        <property name="war.name" value="buildtutorial.war"/>
        <property name="src.dir" location="src"/>
        <property name="server.resources.dir" location="war/${server.resources.name}"/>
        <property name="build.dir" location="build"/>   
        <property name="build.server.resources.dir" location="war/WEB-INF/classes/server_resources"/>       
        <property name="lib.dir" location="war/WEB-INF/lib"/>
        <property name="gwt.client.dir" location="com/rubiconred/soauiext/client"/>

        <path id="project.classpath">       
            <fileset dir="${lib.dir}">
                <include name="**/*.jar"/>
            </fileset>
        </path>  

        <target name="prepare">
            <mkdir dir="${build.dir}"/>
        </target>
       
        <target name="clean">
            <delete dir="${build.dir}"/>
        </target>  

        <!-- Compile the java source code using javac -->
        <target name="compile" depends="prepare">       
            <javac srcdir="${src.dir}" destdir="${build.dir}">
                <classpath refid="project.classpath"/>
            </javac>       
        </target>      
        <!-- Invoke the GWT compiler to create the Javascript for us -->
       <target name="gwt-compile" depends="compile">
            <java failonerror="true" fork="true" classname="com.google.gwt.dev.Compiler">
                <classpath>
                    <!-- src dir is added to ensure the module.xml file(s) are on the classpath -->
                    <pathelement location="${src.dir}"/>               
                    <pathelement location="${build.dir}"/>
                    <path refid="project.classpath"/>
                </classpath>
                <jvmarg value="-Xmx256M"/>
                <arg value="${gwt.module.name}"/>
             </java>
         </target>
        <!-- Package the compiled Java source into a JAR file -->
        <target name="jar" depends="compile">       
            <jar jarfile="${lib.dir}/${jar.name}" basedir="${build.dir}/">
                <!-- Don't wrap any of the client only code into the JAR -->
                <exclude name="${gwt.client.dir}/**/*.class"/>
            </jar>   
        </target>
        <!-- Copy the static server resources into the required
        directory ready for packaging -->   
        <target name="copy-resources">
            <copy todir="${build.server.resources.dir}" preservelastmodified="true">
                <fileset dir="${server.resources.dir}"/>           
            </copy>
        </target>   
        <!-- Package the JAR file, Javascript, static resources
        and external libraries into a WAR file -->
        <target name="war" depends="gwt-compile, jar, copy-resources">
            <war basedir="war" destfile="${war.name}" webxml="war/WEB-INF/web.xml">
                <exclude name="WEB-INF/**" />
                <exclude name="${server.resources.name}/**"/>
                <webinf dir="war/WEB-INF/">
                    <include name="classes/${server.resources.name}/**" />
                    <include name="**/*.jar" />
                    <exclude name="**/gwt-dev.jar" />
                    <exclude name="**/gwt-user.jar" />
                </webinf>
            </war>
        </target>   
       
    </project>

     

     


  • Setup an Oracle Datasource for use in GWT Development Mode by Josh Trotter
    in GWT
    22 Apr 2011  | 1 Comment

    When you run your GWT application in development mode, the code is executed within the embedded Jetty web server. This guide describes a simple approach that we have found effective for configuring the Jetty web server with an Oracle datasource reference.

    The first step is to create a configuration file jetty-web.xml to specify the JNDI name and connection details. The second step is to add a couple of required JARs to your project that Jetty needs in order to perform the datasource lookup.

    Sample jetty-web.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">

    <Configure class="org.mortbay.jetty.webapp.WebAppContext">
            <New id="DSTest" class="org.mortbay.jetty.plus.naming.Resource">           
                <Arg>jdbc/myDS</Arg>
                <Arg>
                     <New class="oracle.jdbc.pool.OracleConnectionPoolDataSource">
                     <Set name="URL">jdbc:oracle:thin:@192.168.1.1:1521:XE</Set>                 
                     <Set name="User">user</Set>
                     <Set name="Password">pwd</Set>
                     </New>
                </Arg>
            </New>
    </Configure>

    In this example file, the first <Arg> element jdbc/myDS is the JNDI reference you will use to lookup this datasource. The second <Arg> element specifies the connection details for your datasource. In this example we are using Oracle Express Edition (XE).

    This file should be placed in the WEB-INF directory along with the standard web.xml file (See screenshot below)

    Add the required JARs

    For Jetty you will need the following JARS added to WEB-INF/lib

    • jetty-naming-6.1.12
    • jetty-plus-6.1.12

    If using an Oracle datasource you will also need to add

    • ojdbc14

    How it looks

    WEB-INF directory structure


    Accessing the datasource with JDBC

                Context ctx = new InitialContext();
                DataSource datasource = (DataSource) ctx.lookup("jdbc/myDS");

  • GWT SerializationException on RPC call when providing List from a ListDataProvider by Josh Trotter
    in GWT
    6 Apr 2011  | 5 Comments

    The Issue: When attempting to send a List<T> object as a parameter of an RPC method call from client to server, the RPC call failed reporting a com.google.gwt.user.client.rpc.SerializationException had been thrown. Our first response then was to assume that our list objects, T, did not properly adhere to the GWT serialization requirements as outlined here Serializable Types. However once we confirmed this was not the case we were left wondering what could cause this issue.

    Up until now we had generally assumed that sending a List<T> across the wire would not cause any serialization issues so long as T could be serialized. However this is not strictly true, the List implementation itself must also be serializable for the RPC call to succeed. Since most of the time your List implementations will likely be an ArrayList or a LinkedList, both of which implement the Java Serializable interface, it could be easy to forget to check this.

    However this issue may begin to crop up more often with the introduction of the ListDataProvider in GWT 2.1. The ListDataProvider class wraps a List object in order to provide data binding to the new GWT cell widgets. What may surprise you is that the wrapped List is not implemented as an ArrayList or LinkedList but as a ListWrapper. The ListWrapper is an inner class of the ListDataProvider which implements List<T> but does not implement any Serializable interface.

    Therefore the following code will throw the aforementioned com.google.gwt.user.client.rpc.SerializationException

           rpcService.anRpcCall(listDataProvider.getList(), new AsyncCallback<Void>) {... //tries to send a ListWrapper

    The solution: Do not attempt to directly pass the result of listDataProvider.getList() to the server. Instead you need to construct a List implementation you know is Serializable and pass in the contents of the ListWrapper.

          List<MySerializableType> serializableList = new ArrayList<MySerializableType>();
          serializableList.addAll(listDataProvider.getList());
          rpcService.anRpcCall(serializableList, new AsyncCallback<Void>) {...

    More generally, always check that your Collection implementations are serializable before transmitting them across the wire. You may even consider declaring the parameter types of your RPC calls to be a concrete type that you know to be serializable instead of an interface - for example declare ArrayList<T> instead of List<T>.