- August 30th, 2006
- 7:12 pm
While I was messing around with GWT and Google Maps, I thought it might be a nice idea to plot POIs like the kind TomTom uses for Point of Interests - hotels, gas stations, and quite a lot of other stuff, which in many cases can be downloaded freely.
POI files (.ov2) contains longitude, latitude and a name in a (simple) binary format. The following code reads a list of POIs:
CODE:
-
public class POI {
-
private double longitude;
-
private double latitude;
-
private String name;
-
-
public POI(String name, double longitude, double latitude) {
-
this.name = name;
-
this.longitude = longitude;
-
this.latitude = latitude;
-
}
-
-
public double getLatitude() {
-
return latitude;
-
}
-
-
public double getLongitude() {
-
return longitude;
-
}
-
-
public String getName() {
-
return name;
-
}
-
-
public static List getPOIs(InputStream is) throws IOException {
-
List res = new ArrayList();
-
int b = -1;
-
while ((b = is.read())> -1) {
-
if (b == 0 || b == 2) {
-
-
long total = readLong(is);
-
-
double longitude = (double) readLong(is) / 100000.0;
-
double latitude = (double) readLong(is) / 100000.0;
-
-
byte[] r = new byte[(int) total - 13];
-
is.read(r);
-
-
POI p = new POI(new String(r), longitude, latitude);
-
res.add(p);
-
}
-
}
-
return res;
-
}
-
-
private static long readLong(InputStream is) throws IOException {
-
long res = is.read();
-
res += is.read() <<8;
-
res += is.read() <<16;
-
res += is.read() <<24;
-
return res;
-
}
-
}
The resulting objects' latitude and longitude can be transferred directly to a Google Map for display, or they can be used to, for example, calculate the nearest gas station.
- August 30th, 2006
- 6:10 pm
I've updated the GWT XDoclet module so that it supports module inheritance from other than the standard com.google.gwt.user.User class.
The new version, 0.2, is available from http://braindump.dk/gwt-xdoclet/xdoclet-gwt-module-0.2.jar.
The only change is in the @gwt.module tag:
CODE:
-
/**
-
* @gwt.module package="dk.contix.gwt" include="client,shared" inherits="dk.contix.gwt.BaseModule"
-
*/
-
public class Module implements EntryPoint {
-
...
-
}
- August 24th, 2006
- 10:57 am
I just stumbled upon this article, Label Placement in Forms, an article about how labels in forms should be placed. Pretty interesting stuff, especially because its results are based on tests on real people in a controlled environment.
- August 17th, 2006
- 10:04 pm
Pagination is a normal and popular method of splitting data across pages. However, a new method has emerged (I forgot where I read about it first). Instead of distributing data across several pages (which is pretty non-Ajax), you place a scrollpanel around the data and load data when the scrollbar reaches the bottom.
See an example here.
Implementing this is actually pretty easy, especially with Google Web Toolkit. I've created a custom widget called ScrollAutoLoader, which handles scroll events. When used, you can implement scroll loading like this:
CODE:
-
public class Module implements EntryPoint {
-
private int count;
-
private ScrollAutoLoader loader;
-
public void onModuleLoad() {
-
final VerticalPanel panel = new VerticalPanel();
-
-
loader = new ScrollAutoLoader(panel, new Loader() {
-
public void load(int offset, int limit) {
-
panel.add(new Label("Line " + ++count));
-
-
loader.fill();
-
}
-
}, 5);
-
-
loader.setHeight("300px");
-
-
RootPanel.get().add(loader);
-
loader.fill();
-
}
-
}
I've used the widget a couple of places, and it works pretty good. It might, however, be too slow if there are a lot of pages, as the browser tends to slow down when the inner panel becomes too large.
- August 14th, 2006
- 10:27 pm
I thought a small tutorial on how to get started with Google Web Toolkit might be in order. The GWT package contains some utilities, but I have found that in order to actually deploy to a Tomcat, you need to do quite a bit of stuff yourself.
This tutorial is based on GWT 1.1.0, Eclipse, Tomcat and Ant. It's assumed that Tomcat is installed somewhere (simply download and unzip/untar, and you're ready to go).
Creating a project
Create a new project in Eclipse - just a regular Java project. Make sure you use separate source and build folders.
Create the following dirs:
lib
lib/build
lib/core
Copy gwt-dev-*.jar and gwt-user.jar from GWT to lib/build.
Download ant-gwt-1.1.0.jar, xdoclet-gwt-module-0.1, gwt-xdoclet-1.2.3.jar, and xjavadoc-1.1.jar and place them in lib/build.
Place gwt-servlet.jar from GWT in lib/core together with any 3rd party libs you're using.
Add all jars (both in lib/build and lib/core) to the Eclipse build path (Right click on a jar and select Build path->Add to build path).
Create the following packages in Eclipse:
dk.contix.gwt
dk.contix.gwt.client
dk.contix.gwt.shared
dk.contix.gwt.service
In the dk.contix.gwt package, create a new dir called "public" and create a new HTML file:
CODE:
-
<html>
-
<head>
-
<title>Name of module</title>
-
<meta name="gwt:module" content="dk.contix.gwt.Module">
-
<link rel="stylesheet" type="text/css" href="style.css">
-
</head>
-
<body>
-
<script type="text/javascript" src="gwt.js"></script>
-
</body>
-
</html>
The file should be called Module.html in this example.
Setting up build
To actually compile and deploy, you need a ant build.xml file. Use this file:
CODE:
-
<?xml version="1.0"?>
-
<project default="compile" name="Module" basedir=".">
-
<property file="build.properties"/>
-
-
<path id="classpath">
-
<pathelement location="src"/>
-
<pathelement location="target/classes"/>
-
<fileset dir="lib">
-
<include name="**/*.jar"/>
-
</fileset>
-
</path>
-
-
<path id="sourcepath">
-
<pathelement location="src"/>
-
</path>
-
-
<target name="init">
-
<tstamp/>
-
<mkdir dir="target"/>
-
<mkdir dir="target/classes"/>
-
<mkdir dir="target/web"/>
-
</target>
-
-
<target name="clean">
-
<delete dir="target"/>
-
</target>
-
-
<target name="compile" depends="init,generate">
-
<javac classpathref="classpath" destdir="target/classes" debug="true">
-
<src refid="sourcepath"/>
-
</javac>
-
-
<taskdef resource="dk/contix/ant/gwt/ant-gwt.xml" classpathref="classpath" />
-
<gwtcompile destdir="target/web" optimize="true">
-
<fileset dir="src">
-
<include name="**/*.gwt.xml"/>
-
</fileset>
-
</gwtcompile>
-
-
<copy todir="target/classes">
-
<fileset dir="src">
-
<exclude name="**/*.java"/>
-
</fileset>
-
</copy>
-
</target>
-
-
<target name="generate">
-
<taskdef resource="xdoclet/modules/gwt/doclet.xml" classpathref="classpath"/>
-
<gwtdoclet destdir="src">
-
<fileset dir="src">
-
<include name="**/*.java"/>
-
</fileset>
-
<interface/>
-
<async/>
-
<module/>
-
<servicefactory class="dk.contix.gwt.client.ServiceFactory"/>
-
<webxml output="../target/web.xml"/>
-
</gwtdoclet>
-
</target>
-
-
<target name="deploy" depends="compile">
-
<fail unless="deploy.root" message="You need to define deploy.root in build.properties"/>
-
-
<mkdir dir="${deploy.root}"/>
-
<mkdir dir="${deploy.root}/WEB-INF"/>
-
<mkdir dir="${deploy.root}/WEB-INF/classes"/>
-
<mkdir dir="${deploy.root}/WEB-INF/lib"/>
-
-
<copy todir="${deploy.root}">
-
<fileset dir="target/web"/>
-
</copy>
-
<copy todir="${deploy.root}/WEB-INF/classes">
-
<fileset dir="target/classes"/>
-
</copy>
-
<copy todir="${deploy.root}/WEB-INF/lib">
-
<fileset dir="lib/core">
-
<include name="**/*.jar"/>
-
</fileset>
-
</copy>
-
<copy file="target/web.xml" todir="${deploy.root}/WEB-INF"/>
-
</target>
-
</project>
Creating a launcher
If you want to be able to run the application in hosted mode, you need a launcher. Use this for the purpose - put the following in launch.sh and run chmod +x launch.sh (replace GWT_HOME with the real path to GWT):
CODE:
-
#!/bin/sh
-
APPDIR=`dirname $0`
-
GWT_HOME=/pack/gwt-linux-1.1.0
-
CLASSPATH="$GWT_HOME/gwt-user.jar:$GWT_HOME/gwt-dev-linux.jar"
-
for i in `find . -name '*.jar'`; do
-
CLASSPATH="$CLASSPATH:$i"
-
done
-
CLASSPATH="$CLASSPATH:bin"
-
export CLASSPATH
-
-
MODULE=$1
-
shitf
-
-
java com.google.gwt.dev.GWTShell -out $APPDIR/www "$@" $MODULE/${MODULE/*./}.html
When you actually have some code, you can run "./launch.sh dk.contix.gwt.Module" to start the application in hosted mode.
Adding some code
Now it's time to add some code. Start with the entry point class, which should be called Module in thie example:
CODE:
-
package dk.contix.gwt.client.Module;
-
-
import com.google.gwt.core.client.EntryPoint;
-
import com.google.gwt.user.client.ui.*;
-
-
/**
-
* Class description here.
-
*
-
* @gwt.module package="dk.contix.gwt" include="shared,client"
-
*/
-
public class Module implements EntryPoint {
-
public void onModuleLoad() {
-
final HorizontalPanel p = new HorizontalPanel();
-
-
Hyperlink link = new Hyperlink();
-
link.setText("Press me");
-
link.addClickListener(new ClickListener() {
-
public void onClick(Widget sender) {
-
}
-
}
-
p.add(link);
-
RootPanel.get().add(p);
-
}
-
}
Deploying
At this point, you should actually be able to compile and deploy this very simple application. First you have to create build.properties in the project root and specify where the application should be deployed, for example
CODE:
-
deploy.root=/pack/apache-tomcat-5.5.15/webapps/gwt
Then just execute the following:
ant deploy
And start tomcat. At this point, you should be able to access http://localhost:8080/gwt/dk.contix.gwt.Module/Module.html and see a link on a page. Pressing the link shouldn't do anything.
Adding a service
Now we want to implement a service and call it by clicking the link. First, define the new service:
CODE:
-
package dk.contix.gwt.service;
-
-
/**
-
* Simple service implementation.
-
* @gwt.interface service="dk.contix.gwt.shared.ModuleService" path="/moduleService"
-
*/
-
public class ModuleServiceImpl extends RemoteServiceServlet {
-
-
/**
-
* @gwt.serviceMethod
-
*/
-
public String getDoubleText(String text) {
-
return text + text;
-
}
-
}
The service class should actually implement an interface, but we're going to generate it automaticaly. Do so by executing
and refresh the project in Eclipse. At this point, dk.contix.gwt.shared should contain two new interfaces, ModuleService and ModuleServiceAsync. Also, a service factory has been created in dk.contix.gwt.client.
Modify the ModuleServiceImpl class so it implements ModuleService:
CODE:
-
public class ModuleServiceImpl extends RemoteServiceServlet implements ModuleService {
Calling services
At this point, we're ready to call the remote service from the client. Modify the ClickListener:
CODE:
-
Hyperlink link = new Hyperlink();
-
link.setText("Press me");
-
link.addClickListener(new ClickListener() {
-
public void onClick(Widget sender) {
-
ServiceFactory.getModuleService().getDoubleText("testing1", new AsyncCallback() {
-
public void onFailure(Throwable caught) {
-
p.add(new Label("Error: " + caught.toString()));
-
}
-
-
public void onSuccess(Object result) {
-
String t = (String)result;
-
p.add(new Label("Result: " + t));
-
}
-
});
-
}
-
}
Deploy the application again. Deploying will automatically generate a new web.xml with a servlet mapping the new service. After restarting Tomcat, you should be able to click the link and get a result back.
That's it
Yes, that's more or less it. The primary reason that it's so easy is the code generation, which means that
- Modules are automatically created, just annotate EntryPoint classed with @gwt.module
- Services are configured and interfaces are generated by annotating RemoteServiceServlet classes with @gwt.interface
- Remote methods are made available when they are annotated with @gwt.serviceMethod
- Access to the remote services goes through a ServiceFactory, which handles url mapping automatically.
For more complex applications, it's probably not necessary (or desirable) to generate web.xml, so just use it as a starting point. You'll probably add filters and other stuff later.
Have fun!
- August 12th, 2006
- 1:19 pm
A new version of the Google Web Toolkit has been released (1.1.0), which means that my Ant task broke. It broke due to some re-organization of classes in the dev jar file, which was fixed without any problems. Worse, however, was the dependency on URLClassLoader deep down in the compiler, which was a problem, as the Ant task created an AntClassLoader, which is not an instance of URLClassLoader. After much fiddling with the classloaders, this problem was also fixed.
No new features have been added, so it should be easy to upgrade - just delete the old ant-gwt.jar and replace it with this new ant-gwt-1.1.0.jar. Documentation is still at
Having used Google Web Toolkit to build an application, I found out that I had to write a good portion of code, which might as well be generated automatically. I then decided to mess around with XDoclet a little to see if it was possible to make a new module. It was, and now you can use it too.
Basically, it can generate service interfaces, async services interface, module definitions (.xml.gwt), and a basic web.xml file. Finally, it can create a service factory for creating new Async-objects.
Service interfaces
When creating a remote service, just annotate the class like this:
CODE:
-
package dk.contix.gwt.service;
-
-
/**
-
* @gwt.interface service="dk.contix.gwt.shared.RemoteService" path="/remoteService"
-
*/
-
public class RemoteServiceImpl implements RemoteService {
-
-
/**
-
* @gwt.serviceMethod
-
*/
-
public String getStuff(String what) {
-
// code here
-
}
-
}
This will create the interface dk.contix.gwt.shared.RemoteService containing all methods which are annotated with @gwt.serviceMethod. It will also generate the async interface in dk.contix.gwt.shared.RemoteServiceAsync
Modules
To generate module definitions (.gwt.xml files), annotate the EntryPoint classes, like this:
CODE:
-
package dk.contix.gwt.client;
-
-
/**
-
* @gwt.module package="dk.contix.gwt" include="client,shared"
-
*/
-
public class Client implements EntryPint {
-
// code here
-
}
This will generate a file in dk/contix/gwt calles Client.gwt.xml. The file will contain the entry-point declaration, all remote servlets, and additional source paths (those specified in the include parameter).
ServiceFactory
If a service factory is generated, a class with static methods for creating instances of the Async interfaces will be created. The factory class will automatically detect url prefixes (if the application is deployed in another context than ROOT).
Generating the files
Create a build.xml file for Ant, if you don't already have one. Include the following target:
CODE:
-
<path id="classpath">
-
<pathelement location="src"/>
-
<pathelement location="${build.dir}/classes"/>
-
<fileset dir="lib">
-
<include name="**/*.jar"/>
-
</fileset>
-
</path>
-
-
<target name="generate">
-
<taskdef resource="xdoclet/modules/gwt/doclet.xml" classpathref="classpath"/>
-
-
<gwtdoclet destdir="src">
-
<fileset dir="src">
-
<include name="**/*.java"/>
-
</fileset>
-
-
<interface/>
-
<async/>
-
<module/>
-
<webxml output="web.xml"/>
-
<servicefactory class="dk.contix.gwt.client.ServiceFactory"/>
-
</gwtdoclet>
-
</target>
Finallt, place xdoclet-gwt-module-0.1.jar in lib/ so that it becomes a part of the classpath. xdoclet-*.jar and xjavadoc-*.jar is also required. They can be downloaded from xdoclet.sourceforge.net.
Bootstrapping
When a new remote service is created, there's no interface to implement. I work around this simply by annotating the service implementation, generating the code, and then adding the implements declaration.