Saturday, February 13, 2010

ServiceAsync? We don't need no stinking...

Hosting a web app on App Engine is not only easy, it is also free -- provided that one can manage to stay in the quota limits that are allotted. One way of achieving this goal: let the browser do as much as possible on his own.



Let's take for example Google's gdata APIs. There is a ton of information available out there for the developer, but getting to it from the browser can sometimes be tricky. For security reasons, Javascript puts limitations on calls to arbitrary web services out there, which is why some apps route their requests through a server-side component (and that's exactly what we are trying to avoid). Getting by without the server makes life a bit more complicated -- or does it? Thanks to the newest Google Web Toolkit, and a couple of open source extensions, building an in-browser application that uses rich data apis has become significantly easier. Let's give it a try and build an browser-only app that sends a user query to Google base and displays the results in our application.

Preparations


We are using the latest Eclipse plugins for Google App Engine (which also contains the Google Web Toolkit) and build a new standard web application. Next, we download the open-source library gwt-data and place it in our WEB-INF/lib folder. We also add that library to the class path of our project, and make it known to the gwt compiler by placing the line
<inherits name='com.google.gwt.gdata.GData' />
in our .gwt.xml file.

Next, we need to sign up for a data api key, which we store in a constant called GDATA_API_KEY. We then open our
main class (whatever implements EntryPoint in the standard google web project) and add code to initialize the API (in this case Google Base):

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.gdata.client.GData;
import com.google.gwt.gdata.client.GDataSystemPackage;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;

public class MyApp implements EntryPoint {

private static final String GDATA_API_KEY = "MYDATA KEY";
private static final String APPLICATION_NAME = "MY APP NAME";

/**
* This is the entry point method.
*/
public void onModuleLoad() {
if (!GData.isLoaded(GDataSystemPackage.GBASE)) {
GData.loadGDataApi(GDATA_API_KEY, new Runnable() {
public void run() {
startApplication();
}
}, GDataSystemPackage.GBASE);
} else {
startApplication();
}
}

private void startApplication() {
if (!GData.isLoaded(GDataSystemPackage.GBASE)) {
Window.alert("GData could not be initialized");
}
// The following is an example and will depend
// on your UI classes
RootPanel.get("application").add(new SearchPanel(APPLICATION_NAME));
}
}



This code is pretty much boilerplate to bootstrap the application. It initializes Gdata, and the brings a SearchPanel onto the screen. The SearchPanel is where the magic happens -- we are going to implement it in this blog post:

UIBinder


If I had to point to a single feature that I like best about GWT 2.0, it would be UIBinder. If anyone still remembers my first experience with GWT, then you might know that I spent a considerate amount of time and code to plug a set of components together to make my application appear on the screen. With UIBinder, I simply use XML that pretty much resembles an html layout.

For example let's assume that my SearchPanel should contain three elements: a text box to enter the search query, a button to trigger the search, and a text area to show the response. The following XML file completely sets this up for me, including css styling:

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui">
<ui:style>
.text {
fornt-style: italic;
}
.important {
font-weight: bold;
}
</ui:style>
<g:HTMLPanel>
<span class="{style.text}">Search for:</span>
<g:TextBox visibleLength="30" ui:field="query" />
<g:Button styleName="{style.important}"
ui:field="button" text="Google Products Search"/>
<br/>
<g:TextArea ui:field="results"
characterWidth="80" visibleLines ="50" />
</g:HTMLPanel>
</ui:UiBinder>


The entire layout is defined in this tiny snippet of XML. If I need access to a particular element from Java code, I can add a ui:field name to it in the XML. Our text box is called query, our button is called button, and out text area is called results. We will see in just a moment how these names are reflected in Java code.

It should be pointed out that the eclipse plugin has a template for uibinder (just like "new class" or "new interface", there is also a "new UiBinder" wizard). This makes it very easy to get started on such a component. Most of the boilerplate shown below was generated by Eclipse for us.

And the Java code?


Let's take a look at the Java code that belongs to our uibinder template:

package com.appenginefan.nookbook.client;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.gdata.client.gbase.GoogleBaseService;
import com.google.gwt.gdata.client.gbase.SnippetsEntry;
import com.google.gwt.gdata.client.gbase.SnippetsFeed;
import com.google.gwt.gdata.client.gbase.SnippetsFeedCallback;
import com.google.gwt.gdata.client.gbase.SnippetsQuery;
import com.google.gwt.gdata.client.impl.CallErrorException;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;

/**
*
* A panel where books can be searched and results displayed.
*
* @author Jens Scheffler
*
*/
public class SearchPanel extends Composite {

private static final String URI = "http://www.google.com/base/feeds/snippets";

private static SearchPanelUiBinder uiBinder = GWT
.create(SearchPanelUiBinder.class);

interface SearchPanelUiBinder extends UiBinder<Widget, SearchPanel> {
}

private final GoogleBaseService service;

@UiField TextBox query;
@UiField TextArea results;

public SearchPanel(String applicationName) {
initWidget(uiBinder.createAndBindUi(this));
this.service = GoogleBaseService.newInstance(applicationName);
}

@UiHandler("button")
void search(ClickEvent event) {
SnippetsQuery gdataQuery = SnippetsQuery.newInstance(URI);
gdataQuery.setMaxResults(25);
gdataQuery.setBq(query.getText());
results.setText("Searching...");
service.getSnippetsFeed(gdataQuery, new SnippetsFeedCallback() {
@Override
public void onFailure(CallErrorException caught) {
results.setText("Call to Google Base failed:\n" + caught);
}
@Override
public void onSuccess(SnippetsFeed result) {
showData(result.getEntries());
}
});
}

private void showData(SnippetsEntry[] entries) {
if (entries.length == 0) {
results.setText("You have no items.");
} else {
StringBuilder output = new StringBuilder();
for (SnippetsEntry entry : entries) {
output.append(entry.getTitle().getText());
output.append("\n");
}
results.setText(output.toString());
}
}
}



As we can see, there are two fields that are annotated with the @UiField annotation:

@UiField TextBox query;
@UiField TextArea results;


The names of the fields match the according label in our xml file. UiBinder will automatically match the fields with the according xml tags and link our GWT code to the html. Notice also that we have not defined such a link to the button from our file. Instead, we have defined a method search and used the UiHandler annotation to let GWT know that this method should be called when the button is hit.

Within the search method, we perform our ajax api query: We get the query string out of our text field and submit it as a SnippetsQuery to the gdata service. If we get a result back from the server, we call our showData method to populate the results area accordingly.

0 comments: