Skip to main content

Rich Internet Applications with Grails, Part 2: Grails and the Google Web Toolkit

Michael Galpin (mike.sr@gmail.com), Software architect, eBay
Michael Galpin's photo
Michael Galpin has been developing Web applications since the late 90s. He holds a degree in mathematics from the California Institute of Technology and is an architect at eBay in San Jose, CA.

Summary:  In this second part of a two-part series, add to the Grails-powered Web services you created in Part 1. You will create a new search page, but this time using the Google Web Toolkit (GWT) to create the application. You will also use some richer UI widgets from the Ext GWT library.

View more content in this series

Date:  10 Mar 2009
Level:  Intermediate PDF:  A4 and Letter (255KB | 24 pages)Get Adobe® Reader®
Activity:  5830 views

About this series

This series explores application architectures that use a Service Oriented Architecture (SOA) on the back end implemented with the Grails framework. Explore how much Grails simplifies creating Web applications in general and Web services in particular. This kind of back end can be easily hooked up to any pure client-side application. In Part 1, you used Adobe Flex to create an application that leveraged the Flash Player. In this article you will use the Google Web Toolkit (GWT) to create the front end in pure JavaScript.

Prerequisites

In this article you will build a Web application using Grails and GWT. The Grails framework is built on the Groovy programming language, a dynamic language for the Java™ platform. Familiarity with Groovy is great, but not completely necessary. Knowledge of Java programming can be a good substitute, or even other dynamic languages like Ruby or Python. Grails 1.0.3 was used with developing this article (see Resources). Grails can work with numerous databases or application servers, but none is needed for this article—Grails comes with both. The front end is built using the Google Web Toolkit, and to use GWT you definitely must be familiar with Java. Google Web Toolkit 1.5.3 was used for this article (see Resources).


New services

In Part 1, you created an application that used a Service Oriented Architecture (SOA) built using Grails. It is possible to build a new application on top of this back end. That is one of the major advantages of using an SOA back end with a pure client-side front end. Your server-side code is completely decoupled from the user interface. However, to further demonstrate how easy it is to create Web services with Grails, let's modify the existing back end and add a search API to it.

Search API service

The existing application had a search business service. We only used its list API to provide a list of all stories in the application. Take a look at what else the service can do, as shown in Listing 1.


Listing 1. The Search Business service

class SearchService {

    boolean transactional = false

    def list() {
        Story.list()
    }

    def listCategory(catName){
        Story.findAllWhere(category:catName)
    }
    
    def searchTag(tag){
        Story.findAllByTagsIlike("%"+tag+"%")
    }
}

The listCategory method finds all of the stories in a given category. The searchTag method finds all of the stories with a given tag. It does not require an exact match and is case insensitive. As mentioned, we have only used the list API so far. So let's create a new Web service that uses these other two methods. All you need to do is add a new method to the ApiController, as shown in Listing 2.


Listing 2. New ApiController

import grails.converters.*

class ApiController {
    // injected services
    def searchService
    def storyService

    def search = {
        def results= null
        def tagResults = null
        if (params.tag){
            tagResults = searchService.searchTag(params.tag)
        }
        def catResults = null
        if (params.category){
            catResults = searchService.listCategory(params.category)
        }
        if (params.tag && params.category){
            def tagMap = [:]
            tagResults.each{ story ->
                tagMap[story.id] = story
            }
            results = catResults.findAll { tagMap[it.id] != null} 
        } else {
            if (params.category){
                results = catResults
            } else {
                results = tagResults
            }
         }
        render results as JSON
    }
}

Listing 2 only shows the new search method. There are two request parameters that it expects: tag and category. It can handle either or both. These parameters, like all request parameters in a Grails application, are exposed through the params object. If the tag parameter is present, then the searchTag method is invoked on the search service. If the category parameter is present, then the listCategory method is invoked on the search service. If both parameters are present (if params.tag and params.category), then things get interesting.

We only want to show the stories that are common to both lists of stories returned from the calls to the search service. So first you create an empty map using the [:] notation. This is a HashMap from Java. You then iterate over the list from the stories with the given tag, putting each into the map using the ID of the story as the key. Use the standard Groovy syntax for a closure. Then you find all of the stories from the category search whose ID is present in the map you just created. Use the convenient findAll method that Groovy adds to lists, and once again use a closure. This time use Groovy's shorthand notation (the 'it' object) for the closure. Finally, whatever the results are, use the JSON converter from Grails to turn the list into a JSON object.

In the previous article (see Resources), you saw how easy Grails makes it to render data as XML, but as you can see, Grails makes it just as easy to render as JSON. You can now test the new Web service. Following the Grails conventions, you know the URL to it will be http://<root>/digg/api/search. Here is some sample output for doing a search where category is equal to "technology," as shown in Listing 3.


Listing 3. Sample search output

[{"id":1,"class":"Story","category":"technology","description":"How to get 
a ternary operator in Scala","link":"http://blog.tmorris.net/does-scala-have-
javas-ternary-operator/","tags":"programming scala","title":"Does Scala have 
Java's ternary operator?","votesAgainst":0,"votesFor":0},{"id":3,"class":"Story",
"category":"technology","description":"New animations available in the Flex 4 'Gumbo'
 release.","link":"http://graphics-geek.blogspot.com/2008/10/flex-specificanimations
-posted.html","tags":"flash flex","title":"Flex Specific Animations Posted",
"votesAgainst":0,"votesFor":0}]

This is standard JSON and is exactly what you would expect. You get an array of objects, where each JSON object represents a story. One interesting thing you should notice is that the Grails serializer adds a class attribute that is the Groovy class of your object. You will not need this in your application here because the list is homogeneous, but you could imagine scenarios where this metadata would be very useful. Now that you've modified the Web services, you can start building a new application that consumes this service using the GWT.


Search application with GWT

We are ready to create a new application for searching stories from the Digg clone application. Like the Flex-based application, this will be a completely client-side application that will call your Grails-powered services. This time, however, you will use the GWT. This allows you to program all of your application in Java, but GWT will compile it into extremely efficient JavaScript that will run on the client. Let's start by looking at the entry point to the GWT-powered application.

Search entry point

The entry point into a GWT application is a Java class whose compiled JavaScript is the script for a given page. It is responsible for creating the user interface and handling interactions. In this case you need to create a form the user can fill out to specify a search query, as well the result of those queries. Take a look at this code in Listing 4.


Listing 4. The DiggApp class

public class DiggApp implements EntryPoint {

    private static final String[][] CATEGORIES = {
        { "Technology", "technology" }, { "World & Business", "business" },
        { "Science", "science" }, { "Gaming", "games" },
        { "Lifestyle", "lifestyle" }, { "Entertainment", "entertainment" },
        { "Sports", "sports" }, { "Offbeat", "miscellaneous" } };

    private final HorizontalPanel searchPanel = createSearchForm();
    private final VerticalPanel resultsPanel = new VerticalPanel();

    public void onModuleLoad() {
        RootPanel.get().add(searchPanel);
        RootPanel.get().add(resultsPanel);
    }
    private HorizontalPanel createSearchForm() {
        HorizontalPanel panel = new HorizontalPanel();
        panel.setTitle("Search for Stories");
        Label tagLabel = new Label("Tag:");
        final TextBox tagBox = new TextBox();
        panel.add(tagLabel);
        panel.add(tagBox);
        Label catLabel = new Label("Category:");
        final ListBox catBox = new ListBox();
        catBox.addItem("");
        for (String[] category : CATEGORIES){
            catBox.addItem(category[0],category[1]);
        }
        panel.add(catLabel);
        panel.add(catBox);
        Button searchBtn = new Button("Search");
        searchBtn.addClickListener(new ClickListener(){
            public void onClick(Widget sender) {
                search(tagBox.getText(), catBox.getValue(catBox.getSelectedIndex()));
            }
        });
        panel.add(searchBtn);
        return panel;
    }
}

The onModuleLoad method is invoked when the page is loaded. In this case it creates a search form with a text box for entering tags, a drop-down list of categories, and a search button for requesting a search. It also creates an empty panel for showing search results. The search method is invoked when the search button is clicked. The code for that is shown in Listing 5.


Listing 5. The DiggApp search method

private final void search(String tag, String category){
    resultsPanel.clear();
    Story.search(tag, category, new RequestCallback(){
        public void onError(Request request, Throwable exception) {
            Label label = new Label("Sorry there was an error");
            resultsPanel.add(label);
        }
        public void onResponseReceived(Request request, Response response) {
            List<Story> stories = Story.fromJson(response.getText());
            SearchTable grid = new SearchTable(stories);
            resultsPanel.add(grid);
        }
    });
}    

This uses a search method on the Story class. The Story class acts as the model in the system, very similar to the Story class created in the Flex application from the first part of this series. It has a static search method and acts as a data model. It is shown in Listing 6.


Listing 6. The Story class

public class Story {

    private static final String BASE_URL = "/digg/api/search?";
    
    private int id;
    private String link;
    private String title;
    private String description;
    private String tags;
    private String category;
    private int votesFor;
    private int votesAgainst;

    public Story(JSONObject obj){
        id = (int) obj.get("id").isNumber().doubleValue();
        link = obj.get("link").isString().stringValue();
        title = obj.get("title").isString().stringValue();
        description = obj.get("description").isString().stringValue();
        tags = obj.get("tags").isString().stringValue();
        category = obj.get("category").isString().stringValue();
        votesFor = (int) obj.get("votesFor").isNumber().doubleValue();
        votesAgainst = (int) obj.get("votesAgainst").isNumber().doubleValue();
    }
    
    public static void search(final String tag, final String category, final 
RequestCallback callback){
        String url = buildRequest(tag, category);
        RequestBuilder builder = 
            new RequestBuilder(RequestBuilder.GET, url);
        try {
            builder.sendRequest(null, new RequestCallback(){
                public void onError(Request request, Throwable exception) {
                    callback.onError(request, exception);
                }
                public void onResponseReceived(Request request, Response response) {
                    callback.onResponseReceived(request, response);
                }                
            });
        } catch (RequestException e) {
            callback.onError(null, e);
        }
    }
}

Note that this is only a partial listing of the class; some helper methods and the getters and setters have been omitted. The search method takes the tag and category and also a RequestCallback. It then uses the GWT RequestBuilder class to create and send an HTTP request to the Web service. RequestCallback is an interface in GWT needed for sending asynchronous HTTP requests. The Story.search method simply delegates the RequestCallback passed in to it. So in this case it will execute the RequestCallback created in Listing 5. Note that in both Listing 5 and Listing 6, we created an anonymous inner class that implemented the RequestCallback interface. This is a common technique in GWT (and in Java UI programming in general) because it gives you access to the fields and methods of the enclosing class. Going back to Listing 5 again, you can see that you get the response text and parse it into a list of Story objects using the Story.fromJson method. This is shown in Listing 7.


Listing 7. The Story.fromJson method

public static List<Story> fromJson(String jsonText){
    JSONValue val = JSONParser.parse(jsonText);
    JSONArray array = val.isArray();
    List<Story> stories = new ArrayList<Story>(array.size());
    for (int i=0;i<array.size();i++){
        Story story = new Story(array.get(i).isObject());
        stories.add(story);
    }
    return stories;
}

This method uses GWT's JSONParser class. It creates an array of JSON objects and passes each object into the constructor of the Story class (shown in Listing 6). Going back to Listing 5 again, you see that once you have the list of story objects, you use it to create a SearchTable object. This is a custom widget and is shown in Listing 8.


Listing 8. The SearchTable widget

public class SearchTable extends FlexTable {
    private List<Story> stories;
    public SearchTable(List<Story> stories) {
        super();
        this.stories = stories;
        this.buildTable();
    }
    private void buildTable(){
        this.setBorderWidth(2);
        this.setText(0, 0, "story");
        this.setText(0, 1, "category");
        this.setText(0, 2, "description");
        if (stories.size() == 0){
            showMessage("Sorry there were no results");
        } else {
            for (int i=0;i<stories.size();i++){
                Story story = stories.get(i);
                setWidget(i+1, 0, story.getTitleLink());
                setText(i+1, 1, story.getCategory());
                setText(i+1, 2, story.getDescription());
            }
        }
    }
    private void showMessage(String msg){
        setText(1,0, msg);
        getFlexCellFormatter().setColSpan(1, 0, 3);
    }    
}

This class extends the core GWT class, FlexTable. It simply iterates over the list of stories and populates a simple table. If there are no stories, then it shows a simple message saying as much. You now have created all of the code for the application, but there is one more thing you need to do. The Story class used GWT's HTTP library and JSON library for sending requests to the Web service and for parsing the response from the service. You need to make these available to the compiler by adding it to the module definition for the application. This is shown in Listing 9.


Listing 9. The module definition

<module>

      <!-- Inherit the core Web Toolkit stuff.                        -->
      <inherits name='com.google.gwt.user.User'/>
    
      <!-- Inherit the default GWT style sheet.  You can change       -->
      <!-- the theme of your GWT application by uncommenting          -->
      <!-- any one of the following lines.                            -->
      <inherits name='com.google.gwt.user.theme.standard.Standard'/>
      <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
      <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

      <!-- Other module inherits                                      -->
      <inherits name="com.google.gwt.http.HTTP" />
      <inherits name="com.google.gwt.json.JSON" />

      <!-- Specify the app entry point class.                         -->
      <entry-point class='org.developerworks.digg.client.DiggApp'/>
    
      <!-- Specify the application specific style sheet.              -->
      <stylesheet src='DiggApp.css' />
</module>

This is mostly just the file that is generated by the GWT applicationCreator script, but you have added two lines to it. Those are the two lines after the 'Other module inherits' comment in Listing 9. All you have done is add the two GWT libraries: HTTP and JSON. Now the application is ready to be compiled into JavaScript and be deployed.


GWT deployment

It is common to see GWT code as part of a Java Web application, so it gets combined into a WAR file. In this case you are only using the client-side parts of GWT—in other words, just HTML, CSS, and JavaScript. This lets you use GWT independent of server technologies. All you need to do is compile the Java to JavaScript, take the results, and combine it with any Web application.

When you created the GWT application (using GWT's applicationCreator script), a compiler script was created for you. It is an executable, so it can be invoked simply, as shown in Listing 10.


Listing 10. Compiling GWT application

$ ./DiggApp-compile
Compiling module org.developerworks.digg.DiggApp
2008-11-08 19:56:14.962 java[1300:c1b] 
      [Java CocoaComponent compatibility mode]: Enabled
2008-11-08 19:56:14.963 java[1300:c1b] [Java CocoaComponent compatibility mode]: 
      Setting timeout for SWT to 0.100000
Compilation succeeded
Linking compilation into ./www/org.developerworks.digg.DiggApp

Now you just copy this folder (the one called org.developerworks.digg.DiggApp) to the Web application. Now recall that GWT runs as JavaScript, and so any calls a GWT application makes to remote servers is limited by the same-origin policy. The simplest thing to do is deploy the GWT application as part of the Grails application. To do this, simply copy it to the web-app folder in the Grails application, as shown in Figure 1.


Figure 1. GWT application deployed to Grails
GWT application deployed to Grails

Now you can run the new search application by opening it in a browser. Using the Grails conventions, the URL will be http://<root>/digg/org.developerworks.digg.DiggApp/DiggApp.html. It should look something like Figure 2.


Figure 2. The search application
The search application

Figure 2 shows the application with only the search form being displayed. Enter either a tag or a category (or both) to perform a search. The result is shown in Figure 3.


Figure 3. The search application with search results
The search application with search results

So there is the application! This is a pretty typical GWT application using its core components. Those components provide a thin wrapper on top of standard HTML. This makes them fast and lightweight, but a little ... underwhelming, at least when compared to a lot of JavaScript-based widget kits (or what you get with Flex). Luckily you have some options, namely Ext GWT.


Using Ext GWT widgets

The Ext JS library is a very popular JavaScript library for creating applications. It has numerous rich widgets, as well as utility methods for common JavaScript tasks. It has also been translated into Java for use with the Google Web Toolkit under the name of Ext GWT. First you need to configure the application to use Ext GWT.

Setting up Ext GWT

Setup with Ext GWT is pretty simple. First, you need the Ext GWT JAR (gxt,jar, part of the Ext GWT download) to be on your classpath. For the application, you created a lib directory and copied gxt.jar to it. You now need to add it to the compiler's classpath, so modify the DiggApp-compile script by adding './lib/gxt.jar' to the classpath there, as shown in Listing 11.


Listing 11. Modified DiggApp-compile script

#!/bin/sh
APPDIR=`dirname $0`;
java -XstartOnFirstThread -Xmx256M -cp "$APPDIR/src:$APPDIR/bin:
/Users/michael/lib/gwt-mac-1.5.3/gwt-user.jar:
/Users/michael/lib/gwt-mac-1.5.3/gwt-dev-mac.jar:./lib/gxt.jar" 
com.google.gwt.dev.GWTCompiler -out "$APPDIR/www" "$@" 
org.developerworks.digg.DiggApp;

Next, you need to make slight modifications to the base HTML page that GWT generates. This can be found in the /public directory. The modified version is shown in Listing 12.


Listing 12. Modified DiggApp.html

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">  

<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <!--                                           -->
    <!-- Any title is fine                         -->
    <!--                                           -->
    <title>DiggApp</title>
    
    <!--                                           -->
    <!-- This script loads your compiled module.   -->
    <!-- If you add any GWT meta tags, they must   -->
    <!-- be added before this line.                -->
    <!--                                           -->
    <script type="text/javascript" language="javascript" 
        src="org.developerworks.digg.DiggApp.nocache.js"></script>
    <link rel="stylesheet" type="text/css" href="css/ext-all.css" />
  </head>

  <!--                                           -->
  <!-- The body can have arbitrary html, or      -->
  <!-- you can leave the body empty if you want  -->
  <!-- to create a completely dynamic UI.        -->
  <!--                                           -->
  <body>

    <!-- OPTIONAL: include this if you want history support -->
    <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' 
        style="position:absolute;width:0;height:0;border:0"></iframe>

  </body>
</html>

Two modifications have been made. First, you changed the DOCTYPE to HTML 3.2, as this is what Ext expects. Next, you added the CSS stylesheet that Ext needs: "css/ext-all.css". Note, you do not need to actually copy this CSS file anywhere; it will be created by the compiler when it sees that the application uses the Ext GWT module. That brings us to the last modification: You need to add the Ext GWT module to the module definition. This is shown in Listing 13.


Listing 13. Module definition with GXT support

<module>

      <!-- Inherit the core Web Toolkit stuff.                        -->
      <inherits name='com.google.gwt.user.User'/>
    
      <!-- Inherit the default GWT style sheet.  You can change       -->
      <!-- the theme of your GWT application by uncommenting          -->
      <!-- any one of the following lines.                            -->
      <inherits name='com.google.gwt.user.theme.standard.Standard'/>
      <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
      <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->

      <!-- Other module inherits                                      -->
      <inherits name="com.google.gwt.http.HTTP" />
      <inherits name="com.google.gwt.json.JSON" />

 <inherits name='com.extjs.gxt.ui.GXT'/> 

      <!-- Specify the app entry point class.                         -->
      <entry-point class='org.developerworks.digg.client.DiggApp'/>
    
      <!-- Specify the application specific style sheet.              -->
      <stylesheet src='DiggApp.css' />
</module>

If you compare Listing 13 to Listing 9, there is only one difference. The module now inherits from the com.extjs.gxt.ui.GXT module. This means that Ext GWT is configured, and you can use Ext's widgets.

Richer search results

Ext includes a Grid widget that is much richer than the GWT table. It allows for many of the same things that the DataGrid in Flex allows for, like sortable and resizable columns. You will use this as a basis for a new search results display widget, as shown in Listing 14.


Listing 14. The StoryGrid widget

public class StoryGrid extends LayoutContainer {

    public StoryGrid(List<Story> stories){
        this.setLayout(new FlowLayout(10));
        this.setSize(750, 300);
        ListStore<BaseModelData> store = this.buildDataModel(stories);
        Grid<BaseModelData> grid = 
            new Grid<BaseModelData>(store, createColumnModel());
        grid.setBorders(true);
        add(grid);
    }
    
    private ColumnModel createColumnModel(){
        List<ColumnConfig> configs = new ArrayList<ColumnConfig>();
        ColumnConfig column = new ColumnConfig();
        column.setId("titleLink");
        column.setHeader("Story");
        column.setWidth(200);
        configs.add(column);
        column = new ColumnConfig();
        column.setId("category");
        column.setHeader("Category");
        column.setWidth(100);
        configs.add(column);
        column = new ColumnConfig();
        column.setId("description");
        column.setHeader("Description");
        column.setWidth(400);
        configs.add(column);
        return new ColumnModel(configs);
    }
    
    private ListStore<BaseModelData> buildDataModel(List<Story> stories){
        ListStore<BaseModelData> data = new ListStore<BaseModelData>();
        for (Story story : stories){
            BaseModelData model = new BaseModelData(story.properties());
            data.add(model);
        }
        return data;
    }
}

A Grid in Ext needs two basic things: column definitions and a data store. The createColumnModel method creates the column definitions for the Grid. It is a list of ColumnConfig objects. The key thing about ColumnConfig is its ID property. This tells the Grid the name of the property of the data model to use. The data model is created by the buildDataModel method. You use the GXT class BaseModelData. This class just wraps around a Java Map. In this case you used the properties() method from the Story class. Take a look at that in Listing 15.


Listing 15. Story.properties method

public class Story {    
    public Map<String,Object> properties(){
        Map<String,Object> props = new HashMap<String,Object>();
        props.put("id",id);
        props.put("link", link);
        props.put("title", title);
        props.put("description",description);
        props.put("tags",tags);
        props.put("category",category);
        props.put("votesFor",votesFor);
        props.put("votesAgainst",votesAgainst);
        props.put("titleLink", getTitleLink().getHTML());
        return props;
    }
}

Now if you go back to the ColumnConfig objects created in the createColumnModel method in Listing 14, you see that when you set ColumnConfig's ID to "titleLink", then that string was used as a key into the Map created in Listing 15. Now you can simply swap out the SearchTable for the StoryGrid back in Listing 5, recompile, and deploy. The application now looks a little different, as shown in Figure 4.


Figure 4. Improved search application
Improved search application

From here you could further enhance the UI. For instance, you could add more columns to the Grid. You could customize what columns can be used for sorting, which ones can be resized, and which ones provide access to the customization menu. You could even replace the search form widgets with more widgets from Ext. The sky's the limit.


Summary

In the first article in this series, you created an SOA back end using Grails and a UI using Flex. This time you took that back end and added a new Web service to it. Once again Grails and Groovy made this very easy to do. You created a new search application using GWT and saw how GWT can work with any Web service using its HTTP and JSON libraries. Finally, you looked at Ext GWT as a way to add richer UI widgets to GWT. Now you can create feature-rich applications in pure Java that is compiled into JavaScript that runs completely on the client.



Downloads

DescriptionNameSizeDownload method
Example Grails Applicationdigg.zip1101KB HTTP
Example GWT app sourcedigg-GWT.zip2990KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

  • Grails: This article uses Grails Version 1.0.3.

  • Get the Google Web Toolkit: This article uses Version 1.5.3.

  • Java SDK: This article uses Java SE 1.6_05.

  • Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2, Lotus, Rational, Tivoli, and WebSphere.

Discuss

About the author

Michael Galpin's photo

Michael Galpin has been developing Web applications since the late 90s. He holds a degree in mathematics from the California Institute of Technology and is an architect at eBay in San Jose, CA.

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=Web development, Open source
ArticleID=375181
ArticleTitle=Rich Internet Applications with Grails, Part 2: Grails and the Google Web Toolkit
publish-date=03102009
author1-email=mike.sr@gmail.com
author1-email-cc=cmwalden@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