Beruflich Dokumente
Kultur Dokumente
At this point, you've downloaded the most recent distribution of Google Web Toolkit. In this section, you'll create the StockWatcher project using either the Google Plugin for Eclipse or the GWT command-line utility webAppCreator. These utilities do the work of generating the project subdirectories and files you need to get started. To test that your project is configured correctly, you'll run the GWT starter application in development mode. Then you'll examine the project files that were created.
1.Create a GWT application. 2.Test the default project components. 3.Examine the project components.
4.(Optional) If you are using Google App Engine, make sure Use
default SDK (App Engine) is selected.
5.If you did not install the SDKs when you installed the Google Plugin for Eclipse, you should click Configure
SDKs... to specify the directory where GWT (and the App Engine SDK if necessary) was unzipped.
-junit
moduleNa me
com.google.gwt.sample.stockwatcher.StockWatcher
1.Create the StockWatcher application. At the command line, run webAppCreator. Enter the command below on a single line. (The example is shown on multiple lines only to improve readability.) Replace the junit.jar path name (highlighted in the example below) with the fully-qualified path name of junit.jar on your system.
webAppCreator -out StockWatcher -junit "C:\eclipse\plugins\org.junit_3.8.2.v200706111738\junit.jar" com.google.gwt.sample.stockwatcher.StockWatcher
Note: The -junit argument is optional. If you do not have junit installed on your system or do not wish to use junit in your application, you can leave it out. Tip: If you include the GWT command-line tools in your PATH environment variable, you won't have to invoke them by specifying their full path. 2.GWT webAppCreator generates the project subdirectories and files you need to get started.
Created directory StockWatcher/src Created directory StockWatcher/war Created directory StockWatcher/war/WEB-INF Created directory StockWatcher/war/WEB-INF/lib Created directory StockWatcher/src/com/google/gwt/sample/stockwatcher Created directory StockWatcher/src/com/google/gwt/sample/stockwatcher/client Created directory StockWatcher/src/com/google/gwt/sample/stockwatcher/server Created directory StockWatcher/test/com/google/gwt/sample/stockwatcher/client Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/StockWatcher.gwt.xml Created file StockWatcher/war/StockWatcher.html Created file StockWatcher/war/StockWatcher.css Created file StockWatcher/war/WEB-INF/web.xml Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/client/StockWatcher.java Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/client/GreetingService.java Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/client/GreetingServiceAsync.java Created file StockWatcher/src/com/google/gwt/sample/stockwatcher/server/GreetingServiceImpl.java Created file StockWatcher/build.xml Created file StockWatcher/README.txt Created file StockWatcher/test/com/google/gwt/sample/stockwatcher/client/StockWatcherTest.java Created file StockWatcher/.project Created file StockWatcher/.classpath Created file StockWatcher/StockWatcher.launch Created file StockWatcher/StockWatcherTest-dev.launch Created file StockWatcher/StockWatcherTest-prod.launch Created file StockWatcher/war/WEB-INF/lib/gwt-servlet.jar
Directories Created
/src/com/google/gwt/sample/stockwatcher Contains the GWT module definition and initial application files.
/test/com/google/gwt/sample/stockwatcher Contains JUnit test directory and a starter test class. /war Contains static resources that can be served publicly, such as image files, style sheets, and HTML host pages. /war/WEB-INF Contains Java web application files. /war/WEB-INF/lib Contains Java web application libraries. Starting with GWT 1.6, static files have been moved to /war.
Files Created
StockWatcher.gwt.xml GWT module definition StockWatcher.html host page StockWatcher.css application style sheet web.xml Java web application descriptor StockWatcher.java GWT entry point class GreetingService.java, GreetingServiceAsync.java, GreetingServiceImpl.java GWT sample RPC classes gwt-servlet.jar GWT server runtime library StockWatcherT est.java Starter test case for StockWatcher
Scripts Created
build.xml Ant build file for running the application in development mode or for invoking the GWT compiler from the command line.
Connecting to the development mode code server (with and without Eclipse)
Once you have started the development mode (from Eclipse or using the build.xml script) and entered the URL into the browser, the browser will attempt to connect. If this is your first time running a GWT application in development mode, you may be prompted to install the Google Web Toolkit Developer Plugin. Follow the instructions on the page to install the plugin, then restart the browser and return to the same URL.
Starter Application
When you create a new web application with GWT, by default it creates a simple, starter application as shown below. This application helps you test that all the components are installed and configured before you start development. When you start writing the StockWatcher application, you'll replace this starter application code with your own.
<!-- the theme of your GWT application by uncommenting <!-- any one of the following lines. <inherits name='com.google.gwt.user.theme.standard.Standard'/>
-->
-->
<entry-point class='com.google.gwt.sample.stockwatcher.client.StockWatcher'/>
-->
</module>
In the module XML file, you specify your application's entry point class. In order to compile, a GWT module must specify an entry point. If a GWT module has no entry point, then it can only be inherited by other modules. It is possible to include other modules that have entry points specified in their module XML files. If so, then your module would have multiple entry points. Each entry point is executed in sequence. By default, StockWatcher uses two style sheets: the default GWT style sheet, standard.css (which is referenced via the inherited theme), and the application style sheet, StockWatcher.css which was generated by webAppCreator. Later in this tutorial, you'll learn how to override the default GWT styles.
Just as for any web page, you can specify multiple style sheets. List multiple style sheets in their order of inheritance; that is, with the most specific style rules in the last style sheet listed.
What's Next
At this point you've created the stub files for the StockWatcher application and loaded the project into Eclipse (or whatever Java IDE you prefer). Now you're ready to design the StockWatcher application. Step 2: Designing the Application
After studying StockWatcher's functional requirements, you decide you need these UI elements: a table to hold the stock data two buttons, one to add stocks and one to remove them an input box to enter the stock code a timestamp to show the time and date of the last refresh The design team has suggested the following additions: a logo a header colors to indicate whether the change in price was positive or negative
What's Next
At this point you've reviewed StockWatcher's functional requirements. You have a clear idea of what StockWatcher does. You know what UI elements you need to implement and how you want to lay them out. Now you're ready to build the user interface using GWT widgets and panels. Step 3: Building the User Interface
1.Select the GWT widgets needed to implement the UI elements. 2.Select the GWT panels needed to lay out the UI elements. 3.Embed the application in the host page, StockWatcher.html. 4.Implement the widgets and panels in StockWatcher.java. 5.Test the layout in development mode.
GWT shields you from worrying too much about cross-browser incompatibilities. If you construct the interface with GWT widgets and composites, your application will work on the most recent versions of Chrome, Firefox, Internet Explorer, Opera, and Safari. However, DHTML user interfaces remain remarkably quirky; therefore, you still must test your applications thoroughly on every browser.
Buttons
Whenever possible, GWT defers to a browser's native user interface elements. For instance, a Button widget becomes a true HTML <button> rather than a synthetic button-like widget that's built, for example, from a <div>. This means that GWT buttons render as designed by the browser and client operating system. The benefit of using native browser controls is that they are fast, accessible, and most familiar to users. Also, they can be styled with CSS.
Input Box
GWT provides several widgets to create fields that users can type in: TextBox widget, a single-line text box PassWordTextBox widget, a text box that visually masks input TextArea widget, a multi-line text box SuggestBox, a text box that displays a pre-configured set of items StockWatcher users will type in a stock code which is single line of text; therefore, implement a TextBox widget.
Label
In contrast with the Button widget, the Label widget does not map to the HTML <label> element, used in HTML forms. Instead it maps to a <div> element that contains arbitrary text that is not interpreted as HTML. As a <div> element, it is a block-level element rather than an inline element.
<div class="gwt-Label">Last update : Oct 1, 2008 1:31:48 PM</div>
If you're interested in taking a peek at the API reference for the GWT widgets you'll use to build the StockWatcher interface, click on the links in the table below. UI element a table to hold the stock data two buttons, one to add stocks and one to remove them an input box to enter the stock code a timestamp to show the time and date of the last refresh a logo a header colors to indicate whether the change in price was positive or negative GWT implementation FlexTable widget Button widget TextBox widget Label widget image file referenced from HTML host page static HTML in HTML host page dynamic CSS
In Depth: If you don't find a widget that meets the functional requirements of your application, you can create your own. For details on creating composite widgets or widgets from scratch using Java or JavaScript, see the Developer's Guide, Creating Custom Widgets.
Horizontal Panel
The two elements used to add a stockthe input box for typing in a new stock symbol and the Add buttonare closely related functionally and you want keep them together visually. To lay them out side-by-side, you'll put the TextBox widget and a Button widget in a horizontal panel. In the Java code, you'll create a new instance ofHorizontalPanel and name it addPanel.
Vertical Panel
You want to lay out the remaining elements vertically: the FlexTable widget for the stock table the Add Stock panel, which contains the input box and Add button the Label widget for the timestamp You'll do this with a vertical panel. In the Java code, you'll create a new instance of VerticalPanel and name it mainPanel.
Root Panel
There is one more panel you need that is not visible in the user interface: a Root panel. A Root panel is the container for the dynamic elements of your application. It is at the top of any GWT user interface hierarchy. There are two ways you can use a Root panel, either to generate the entire body of the page or to generate specific elements embedded in the body. The Root panel works by wrapping the <body> or other element in the HTML host page. By default (that is, if you don't add any placeholders in the host page) the Root panel wraps the <body> element. However, you can wrap any element if you give it an id and then, when you call the Root panel, pass the id as a parameter. You'll see how this works in the next two sections when you do it for StockWatcher.
RootPanel.get() RootPanel.get("stockList") // Default. Wraps the HTML body element. // Wraps any HTML element with an id of "stockList"
A host page can contain multiple Root panels. For example, if you're embedding multiple GWT widgets or panels into a host page, each one can be implemented independently of the others, wrapped in its own Root panel.
<h1>StockWatcher</h1> <div id="stockList"></div> <iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe> <noscript> <div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif"> Your web browser must have JavaScript enabled in order for this application to display correctly. </div> </noscript>
<h1>Web Application Starter Project</h1> <table align="center"> <tr> <td colspan="2" style="font-weight:bold;">Please enter your name:</td>
</tr> <tr> <td id="nameFieldContainer"></td> <td id="sendButtonContainer"></td> </tr> </table> </body> </html>
/** * Entry point method. */ public void onModuleLoad() { // TODO Create table for stock data.
// TODO Assemble Add Stock panel. // TODO Assemble Main panel. // TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box.
Along the left edge, Eclipse flags the variable definitions with a red "x" because their types are undefined. Tip: One way you can leverage Eclipse is to use its "suggest" feature to add the required import declarations, as follows. 2.Display suggested corrections by clicking on the first red "x". Select "import EntryPoint (com.google.gwt.core.client.EntryPoint)" by pressing return. 3.Resolve all the other errors by declaring the import declarations in the same way. If you are not using Eclipse, cut and paste from the highlighted code below.
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // TODO Create table for stock data. // TODO Assemble Add Stock panel. // TODO Assemble Main panel. // TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box.
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove");
// TODO Assemble Add Stock panel. // TODO Assemble Main panel. // TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box.
You can see that adding to a table can be accomplished with a call to the setText method. The first parameter indicates the row, the second the column, and the final parameter is the text that will be displayed in the table cell.
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove");
// TODO Associate the Main panel with the HTML host page. // TODO Move cursor focus to the input box.
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol");
// Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel);
Eclipse flags RootPanel and suggests the correct import declaration. 2.Include the import declaration.
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel();
private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove");
// Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel);
Summary
Here's what you've done to this point.
package com.google.gwt.sample.stockwatcher.client;
import com.google.gwt.core.client.EntryPoint; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove");
// Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel);
debugging, in Eclipse, run StockWatcher in debug mode. Then you'll be able to switch between Java and Debug perspectives without having to relaunch StockWatcher. 1.Save the edited file: Save StockWatcher.java 2.If the StockWatcher project is still running from the startup application, stop it by going to the Development Mode tab an clicking on the red square in its upper right corner, whose tooltip says "Terminate Selected Launch", and then the gray "XX" to its right, whose tooltip says "Remove All Terminated Launches". It may take a minute for it to complete, before you can do the next step. 3.Launch StockWatcher in development mode. From the Eclipse menu bar, select Run > Debug As > Web Application If you are not using Eclipse, from the command line enter ant devmode 4.The browser displays your first iteration of the StockWatcher application. The button will not work until we later implement it.
StockWatcher displays the header of the flex table, the input box, and the Add button. You haven't yet set the text for the Label, so it isn't displayed. You'll do that after you've implemented the stock refresh mechanism. 5.Leave StockWatcher running in development mode. In the rest of this tutorial, you'll frequently be testing changes in development mode.
What's Next
At this point you've built the basic UI components of StockWatcher by implementing GWT widgets and panels. The widgets don't respond to any input yet.
Now you're ready to code event handling on the client. You'll wire up the widgets to listen for events and write the code that responds to those events. Step 4: Managing Events on the Client
1.Review the functional requirements. 2.Listen for events. 3.Respond to events. 4.Test event handling.
GWT provides a number of different event handler interfaces. To handle click events on the Add and Remove buttons, you'll use the ClickHandler interface. To handle keyboard events in the input box, you'll use the KeyPressHandler interface. Starting with GWT 1.6, the ClickHandler, KeyDownHandler, KeyPressHandler, and KeyUpHandler interfaces have replaced the now deprecated ClickListener and KeyBoardListener interfaces.
1.Add an event handler to the Add button so it can receive click events. In Stockwatcher.java, in the onModuleLoad method, cut and paste the code commented "Listen for mouse events on the Add button." that is highlighted below. Eclipse flags ClickHandler and suggests you include the import declaration. 2.Include the import declarations for ClickHandler and ClickEvent. Eclipse flags addStock. 3.In StockWatcher.java, create the stub for the addStock method. Select the Eclipse shortcut, Create method addStock() in type 'StockWatcher'. Or copy and paste from the code highlighted below. Note: Depending on your Eclipse configuration, it might create the addStock method with an access modifier of protected. You aren't going to subclass StockWatcher, so later when you implement the addStock method, you'll change its access to private.
package com.google.gwt.sample.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.ui.Button; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel;
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label();
/** * Entry point method. */ public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove");
addPanel.add(newSymbolTextBox); addPanel.add(addStockButton);
// Associate the Main panel with the HTML host page. RootPanel.get("stockList").add(mainPanel);
// Listen for mouse events on the Add button. addStockButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { addStock(); } });
/** * Add stock to FlexTable. Executed when the user clicks the addStockButton or * presses enter in the newSymbolTextBox. */ private void addStock() { // TODO Auto-generated method stub
Implementation Note: For smaller applications, such as StockWatcher, that handle relatively few events, using anonymous inner classes gets the job done with minimal coding. However, if you have large number of event handlers subscribing to events, this approach can be inefficient because it could result in the creation of many separate event handler objects. In that case, it's better to have a class implement the event handler interface and handle events coming from multiple event publishers. You can distinguish the source of the event by calling its getSource() method. This makes better use of memory but requires slightly more code. For a code example, see the Developer's Guide, Event Handlers.
To subscribe to keyboard events, you can call the addKeyPressHandler(KeyPressHandler) method and pass it a KeyPressHandler. 1.Hook up the keypress event handler for the input box, newSymbolTextBox. In the onModuleLoad method, cut and paste the code commented "Listen for keyboard events in the input box." that is highlighted below.
// Listen for mouse events on the Add button. addStockButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { addStock(); } });
// Listen for keyboard events in the input box. newSymbolTextBox.addKeyPressHandler(new KeyPressHandler() { public void onKeyPress(KeyPressEvent event) { if (event.getCharCode() == KeyCodes.KEY_ENTER) { addStock(); } } });
/** * Add stock to FlexTable. Executed when the user clicks the addStockButton or * presses enter in the newSymbolTextBox. */ private void addStock() { // TODO Auto-generated method stub
Eclipse flags KeyPressHandler and suggests you include the import declaration. 2.Include the import declarations.
import com.google.gwt.event.dom.client.KeyCodes; import com.google.gwt.event.dom.client.KeyPressEvent; import com.google.gwt.event.dom.client.KeyPressHandler;
The event handlers are now wired up and ready for an event. Your next step is to fill out the stub addStock method.
should make when it detects an event: add the stock. StockWatcher responds on the client side without sending any requests back to server or reloading the HTML page.
// 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("");
Eclipse flags Window and suggests you include the import declaration. 2.Include the import declaration.
import com.google.gwt.user.client.Window;
Tip: Changes made to your Java code are immediately shown in the browser after pressing refresh. If development mode is already running, you don't need to restart it. Just click the Refresh button in your browser to reload your updated GWT code. Although you have not compiled StockWatcher yet, you can test it in production mode here: Run StockWatcher
What's Next
At this point you've implemented event handler interfaces for mouse and keyboard events that signal the user has entered a stock. Stockwatcher responds by validating the input. Now you're ready to implement the code on the client that adds the stock to the table and provide a button to remove it. You will also display the stock prices and display data and a timestamp showing when the data was last updated. Step 5: Coding Functionality on the Client
1.Add and remove stocks from the stock table. 2.Refresh the Prices and Change fields for each stock in the table. 3.Implement the timestamp showing the time of last update.
Your initial implementation of StockWatcher is simple enough that your can code all its functionality on the client side. Later you'll add calls to the server to retrieve the stock data.
private VerticalPanel mainPanel = new VerticalPanel(); private FlexTable stocksFlexTable = new FlexTable(); private HorizontalPanel addPanel = new HorizontalPanel(); private TextBox newSymbolTextBox = new TextBox(); private Button addStockButton = new Button("Add"); private Label lastUpdatedLabel = new Label(); private ArrayList<String> stocks = new ArrayList<String>();
2.Eclipse flags ArrayList and suggests you include the import declaration. 3.Include the import declaration.
import java.util.ArrayList;
2.If the stock doesn't exist, add it. In the addStock method, replace the TODO comment with this code.
// Add the stock to the table. int row = stocksFlexTable.getRowCount(); stocks.add(symbol); stocksFlexTable.setText(row, 0, symbol);
When you call the setText method, the FlexTable automatically creates new cells as needed; therefore, you don't need to resize the table explicitly.
Add a stock code that already exist in the table. StockWatcher should clear the input box but not add the the same stock code again. 4.Delete a stock. Click the Remove button. The stock is deleted from the table and the table resizes.
Now you'll tackle that last TODO: get the stock price.
When a Timer fires, the run method executes. For StockWatcher you'll override the run method with a call to the refreshWatchList method which refreshes the Price and Change fields. For now, just put in a stub for the refreshWatchList method; later in this section, you'll implement it. 1.Implement the timer. Modify the onModuleLoad method to create a new instance of Timer, as follows:
public void onModuleLoad() {
...
// Move cursor focus to the text box. newSymbolTextBox.setFocus(true); // Setup timer to refresh list automatically. Timer refreshTimer = new Timer() { @Override public void run() { refreshWatchList(); } }; refreshTimer.scheduleRepeating(REFRESH_INTERVAL);
...
Eclipse flags Timer, REFRESH_INTERVAL and refreshWatchList. 2.Declare the import for Timer. If you are using Eclipse shortcuts, be sure to select the GWT Timer.
import com.google.gwt.user.client.Timer;
3.Specify the refresh rate. If you are using Eclipse shortcuts, select Create constant 'REFRESH_INTERVAL' then specify the refresh interval in milliseconds, 5000. Otherwise, just cut and paste from the highlighted code below.
public class StockWatcher implements EntryPoint {
private static final int REFRESH_INTERVAL = 5000; // ms private VerticalPanel mainPanel = new VerticalPanel();
4.Populate the price and change values as soon as a new stock is added. In the addStock method, replace the TODO comment with the highlighted code.
private void addStock() {
...
Button removeStockButton = new Button("x"); removeStockButton.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { int removedIndex = stocks.indexOf(symbol); stocks.remove(removedIndex); stocksFlexTable.removeRow(removedIndex + 1); } }); stocksFlexTable.setWidget(row, 3, removeStockButton);
Eclipse flags refreshWatchList. 5.In the StockWatcher class, create a stub for the refreshWatchList method.
private void refreshWatchList() { // TODO Auto-generated method stub
public StockPrice() { }
public StockPrice(String symbol, double price, double change) { this.symbol = symbol; this.price = price; this.change = change; }
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);
updateTable(prices); }
Eclipse flags updateTable. Create a stub for the updateTable(StockPrice) method. 2.Implement the method updateTable(StockPrice). Replace the stub with the following code.
/** * Update a single row in the stock table. * * @param price Stock data for a single row. */ private void updateTable(StockPrice price) { // Make sure the stock is still in the stock table. if (!stocks.contains(price.getSymbol())) { return; }
// Format the data in the Price and Change fields. String priceText = NumberFormat.getFormat("#,##0.00").format( price.getPrice()); NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00"); String changeText = changeFormat.format(price.getChange()); String changePercentText = changeFormat.format(price.getChangePercent());
// Populate the Price and Change fields with new data. stocksFlexTable.setText(row, 1, priceText); stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText + "%)"); }
// Display timestamp showing last refresh. lastUpdatedLabel.setText("Last update : " + DateTimeFormat.getMediumDateTimeFormat().format(new Date()));
C.Test the timestamp. Save your changes. In the browser, press Refresh to load the changes. The timestamp label should be displayed beneath the stock table. As the Price and Change fields refresh, the timestamp should display the date and time of the last update. Implementation Note: You may have noticed that the classes DateTimeFormat and NumberFormat live in a subpackage of com.google.gwt.i18n, which suggest that they deal with internationalization in some way. And indeed they do: both classes will automatically use your application's locale setting when formatting numbers and dates. You'll learn more about localizing and translating your GWT application into other languages in the tutorial Internationalizing a GWT Application.
What's Next
At this point you've built the interface components and coded all the underlying client-side functionality for the StockWatcher application. The user can add and remove stocks. The Price and Change fields update every 5 seconds. A timestamp shows when the last refresh occurred.
Although you have not compiled StockWatcher yet, you can test it in production mode here: Run StockWatcher
A Bug
For the sake of this tutorial, we introduced an error into the code. Can you detect it? Look at the change percentages. Don't they seem a bit small? If you do the math, you'll discover that they appear to be exactly an order of magnitude smaller than they should be. There's an arithmetic error hiding somewhere in the StockWatcher code. Using the tools provided by GWT and your Java IDE, your next step is to find and fix the error. Step 6: Debugging a GWT Application
1.Find the bug. 2.Fix the bug. 3.Test the bug fix by running StockWatcher in development mode. Benefits
You can debug the Java source code before you compile it into JavaScript. This GWT development process help you take advantage of the debugging tools in your Java IDE. You can: Set break points. Step through the code line by line. Drill down in the code. Inspect the values of variables. Display the stack frame for suspended threads. One of attractions of developing in JavaScript is that you can make changes and see them immediately by refreshing the browser without having to do a slow compilation step. GWT development mode provides the exact same development cycle. You do not have to recompile for every change you make; that's the whole point of development mode. Just click "Refresh" to see your updated Java code in action.
Looking at the values in the Price and Change fields, you can see that, for some reason, all of the change percentages are only 1/10 the size of the correct values. The values for the Change field are loaded by the updateTable(StockPrice) method.
/** * Update a single row in the stock table. * * @param price Stock data for a single row. */
private void updateTable(StockPrice price) { // Make sure the stock is still in the stock table. if (!stocks.contains(price.getSymbol())) { return; }
// Format the data in the Price and Change fields. String priceText = NumberFormat.getFormat("#,##0.00").format( price.getPrice()); NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00"); String changeText = changeFormat.format(price.getChange()); String changePercentText = changeFormat.format(price.getChangePercent());
// Populate the Price and Change fields with new data. stocksFlexTable.setText(row, 1, priceText); stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText + "%)"); }
Just glancing at the code, you can see that the value of the changePercentText variable is being set elsewhere, in price.getChangePercent. So, first set a breakpoint on that line and then drill down to determine where the error in calculating the change percentage is.
Eclipse switches to Debug perspective. 2.Run the code that has the error. To run the code in the updateTable method where you suspect the error, just add a stock to the stock list in the browser running in development mode. Execution will stop at the first breakpoint. 3.Check the values of the variables priceText and changeText. In the Eclipse Debug perspective, look at the Variables pane. 4.Run the code to the next break point, where priceText is set. In the Debug pane, press the Resume icon. 5.Check the values of the variables priceText, changeText, changePercentText. In the Eclipse Debug perspective, look at the Variables pane. If you like, double-check the math to see the error.
6.Loop back to the first break point, where changePercentText is set. In the Debug pane, press the Resume icon.
Looking at the getChangePercent method, you can see the problem: it's multiplying the change percentage by 10 instead of 100. That corresponds exactly with the output you saw before: all of the change percentages were only 1/10 the size of the correct values.
Tip: In Eclipse, if you find it easier to edit in the Java perspective rather than the Debug perspective, you can switch back and forth while running StockWatcher in development mode.
What's Next
At this point you've implemented all your functional requirements. StockWatcher is running and you've found and fixed a bug. Now you're ready to enhance StockWatcher's visual design. You'll apply CSS style rules to the GWT widgets and add a static element (a logo) to the page. Step 7: Applying Style
1.Associate style sheets with the project. 2.Change the theme. 3.Create a secondary style. 4.Create a dependent secondary style. 5.Update styles dynamically. 6.Set an element's HTML attributes 7.Add images or other static HTML elements.
Benefits of CSS
GWT provides very few Java methods directly related to style. Rather, we encourage you to define styles in Cascading Style Sheets. When it comes to styling web applications, CSS is ideal. In addition to cleanly separating style from application logic, this division of labor helps applications load and render more quickly, consume less memory, and even makes them easier to tweak during edit/debug cycles because there's no need to recompile for style tweaks.
<?xml version="1.0" encoding="UTF-8"?> <module rename-to='stockwatcher'> <!-- Inherit the core Web Toolkit stuff. <inherits name='com.google.gwt.user.User'/> -->
<!-- the theme of your GWT application by uncommenting <!-- any one of the following lines. <inherits name='com.google.gwt.user.theme.standard.Standard'/>
-->
-->
<title>StockWatcher</title>
<body>
<h1>StockWatcher</h1>
<div id="stockList"></div>
</body> </html>
3.Test the change. Save your changes to the StockWatcher.gwt.xml file. If development mode is still running, terminate it and then relaunch StockWatcher. For StockWatcher, you are going to build on the Standard theme. So after you've played around to see how themes work, set the theme back to Standard. Note: GWT themes also come in RTL (right-to-left) versions to support languages written from right-to-left, such as Arabic. To use a right-to-left theme, append RTL to the theme name. <inherits
name='com.google.gwt.user.theme.standard.StandardRTL'/>
Changes made with this approach apply to all widgets of a type, for example, to all Buttons. You can append a secondary style to the HTML element by adding another class attribute. Changes made with this approach apply to a single widget, for example just the Remove button in StockWatcher. You can override a widget's primary style name. If you need to clear any existing styles that you are inheriting from the theme or other style sheets, you can change the default primary class associated with the widget. If you are embedding a GWT application into an existing page, you can edit your own style sheet. You can start completely from scratch. For the StockWatcher application, you'll focus mostly on the second approach: you'll learn to append a secondary style.
In GWT, each class of widget has an associated style name (like gwt-Button) that binds it to a CSS style rule. This is the widget's primary style. Default values are defined for the primary style in the theme style sheet. Type of Element Buttons in static HTML and GWT-generated buttons Only GWT-generated buttons Only my special GWT-generated button HTML Tag <button> <button class="gwt-Button"> <button class="gwt-Button mybutton"> CSS Selector button button.gwtButton button.mybutton
Tip: You can look up the name of the style rule (the CSS selector) for each widget by accessing the GWT API Reference via the Widget Gallery. GWT takes advantage of the fact that you can associate multiple classes with an HTML element so that you can specify a style for a specific GWT-generated element and not affect others of the same type. In this section, you'll learn how to set the secondary class on a GWT-generated HTML element.
However, GWT elements are created dynamically at runtime. So you'll set the HTML class attributes in the Java source using the addStyleName method. You'll specify the row (the header is row 0) and the name of the secondary class, watchListHeader. 1.In StockWatcher.java, in the onModuleLoad method, add a secondary style to the header row in the stock table.
public void onModuleLoad() { // Create table for stock data. stocksFlexTable.setText(0, 0, "Symbol"); stocksFlexTable.setText(0, 1, "Price"); stocksFlexTable.setText(0, 2, "Change"); stocksFlexTable.setText(0, 3, "Remove");
2.Save your changes to StockWatcher.java and then press Refresh in the browser running development mode to see them. The the header row in the flex table displays white italic headings against a blue background.
/* stock list flex table */ .watchList { border: 1px solid silver; padding: 2px; margin-bottom:6px; }
2.Apply the style. In StockWatcher.java, add a secondary class attribute to the stock flex table.
// Add styles to elements in the stock list table. stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader"); stocksFlexTable.addStyleName("watchList");
3.Save your changes and then press Refresh in the browser running development mode to see them. The stock list table has a silver border.
/* stock list Price and Change fields */ .watchListNumericColumn { text-align: right; width:8em; }
2.Apply the style. In StockWatcher.java, add a secondary class attribute to both the Price and Change fields.
// Add styles to elements in the stock list table. stocksFlexTable.getRowFormatter().addStyleName(0, "watchListHeader"); stocksFlexTable.addStyleName("watchList"); stocksFlexTable.getCellFormatter().addStyleName(0, 1, "watchListNumericColumn"); stocksFlexTable.getCellFormatter().addStyleName(0, 2, "watchListNumericColumn");
3.Save your changes and then press Refresh in the browser running development mode to see them. The Price and Change columns have a set width and the text in the header row is right-aligned.
2.Apply the style. In StockWatcher.java, add a secondary class attribute to the Remove field.
// Add styles to elements in the stock list table. 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");
3.Save your changes and then press Refresh in the browser running development mode to see them. The caption takes up the entire width of the field. You'll be able to see that the buttons are centered in the Remove column after you format the data rows in the next step.
Apply the same cell formatting to the rows that hold the stock data
You've formatted the header row of the flex table, which is displayed when StockWatcher starts up. Remember, however, that in a flex table, the rows holding the stocks aren't created until the user adds a stock to the list. Therefore, you will add the code for formatting the stock data in the addStock method rather than in the onLoad method. 1.You have already defined the style in StockWatcher.css. 2.Apply the style. In StockWatcher.java, in the addStock method, add secondary class attribute to the table cells in the Price, Change, and Remove columns.
// Add the stock to the table. int row = stocksFlexTable.getRowCount();
3.Save your changes and then press Refresh in the browser running development mode to see them. Add stocks to the list. The Price and Change data is right-aligned. The Remove button is centered.
2.Apply the style. In StockWatcher.java, in the onModuleLoad method add a secondary class attribute to the addPanel.
// Assemble Add Stock panel. addPanel.add(newSymbolTextBox); addPanel.add(addStockButton); addPanel.addStyleName("addPanel");
3.Save your changes and then press Refresh in the browser running development mode to see them. The margin between the stock table and the Add Stock panel has increased.
Summary of Changes
Here the summary of the changes we've done so far.
Changes to StockWatcher.css
/* Formatting specific to the StockWatcher application */
/* stock list header row */ .watchListHeader { background-color: #2062B8; color: white; font-style: italic; }
/* stock list flex table */ .watchList { border: 1px solid silver; padding: 2px; margin-bottom:6px; }
/* stock list Price and Change fields */ .watchListNumericColumn { text-align: right; width:8em; }
// Add styles to elements in the stock list table. 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");
For StockWatcher, you want your style change to apply only to the Remove button. So you'll do just as you've been doing: add a secondary style to the Remove button element. But this time, you'll make the secondary style dependent on the primary style. Dependent styles are powerful because they are automatically updated whenever the primary style name changes. In contrast, secondary style names that are not dependent style names are not automatically updated when the primary style name changes. To do this, you'll use the addStyleDependentName method instead of the addStyleName method. 1.Define the style rule.
/* Add Stock panel */ .addPanel { margin: 10px 0px 15px 0px; }
2.Apply the style. In StockWatcher.java, use addStyleDependentName to add a secondary, dependent class attribute to the Remove button.
// Add a button to remove this stock from the table. Button removeStockButton = new Button("x"); removeStockButton.addStyleDependentName("remove");
3.Save your changes and then press Refresh in the browser running development mode to see them. The Remove button is wider than it is tall. The Add button is unaffected by this change. 4.Now the resulting generated HTML has two class attributes.
<button class="gwt-Button gwt-Button-remove" tabindex="0" type="button">x</button>
/* Dynamic color changes for the Change field */ .noChange { color: black; } .positiveChange { color: green; } .negativeChange { color: red; }
2.Insert a Label widget in a table cell. In StockWatcher.java, in the addStock method, create a Label widget for every cell in column 2.
// Add the stock to the table. 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");
Instead of setting the text on the table cells, now you have to set the text for the Label widget. 3.Set text on the changeWidget. In the updateTable(StockPrice) method, delete the call to setText for the Change column (column 2). Create an instance of the Label widget and call it changeWidget. Set the text on changeWidget.
// Populate the Price and Change fields with new data. stocksFlexTable.setText(row, 1, priceText); stocksFlexTable.setText(row, 2, changeText + " (" + changePercentText
+ "%)"); 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 (price.getChangePercent() < -0.1f) { changeStyleName = "negativeChange"; } else if (price.getChangePercent() > 0.1f) { changeStyleName = "positiveChange"; }
changeWidget.setStyleName(changeStyleName);
5.Save your changes and then press Refresh in the browser running development mode to see them. The color of the values in the Change field are red, green, or black depending on whether the change was negative, positive, or none.
2.Save your changes and then press Refresh in the browser running development mode to see them.
To include static images in the application. 1.Create an directory to hold the image files associated with this application. In the war directory, create an images directory.
StockWatcher/war/images
2.From this page, copy the image of the logo and paste it into the images directory.
StockWatcher/war/images/GoogleCode.png
<title>StockWatcher</title>
<body>
<h1>StockWatcher</h1>
<div id="stockList"></div>
</body> </html>
Note: HTML comments have been omitted for brevity. 4.Save your changes and then press Refresh in the browser running development mode to see them.
In Depth: For more information on including style sheets, JavaScript files, and other GWT modules, see the Developer's Guide, Automatic Resource Inclusion.
What's Next
At this point you've finished with the initial implementation of StockWatcher. The client-side functionality is working and the user interface has a new visual design.
Note: For the sake of simplicity, we created the user interface for this tutorial programmatically using widgets. This works fine for StockWatcher because the UI is fairly simple. However, GWT has a powerful tool called UiBinder that allows you to create complex interfaces using declarative XML files, which can reduce code size and complexity. Check out Developer's Guide sections on Declarative Layout with UiBinder and Build User Interfaces for more info about UiBinder and UI design in general. Now you're ready to compile StockWatcher. You'll compile your Java code into JavaScript and check that StockWatcher runs the same way in production mode as it has in development mode. Step 8: Compiling a GWT Application
1.Compile the Java source code. 2.Test StockWatcher in production mode. 3.Deploy StockWatcher to a web server.
You'll also learn about deferred binding, GWT's mechanism for serving just the code required depending on browser or, optionally, other factors such as locale.
Compiler Output
Take a look at the files generated by the GWT compiler. In the output directory StockWatcher/war/stockwatcher, you see a set of files similar to this:
14A43CD7E24B0A0136C2B8B20D6DF3C0.cache.png 29F4EA1240F157649C12466F01F46F60.gwt.rpc 34BCFF35CB8FD43BCBFC447B883BCADC.cache.html 52BE5EB1FD9F0C2659714EE874E24999.cache.html 548CDF11D6FE9011F3447CA200D7FB7F.cache.png 667F52D77BB3D008A2A6A484569C3C35.cache.html 9DA92932034707C17CFF15F95086D53F.cache.png A7CD51F9E5A7DED5F85AD1D82BA67A8A.cache.png B8517E9C2E38AA39AB7C0051564224D3.cache.png CC9E2ADC408F47959407F6440C301B88.cache.html DF6EEF5BB835EE6FD9BBA5E092B0C429.cache.html clear.cache.gif gwt hosted.html stockwatcher.nocache.js
In addition to the static resources in StockWatcher/war (such as the HTML host page, style sheet, and images directory), notice the other file names contain GUIDs. These files contain the various JavaScript implementations of StockWatcher. GWT generates multiple implementations of your application, a unique permutation for each supported web browser.
What's Next?
At this point you've tested StockWatcher both in development mode and in production mode. By now you should have a pretty good idea of how to develop a GWT application, which has only client-side functionality, from start to finish. To build upon the initial implementation of StockWatcher and learn additional features of GWT, select from the following tutorials:
Internationalizing a GWT application Communicating with the server via GWT RPC. Retrieving JSON data via HTTP
Unit testing with JUnit Running a GWT application on Google App Engine