Learn how to embed your GWT modules in a live Tapestry page
June, 2006
By Geoffrey Longman, Clean Code LLC
The toolkit known as GWT is all about creating dynamic client side javascript widgets and applications. The intriguing part of the toolkit is that these "scripty" bits are written as normal Java source code and are transcoded by the GWT compiler into relatively small and efficient javascript code. A "hosted" environment is provided by the toolkit which embeds a browser where developers can load their GWT enabled pages during development. When a page is loaded into this embedded browser, the GWT component javascript is not loaded. Rather the compiled java classes that produced them are hooked up instead. This means that while the page loads an behaves as it would in a live environment it is possible, using a regular IDE, to debug components in the traditional manner. It's quite fun to work with.
From the Tapestry homepage:
Tapestry is an open-source framework for creating dynamic, robust, highly scalable web applications in Java. Tapestry complements and builds upon the standard Java Servlet API, and so it works in any servlet container or application server.
At it core Tapestry is a page generation framework. URLs and such are controlled by the framework which, as we will see, does not mesh well with prescribed project layout and development flow of GWT. But, with some minor changes, it is possible to embed GWT modules into any Tapestry page with a minimum of pain and effort.
This article is about embedding GWT into Tapestry. In other words it about the minimum a developer needs to do to make a GWT component appear on a dynamically generated Tapestry page. The example described is very simple by design.
Since this article was first written new versions of GWT have been released. So, some of the screenshots show the older versions of the GWT jars. The methods and projects described and made available from this article have been tested and shown to work with GWT version 1.2.22, the latest version of GWT as of December 2006.
Table of Contents
- Intended Audience
- The GWT Module
- The Tapestry Project
- Step 1 - Add GWT Module Source
- Step 2 - Compiling GWT Java Source into Javascript
- Step 3 - Making MortgageCalc.html into a Tapestry template
- Step 4 - Debugging GWT Code in a Tapestry Page
- The Final Project
- Conclusion
Intended Audience
It is assumed that the audience:
- Has experience building and running web applications from within Eclipse.
- Has downloaded GWT, read the docs, run the sample application, and successfully built their own "Hello World" module using the toolkit
- Is familiar enough with Tapestry 4.0 to build a "Hello World!"
The GWT Module
In this article it will be shown how one can embed a mortgage calculator written using GWT into a Tapestry page. The widget, "The Colorado Mortgage Calculator" was built and made public by Dan Moore here. One can view a live (non Tapestry) page displaying the widget here. I chose to use the standalone version of this component although the "database-connected" version would also work but the extra server side bits are beyond the scope of this article. You can find more information on how Dan developed the standalone version here
The Tapestry Project
The code for this article was developed using Tapestry 4.0.2. The application has one page, the minimum needed to demonstrate the embedding without getting bogged down in a lot of Tapestry administrivia. The project looks like this:
This project can be downloaded.
Step 1 - Add GWT Module Source
Dan Moore has made his Mortgage Calculator project available for download. That project is based on a skeleton project generated by the GWT tools. Go get the project and extract it somewhere temporary. In order to get things working in our Eclipse project we will move far enough away from the structure generated by GWT that we will only need the source code in Dan's project.
From the Mortgage Calculator project, copy the "src" folder into the Eclipse project and add it to the project build path. You will notice that Eclipse will immediately report compile errors as the GWT user jar is missing from the project classpath. What I did was to add a classpath variable called GWT to Eclipse that points to the directory where I installed GWT.
Once you have done all this the project will look like this:
The package structure above is the structure recommended by Google. Note that the html file is included in the classpath. The goal of this article is to make MortgageCalc.html a dynamic Tapestry page and for that, this location just will not do. But first, lets show that the calculator page and component will work in our project as it sits now.
Step 2 - Compiling GWT Java Source into Javascript
In Step 1 we only copied the GWT source from the Mortgage Calculator distribution. We did not copy over the included compile batch file. We are going to use Ant to translate the java source into javascript using the GWT compiler. First we will write a script that duplicates the work done by the batch file in the Mortgage Calculatordistribution. This new Ant build file is provided that you can drop in the project (build.xml.v1 - you will need to rename it).
Running Ant now puts invokes the GWT comipler and a directory named for the Mortgage Calculator module now appears in the webapp directory:
We can now restart the servlet container and see the Mortgage Calculator in all it's glory by entering the following url:
http://localhost:8080/com.cohomefinder.gwt.mortgagecalculator.MortageCalc/MortgageCalc.html
If you click on the image at right you see that this works but it's not integrated with Tapestry and, wow, that url is terrible. The following url would be much nicer:
http://localhost:8080/calc/MortgageCalc.html
To achieve this the build file was modified to instruct the GWT compiler to put its output into a temporary location and then it copies the results into webapp/calc (build.xml.v2). This is needed as the compiler will always put the results in a new folder named after the module.
Now we can access MortgageCalc.html using the shorter url.
Step 3 - Making MortgageCalc.html into a Tapestry template
We could embed the Mortgage Calculator widget into our existing Home.html file. I want to make the point is that it is unnecessary for any HTML to reside in the classpath. The fact that GWT's prescribed style is to do this is fine but it doesn't fit with the goal of putting the Mortgage Calculator in a Tapestry page. We will replace the template Home.html with MortgageCalc.html and "Tapestrize" it.
Home.html is now gone and we have moved MortgageCalc.html from webapp/calc/ to webapp. Tapestry will have a problem with that unless we tell it that MortgageCalc.html is the new "Home" page. We do this by adding a <meta> tag to the embed.application file:
<meta key="org.apache.tapestry.home-page" value="MortgageCalc"/>
After doing this I restarted the servlet container and see that Tapestry is indeed using MortgageCalc.html as the Home page:
But, unfortunately, no GWT component is loaded! This is happening as the script file "gwt.js" is loaded relative to the page and since we moved the page....you get the idea. In order to make this work we must make some minor changes to MortgageCalc.html:
<meta name='gwt:module' content='com.cohomefinder.gwt.mortgagecalculator.MortgageCalc'>
becomes:
<meta name='gwt:module' content='/calc=com.cohomefinder.gwt.mortgagecalculator.MortgageCalc'>
This change tells the GWT runtime that MortgageCalc is to be found in /calc instead of com.cohomefinder.gwt.mortgagecalculator.MortgageCalc
<script language="javascript" src="/gwt.js"></script>
becomes
<script language="javascript" src="/calc/gwt.js"></script>
The old path to gwt.js indicates that it is found elative to the html file. Since the html file is now a Tapestry page, relative urls will no longer work as Tapestry does not understand the relative url. Once the above changes have been made, everything works and we have embedded the Mortgage Calculator widget into the home page of a Tapestry application:
Step 4 - Debugging GWT Code in a Tapestry Page
Please note that I could not get hosted mode debugging to work on Windows XP with IE 7 installed.
Works fine if IE6 is the installed browser.
We have thrown away the Eclipse project, ant build file, and batch files generated by the GWT tools and substituted our own equivalents. Unfortunately doing this also dicarded the easy way to debug the GWT code in a running Tapestry application. Here's how to do it. It involves the use, unfortunately :^), of one JSP.
The GWT hosted browser makes a special call to obtain some extra javascript. This file is called gwt-hosted.html. The file is not static so we use a JSP to insert the needed extra bits. This JSP approach is described by Luca Masini , the developer who first published the approach (Thanks Luca!).
webapp/WEB-INF/jsp/gwt-hosted.jsp:
<html> <body> <script> debugger; var $wnd = parent; var $doc = $wnd.document; var q = location.search.substring(1); var i = q.lastIndexOf("="); var $moduleName = q; var $moduleBaseURL = "<%= request.getContextPath()%>/" + $moduleName; if (i != -1) { $moduleBaseURL = q.substring(0, i); $moduleName = q.substring(i+1); } parent.__gwt_initHostedModeModule(this, $moduleName); </script> </body> </html>
Some additions are needed in webapp/WEB-INF/web.xml:
<servlet> <servlet-name>gwt-hosted</servlet-name> <jsp-file>/WEB-INF/jsp/gwt-hosted.jsp</jsp-file> </servlet> <servlet-mapping> <servlet-name>gwt-hosted</servlet-name> <url-pattern>/calc/gwt-hosted.html</url-pattern> </servlet-mapping>
Next, we will create an Eclipse launch configuration that will start the Hosted browser with no embedded tomcat server. This means one must launch the application (using Jetty with, for example, JettyLauncher) and then launch the hosted browser separately. The advantage is that you control the servlet container, not Google. The hosted tomcat instance is severly crippled IMHO and the setup here works much better.
To launch the hosted browser the way We want, we must add both GWT library jars to the classpath of the project:
Next we create a new Launch configuration:
And set the arguments to launch the shell without the embedded tomcat server:
And lastly the classpath for the launch configuration must include:
- Any source folders containing GWT module source;
- The location of the compiled classes; and
- Both GWT jars.
Here is what the launch classpath for the Hosted browser looks like:
When we use the new launch configuration, the hosted browser will open on our homepage. If we set a breakpoint in MortgageCalc.java, execution will stop as expected!
The Final Project
The completed project, including all Tapestry jars, is available here. You can import the project into Eclipse in the usual way.
To use the project you need to do the following steps after import:
- Set a classpath variable called GWT that points to the GWT installation directory
- Generate the MortgageCalc javascript by executing the gwt-compile-client target
- Start the project in your favorite servlet container
- Visit the url http://localhost:8080/embed
Conclusion
This article was about the minimum work required to embed a GWT module into a dynamically generated Tapestry page. The Google recommended source code layout and build tools have been abandoned in favour of a simple ant script. This project setup makes more sense to me and, as an added bonus, debugging in hosted mode has been preserved.
Integrating GWT components into Tapestry itself would be much cooler. An example of an integration point would be form validation. Envisioned is being able to write client side validation code in Java source and "attaching" the GWT compiled javascript code to form elements generated by Tapestry. Such magic is a topic for another article.
About CleanCode:
CleanCode provides business and implementation consultancy services to European and North American private and public sector organizations with emphasis on portal and web application technologies. See more at http://www.cleancode.com/
CleanCode Limited (UK)
51 Essendine Road,
London, W9 2LX
United Kingdom
T: +44 20 7099 4514
F: +44 87 0460 6473
CleanCode Limited (Canada)
6 Hamilton Avenue North, Suite 203
Ottawa, Ontario, K1Y 4R1
Canada
T: +1 613 761 6152
F: +1 613 761 9062