|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Google Web Toolkit (GWT) is a Java development framework that seeks to ease AJAX (Asynchronous JavaScript and XML) application development. GWT allows you to develop and debug AJAX-based applications in Java using common Java development tools and deploy the same applications as client-side HTML and JavaScript, and server-side Java. GWT also simplifies typical AJAX-style RPC (remote procedure call) conversations between client-side JavaScript and server-side Java services.
In this article, I discuss the basics of GWT and show how Java developers can use the GWT to create a simple AJAX application to retrieve search results from a remote API to display in a browser.
The Google Web Toolkit is an application development platform composed of a Java class library, AJAX-style UI components called
widgets, an RPC-based request/response communication framework, and an integrated debugging environment. GWT provides a subset of
the java.lang and java.util packages, along with a Java API that facilitates component-based GUI development, which can be compiled to HTML and JavaScript
for deployment to a browser.
GWT applications can be executed in two modes:
GWT has four major components/libraries, as follows:
java.lang and java.util packages
GWT applications are required to provide an "entry-point" class and discreet units of configuration data, bundled together,
to form an application "module." Each module consists of a configuration file, named according to the pattern module-name.gwt.xml, and a class that implements the com.google.gwt.core.client.EntryPoint interface, which acts as the application's main entry point. GWT's runtime JavaScript library relies on this module-based
structure being in place. A typical module configuration file is illustrated in the following snippet:
<module>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<!-- Specify the app entry point class. -->
<entry-point class='com.example.client.MyApp'/>
</module>
A module's entry-point class must implement the com.google.gwt.core.client.EntryPoint interface and provide a no-arg constructor. When a module is loaded, its entry-point class is instantiated and its onModuleLoad() method is invoked by the GWT framework.
To get started using GWT, first download and extract the GWT SDK for your specific operating system from Google.
GWT's library of classes and interfaces, or widgets, can be used to construct UI components for an AJAX application. The layout of widget components is managed by container widgets, known as panels, that can be nested inside other panel components.
The following illustrates how a button widget can be instantiated and embedded within a container panel called MyContanerPanel:
final com.google.gwt.user.client.ui.Button button =
new com.google.gwt.user.client.ui.Button("Click me");
button.addClickListener(new com.google.gwt.user.client.ui.ClickListener()
{
public void onClick(com.google.gwt.user.client.ui.Widget sender)
{
System.out.println("The 'Click me' button was clicked");
}
});
com.google.gwt.user.client.ui.RootPanel.get("MyContainerPanel").add(button);
The GUI for a GWT application is composed of Java code similar to the preceding example that can be debugged in hosted mode
using standard Java debugging tools. Applications executed in hosted mode run within a proprietary shell embodied by the com.google.gwt.dev.GWTShell class. This class can be executed standalone or within an IDE. When running in hosted mode, GWT executes Java bytecodes within
a proprietary browser window, as shown below:
Figure 1. Click on thumbnail to view full-sized image.
Once an application is ready for deployment, the GWT compiler can be used to convert the Java source code into JavaScript,
thereby transforming the Java application into an analogous JavaScript application. The com.google.gwt.dev.GWTCompiler class is used to compile a GWT application to JavaScript from the command line.
In respect to GWT, all activity that would typically occur within a Web browser is referred to as client-side processing. When you write client-side code intended to run in the Web browser, remember that it ultimately becomes JavaScript. Thus, it is important to use only libraries and Java language constructs that can be translated by the GWT compiler described above.
Likewise, all activity that would typically occur on a server host is referred to as server-side processing. When an application interacts with a server, it makes browser (client-side) requests to server-side code using GWT's remote procedure call (RPC) framework.
GWT ships with a command line utility called applicationCreator that automatically generates all the files needed to run a minimal GWT application. These files embody a project outline that can be used as a starting point for an application.
This article demonstrates an application that can search for books from a remote site and display the results in an AJAX-based GUI in a Web browser. To create a skeleton for the application called BookSearch, instigate the applicationCreator utility with the command:
<GWT_HOME_DIR>applicationCreator -out ./BookSearch com.example.client.BookSearch
Be sure to replace <GWT_HOME_DIR> with the directory name of your GWT installation. The applicationCreator utility generates numerous files in the <GWT_HOME_DIR>BookSearch
directory, including some basic client-side functionality in the class com/example/client/BookSearch.java. The utility also generates a hosted mode launch script called BookSearch-shell and a compilation script called BookSearch-compile.
The directory structure for the newly-created BookSearch application is illustrated in the following table:
|
To run the BookSearch skeleton application in hosted mode, execute the BookSearch-shell script. You should see something resembling the following image:
Figure 2. Click on thumbnail to view full-sized image.The final BookSearch application uses one table with two td elements; one contains a widget for processing search terms and the other, a widget for displaying a list of book-related
data. This table is added to BookSearch.html in the com/example/public directory as follows:
<table width="100%" border="0" summary="Book Search">
<tr valign="top">
<td id="searchterm" align="center" width="100%">
</td>
</tr>
<tr valign="top">
<td id="booklist" align="center" width="100%">
</td>
</tr>
</table>
Now, the UI layout can be initialized so that UI widgets can be added.
The following snippet shows the code needed to initialize the UI layout for the BookSearch application:
public void onModuleLoad()
{
private static final int VISIBLE_ROWS = 5;
public void onModuleLoad()
{
// Retrieve the panel for the booklist widget
//
RootPanel booklistPanel = RootPanel.get("booklist");
if (booklistPanel != null)
{
BookListWidget booklistWidget = new BookListWidget(VISIBLE_ROWS);
booklistPanel.add(booklistWidget);
// Retrieve the panel for the searchterm widget
//
RootPanel searchtermPanel = RootPanel.get("searchterm");
if (searchtermPanel != null)
{
SearchTermWidget searchTermWidget =
new SearchTermWidget(booklistWidget);
searchtermPanel.add(searchTermWidget);
}
}
}
}
All initialization code goes inside the BookSearch class's onModuleLoad() method. The onModuleLoad() method is the only method defined by the com.google.gwt.core.client.EntryPoint interface. This method is invoked when the module is loaded. Notice how the com.google.gwt.user.client.ui.RootPanel class is used to retrieve references to the BookSearch.html elements by their IDs. GWT relies on naming conventions to locate
widget classes that map to HTML element IDs. For example, the "booklist" HTML ID is used to locate a widget named "BookListWidget."
Now that the application's form and structure is established, client-side behavior for the application can be built. Our application's client-side behavior is encapsulated within three main UI widget instances:
The following code illustrates the widget that handles the retrieval of a booklist and contains the paginated list:
public class BookListWidget extends com.google.gwt.user.client.ui.Composite
{
private final PageableListWidget pageableListWidget;
private String searchTerm = "";
/**
*
* @param visibleRows
*/
public BookListWidget(int visibleRows)
{
String[] columns = new String[]{"Title",
"ISBN",
"Edition",
"MSRP"};
String[] styles = new String[]{"title",
"isbn",
"edition",
"msrp"};
pageableListWidget = new PageableListWidget(bookSearchProvider,
columns,
styles,
visibleRows);
initWidget(pageableListWidget);
}
protected void onLoad()
{
pageableListWidget.refresh();
}
/**
*
* @param searchTerm
*/
protected void setSearchTerm(String searchTerm)
{
if (this.searchTerm.equals(searchTerm))
{
// No change
//
return;
}
this.searchTerm = searchTerm;
pageableListWidget.refresh();
}
}
The BookListWidget class extends the com.google.gwt.user.client.ui.Composite class to facilitate an aggregate UI component incorporating one or more associated widgets. In this case, only one nested
composite-based widget, PageableListWidget, is used.
The PageableListWidget class also extends the com.google.gwt.user.client.ui.Composite class and contains multiple child widgets, including a custom navigation-bar widget and a com.google.gwt.user.client.ui.Grid widget that handles a list of book-related data. The navigation-bar widget incorporates an instance of the com.google.gwt.user.client.ui.DockPanel widget and several instances of the com.google.gwt.user.client.ui.Button class.
The following illustrates the PageableListWidget class:
public class PageableListWidget extends com.google.gwt.user.client.ui.Composite
{
private final RowDataAcceptor acceptor = new RowDataAcceptorImpl();
private final NavBar navbar = new NavBar();
private final DockPanel outer = new DockPanel();
private final SearchProvider provider;
private int startRow = 0;
private final Grid grid = new Grid();
/**
* Navigation Bar widget
*/
private class NavBar extends Composite
implements ClickListener
{
public final DockPanel bar = new DockPanel();
public final Button gotoFirst = new Button("First", this);
public final Button gotoNext = new Button("Next", this);
public final Button gotoPrev = new Button("Prev", this);
public final HTML status = new HTML();
public NavBar()
{
initWidget(bar);
bar.setStyleName("navbar");
status.setStyleName("status");
HorizontalPanel buttons = new HorizontalPanel();
buttons.add(gotoFirst);
buttons.add(gotoPrev);
buttons.add(gotoNext);
bar.add(buttons, DockPanel.EAST);
bar.setCellHorizontalAlignment(buttons,
DockPanel.ALIGN_RIGHT);
bar.add(status, DockPanel.CENTER);
bar.setVerticalAlignment(DockPanel.ALIGN_MIDDLE);
bar.setCellHorizontalAlignment(status,
HasAlignment.ALIGN_RIGHT);
bar.setCellVerticalAlignment(status,
HasAlignment.ALIGN_MIDDLE);
bar.setCellWidth(status, "100%");
// Initially disable prev & first buttons
//
gotoPrev.setEnabled(false);
gotoFirst.setEnabled(false);
}
public void onClick(Widget sender)
{
// handle nav-bar button clicks
}
}
public PageableListWidget(SearchProvider provider,
String[] columns,
String[] columnStyles,
int rowCount)
{
this.provider = provider;
initWidget(outer);
grid.setStyleName("table");
outer.add(navbar, DockPanel.NORTH);
outer.add(grid, DockPanel.CENTER);
initTable(columns, columnStyles, rowCount);
setStyleName("BookSearch-PageableListWidget");
}
/**
*
* @param columns
* @param columnStyles
* @param rowCount
*/
private void initTable(String[] columns,
String[] columnStyles,
int rowCount)
{
// Set up the header row to one greater than the number of visible rows
//
grid.resize(rowCount + 1, columns.length);
for (int i = 0, n = columns.length; i < n; i++)
{
grid.setText(0, i, columns[i]);
if (columnStyles != null)
{
grid.getCellFormatter().setStyleName(0, i, columnStyles[i] + " header");
}
}
}
public void refresh()
{
// Disable buttons temporarily to stop the user from overrunning the table
//
navbar.gotoFirst.setEnabled(false);
navbar.gotoPrev.setEnabled(false);
navbar.gotoNext.setEnabled(false);
setStatusText("Please wait...");
// update table
updateTable(startRow, getDataFromService());
}
private void updateTable(int startRow, String[][] data)
{
int destRowCount = getDataRowCount();
int destColCount = grid.getCellCount(0);
assert (data.length <= destRowCount) : "Too many rows";
int srcRowIndex = 0;
int srcRowCount = data.length;
int destRowIndex = 1; // skip navbar row
for (; srcRowIndex < srcRowCount; ++srcRowIndex, ++destRowIndex)
{
String[] srcRowData = data[srcRowIndex];
assert (srcRowData.length == destColCount) : " Column count mismatch";
for (int srcColIndex = 0; srcColIndex < destColCount; ++srcColIndex)
{
String cellHTML = srcRowData[srcColIndex];
grid.setText(destRowIndex, srcColIndex, cellHTML);
}
}
// Clear any remaining table rows
//
boolean isLastPage = false;
for (; destRowIndex < destRowCount + 1; ++destRowIndex)
{
isLastPage = true;
for (int destColIndex = 0; destColIndex < destColCount; ++destColIndex)
{
grid.clearCell(destRowIndex, destColIndex);
}
}
// Synchronize the nav buttons
//
navbar.gotoNext.setEnabled(!isLastPage);
navbar.gotoFirst.setEnabled(startRow > 0);
navbar.gotoPrev.setEnabled(startRow > 0);
}
public void setRowCount(int rows)
{
grid.resizeRows(rows);
}
private int getDataRowCount()
{
return grid.getRowCount() - 1;
}
}
The SearchTermWidget extends the com.google.gwt.user.client.ui.Composite class to contain an aggregate of a label, a text box, and a button. The text box contains search terms, and the button initiates
each search.
The following illustrates the SearchTermWidget class:
public class SearchTermWidget extends com.google.gwt.user.client.ui.Composite
{
private final HorizontalPanel outer = new HorizontalPanel();
private BookListWidget booklistWidget = null;
private TextBox searchTermTxtBox = null;
public SearchTermWidget(final BookListWidget booklist)
{
initWidget(outer);
setStyleName("BookSearch-SearchTermWidget");
this.booklistWidget = booklist;
Label lbl = new Label("Search term: ");
lbl.setHeight("1.5em");
searchTermTxtBox = new TextBox();
searchTermTxtBox.setHeight("1em");
searchTermTxtBox.setText("");
Button searchBtn = new Button("Search", new ClickListener()
{
public void onClick(Widget sender)
{
booklistWidget.setSearchTerm(searchTermTxtBox.getText());
}
});
searchBtn.setHeight("1.5em");
HorizontalPanel hp = new HorizontalPanel();
hp.setHorizontalAlignment(HasAlignment.ALIGN_CENTER);
hp.add(lbl);
hp.add(searchTermTxtBox);
hp.add(searchBtn);
outer.add(hp);
outer.setCellVerticalAlignment(hp, HasAlignment.ALIGN_MIDDLE);
outer.setCellHorizontalAlignment(hp, HasAlignment.ALIGN_CENTER);
}
}
Now the application is prepared to call the server-side service.
Server-side functionality can now be added to the application. For the BookSearch application, a remote API is invoked to retrieve a list of books matching a given search term. GWT provides an RPC framework used to expose the service to the client and invoked to do the actual search.
The communication interactions for the BookSearch application are illustrated in the following figure:
Figure 3. Click on thumbnail to view full-sized image.
The first step in implementing the server-side code is to define an interface for the search service. This interface must
extend the com.google.gwt.user.client.rpc.RemoteService interface and contain the methods that will be exposed to the GWT client-side code.
The following code illustrates the search service interface. Its one and only method takes a search term as input and returns
an array of Book objects containing data representing a list of matches:
public interface SearchService
extends com.google.gwt.user.client.rpc.RemoteService
{
Book[] getBooks(String searchTerm,
int startIndex,
int maxCount);
}
AJAX calls from JavaScript are asynchronous; therefore, an asynchronous interface corresponding to the RemoteService interface must be defined. The method signatures of the asynchronous interface match those of the remote interface with the
addition of one more parameter of type com.google.gwt.user.client.rpc.AsyncCallback, which will be invoked when the asynchronous service completes. The return type is removed as well, since the callback object
will be used to communicate the response.
The following listing shows the asynchronous interface for SearchService:
public interface SearchServiceAsync
{
void getBooks(String searchTerm,
int startIndex,
int maxCount,
com.google.gwt.user.client.rpc.AsyncCallback callback);
}
The AsyncCallback class has two methods: onSuccess() and onFailure(), which are called on success or failure of the remote service.
The asynchronous implementation class for the SearchService class can now be created. The logic for the actual search action is facilitated by using the Apache HTTPClient framework
to invoke the HTTP GET method on a remote, search-related API. In this case, the search API is provided by Safari Books Online
and is invoked with the URL: http://my.safaribooksonline.com/xmlapi/?search=. The result returned from the Safari Books Online
search API is an XML document. This document is handled as a DOM (Document Object Model) document using the Java API for XML
Processing (JAXP) framework.
The following illustrates an invocation to the search API employing the Apache Commons HttpClient class and handling the result as a DOM document using the JAXP API:
HttpClient client = new HttpClient();
GetMethod get = new GetMethod(url);
org.w3c.dom.Document xmlDoc = null;
try
{
// Invoke the remote search API
int resultCode = client.executeMethod(get);
if (resultCode == 200)
{
InputStream in = get.getResponseBodyAsStream(); // Build the DOM document from the response stream
DocumentBuilder builder = builderFactory.newDocumentBuilder();
xmlDoc = builder.parse(in);
}
else
{
throw new IOException("HTTP error with response code: "
+ resultCode);
}
}
finally
{
// Release the connection
get.releaseConnection();
}
Once the DOM document has been created, its individual elements can be traversed to find the items to add to the list of books, as illustrated in the following code:
org.w3c.dom.NodeList nodeList = xmlDoc.getElementsByTagName("book");
if (nodeList != null)
{
int len = nodeList.getLength();
for (int i = 0; i < len; i++)
{
org.w3c.dom.Element bookElement =
(org.w3c.dom.Element)nodeList.item(i);
org.w3c.dom.Element title = (org.w3c.dom.Element)
bookElement.getElementsByTagName("title").item(0);
String titleStr = (title != null ? title.getTextContent() : "");
org.w3c.dom.Element isbn = (org.w3c.dom.Element)
bookElement.getElementsByTagName("isbn").item(0);
String isbnStr = (isbn != null ? isbn.getTextContent() : "");
org.w3c.dom.Element edition = (org.w3c.dom.Element)
bookElement.getElementsByTagName("edition").item(0);
String editionStr = (edition != null ? edition.getTextContent() : "");
org.w3c.dom.Element msrp = (org.w3c.dom.Element)
bookElement.getElementsByTagName("msrp").item(0);
String msrpStr = (msrp != null ? msrp.getTextContent() : "");
books.add(new Book(titleStr, isbnStr, editionStr, msrpStr));
}
With the search service classes and interfaces implemented, the client classes can be enhanced to invoke the service and handle its response.
With the search service classes and interfaces implemented, the client classes can be enhanced to invoke the service and handle its response. The steps are as follows:
SearchServiceAsync class as follows: searchService = (SearchServiceAsync)GWT.create(SearchService.class);SearchServiceAsync instance as follows:
ServiceDefTarget target = (ServiceDefTarget)searchService;
String moduleRelativeURL = GWT.getModuleBaseURL() + "booksearch";
target.setServiceEntryPoint(moduleRelativeURL);
onClick method of the SearchTermWidget class to update the associated BookListWidget class with the search term found in the text box of the SearchTermWidget class as follows:
Button searchBtn = new Button("Search", new ClickListener()
{
public void onClick(Widget sender)
{
booklistWidget.setSearchTerm(searchTermTxtBox.getText());
}
});
PageableListWidget instance's UI by calling its refresh method
BookSearchProvider with the new search term by calling its updateRowData method, which will invoke the search service's getBooks method
onFailure or onSuccess method of the AsyncCallback instance passed to it via the getBooks method:
searchService.getBooks(searchTerm, startRow, maxRows,
new AsyncCallback()
{
public void onFailure(Throwable caught)
{
// handle failures
}
public void onSuccess(Object result)
{
// update com.google.gwt.user.client.ui.Grid
// Widget with results
}
});
When the onSuccess method is called, the instance of the com.google.gwt.user.client.ui.Grid widget can be updated with the new book list.
The following figure shows the main page of the application, displaying a list of books retrieved from the Safari Books Online API using "Java" as the search term:
Figure 4. Click on thumbnail to view full-sized image.Google Web Toolkit is a Java development framework for AJAX application development. GWT removes much of the technical details of AJAX-based RPC communication and provides a library of widget components for building rich UIs.
GWT allows a developer to implement and debug AJAX-based applications in Java using common Java development tools and then compile and deploy the applications as client-side HTML and JavaScript, and server-side Java.
GWT fuses client-side and server-side code together with Java as the common language. This common environment along with features such as enhanced debugging does come with a few drawbacks. For example, GWT is completely dependent on the availability of JavaScript. If JavaScript is not available, the UI will simply not work. Also, where traditional Web-client development technologies deliberately seek to underscore security vulnerabilities, GWT's use of Java for both client and server development can conceal vulnerabilities and lead to a false sense of runtime security.
GWT's abstractions form a black-box framework that eliminates many common Web application development challenges, as it steers developers towards an AJAX-styled development model. However, this black-box environment complicates integration of other non-AJAX technologies. Therefore, GWT is most applicable to Web applications designed around a rich GUI, single page model.
Archived Discussions (Read only)