Tutorial: Creating a Stock Watcher with GWT Designer (UPDATED)

Post to Twitter

A while back I wrote three tutorials on using the GWT Designer (by Instantiations, now owned by Google). Although the article was not as “stale” as I expected it might be I still wanted to update it to the latest and greatest version of the GWT Designer (as of Sept. 2010), update the links as well as the source code where needed. For example you will notice I use a ClientBundle now for the image. My original article has remained one of the most popular tutorials I’ve ever written and is even on the GWT website now.

Obviously with a powerful tool like the GWT Designer I cannot show off all the bells and whistles in one tutorial but hopefully this grabs your attention enough to see what is possible and to experiment further.

Ready to start? Lets go…


Make sure You have the GWT Designer installed, if not check out my article on how to do that here.

Create a new project by going into Eclipse and from the menu select: File > New > Other. Browse through the listed options until you see WindowBuilder and expand that. Select the GWT Java Project option.

Press the Next button. For a project name give it StockWatcher.

Press the Next button. Click on the Create GWT Project checkbox and then change the Module Name to StockWatcher and the package name to com.google.gwt.sample.stockwatcher.StockWatcher

Note: Make sure the default Use Standard GWT only is selected.

Press the Finish button.

You should have a project structure that looks similar to this:

There will be some default generated code for you in the StockWatcher.java file:

package com.google.gwt.sample.stockwatcher.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class StockWatcher implements EntryPoint {
	private Button clickMeButton;

	public void onModuleLoad() {
		RootPanel rootPanel = RootPanel.get();

		clickMeButton = new Button();
		rootPanel.add(clickMeButton);
		clickMeButton.setText("Click me!");
		clickMeButton.addClickHandler(new ClickHandler() {
			public void onClick(ClickEvent event) {
				Window.alert("Hello, GWT World!");
			}
		});
	}
}

I’m going to get rid of the Hello World boilerplate code and button. Click on the Design tab at the bottom of the source file editor and GWT Designer will render a visual layout of the current page. If you don’t see the Design tab, right-click on the Java class and select: Open With > WindowBuilder Editor.

You will see the visual representation of the page like so:

When you’re in Design mode, you should see the Designer’s Palette that lists all the supported Panels and Widgets and the Properties pane for editing widget properties. Remove the button by clicking on the Click me! button and press delete on your keyboard.

Save the project and then click on the Source tab and you’ll notice that the generated Hello World code is now gone.

So lets get an idea of what exactly we are trying to build. According to the original GWT Stock Watcher tutorial the UI will look similar to this:

Before we get into designing the UI there is something we will set to make things a little easier/cleaner. Go into the menu and select: Window > Preferences and go into the WindowBuilder and pick Code Generation. Now select the Local tab and check the Declare variable as “final” and then select the Field tab and check the Prefix field access with “this”. Press the Apply button and exit out of the preferences window.

The first thing now to get the UI under way is to go back into the Design tab. From there I’m going to select a VerticalPanel (just click on it) and drop it onto the root panel/content area by clicking on that.

Lets make the vertical panel consume the content area by dragging out the width and height to fill the available space.

I’m going to update the vertical panel’s variable name to something else. Click on the verticalPanel in the Components section and then in the properties set the Variable property to mainPanel.

Several more things need to get added and the way we do this is the same way I just did with the VerticalPanel. Repeat these steps for the following (in the order as they appear below):

1. Add a FlexTable inside of the existing VerticalPanel.
2. Change the FlexTable’s variable name to stocksFlexTable
3. Add a HorizontalPanel below the FlexTable
4. Change the HorizontalPanel’s variable name to addPanel
5. Add a TextBox inside of the HorizontalPanel
6. Give the TextBox a variable name of newSymbolTextBox
7. Set the TextBox’s Focus property to true
8. Add a Button beside the TextBox on the right-hand side of the TextBox)
10. Change the Button’s variable name to addButton
11. Change the Button’s text property to Add
12. Add a Label below the HorizontalPanel
13. Change the Label’s variable name to lastUpdatedLabel

Your layout should look like this:

The structure should look like this:

Save the file.

If you go into the StockWatch.java file and look at the source code you’ll see something similar to this:

package com.google.gwt.sample.stockwatcher.StockWatcher.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Label;

/**
 * Entry point classes define <code>onModuleLoad()</code>.
 */
public class StockWatcher implements EntryPoint {
	private VerticalPanel mainPanel;
	private FlexTable stocksFlexTable;
	private HorizontalPanel addPanel;
	private TextBox newSymbolTextBox;
	private Button addButton;
	private Label lastUpdatedLabel;

	public void onModuleLoad() {
		RootPanel rootPanel = RootPanel.get();

		this.mainPanel = new VerticalPanel();
		rootPanel.add(this.mainPanel, 10, 10);
		this.mainPanel.setSize("244px", "280px");

		this.stocksFlexTable = new FlexTable();
		this.mainPanel.add(this.stocksFlexTable);

		this.addPanel = new HorizontalPanel();
		this.mainPanel.add(this.addPanel);

		this.newSymbolTextBox = new TextBox();
		this.newSymbolTextBox.setFocus(true);
		this.addPanel.add(this.newSymbolTextBox);

		this.addButton = new Button("New button");
		this.addButton.setText("Add");
		this.addPanel.add(this.addButton);

		this.lastUpdatedLabel = new Label("New label");
		this.mainPanel.add(this.lastUpdatedLabel);
	}
}

While your in the source code lets add some code to get the FlexTable populated with some column headers.

Right before this code:

this.mainPanel.add(this.lastUpdatedLabel);

Add this code:

stocksFlexTable.setText(0, 0, "Symbol");
stocksFlexTable.setText(0, 1, "Price");
stocksFlexTable.setText(0, 2, "Change");
stocksFlexTable.setText(0, 3, "Remove");

Save the file.

Assuming everything is done correctly you can run this project to see what it looks like so far. Right-click on the project and select: Run as > Web Application

You can choose the Launch Default Browser button and see the results.

Result:

Well its ugly… Don’t worry we can fix that later because right now I’m going to wire up some events. In fact I’ll be adding two events. One will add a listener when the button is clicked and another one will listen for when a keydown event is triggered in the textbox.

There is good info on the GWT site on how to work with event handling so you can check that out if you wish.

Right-click on the button and from the menu that appears choose: Add event handler > click (click handler) > onClick

Once I do this I get dropped into the code for that new event. Inside the code add a call to a function we will need to implement later:

this.addButton.addClickHandler(new ClickHandler() {
	public void onClick(ClickEvent event) {
		// TODO addStock function
		// Add this line of code
		addStock();
	}
});

Go beck into Design view and click on the TextBox. We will now learn another way to add events (keep in kind you could create this event the same way I did with the button). Inside the Properties click on the Show Events button.

Go into the: keyPress > onKeyPress and double click and the code will be added for us and just like before we will be dropped into the code for the event handler.

Add the same call to addStock but this time I also want to check to ensure the key that was pressed is the Enter key and nothing else:

this.newSymbolTextBox.addKeyPressHandler(new KeyPressHandler() {
	public void onKeyPress(KeyPressEvent event) {
		// Add these lines of code
		if (event.getCharCode() == KeyCodes.KEY_ENTER)
		{
			// TODO addStock function
			addStock();
		}
	}
});

Update (June 13, 2012): I’ve been informed by a reader that the KeyPressHandler to register the Enter key might not work in FireFox. In that case you might want to try the KeyDownHandler instead. I have not verified this.

Notice the red squiggle below the addStock call. Hover your mouse over that squiggle and choose: Create Method addStock() in type StockWatcher.

Note: Depending on your Eclipse configuration, it might create the addStock method with an access modifier of protected. Since I’m not going to subclass StockWatcher I’ll change it’s access from protected to private.

Now that we’ve created the addStock function we need to add some code into it. In this function I’m going to add some basic code to help determine what the user types is as valid as possible. Here is the code I’m going to use:

private void addStock() {
    final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
    newSymbolTextBox.setFocus(true);

    // Stock code must be between 1 and 10 chars that are numbers, letters, or dots.
    if (!symbol.matches("^[0-9A-Z\\.]{1,10}$")) {
      Window.alert("'" + symbol + "' is not a valid symbol.");
      newSymbolTextBox.selectAll();
      return;
    }

    newSymbolTextBox.setText("");

    // TODO Don't add the stock if it's already in the table.
    // TODO Add the stock to the table.
    // TODO Add a button to remove this stock from the table.
    // TODO Get the stock price.
}

Save and run the project again just like before. Type in some invalid characters into the textbox and press the enter key in the textbox or press the add button.

Note: Keep in mind in a real GWT production app you not only need to validate the input from the user on the client but on the server as well. Don’t trust any data that is only validated on the client!

I’m going to finish off the last bit of code now to actually add the stock to the Stock Watcher. I’ll use the ArrayList so you will need to add an import:

import java.util.ArrayList;

Note: With Eclipse you typically get a red squiggle under an unknown object, hovering over that will give you options to import.

Create an ArrayList variable now just below with the other class variables:

public class StockWatcher implements EntryPoint {
	private VerticalPanel mainPanel;
	private FlexTable stocksFlexTable;
	private HorizontalPanel addPanel;
	private TextBox newSymbolTextBox;
	private Button addButton;
	private Label lastUpdatedLabel;
	private ArrayList <String> stocks = new ArrayList<String>();  // Add this line

Now to finish off the code in the addStock function:

private void addStock() {
	final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
	newSymbolTextBox.setFocus(true);

	// symbol must be between 1 and 10 chars that are numbers, letters, or dots
	if (!symbol.matches("^[0-9a-zA-Z\\.]{1,10}$"))
	{
	    Window.alert("'" + symbol + "' is not a valid symbol.");
	    newSymbolTextBox.selectAll();
	    return;
	}

	newSymbolTextBox.setText("");

	// don't add the stock if it's already in the watch list
    if (stocks.contains(symbol))
        return;

    // add the stock to the list
    int row = stocksFlexTable.getRowCount();
    stocks.add(symbol);
    stocksFlexTable.setText(row, 0, symbol);

    // add button to remove this stock from the list
    Button removeStock = new Button("x");
    removeStock.addClickHandler(new ClickHandler() {
    public void onClick(ClickEvent event) {
        int removedIndex = stocks.indexOf(symbol);
        stocks.remove(removedIndex);
        stocksFlexTable.removeRow(removedIndex + 1);
    }
    });
    stocksFlexTable.setWidget(row, 3, removeStock);
}

Save and run the project (or refresh the browser if it’s still running). Mess around adding/removing stock symbols.

In order to simulate stock prices changing throughout the day I’m going to use a Timer that will change the stock prices to a random value.

Here is the import you’ll need:

import com.google.gwt.user.client.Timer;

Add this code just below the other class variables where you put the ArrayList variable:

private static final int REFRESH_INTERVAL = 5000;

Add this code below to the bottom of the onModuleLoad function:

// Setup a timer to refresh the stock list automatically
Timer refreshTimer = new Timer() {
	public void run()
	{
		refreshWatchList();
	}
};
refreshTimer.scheduleRepeating(REFRESH_INTERVAL);

Create the refreshWatchList function.

protected void refreshWatchList() {
	// TODO Auto-generated method stub
}

Now I’m going to add a new class called StockPrice. Right-click on the com.google.gwt.sample.stockwatcher.client package and choose: File > New > Class. Ensure the class name is StockPrice. The rest of the defaults should be fine so just press Finish.

Here is the code for the new class:

package com.google.gwt.sample.stockwatcher.StockWatcher.client;

public class StockPrice{
	private String symbol;
	private double price;
	private double change;

	public StockPrice()
	{
	}

	public StockPrice(String symbol, double price, double change)
	{
		this.symbol = symbol;
		this.price = price;
		this.change = change;
	}

	public String getSymbol()
	{
		return this.symbol;
	}

	public double getPrice()
	{
		return this.price;
	}

	public double getChange()
	{
		return this.change;
	}

	public double getChangePercent()
	{
		return 100.0 * this.change / this.price;
	}

	public void setSymbol(String symbol)
	{
		this.symbol = symbol;
	}

	public void setPrice(double price)
	{
		this.price = price;
	}

	public void setChange(double change)
	{
		this.change = change;
	}
}

Go back to the StockWatcher.java file and add the following imports:

import com.google.gwt.user.client.Random;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.NumberFormat;
import java.util.Date;

What I want to do now is populate an array of StockPrice objects with some values and pass them onto a function that will update the FlexTable. Time to finish the refreshWatchList function with the following code:

private void refreshWatchList(){
	final double MAX_PRICE = 100.0; // $100.00
	final double MAX_PRICE_CHANGE = 0.02; // +/- 2%

	StockPrice[] prices = new StockPrice[stocks.size()];
	for (int i = 0; i < stocks.size(); i++)
	{
		double price = Random.nextDouble() * MAX_PRICE;
		double change = price * MAX_PRICE_CHANGE
				* (Random.nextDouble() * 2.0 - 1.0);

		prices[i] = new StockPrice((String) stocks.get(i), price, change);
	}

	updateTable(prices);
}

Just like before hover over the red squiggle under updateTable and add the function and add the code for it:

private void updateTable(StockPrice[] prices){
	for (int i = 0; i < prices.length; i++)
	{
		updateTable(prices[i]);
	}

	// Change the last update timestamp
	lastUpdatedLabel.setText("Last update : "
		+ DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
}

updateTable also needs to be capable of taking single StockPrice objects so we need to overload the function like so:

private void updateTable(StockPrice stockPrice){
	// Make sure the stock is still in our watch list
	if (!stocks.contains(stockPrice.getSymbol()))
	{
		return;
	}

	int row = stocks.indexOf(stockPrice.getSymbol()) + 1;

	// Format the data in the Price and Change fields.
	String priceText = NumberFormat.getFormat("#,##0.00").format(stockPrice.getPrice());
	NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00");
	String changeText = changeFormat.format(stockPrice.getChange());
	String changePercentText = changeFormat.format(stockPrice.getChangePercent());

	// Populate the Price and Change fields with new data.
	stocksFlexTable.setText(row, 1, priceText);
	stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText + "%)");
}

Save and run the project. Add some stocks and notice how they get updated automatically now.

Well its still ugly so its almost now time to use the GWT Designer’s support for CSS however before I get to that lets add the logo for the Stock Watcher.

The first thing I did was right-click on the src folder and added a new package called images. You can put this images folder wherever you want, I’m simply putting it somewhere easy to access for the sake of simplicity. Right-click the image below and save it to a folder of your choice on your computer.

Right-click on src/images and choose import from the menu. Then in the import window choose: General > File System and browse to where you saved the logo and import it.

Make sure your in the StockWatcher.java file and click on the Design tab. Click on the Image widget and put one just above the FlexTable. In the properties for the new image make sure the variable name is set to logoImage.

I’m going to also add another Label widget to hold the title of the Stock Watcher app. Add a label right below the image. Set the label’s text property to Stock Watcher. Your structure should look like this:

I’m going to populate the image using a ClientBundle but first lets style this application.

Go to the newly added Stock Watcher label’s properties and select the styleName property and click on the small css button on the right hand side. The CSS Style Editor window opens. Click the Add… button and change the default style name to .gwt-Label-StockWatcher. Press OK to close the dialog.

While still in the CSS Style Editor press the Edit… button. Set the size to 18 and the weight to bold and then press Ok to close the CSS Rule Editor. Close the CSS Style Editor now by pressing OK.

The resulting CSS will look like this:

gwt-Label-StockWatcher {
	font-size: 18px;
	font-weight: bold;
}

Another way to get into the CSS Style Editor is to right-click on the StockWatcher.css file and select: Open With > CSS Editor

Notice now at the bottom of the StockWatcher.css class you can choose between a Source and Design tab. Click on each of those.

I followed the GWT Tutorial style attributes to complete the rest of the styling.

Use the editor just like above to map the Add button to .gwt-Button-Add and set it like this.

.gwt-Button-Add {
    color: White;
    font-size: 14px;
    background-color: #2062B8;
}

You can use this code for the FlexTable, copy-and-paste it into onModuleLoad below the rest of the FlexTable code:

// Add styles to elements in the stock list table.
stocksFlexTable.setCellPadding(6);
stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader");
stocksFlexTable.addStyleName("watchList");
stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn");
stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn");
stocksFlexTable.getCellFormatter().addStyleName(0, 3, "watchListRemoveColumn");

Update the following functions to add styling:

private void updateTable(StockPrice stockPrice) {
	// make sure the stock is still in our watch list
	if (!stocks.contains(stockPrice.getSymbol()))
	{
		return;
	}

	int row = stocks.indexOf(stockPrice.getSymbol()) + 1;

    // Format the data in the Price and Change fields.
    String priceText = NumberFormat.getFormat("#,##0.00").format(stockPrice.getPrice());
    NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00");
    String changeText = changeFormat.format(stockPrice.getChange());
    String changePercentText = changeFormat.format(stockPrice.getChangePercent());

    // Populate the Price and Change fields with new data.
    stocksFlexTable.setText(row, 1, priceText);

    Label changeWidget = (Label)stocksFlexTable.getWidget(row, 2);
    changeWidget.setText(changeText + " (" + changePercentText + "%)");

 // Change the color of text in the Change field based on its value.
    String changeStyleName = "noChange";
    if (stockPrice.getChangePercent() < -0.1f) {
      changeStyleName = "negativeChange";
    }
    else if (stockPrice.getChangePercent() > 0.1f) {
      changeStyleName = "positiveChange";
    }

    changeWidget.setStyleName(changeStyleName);
}
private void addStock() {
	final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
    newSymbolTextBox.setFocus(true);

    // Stock code must be between 1 and 10 chars that are numbers, letters, or dots.
    if (!symbol.matches("^[0-9A-Z\\.]{1,10}$")) {
      Window.alert("'" + symbol + "' is not a valid symbol.");
      newSymbolTextBox.selectAll();
      return;
    }

    newSymbolTextBox.setText("");

 // Don't add the stock if it's already in the watch list
    if (stocks.contains(symbol))
        return;

    // Add the stock to the list
    int row = stocksFlexTable.getRowCount();
    stocks.add(symbol);
    stocksFlexTable.setText(row, 0, symbol);
    stocksFlexTable.setWidget(row, 2, new Label());
    stocksFlexTable.getCellFormatter().addStyleName(row, 1, "watchListNumericColumn");
    stocksFlexTable.getCellFormatter().addStyleName(row, 2, "watchListNumericColumn");
    stocksFlexTable.getCellFormatter().addStyleName(row, 3, "watchListRemoveColumn");

    // Add button to remove this stock from the list
    Button removeStock = new Button("x");
    removeStock.addStyleDependentName("remove");
    removeStock.addClickHandler(new ClickHandler() {
    public void onClick(ClickEvent event) {
        int removedIndex = stocks.indexOf(symbol);
        stocks.remove(removedIndex);
        stocksFlexTable.removeRow(removedIndex + 1);
    }
    });
    stocksFlexTable.setWidget(row, 3, removeStock);
}

My full CSS looks like this (the relevant stuff is near the bottom):

body {
	background-color: white;
	color: black;
	font-family: Arial, sans-serif;
	font-size: small;
	margin: 5px;
}

a {
	color: darkblue;
}

a:visited {
	color: darkblue;
}

.gwt-DialogBox {
	border: 2px solid #AAAAAA;
	background-color: white;
}

.gwt-DialogBox .Caption {
	background-image: url(gray_gradient.gif);
	background-repeat: repeat-x;
	padding: 4px;
	padding-bottom: 8px;
	font-weight: bold;
	cursor: default;
}

.gwt-MenuBar {
	background-color: #C3D9FF;
	cursor: default;
}

.gwt-MenuItem {
	font-size: 80%;
	margin: 1px;
	cursor: default;
}

.gwt-MenuItem-selected {
	background-color: #E8EEF7;
}

.gwt-Tree {
}

.gwt-Tree .gwt-TreeItem {
	font-size: 80%;
	cursor: default;
}

.gwt-Tree .gwt-TreeItem-selected {
	background-color: #C3D9FF;
}

.gwt-StackPanel {
	background-color: white;
	border: 1px solid #AAAAAA;
	width: 15em;
}

.gwt-StackPanel .gwt-StackPanelItem {
	background-image: url(blue_gradient.gif);
	background-repeat: repeat-x;
	background-color: #EEEEEE;
	cursor: pointer;
	cursor: hand;
}

.gwt-StackPanel .gwt-StackPanelItem-selected {
}

.gwt-TabPanel {

}

.gwt-TabPanelBottom {
	border-left: 1px solid #87b3ff;
	border-right: 1px solid #87b3ff;
	border-bottom: 1px solid #87b3ff;
}

.gwt-TabBar {
	font-size: smaller;
}

.gwt-TabBar .gwt-TabBarFirst {
	height: 100%;
	border-bottom: 1px solid #87b3ff;
	border-right: 1px solid #87b3ff;
	padding-left: 3px;
}

.gwt-TabBar .gwt-TabBarRest {
	border-bottom: 1px solid #87b3ff;
	padding-right: 3px;
}

.gwt-TabBar .gwt-TabBarItem {
	border-top: 1px solid #87b3ff;
	border-bottom: 1px solid #87b3ff;
	padding: 2px;
	cursor: hand;
	white-space: nowrap;
	border-right: 1px solid #87b3ff;
}

.gwt-TabBar .gwt-TabBarItem-selected {
	font-weight: bold;
	background-color: #e8eef7;
	border-top: 1px solid #87b3ff;
	border-right: 1px solid #87b3ff;
	border-bottom: 1px solid #e8eef7;
	padding: 2px;
	cursor: default;
	white-space: nowrap;
}

.gwt-PushButton-up {
  background-color: #C3D9FF;
  padding: 2px;
  border: 2px solid transparent;
  border-color: #E8F1FF rgb(157, 174, 205) rgb(157, 174, 205) rgb(232, 241, 255);
  cursor: pointer;
  cursor: hand;
}

.gwt-PushButton-up-hovering {
  background-color: #C3D9FF;
  padding: 2px;
  border: 2px solid transparent;
  border-color: #E8F1FF rgb(157, 174, 205) rgb(157, 174, 205) rgb(232, 241, 255);
  cursor: pointer;
  cursor: hand;
}

.gwt-PushButton-down {
  background-color: #C3D9FF;
  padding: 2px;
  border: 2px solid transparent;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
  cursor: pointer;
  cursor: hand;
}

.gwt-PushButton-down-hovering {
  background-color: #C3D9FF;
  padding: 2px;
  border: 2px solid transparent;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
  cursor: pointer;
  cursor: hand;
}

.gwt-ToggleButton-up {
  background-color: #C3D9FF;
  padding: 2px;
  border: 2px solid transparent;
  border-color: #E8F1FF rgb(157, 174, 205) rgb(157, 174, 205) rgb(232, 241, 255);
  cursor: pointer;
  cursor: hand;
}

.gwt-ToggleButton-up-hovering {
  background-color: #C3D9FF;
  padding: 2px;
  border: 2px solid transparent;
  border-color: #E8F1FF rgb(157, 174, 205) rgb(157, 174, 205) rgb(232, 241, 255);
  cursor: pointer;
  cursor: hand;
}

.gwt-ToggleButton-down {
  background-color: #C3D9FF;
  padding: 2px;
  background-color: #E8F1FF;
  border: 2px solid transparent;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
  cursor: pointer;
  cursor: hand;
}

.gwt-ToggleButton-down-hovering {
  background-color: #C3D9FF;
  padding: 2px;
  background-color: #E8F1FF;
  border: 2px solid transparent;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
  cursor: pointer;
  cursor: hand;
}

.gwt-RichTextArea {
  border: 1px solid black;
  background-color: white;
}

.gwt-RichTextToolbar {
  background-color: #C3D9FF;
  padding: 2px;
}

.gwt-RichTextToolbar .gwt-PushButton-up {
  margin-right: 2px;
  border: 1px solid #C3D9FF;
}

.gwt-RichTextToolbar .gwt-PushButton-up-hovering {
  margin-right: 2px;
  border: 1px solid #C3D9FF;
  border-color: #E8F1FF rgb(157, 174, 205) rgb(157, 174, 205) rgb(232, 241, 255);
}

.gwt-RichTextToolbar .gwt-PushButton-down {
  margin-right: 2px;
  border: 1px solid #C3D9FF;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
}

.gwt-RichTextToolbar .gwt-PushButton-down-hovering {
  margin-right: 2px;
  border: 1px solid #C3D9FF;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
}

.gwt-RichTextToolbar .gwt-ToggleButton-up {
  margin-right: 2px;
  border: 1px solid #C3D9FF;
}

.gwt-RichTextToolbar .gwt-ToggleButton-up-hovering {
  margin-right: 2px;
  border: 1px solid #C3D9FF;
  border-color: #E8F1FF rgb(157, 174, 205) rgb(157, 174, 205) rgb(232, 241, 255);
}

.gwt-RichTextToolbar .gwt-ToggleButton-down {
  margin-right: 2px;
  background-color: #E8F1FF;
  border: 1px solid #C3D9FF;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
}

.gwt-RichTextToolbar .gwt-ToggleButton-down-hovering {
  margin-right: 2px;
  background-color: #E8F1FF;
  border: 1px solid #C3D9FF;
  border-color: #9DAECD rgb(232, 241, 255) rgb(232, 241, 255) rgb(157, 174, 205);
}

.gwt-HorizontalSplitPanel {
	border: 8px solid #C3D9FF;
}

.gwt-HorizontalSplitPanel .splitter {
	background-color: #C3D9FF;
	cursor: move;
}

.gwt-HorizontalSplitPanel .left {
  background-color: #E8EEF7;
}

.gwt-VerticalSplitPanel {
	border: 8px solid #C3D9FF;
}

.gwt-VerticalSplitPanel .splitter {
	background-color: #C3D9FF;
	height: 8px;
	cursor: move;
}

.gwt-SuggestBoxPopup {
	border: 2px solid #C3D9FF;
}

.gwt-SuggestBoxPopup .item {
	padding: 2px;
}

.gwt-SuggestBoxPopup .item-selected {
	background-color: #C3D9FF;
	padding: 2px;
}

.gwt-DisclosurePanel {
	border: 2px solid #C3D9FF;
}

.gwt-DisclosurePanel .header {
	background-color: #e8eef7;
	cursor: hand;
}
.gwt-Label-StockWatcher {
	font-size: 18px;
	font-weight: bold;
}
.gwt-Button-Add {
	color: White;
	font-size: 14px;
	background-color: #2062B8;
}
/* stock list header row */
.watchListHeader {
	color: white;
	font-style: italic;
	text-align: center;
	background-color: #2062B8;
}

/* stock list flex table */
.watchList {
	margin-bottom: 6px;
	padding: 2px;
	border: 1px solid silver;
}

/* stock list Price and Change fields */
.watchListNumericColumn {
  text-align: right;
  width:8em;
}

/* stock list Remove column */
.watchListRemoveColumn {
  text-align: center;
}
/* Add Stock panel */
.addPanel {
  margin: 10px 0px 15px 0px;
}

/* stock list, the Remove button */
.gwt-Button-remove {
  width: 50px;
}

/* Dynamic color changes for the Change field */
.noChange {
  color: black;
}

.positiveChange {
  color: green;
}

.negativeChange {
  color: red;
}

The last thing that needs to be done is to get the logo into the Image widget I added earlier. For this I’m going to use a ClientBundle. Right-click on the com.google.gwt.sample.stockwatcher.StockWatcher.client package and select: New > ClientBundle. Name the new class Resources.

Inside the new class add the following just above the @Source

public static final Resources INSTANCE =  GWT.create(Resources.class);

The code should look like this:

package com.google.gwt.sample.stockwatcher.StockWatcher.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;

public interface Resources extends ClientBundle {
	public static final Resources INSTANCE =  GWT.create(Resources.class);

	@Source("images/googlecode.png")
	ImageResource googlecode();
}

Go back into the StockWatcher.java file and right below this line of code:

this.logoImage = new Image((String) null);

Add this line of code:

this.logoImage.setResource(Resources.INSTANCE.googlecode());

Save and run the project. Here is the final result:

The source code can be found here.

Post to Twitter

This entry was posted in GWT, Java, Open Source. Bookmark the permalink.

26 Responses to Tutorial: Creating a Stock Watcher with GWT Designer (UPDATED)

  1. Pingback: Tutorial: Getting started with GWT and the GWT Designer by Instantiations « Giant Flying Saucer

  2. Roger says:

    event.getCharCode() returns ‘f’ versus the ascii character code.

    thus you won’t get back ’13’ for enter.

  3. Roger says:

    I take it back.

    GWT 2.1, on OSX, returns a “0” for multiple keys (enter, shift, alt etc)

    thus GWT + OSX == not a useable platform at the moment.

  4. respergue says:

    Awesome and up to date tutorial, great job It work at least for me.
    Can you create a tutorial when interacting with server side??

  5. Tim says:

    I loved the tutorial. Thanks so much for introduction. Here is a list of a few typos that I noticed.

    Text: Ready to start? Lets go
    Correction: let us ==> let’s

    Text: While your in the source code lets add some code to get the FlexTable populated with some column headers.
    Correction: you are ==> you’re let us ==> let’s

    Text: Make sure your in the StockWatcher.java file
    Correction: you are ==> you’re

    Text: Well its ugly…
    Correction: it is ==> it’s

    Text: Well its still ugly so its almost now time
    Correction: it is ==> it’s it is ==> it’s

    Text: So lets get an idea of what
    Correction: let us ==> let’s

    Text: Lets make the vertical panel
    Correction: let us ==> let’s

    Text: While your in the source code lets add
    Correction: let us ==> let’s

    Text: however before I get to that lets add the logo for the Stock Watcher.
    Correction: let us ==> let’s

    Text: but first lets style this application.
    Correction: let us ==> let’s

  6. Wayne says:

    thanks for the tutorial, why not add some information about how to retrieve the real stock price using RequestBuilder, ( i believe there is some web service abvailable free for that), this might be really much more helpful as lots of people are looking for ways to resolve the GWT SOP issue

  7. Jeff Schwartz says:

    Great tutorial. I always appreciate when someone takes the time to update their own previous tutorial when the technology is updated.

    I have one question, though, and that is that in the Google docs for ClientBundle, located at http://code.google.com/p/google-web-toolkit/wiki/ClientBundle, it states:

    To use the ClientBundle, add an inherits tag to your gwt.xml file:

    In the tutorial though you didn’t add this entry to the gwt.xml file.

    Is it an oversight on your part or is it not needed as the docs say it is or is it because of something else altogether? As the tutorial works without it I am guessing that it isn’t needed but then why would the docs say it is?

    Thanks in advance for your response.

    Sincerely,
    Jeff

  8. Chad Lung says:

    @Jeff,

    Hi Jeff. I’ve found I don’t need to have the ClientBundle in the gwt.xml file. Not sure why the docs list it that way.

    Chad

  9. Jeff Schwartz says:

    Hi Chad,

    I thought that was the case as it works without it. I am new to GWT so obviously I have lots of questions.

    I am going to now try try to implement the symbol updates via RPC which should be interesting.

    Thanks again for a very interesting and valuable tutorial.

    Jeff

  10. Chad Lung says:

    @Jeff,

    Welcome to GWT! You’ll find you can do a lot with GWT and there is a large community out there. You can have a look at my GWT RPC tutorials if you wish:

    http://giantflyingsaucer.com/blog/?p=117
    http://giantflyingsaucer.com/blog/?p=1017

    Chad

  11. Jeff Schwartz says:

    Chad,

    Thanks for the links. I will certainly read them.

    I was able to implement the RPC using the code from this tutorial’s refreshWatchList method in my StockPriceServiceImpl which I passed the stocks ArrayList as a parameter. I also had to change com.google.gwt.user.client.Random to a java.util.Random and instantiate it but that was it. Very straight forward and intuitive.

    I used the GWT Designer’s tooling in Eclipse to generate the boilerplate interfaces for the client and the implementation class for the server. The tooling also maintained the web.xml config file for me adding the proper servlet definitions.

    I am very impressed with GWT & Designer and after I get a little more exposure to both I intend to begin a conversion of a rather large App Engine project that is written in Groovy.

    Thanks again for a great tutorial.

    Jeff

  12. Pingback: Daily del.icio.us for October 25th through October 29th — Vinny Carpenter's blog

  13. Vlado says:


    Roger:

    I take it back.
    GWT 2.1, on OSX, returns a “0″ for multiple keys (enter, shift, alt etc)
    thus GWT + OSX == not a useable platform at the moment.

    just replace event.getCharCode() with event.getNativeEvent().getKeyCode()

  14. globalfinanceschool says:

    Nice blog provided with details and scripts.
    Thanks.

  15. Excellent article!Clap !Clap!Clap!

  16. Bob Lawson says:

    Chad – when i choose the Run As option from the menu, I don’t see the GWT Application as one of my choices. Can you help?

  17. Chad Lung says:

    @Bob,

    Run as > Web Application

    I’ve updated the article. Thanks.

    Chad

  18. Bob Lawson says:

    chad – if you right click on StockWatcher.java, you can run as a GWT Application.

    Thanks so much for this tutorial, as well as the one for GWT RPC. They are a huge help. Have you done any other GWT related tutorials?

  19. Chad Lung says:

    @Bob,

    You can find all my GWT tutorials/articles here:
    http://www.giantflyingsaucer.com/blog/?category_name=gwt

    Chad

  20. Bob Lawson says:

    Great. Thanks.

    Do you have a sense for how popular GWT is becoming in the JAVA EE community? I mean, is there a possibility that GWT will eventually be the choice over straight Javascript in the development community? I ask because I want to invest my time wisely in working with new technologies and hopefully invest in those that have a future and broad appeal.

  21. Chad Lung says:

    @Bob,

    I can really only comment on the traffic to this blog with regards to GWT’s popularity and my own personal experience so take that with a grain of salt. The GWT topics are some of the most popular for my blog according to my stats. I don’t think GWT is a good fit for every Java web project out there but it could probably be a good fit for many of them – it all depends. Investing in JavaScript is something that I don’t think anyone will regret especially since JQuery is such a huge player in the world now and with things like NodeJS picking up steam a solid foundation in JavaScript is becoming more and more critical. Even with GWT you will find it doesn’t do everything for you and the time will come when you need to use GWT JSNI. I’d learn GWT, but at the same time I’d keep learning JavaScript along with JQuery and some Spring knowledge wouldn’t hurt as well if you are going down the Java road. GWT won’t ever replace the need know JavaScript in my opinion.

    Personally I keep a close eye on almost everything out there so I have some knowledge about each. I focus on specific technologies though when it is time to do a project. A current project I’m working on is using JQuery Mobile with PHP (on Ubuntu). It could have been done in GWT but I found the mobile support better for the requirements to be built with JQuery Mobile. I have other projects though that I used GWT for, it just depends on the requirements really what tool is best for the task.

    Chad

  22. Bob Lawson says:

    One more question. If I start an application using GWT, and then later on decide that I want to just use Javascript because it gives more more flexibility, is it possible to do that without having to rewrite everything?

  23. Chad Lung says:

    @Bob,

    You can always use your own custom JavaScript with GWT via JSNI. Its all going to come down to your project’s requirements. If you start a project you want to make sure what you choose to use will support the given requirements. Example: Does GWT (or Vaadin) have the widgets you want? Does JQuery UI? Whats easier for you to work with? How much flexibility might you need?

    Chad

  24. Chad Lung says:

    @Bob,

    You can also scour the web for conversations like this:
    http://stackoverflow.com/questions/1205278/why-should-i-use-jquery-instead-of-gwt

    You have to be careful though to remove any bias people might have and go with what works best for you but there are some good points here and there.

    Chad

  25. Bob Lawson says:

    I wish it was that easy for us, but our requirements are constantly changing, hence I need a crystal ball to know what tools I should use. Ideally, I’d like to start with the easiest approach, as long as I can migrate to something more flexible as needed. Sounds like GWT gives you that ability.

    can you recommend a good tutorial on jsni?

  26. Eqbal Murad says:

    Very good article .. much appreciated

Comments are closed.