Skip to main content

Integrate XForms with the Google Web Toolkit, Part 3: Using GWT to create XForms

Create a Web application for rock star hopefuls

Michael Galpin (mike.sr@gmail.com), Developer, Ludi Labs
Michael Galpin has been developing Java software professionally since 1998. He currently works at Ludi Labs, a start-up in Mountain View, Calif. He holds a degree in mathematics from the California Institute of Technology.

Summary:  This four-part series demonstrates how to use the Google Web Toolkit (GWT) and XForms together to create a dynamic Web application. Part 1 looked at the two technologies and how both had JavaScript underpinnings. Part 2 shows how to create a small application with two pages. One page uses GWT to show a list of artists managed by a record company. The second page uses XForms to display the albums recorded by a particular artist. Part 3 uses GWT and XForms on the same page. See how to take advantage of each technology's bindings to JavaScript by using JavaScript to achieve interactivity between GWT and XForms.

View more content in this series

Date:  09 Oct 2007
Level:  Intermediate
Activity:  3476 views

Introduction

Get up to speed with the four-part series Integrate XForms with the Google Web Toolkit:
  • Part 1: Introducing GWT's JavaScript Native Interface
  • Part 2: Create an artist and album management form

In this installment, Part 3, you'll refactor the rock star application built in Part 2. That application allows for record executives to manage artists and their albums. You're not going to change any of the functionality from Part 2. Instead you'll change the implementation of that functionality by mixing GWT and XForms together. You'll see how GWT can be easily introduced to any existing Web page. You'll use a GWT-style Ajax call to load your data dynamically and then use GWT's JSNI to dynamically create your XForms model. This will allow you to simplify your page, and you'll simplify it even more by using GWT JSNI to dynamically create your XForms controls. Mixing GWT elements will not only simplify the server-side code, it will create smaller initial pages, with faster downloads and rendering.


Prerequisites

This article uses GWT version 1.4 and the Mozilla XForms plugin 0.8 (see the Resources for download links). The Mozilla XForms plugin works with any Mozilla-based Web browser, such as Firefox and Seamonkey. GWT requires knowledge of the Java™ language, and Web technologies such as HTML and CSS. This article makes heavy use of JavaScript as well. XForms makes heavy use of the Model-View-Control paradigm, so familiarity with that is helpful. Prior exposure to XForms and GWT is helpful of course, but is not necessary.


Dynamic XForms?

So far you've seen XForms render model instance data that was inlined on the same page. The model data was dynamic. You used a JSP scriptlet to query and filter the data, and then to write the XML to the page. Now you will make the XForms page much more dynamic. Instead of writing the instance data to the page, you'll retrieve it asynchronously from the server and then dynamically add it to the model on the page. You'll use GWT for the Ajax call and GWT's JSNI for modifying your XForms model.


Adding GWT to an XForms page

You're going to take your XForms albums page and use GWT on it. So this begs the question, how do you add GWT to an existing Web page? It's very simple. All you need to do is include the JavaScript library that the GWT Java-to-JavaScript compiler creates from your code. There is a single file per module. On your XForms page, this just becomes the line of code shown in Listing 1.


Listing 1. Adding GWT to the albums page
                
<xhtml:script language="text/javascript" 
src="org.developerworks.rockstar.RockStarMain.nocache.js"></xhtml:script>

Developers are often surprised by how easy it is to add GWT to a page. This is definitely by design. Most examples that you'll see of GWT show "green field" applications, in other words, applications built from scratch using GWT. Legacy applications are at least as common in practice, and GWT was designed to be useful for both. Remember, all the GWT code is just JavaScript. So it makes sense that it should be this easy. Now that you've dropped GWT into your page, you can start writing GWT code (or, Java code that will be compiled into JavaScript) for your page.


Loading album data using Ajax: JSNI to manipulate XForms data

In Part 2 of this series, you used GWT to load a list of artists on a page. You did this asynchronously. In other words, you created your page and made an Ajax request for the list of artists. You're going to use the same technique now for the albums. Thus you're going to need a service for loading albums using GWT's Ajax.


Creating the remote service

So you'll declare your service interface, as shown in Listing 2.


Listing 2. Album Service interface
                
package org.developerworks.rockstar.client;

import com.google.gwt.user.client.rpc.RemoteService;

public interface AlbumService extends RemoteService{
     public Album[] getAlbumsForArtist(int artistId);
     public void addAlbum(Album newAlbum);
}

This is just like the service you created in Part 2 of this series. Similarly, you'll need an asynchronous version of your service, shown in Listing 3.


Listing 3. Asynchronous service interface declaration
                
package org.developerworks.rockstar.client;

import com.google.gwt.user.client.rpc.AsyncCallback;

public interface AlbumServiceAsync {
     public void getAlbumsForArtist(int artistId, AsyncCallback callback);
     public void addAlbum(Album newAlbum, AsyncCallback callback);
}

Finally, you need a server-side implementation of your service that extends GWT's RemoteServiceServlet so that it can be called by clients using HTTP. This is shown in Listing 4.


Listing 4. Album service implementation
                
package org.developerworks.rockstar.server;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.developerworks.rockstar.client.Album;
import org.developerworks.rockstar.client.AlbumService;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class AlbumServiceImpl extends RemoteServiceServlet implements AlbumService {

     private static final long serialVersionUID = -2706402745094297460L;
     private Map<Integer,List<Album>> albumCache;
     private AlbumDao dao;
     
     public AlbumServiceImpl(){
          this.dao = new AlbumFileDao();
          List<Album> allAlbums = this.dao.getAllAlbums();
          // initialize cache
          int size = allAlbums.size();
          this.albumCache = new HashMap<Integer, List<Album>>(size);
          for (Album album : allAlbums){
               int artistId = album.getArtistId();
               List<Album> albums = this.albumCache.get(artistId);
               if (albums == null){
                    albums = new ArrayList<Album>();
                    this.albumCache.put(artistId, albums);
               }
               albums.add(album);
          }
     }

     public void addAlbum(Album newAlbum) {
          int artistId = newAlbum.getArtistId();
          List<Album> albums = this.albumCache.get(artistId);
          if (albums == null){
               albums = new ArrayList<Album>();
               this.albumCache.put(artistId, albums);
          }
          albums.add(newAlbum);
          List<Album> all = this.getAllAlbums();
          this.dao.saveAlbums(all);
     }

     public Album[] getAlbumsForArtist(int artistId) {
          List<Album> albums = this.albumCache.get(artistId);
          if (albums == null){
               return null;
          }
          Album[] array = new Album[albums.size()];
          array = albums.toArray(array);
          return array;
     }
     
     private List<Album> getAllAlbums(){
          List<Album> allAlbums = new ArrayList<Album>();
          for (int artistId : this.albumCache.keySet()){
               List<Album> albums = this.albumCache.get(artistId);
               allAlbums.addAll(albums);
          }
          return allAlbums;
     }

}

Again, you've used a Data Access Object (DAO) pattern for abstracting the management of the physical data (retrieving from the file system, parsing XML, etc.) This would allow you to easily swap out your simplistic XML-file based implementation for a more standard database-driven implementation. Now you can start using your new service from GWT code.


Calling the remote service using GWT

All the code in your org.developerworks.rockstar.client package will be compiled into JavaScript and available to any page that includes the JavaScript library as shown in Listing 1. So you'll create a new Java class for working with your albums page. Its source code is shown in Listing 5.


Listing 5. AlbumLib class
                
package org.developerworks.rockstar.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.ServiceDefTarget;

public class AlbumLib {
     public void loadAlbums(int artistId){
          AlbumServiceAsync albumService = this.getAlbumService();
          AsyncCallback callback = new AsyncCallback(){

               public void onFailure(Throwable caught) {
                    // TODO Auto-generated method stub
                    
               }

               public void onSuccess(Object result) {
                    Album[] albums = (Album[]) result;
                    for (int i=0;i<albums.length;i++){
                         addAlbumToModel(albums[i]);
                    }
                    refreshXformsModel();
               }
               
          };
          albumService.getAlbumsForArtist(artistId, callback);
     }
     
     private AlbumServiceAsync getAlbumService(){
          AlbumServiceAsync albumService = (AlbumServiceAsync) 
		             GWT.create(AlbumService.class);
          ServiceDefTarget endpoint = (ServiceDefTarget) albumService;
          String moduleRelativeUrl = GWT.getModuleBaseURL() + "albumService";
          endpoint.setServiceEntryPoint(moduleRelativeUrl);
          return albumService;
     }
     
     private native void addAlbumToModel(Album album)/*-{
          var model = $doc.getElementById("albums");
          var instance = model.getInstanceDocument("albumData");
          var dataElement = instance.getElementsByTagName("Data")[0];
          // create the new album node
          var newAlbumElement = instance.createElement("Album");
          var titleElement = instance.createElment("Title");
          titleElement.appendChild(instance.createTextNode(album.getTitle()));
          newAlbumElement.appendChild(titleElement);
          var yearElement = instance.createElement("Year");
          yearElement.appendChild(instance.createTextNode(album.getYear()));
          newAlbumElement.appendChild(yearElement);
          dataElement.appendChild(newAlbumElement);
     }-*/;
     
     private native void refreshXformsModel()/*-{
          var model = $doc.getElementById("albums");
          model.rebuild();
          model.recalculate();
          model.refresh();
     }-*/;
}

There's a lot going on in this Java class. First, you have two Java methods used for making the remote invocation of the AlbumService you created earlier. This is very similar to how you called the ArtistService in Part 2. This code includes a callback function when your service responds successfully to your asynchronous request. In this case the callback is making use of two other methods, addAlbumToModel() and refreshXformsModel().

These two methods are JavaScript native methods, like the examples you saw in Part 1.

The addAlbumToModel() method accesses the JavaScript objects that represent your XForms model. This lets us access the XML instance data. You saw how to do this in Part 1 by using the familiar JavaScript paradigm, document.getElementById(...). Notice that because you are doing this from GWT, you used $doc instead of the document object that is implicit in JavaScript. Once you have a reference to that, you can use familiar DOM methods to add a new Album to the XML object. The addAlbumToModel method gets called repeatedly, adding all the albums returned from the server. Once you are done iterating over the server response, then you call the refreshXformsModel() method. Again, this is a native method. It once again gets a handle on the XForms model, and then uses that object's APIs to refresh the controls that are bound to the model.

Finally, you need to make sure your GWT method is called when the page is loaded. To do this you modify your original Albums.jsp as shown in Listing 6.


Listing 6. Calling GWT JavaScript from JSP
                
<?xml version="1.0" encoding="UTF-8"?>
<xhtml:html xmlns:ev="http://www.w3.org/2001/xml-events" 
xmlns:xforms="http://www.w3.org/2002/xforms" 
     xmlns:xhtml="http://www.w3.org/1999/xhtml" 
	 xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <xhtml:head>
        <xhtml:title>Albums</xhtml:title>
        <xhtml:script language="text/javascript"
                 src="org.developerworks.rockstar.RockStarMain.nocache.js">
                 </xhtml:script>
        <xforms:model id="albums" xmlns="http://www.w3.org/1999/xhtml" 
                xmlns:xforms="http://www.w3.org/2002/xforms">
            <xforms:instance id="albumData" xmlns=""/>
        </xforms:model>
    </xhtml:head>
    <xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
         <xhtml:div id="albumList">
              <xforms:repeat id="repeatItem" 
                          nodeset="instance('albumData')/Data/Album"
                          xmlns="http://www.w3.org/1999/xhtml" 
                          xmlns:xforms="http://www.w3.org/2002/xforms">
                   <xhtml:div>
                        <xforms:output ref="Title" 
                                 xmlns="http://ww.w3.org/1999/xhtml" 
                                 xmlns:xforms="http://www.w3.org/2002/xforms">
                             <xforms:label 
                                 xmlns:xforms="http://www.w3.org/2002/
                                 xforms">Title:</xforms:label>
                        </xforms:output>
                        <xforms:output ref="Year" 
                                 xmlns="http://www.w3.org/1999/xhtml"
                                 xmlns:xforms="http://www.w3.org/2002/xforms">
                             <xforms:label 
                                 xmlns:xforms="http://www.w3.org/2002/
                                 xforms">Year:</xforms:label>
                        </xforms:output>
                   </xhtml:div>
              </xforms:repeat>
         </xhtml:div>
    </xhtml:body>
</xhtml:html>

As you can see, you simply call your GWT method once the body of the page has loaded. Also, you eliminated the scriptlet that you had used earlier. You do still have the very small scriptlet on the body declaration. This simply passes in the artistId request parameter. Most of your code now is for creating UI controls. Let's take a look at how you can do that programmatically through GWT as well.


Creating XForms controls with GWT JSNI

You've seen how you can access the XForms model and instance data using GWT and JSNI. Let's take things a step further and use GWT and JSNI to dynamically create XForms controls. You'll start by replacing your repeat control with JavaScript as shown in Listing 7.


Listing 7. Dynamic XForms controls from GWT JSNI
                
private native void createControls()/*-{
    var xfNs = "http://www.w3.org/2002/xforms";
     // get the container div
    var container = $doc.getElementById("albumList");
    
    var repeater = $doc.createElementNS(xfNs,"xforms:repeat");
    repeater.setAttribute("id", "repeatItem");
    repeater.setAttribute("nodeset", "instance('albumData')/Data/Album");
         
    var titleOut = $doc.createElementNS(xfNs, "xforms:output");
    titleOut.setAttribute("ref", "Title");
    var titleLabel = $doc.createElementNS(xfNs, "xforms:label");
    titleLabel.appendChild($doc.createTextNode("Title:"));
    titleOut.appendChild(titleLabel);
    repeater.appendChild(titleOut);
         
    var yearOut = $doc.createElementNS(xfNs, "xforms:output");
    yearOut.setAttribute("ref", "Year");
    var yearLabel = $doc.createElementNS(xfNs, "xforms:label");
    yearLabel.appendChild($doc.createTextNode("Year:"));
    yearOut.appendChild(yearLabel);
    repeater.appendChild(yearOut);
    
    container.appendChild(repeater);
}-*/;

Again this is just using simple DOM programming to create your XForms controls using native JavaScript. Now that you have this code, all you have to do is add it to the loadAlbums() method that gets called when the body is loaded. Your JSNI code will create your controls and then invoke a remote service to retrieve album data. The album data will then be used to programmatically create the XForms model instance data. The XForms model will then be refreshed so that the bound controls show the new data. The body of your JSP is now very simple and is shown in Listing 8.


Listing 8. Simplified JSP -- No XForms controls
                
    <xhtml:body onload="loadAlbums(<%=request.getParameter("artistid") %>)">
         <xhtml:div id="albumList">
         </xhtml:div>
    </xhtml:body>
	

Suddenly your XForms JSP looks a lot like the GWT HTML page for showing the list of artists. All of your UI is being programmatically using JavaScript from Java classes. The data is being retrieved using GWT Ajax.

Summary

In Part 3, you've taken an XForms page that relied heavily on a JSP scriptlet to inline its data, and transformed into a minimal page that relies heavily on GWT. You used GWT's Ajax mechanism to load data from the server asynchronously. You then used GWT's JSNI facilities to dynamically create the instance data in your XForm's model. Finally, you went even further down the JSNI road by using it to dynamically create all of our XForms controls used for showing the album data. In Part 4, you'll see how you can use XForms controls to invoke GWT Ajax on-demand.



Download

DescriptionNameSizeDownload method
Part 3 sample coderockstar3_src.zip14KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Michael Galpin has been developing Java software professionally since 1998. He currently works at Ludi Labs, a start-up in Mountain View, Calif. He holds a degree in mathematics from the California Institute of Technology.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Java technology
ArticleID=260468
ArticleTitle=Integrate XForms with the Google Web Toolkit, Part 3: Using GWT to create XForms
publish-date=10092007
author1-email=mike.sr@gmail.com
author1-email-cc=ruterbo@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Special offers