Using GWT History mechanism to create an RSS reader app Tutorial

Posted by

Introduction

This tutorial takes the background information on GWT History Management provided in the Managing History and Hyperlinks tutorial and uses it to create an RSS reader application that uses this history mechanism to load initialization parameters. The application that’s built in this tutorial takes the RSS feed URL as a parameter passed to the web app’s URL. The feed URL must be preceded by “#”. For example, to load this feed: http://feeds.feedburner.com/blogspot/NWLT, you would have to load the following URL in the GWT test browser (in hosted mode):

If you are unfamiliar with GWT basics, please refer to our GWT Intro, Anatomy of a GWT Project, and Hello World tutorials. To learn more about GWT, I recommend reading these good books on Safari Books Online GWT Solutions, GWT Applications, AJAX Security.

ROME – Java RSS API

We are going to build a GWT service that actually loads the RSS feed using ROME – https://rome.dev.java.net/. It’s a very easy-to-use API that allows you to consume and generate various types of syndication feeds (RSS and Atom formats including RSS 0.90, RSS 0.91 Netscape, RSS 0.91 Userland, RSS 0.92, RSS 0.93, RSS 0.94, RSS 1.0, RSS 2.0, Atom 0.3, and Atom 1.0). When we get to the service implementation portion of the tutorial, I will show you how to use it, if you don’t already know how.

The RSS Reader App

This tutorial is meant to show you how to write an application that uses history. The Managing History and Hyperlinks tutorial gives you the background information you need, and shows you how to “think” about History when building your GWT app. This tutorial takes the next step and shows you a sample application that uses history to load an RSS feed. There are other ways to pass parameters to GWT apps that don’t involve using the History mechanism. However, the application in this tutorial will take this approach.

When you click on the links inside theis app, it uses GWT history to send the RSS Reader App the history token, which is a URL to load the RSS feed. This results in a service call to the RPC service that gets the contents of the feed and returns this data to the app. The app then displays this information in a grid. For deployment, the entire app can be loaded in an iframe. You can choose to deploy it using a div as well, but this would require you to include the style sheet for this app to your webpage’s HTML HEAD tag. If you don’t want to mess with the HTML HEAD tag to include the stylesheet, just use an iframe.

This app does a lot of things (load RSS feeds, create widgets, create and show popups, use history to pass parameters, etc). Let’s take a deeper look at each one of these in the following sections. You can download all the source code and IDEA project files here.

Using browser history to pass parameters

Let’s take a look at the code that integrates the GWT history mechanism into the RSS Reader App:

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// history mgmt - ie, loading new feeds
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/**
 * this method is called when the fwd/back buttons are invoked on the browser.
 * this is also invoked when hyperlinks (generated by the app) are clicked.
 */
public void onHistoryChanged(String historyToken) {
  updateStateWithNewFeed(historyToken);
}
/**
 * this is called when the app first starts up... check to see if an existing state token is
 * provided in the URL of the browser...
 */
public void initHistory() {
  // wire this panel up to the browser's history mechanism
  History.addHistoryListener(this);
  // see if any history tokens are in the app's URI...
  String token = History.getToken();
  if (token.length() == 0) {
    replaceLoadingMessage();
    onHistoryChanged(NO_URL);
  }
  else {
    onHistoryChanged(token);
  }
}
  1. The initHistory() method is called when the app is first launched from the ReaderEntryPoint class. This method registers a listener for history change events with the History class. When the history changes, the onHistoryChanged() method is called, which simply passes the token to the updateStateWithNewFeed() method – this method takes care of resolving the URI passed as the token, and displaying it in the app.
  2. When the initHistory() method is executed for the first time, when the app loads via it’s entry point class, it checks to see if there are any tokens that were passed to the app via the URI, and if none were passed, it sets an initial history token (NO_URL), and this lets the updateStateWithNewFeed() method know that there’s no URI to resolve into feed data. The app then displays the “Got a new browser history change token: INITIAL STATE” message (in a label on the bottom) and it will respond to you clicking on any of the 3 hyperlinks (Google Web Toolkit Blog RSS feed, Sun Developer News RSS feed, and SwingLabs Forum RSS feed).
  3. By the way, these 3 hyperlinks were created using the Hyperlink class, which can take a history token as a parameter. When you click on this hyperlink, it will trigger GWT’s history mechanism, and it causes the onHistoryChanged() method, which will then cause the RSS feed to be loaded and displayed in the app. So really, the hyperlink becomes the way to trigger functionality in your app, and it causes the state of the app to change, and based on this state change, you can activate different parts of your app’s functionality.
  4. Since the app itself is loaded in an iframe, once you’ve click on a few RSS feeds, you can go back and forth between the browser history (inside that iframe) by right clicking on the iframe and selecting Back and Forward.

So that’s the main driver for this application, hyperlinks with tokens embedded in them, that you can activate by clicking on these hyperlinks. Each click translates to a call to onHistoryChanged(), which then makes the app load the URI passed as the token.

Using ROME to get data from an RSS feed URI

Let’s take a look at the code that gets executed when you do click on any one of the 3 hyperlinks:

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// respond to history change event...
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/**
 * this method is called when the MainPanel's contents must be changed due to a state change.
 * in the context of this app, a state change represents a new URL being loaded for the RSS reader.
 */
private void updateStateWithNewFeed(String feedURL) {
  String msg = "<b>Got a new browser history change token:</b><br>";
  // if this special String is passed, it means that no URL is provided by the user.
  if (feedURL.equals(MainPanel.NO_URL)) {
    displayStatus(msg + "INITIAL STATE");
    return;
  }
  displayStatus(msg + feedURL);
  // make a service call to resolve the feedURL to a FeedList
  GetFeedService.App.getInstance().getFeedEntries(feedURL, new AsyncCallback() {
    public void onFailure(Throwable caught) {
      displayStatus("problem with feed url:" + caught.getMessage());
    }
    public void onSuccess(Object result) {
      // load the grid and display it
      updateGrid((FeedEntryList) result);
    }
  });
}
/**
 * this method is called when a new URL/history token is loaded by {@link #updateStateWithNewFeed(String)}. make
 * sure to turn the feed into a grid and display it.
 */
private void updateGrid(FeedEntryList feedList) {
  replaceLoadingMessage();
  // get a new grid for the feedList
  Grid newgrid = GridBuilder.create(feedList);
  newgrid.setStyleName("grid");
  if (grid != null) gridscrollpanel.remove(grid);
  gridscrollpanel.add(newgrid);
  grid = newgrid;
}
  1. The updateStateWithNewFeed() method takes the token that’s passed in the URI (from the Hyperlink that was created with the same token), and it displays it to the browser in a label. This label is styled using the CSS style “statuslabel“. You can change the way this label looks by changing that style in the Style.css file that’s provided in the source code.
  2. Once the feed URI is displayed on the bottom of the app, the app makes a call to the service GetFeedService’s getFeedEntries() method, and it passes the token as a parameter. The GetFeedServiceImpl then takes the URI/token parameter and uses ROME to load the RSS feed into a List of FeedEntry objects (which contain the name, description, link, and author of each feed entry). All the FeedEntry objects are sent back to the client in a FeedEntryList, which is then processed by the updateGrid() method.

Here are more details on the GetFeedServiceImpl’s getFeedEntries() method:

/**
 * resolve the feedURI into a list of FeedEntry objects and return it to the client. The following information
 * is retrieved from the RSS feed:
 * <ol>
 * <li>title
 * <li>description
 * <li>author
 * <li>uri
 * </ol>
 * 
 * If you want to add more information to send back to the client, then just add them here, and extend the 
 * {@link FeedEntry} class to suit your needs.
 * 
 * @param feedURI
 * @return {@link FeedEntryList} object that holds all the {@link FeedEntry} objects that are retrieved from the URI
 */
public FeedEntryList getFeedEntries(String feedURI) {
  System.out.println("got request from client:" + feedURI);
  FeedEntryList list = new FeedEntryList();
  // try and get the rss feed...
  try {
    SyndFeedInput input = new SyndFeedInput();
    SyndFeed feed = input.build(new XmlReader(new URL(feedURI)));
    //System.out.println(feed);
    // Iterate through feed items
    Iterator entryIter = feed.getEntries().iterator();
    while (entryIter.hasNext()) {
      SyndEntry entry = (SyndEntry) entryIter.next();
      String title = entry.getTitle();
      String uri = entry.getUri();
      String author = entry.getAuthor();
      String descr = "";
      if (entry.getDescription() != null) {
        descr = entry.getDescription().getValue();
      }
      list.addFeedEntry(title, uri, author, descr);
    }
  }
  catch (Exception e) {
    e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
  }
  return list;
}
  1. The main class that we use from ROME is SyndFeed and SyndFeedInput. We ask SyndFeedInput to resolve the provided URI to a SyndFeed. Also, ROME uses JDOM to do it’s XML parsing.
  2. A SyndFeed object contains a list of SyndEntry objects, which are then queried to get the author, title, uri, and description out of. If you want to send back more information then just copy more fields out of the SyndFeed object and send it back to the client by passing it to the FeedEntry class.

In order to send data back from the SyndFeed to the GWT app, select fields are copied to a FeedEntry object:

public FeedEntry(String name, String link, String author, String descr) {
  this.name = name;
  this.link = link;
  this.author = author;
  this.descr = descr;
}
  1. The FeedEntry class and FeedEntryList class are serializable by GWT.

The FeedEntryList class is made serializable using the typeArgs annotation:

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// data
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/**
 * @gwt.typeArgs <Reader.client.datamodel.FeedEntry>
 */
private ArrayList feedEntryList;
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// constructor
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
public FeedEntryList(){
  feedEntryList = new ArrayList();
}
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// methods
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
public void addFeedEntry(String name, String link, String author, String value) {
  feedEntryList.add(new FeedEntry(name, link, author, value));
}
  1. Since the ArrayList contains only objects of type FeedEntry, the typeArgs annotation is provided for the GWT compiler.

Once the client app gets the FeedEntryList, it creates HTML objects out of them and then adds them a Grid, and this is displayed in the browser.

You can see all the code that builds the Grid and PopupPanels here:

/**
 * GridBuilder simply takes the FeedList and populates a grid with it.
 *
 * @author Nazmul Idris
 * @version 1.0
 * @since Jan 10, 2008, 10:03:22 AM
 */
public class GridBuilder {
/**
 * this builds a {@link Grid} with the given list. the resulting grid object only has 1 col and as many rows
 * as the elements in the {@link FeedEntryList}. simple hyperlinks (in HTML) are produced from the {@link FeedEntry}
 * objects contained in the list.
 */
public static Grid create(FeedEntryList list) {
  final int size = list.getSize();
  Grid grid = new Grid(size, 1);
  for (int rowIdx = 0; rowIdx < size; rowIdx++) {
    // add each of the FeedEntry object to the grid
    FeedEntry entry = list.getFeedEntry(rowIdx);
    grid.setWidget(rowIdx,
                   0,
                   buildHTML(entry)
    );
  }
  return grid;
}
public static final HTML buildHTML(FeedEntry entry) {
  // get the html widget for the entry...
  HTML htmlWidget = new HTML(entry.toHTMLString(), true);
  // create a popup for mouseover the entry...
  final String htmlPopupText = "<b>Author: </b>" + entry.author +
                               "<br><b>Description: </b>" + entry.descr;
  htmlWidget.addMouseListener(new MouseListenerAdapter() {
    PopupPanel popup;
    public void onMouseEnter(final Widget widget) {
      popup = new PopupPanel(false, false);
      popup.setStyleName("infopopuplabel");
      HTML html = new HTML(htmlPopupText, true);
      html.addClickListener(new ClickListener() {
        public void onClick(Widget sender) {
          popup.hide();
        }
      });
      popup.setWidget(html);
      popup.setPopupPosition(
          400, 0
      );
      popup.show();
    }
    public void onMouseLeave(Widget widget) {
      popup.hide();
    }
  });
  return htmlWidget;
}
}//end class GridBuilder
  1. The GridBuilder class is responsible for taking a FeedEntryList and populating a Grid with it. This Grid is then added to a ScrollPanel that’s contained in the MainPanel class that creates the UI.
  2. Note the use of PopupPanel again to display more details on each FeedEntry object. The author and description are put in a PopupPanel and then displayed on the right side of the app. If you want to change the appearance of this popup, change the following style in Style.css: “infopopuplabel“. The popup is displayed as a result of the user’s mouse entering each row of the grid. When the mouse leaves a row, it’s associated popup is supposed to go away (hide() gets called). If you find that a popup is still visible, even though you’ve moved your mouse away from it’s row in the grid, then just click on it, and it will hide.

Finally, here’s the MainPanel that assembles the UI:

//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// data
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
private DockPanel panel;
private Hyperlink[] linkRay;
private ScrollPanel gridscrollpanel;
private Grid grid;
private boolean _runOnce = false;
public RSSFeedSource RSS1 = new RSSFeedSource(
    "http://feeds.feedburner.com/blogspot/NWLT",
    "Google Web Toolkit Blog RSS feed");
public RSSFeedSource RSS2 = new RSSFeedSource(
    "http://blogs.sun.com/SDNProgramNews/feed/entries/atom",
    "Sun Developer News RSS feed");
public RSSFeedSource RSS3 = new RSSFeedSource(
    "http://forums.java.net/jive/rss/rssmessages.jspa?forumID=73",
    "SwingLabs Forum RSS feed");
public RSSFeedSource[] feedSrcRay = {RSS1, RSS2, RSS3};
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// constructor
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
public MainPanel() {
  panel = new DockPanel();
  linkRay = new Hyperlink[feedSrcRay.length];
  for (int i = 0; i < feedSrcRay.length; i++) {
    linkRay[i] = new Hyperlink(
        feedSrcRay[i].getTitle(),
        feedSrcRay[i].getUrl()
    );
  }
  VerticalPanel linksPanel = new VerticalPanel();
  linksPanel.setStyleName("linkspanel");
  for (int i = 0; i < linkRay.length; i++) {
    linksPanel.add(linkRay[i]);
  }
  panel.add(linksPanel, DockPanel.NORTH);
  gridscrollpanel = new ScrollPanel();
  gridscrollpanel.setStyleName("gridscrollpanel");
  panel.add(gridscrollpanel, DockPanel.CENTER);
  initHistory();
}
public Widget getWidget() {
  return panel;
}
  1. Essentially the main user interface is made up of a DockPanel to which some links get added on the top, and the grid containing FeedEntryList on the bottom. You can change the appearance of these links by changing this CSS rule: “linkspanel“. You can change the appearance of the ScrollPanel added to the DockPanel by changing this rule: “gridscrollpanel“.
  2. Note the call to initHistory() in the constructor. This constructor is called by the entry point class.

Application loading screen revisited

There are 2 loading messages displayed by the RSS reader app:

  1. JS loading” – the first message “Loading the javascript files…” is shown in the HTML host page in a div with id “loadingdiv”. This message is loaded while the JS files for the app are downloaded by the browser. If the JS files are really big, then this message can show the user that something is afoot, and to expect something soon.
  2. app loading” the 2nd message “Starting RSS Reader App” is shown when the app is executing, but hasn’t displayed the INITIAL STATE message or loaded the very first RSS feed (if a feed URI is passed as a token).

This application shows a loading screen until the INITIAL STATE message is displayed in a popup, or a feed is loaded if it’s passed in the URI the first time the app is loaded. In order to do this, the entry point class expects a div of id “appdiv” to exist in the HTML host page. Here’s the code for the entry point:

/** Entry point classes define <code>onModuleLoad()</code>. */
public class ReaderEntryPoint implements EntryPoint {
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// constants
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/** on the HTML host page, this div is where the MainPanel's contents should go */
public static final String AppDiv = "appdiv";
public static final String LoadingDiv = "loadingdiv";
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// data
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
private MainPanel panel;
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
// main method
//XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
/** This is the entry point method. */
public void onModuleLoad() {
  GWTUtils.removeElementFromDOM(LoadingDiv);
  addLoadingMessage();
  DeferredCommand.addCommand(new Command() {
    public void execute() {
      // create the UI component
      panel = new MainPanel();
    }
  });
}
public static final void addLoadingMessage() {
  GWTUtils.addLoadingMessage("loading.gif", "Starting RSS Reader App", AppDiv);
}
public static void replaceWaitingMessage(Widget widget) {
  GWTUtils.replaceLoadingMessageWith(AppDiv, widget);
}
}// end ReaderEntryPoint

Here’s the HTML host page:

<html>
<head>
  <title>RSS Reader HTML Host Page</title>
  <link href="Style.css" rel="stylesheet" type="text/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>
<!--                                            -->
<!-- This script is required bootstrap stuff.   -->
<!-- You can put it in the HEAD, but startup    -->
<!-- is slightly faster if you include it here. -->
<!--                                            -->
<!--http://gwt.js-->
   1:
   2: <script type="text/javascript" language="JavaScript" src="Reader.Client.nocache.js">

</script>

<!-- this is for history support -->
<iframe src="javascript:''" id='__gwt_historyFrame' style='width:0;height:0;border:0'></iframe>
<h1>RSS Reader App</h1>
<!--this is where the app will go-->
<div id="loadingdiv" class="loadingdiv">Loading the javascript files...</div>
<div class="appdiv" id="appdiv">
</div>
</body>
</html>
  1. The HTML host page has a div with id “loadingdiv”. This is where the “JS loading” message is created. This is removed as soon as the app’s entry point class executes GWTUtils.removeElementFromDOM(LoadingDiv).
  2. The second thing that happens when this module loads is the “app loading” message gets displayed on the browser, via a call to GWTUtils.addLoadingMessage(“loading.gif”, “Starting RSS Reader App”, AppDiv). This loading message is inserted in the div with id “appdiv” by the GWTUtils.addLoadingMessage() method. It basically inserts an HTML widget in the app’s div element, and this widget displays a message and an animated image to show something is loading.
  3. Also note that the MainPanel itself is created in a DeferredCommand. This is just to make it so that the browser stays responsive once the loading screen is displayed.
  4. In the MainPanel class, once the INITIAL STATE message is displayed in a popup or the first feed data is loaded, the “app loading” message is replaced with the MainPanel’s DockPanel. This is done using GWTUtils.replaceLoadingMessageWith() method.

This is a very simple way to ensure that the loading screen is displayed consistently on all browsers, and the code is relatively simple. Feel free to use GWTUtils in your own projects. Also, another advantage of using this technique is that you don’t have to do a whole lot in the HTML host page. Just make sure there’s are 2 divs in your app and you should be good to go to use GWTUtils:

  1. a div for the app, and make sure to assign it the id of “appdiv
  2. a div for the JS loading message, and make sure to assign it the id of “loadingdiv”.

Source code download

  • Download GWTUtils.java here.
  • Download the source code for this project and the IDEA project for it here.