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.
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.
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.
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.
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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Part 3 sample code | rockstar3_src.zip | 14KB | HTTP |
Information about download methods
Learn
-
For a great introduction to XForms read the three-part series Introduction to XForms (Chris Herbroth, developerWorks, September 2006).
-
Learn more about using JavaScript and XForms together in XForms tip: Call JavaScript from an XForms form (Nicholas Chase, developerWorks, January 2007).
-
See how JavaScript can improve the functionality of XForms in the article Use JavaScript to make your XForms more robust (Michael Galpin, developerWorks, July 2007).
-
Learn how XForms can work with Ajax to create auto-suggest functionality in the article Use XForms and Ajax to create an autosuggest form field (Michael Galpin, developerWorks, July 2007).
-
Read one of the first in-depth tutorials on GWT in the article Ajax for Java
developers: Exploring the Google Web Toolkit (Philip McCarthy, developerWorks, June 2006).
-
Learn more about building Web applications with GWT in the four-part developerWorks tutorial series Build an Ajax application using Google Web Toolkit, Apache Derby, and Eclipse (Noel Rappin, developerWorks, December 2006).
-
Get familiar with GWT's Ajax capabilities in the two-part tutorial series Build an Ajax-enabled application using the Google Web Toolkit and Apache Geronimo (Michael Galpin, developerWorks, May 2007).
-
Check out IBM developerWorks' Ajax Resource Center
-
Visit the XForms home at W3C.
-
developerWorks technical events and webcasts: Stay current with developerWorks technical events and webcasts.
-
Podcasts: Tune in and catch up with IBM technical experts.
Get products and technologies
-
The best place for GWT information is straight from the source, at the official Google Web Toolkit site.
-
Get the XForms extension for Mozilla, Firefox, or Seamonkey.
Discuss
Comments (Undergoing maintenance)





