|
GWTGrailsTutorial
A tutorial showing how do develop a Grails application with a GWT UI. Google GroupThere's a Google Group (http://groups.google.com/group/grails-gwt) for discussion and help around Grails and GWT. Please ask your questions there instead of writing comments below. Prepare your systemThe first step is to set up your system for running Grails and the GWT. You will also need to create a new Grails application with the command grails create-app GwtTutorial Installing the Grails GWT plugin with the command grails install-plugin gwt is required too. This tutorial uses Grails 1.0.3, GWT 1.4 and GWT plugin 0.2.4. It will be upgraded in the future to new versions, if available. General information on Grail GWT plugin can be found here: http://grails.org/GWT+Plugin. You can even check out the code from Mercurial repository with hg clone -r 1.0.0 http://derjanandhisblog.googlecode.com/hg GwtGrailsTutorial or browse the code online http://code.google.com/p/derjanandhisblog/source/browse?r=1.0.0#hg/GwtTutorial. Of cause the code is free and thus you can use it. Creating your domain modelWe want create a book store application and our domain model needs two domain classes Book and Author. You should know, how to create domain classes in Grails, else read the User-Guide. Edit them like the following: Book: class Book {
String title
static belongsTo = [author: Author]
}class Author {
String firstName
String lastName
static hasMany = [books: Book]
static constraints = {
books(nullable: true)
}
}These model is very simple, but contains some of the cool features Grails offers. A Book belongs to an Author and an Author can have more than one Book, but can also have no Books, if he is just writing his first. We should also have some default data in our database during development so you should edit the BootStrap.groovy file like this: class BootStrap {
def init = { servletContext ->
def author1 = new Author(firstName: "Jan", lastName: "Ehrhardt")
def book1 = new Book(title: "GwtTutorial")
author1.addToBooks(book1)
author1.save()
}
def destroy = {
}
} Ok, this isn't a real book, but it's just for development ;-) Creating a service for GWT clientAfter having a domain model and some development data, we will need a middle tire. Unlike a classic Grails application, we don't need controllers and GSP views. The GWT brings all this stuff to the richer GWT client, which runs in the browser. Thus we only need a service, which gets our data from the database and brings it to the browser using GWT RPC mechanism. For this we need to put a static property called "expose" to our service and the GWT plugin will start to create automatically the Java interfaces needed by the GWT client to access the service. Another problem is, that the GWT allows us to create a client application, which has a design according to MVC-Pattern, thus it would be great, if we could serialize our domain model directly and reuse it as a model in the GWT client. Unfortunately, this isn't possible yet. But a cool way to solve this problem is using JSON. GWT provides client side support for JSON and Grails provides server side support for domain serialzation to JSON. So we can use a JSON based model in client and use domain class serialization on the server. Our BookService should look like this: class BookService {
boolean transactional = true
static expose = ["gwt:org.grails.gwttutorial.client"]
String getAuthor(int id) {
def author = Author.get(id)
return author.encodeAsJSON()
}
}You can see, that the "expose" property points the GWT plugin to "org.grails.gwttutorial.client", which is the the package, where the service interfaces shall be placed, when the plugin generates them. Currently, we didn't create it, but soon we'll. There is just one method so far, which does nothing else than getting an Author from the database and sending it to the client, encoded as JSON. The method for encoding is provided by Grails as a standard. The GWT clientFor creating our client, you should run the following commands: grails create-gwt-module org.grails.gwttutorial.Application grails create-gwt-page bookStore/index.gsp org.grails.gwttutorial.Application The second one will prompt you, if you want to create the Controller BookStore. You should accept this by typing "y" and don't care any longer about this (this controller is needed, to return the GSP, we just create him, nothing else). Now edit the bookStore/index.gsp page: <html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Book Store</title>
<script type="text/javascript" src="${createLinkTo(dir: 'gwt/org.grails.gwttutorial.Application', file: 'org.grails.gwttutorial.Application.nocache.js')}"></script>
</head>
<body>
<iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe>
<div id="bookStoreContainer"></div>
</body>
</html>Than open the file org.grails.gwttutorial.Application.gwt.xml in the src/java directory and edit it like this: <module>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name="com.google.gwt.user.User"/>
<!-- Inherit the client side JSON handling. -->
<inherits name='com.google.gwt.json.JSON'/>
<!-- Specify the module entry point class. -->
<entry-point class="org.grails.gwttutorial.client.Application"/>
</module>This brings the JSON modul to our GWT client (If you're using Eclipse, you shall additionally put your GWT_HOME directory to the projects classpath, else you'll get some trouble with the Java editor). Further more copy the gwt-user.jar from GWT_HOME to the lib directory of your Grails project. This is a good moment for running our app first time ("grails run-app" command ;-)). While starting the GWT plugin hooks into the build process and generates the service interfaces, we'll need for RPC calls (and for the Eclipse editor, during editing of the Application.java). If everythig works, let's start creating the client. Currently it shouldn't be a real store. It only shows the one existing Author from the database in a tree view ;-), but hopefully the next version of the sample application will do more. Here is the code: public class Application implements EntryPoint {
private BookServiceAsync bookService;
private Tree tree;
public void onModuleLoad() {
tree = new Tree();
bookService = (BookServiceAsync) GWT.create(BookService.class);
ServiceDefTarget endpoint = (ServiceDefTarget) bookService;
String moduleRelativeUrl = GWT.getModuleBaseURL() + "rpc";
endpoint.setServiceEntryPoint(moduleRelativeUrl);
bookService.getAuthor(1, new AsyncCallback() {
public void onFailure(Throwable arg0) {
}
public void onSuccess(Object author) {
JSONValue value = JSONParser.parse((String)author);
tree.removeItems();
tree.setVisible(true);
TreeItem item = tree.addItem("Response");
addChildren(item, value);
}
});
RootPanel panel = RootPanel.get("bookStoreContainer");
if(panel != null) {
panel.add(tree);
}
}
private void addChildren(TreeItem treeItem, JSONValue jsonValue) {
JSONArray jsonArray;
JSONObject jsonObject;
JSONString jsonString;
if ((jsonArray = jsonValue.isArray()) != null) {
for (int i = 0; i < jsonArray.size(); ++i) {
TreeItem child = treeItem.addItem(getChildText("[" + Integer.toString(i) + "]"));
addChildren(child, jsonArray.get(i));
}
} else if ((jsonObject = jsonValue.isObject()) != null) {
Set keys = jsonObject.keySet();
for (Iterator iter = keys.iterator(); iter.hasNext();) {
String key = (String) iter.next();
TreeItem child = treeItem.addItem(getChildText(key));
addChildren(child, jsonObject.get(key));
}
} else if ((jsonString = jsonValue.isString()) != null) {
treeItem.addItem(jsonString.stringValue());
} else {
treeItem.addItem(getChildText(jsonValue.toString()));
}
}
private String getChildText(String text) {
return "<span style='white-space:normal'>" + text + "</span>";
}
}So what is this code doing? Well, nothing else, the creating a Tree, hookink the Tree into the page, calling our RPC method for the Author with id 1, parsing the result with a JSON parser and putting the values into the Tree. That's all. Now start the Grails application again and run the command: grails run-gwt-client This should open the GWT client and allows you to see the result. Another command grails compile-gwt-modules will compile your GWT code to JavaScript. This allows you to see it in a real browser (not Firefox 3 RC2 on my Mac but maybee on yours). Additional notesThis is just a very basic tutorial and hopefully it will be extended in the next weeks and months and when new releases are available and so on. If you want to be informed about new versions, start reading my blog http://derjanandhisblog.blogspot.com, where updates will be announced. LicenseThe content of this page is under a Creative-Commons-License, so reuse it :-) |
Thanks
Good tutorial. Thanks!
I went this tutorial, but when i'm running, i receive this follow error: ERROR? Unable to find 'GwtTutorial?.gwt.xml' on your classpath; could be a typo, or maybe you forgot to include a classpath entry for source?
my gwt version is 1.4.x
tks
Thanks about your tutorial, it helps a lot!
With jdk 6/gwt 1.4.x (latest)/grails 1.0.3 works fine, except that books did not load at all within the tree. Until I changed little bit the service code to get all sended to the browser:
1. option, when you will send all books of an author:
import grails.converters.JSON class BookService? {
}2. option, when you will send only keys of books:
class Book {
}class Author {
}class BookService? {
}Tk
Missing lines at the begin of Application.java:
package org.grails.gwttutorial.client;
import com.google.gwt.core.client.;
import com.google.gwt.user.client.ui.;
import com.google.gwt.user.client.rpc.;
import com.google.gwt.json.client.;
import java.util.HashSet?;
import java.util.Set;
import java.util.Iterator;
Here are the three commands you need at the beginning of this tutorial, be careful to the case of each parameter:
It's no easy.
In case you are running into some kind of
'Exception while dispatching incoming RPC call javax.servlet.ServletException?: Content-Type must be 'text/x-gwt-rpc'' or ' Exception while dispatching incoming RPC call javax.servlet.ServletException?: Content-Type must be 'text/plain''
Exception - this is because you have different GWT versions on your machine the changed the content-type to 'text/x-get-rpc' in version 1.5 so depending on the message you either want to upgrade your gwt installation or the grails gwtlib.
I just verbatim the instruction above. And for some reason I am getting errors on BookServiceAsync? which I know I didn't create. So is there some assumption that GWT or Grails will create certain classes for you that is Async? I know the definition and syntax for grails works, just the portion where JSON is brought in is where things got confusing.
I keep getting the following error:
com.google.gwt.user.server.rpc.UnexpectedException?: Service method 'public abstract java.lang.String org.grails.gwttutorial.client.BookService?.getAuthor(java.lang.Integer)' threw an unexpected exception: groovy.lang.MissingMethodException?: No signature of method: BookService?.getAuthor() is applicable for argument types: (java.lang.Integer) values: {1}
Any help would be great.
CW
I wonder what exactly you mean by:
"if we could serialize our domain model directly and reuse it as a model in the GWT client."
one tiny spelling mistake:
"middle tire"
in an otherwise nice tutorial.
Thanks !
Thanks for this tutorial. People with little experience in Grails might take advantage of this document. ;-)
Just a few comments because it wasn't too clear:
After you create groovy service, run 'grails run-app', and the compiler will create Java versions of BookService? and BookServiceAsync? for you. Then you can update Application class using BookService? & BookServiceAsync? classes as they will be generated in the src/java folder of your Grails projet.
Jan, does this tutorial really work?? I've tried going through it and I noticed that the instructions are different from the code here: http://code.google.com/p/derjanandhisblog/source/browse/trunk/GwtTutorial/src/java/org/grails/gwttutorial/client/BookService.java?r=15
e.g. The BookService? is a java class in SVN, but it's a Groovy class in the tutorial above. The BookServiceAsync? isn't created anywhere up above, but it's in SVN.
I tried including the code from SVN and running it and received:
C:\work\workspace\GwtTutorial?>grails run-gwt-client Welcome to Grails 1.1 - http://grails.org/ Licensed under Apache Standard License 2.0 Grails home is set to: c:\grails-1.1
Base Directory: C:\work\workspace\GwtTutorial? Running script C:\Documents and Settings\BBonner\.grails\1.1\projects\GwtTutoria? l\plugins\gwt-0.3-SNAPSHOT\scripts\RunGwtClient?.groovy Environment set to development Warning, target causing name overwriting of name default
oved in a future release. C:\work\workspace\GwtTutorial?>grails run-gwt-client Welcome to Grails 1.1 - http://grails.org/ Licensed under Apache Standard License 2.0 Grails home is set to: c:\grails-1.1Base Directory: C:\work\workspace\GwtTutorial? Running script C:\Documents and Settings\BBonner\.grails\1.1\projects\GwtTutoria? l\plugins\gwt-0.3-SNAPSHOT\scripts\RunGwtClient?.groovy Environment set to development Warning, target causing name overwriting of name default
oved in a future release.Any thoughts???
Good tutorial, only a little outdated by now it feels. I'm trying to get this running on my macbook with the following setup:
java 1.5.0_16 gwt mac-1.5.3 grails 1.1 grails gwt plugin 0.3-snapshot
Everything seems to be working fine but when I run the app nothing is rendered on http://localhost:8080/GwtTutorial/bookStore/index except for the grails logo. I actually have a feeling like my GWT java code actually doesn't execute because when trying to debug it in Eclipse it never stops at my breakpoints.
I have a feeling like I'm close but something conflicts in my setup. Another hint is that when I'm running "grails compile-gwt-modules" to generate javascript I get the following stack trace:
[echo] Compiling GWT modules [echo] Module: org.grails.gwttutorial.Application [java] Compiling module org.grails.gwttutorial.Application [java] Scanning for additional dependencies: file:/Users/username/dev/grails/GwtTutorial/src/java/org/grails/gwttutorial/client/Application.java [java] Computing all possible rebind results for 'org.grails.gwttutorial.client.BookService' [java] Rebinding org.grails.gwttutorial.client.BookService [java] Invoking <generate-with class='com.google.gwt.user.rebind.rpc.ServiceInterfaceProxyGenerator'/> [java] Generating client proxy for remote service interface 'org.grails.gwttutorial.client.BookService' [java] Analyzing 'org.grails.gwttutorial.client.BookService' for serializable types [java] Analyzing methods: [java] public abstract java.lang.Object super$1$clone() [java] Return type: java.lang.Object [java] java.lang.Object [java] [ERROR] In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type [java] public abstract java.lang.Class super$1$getClass() [java] Return type: java.lang.Class [java] java.lang.Class [java] [ERROR] Type 'java.lang.Class' was not serializable and has no concrete serializable subtypes [java] [ERROR] Errors in 'file:/Users/username/dev/grails/GwtTutorial/src/java/org/grails/gwttutorial/client/Application.java' [java] [ERROR] Line 29: Failed to resolve 'org.grails.gwttutorial.client.BookService' via deferred binding [java] [ERROR] Cannot proceed due to previous errors [java] [ERROR] Build failed [java] Java Result: 1Anybody out there who can point me in the right direction?
I ran into the same issue as jay.kirc - anyone an idea how to solve that?
@Jay.kirc, m.c...@gmx.net Hi, the issue is caused by some of the methods the plugin creates during client-side service stub generation. If you examine BookService? you'll find a bunch of "super$xxxx$nnn" that are generated through reflection and can be safely deleted (you'll lose plugin auto-synchronization, but...)
HTH, Alessio
Wow, thanks Ale that really worked. I hadn't seen that generated code in my eclipse, or I would've tried. Anyway, i guess the only two methods that you really need to delete are clone() and getClass() inherited from Object, since they return something other than void.
But, there has to be a better way to do this, since this is going to happen with any class generated since they all inherit from Object. Can't the stubGen just ignore those two methods from Obj? I don't wanna loose auto-sync for all classes in my complete project just because of this...
So, it seems that the problem lies in this one line: boolean transactional = true And since, at least according to the grails docu, services are transactional by default, do we really need that there?
I introduced that to the other sample project that I found and had worked (http://www.spenceruresk.com/2008/06/15/grails-gwt-with-gwt-ext-googlemaps-maxmind-sample-application/) and recompiled which then added all the super1$xx$nn functions to the code. Lost my auto-sync with that, too, but oh well...
@alessios Sweet, thanks so much man! I already tried removing the 2 methods working with Object directly but that apparently wasn't enough. After your hint I removed all the super invocations and it's working like a charm! Awesome!
@mittiprovence
If this pluging was combined with features from Gilead (used to be Hibernate4GWT) http://noon.gilead.free.fr/gilead/ it would be extreemly powerful as the domain objects (which typically come across implementing Serializable) can be transparently converted to GWT friendly objects...
How would I add another module? In this case, I want to use "Google AJAX Search library for GWT" from my main module. In my main gwt.xml, I've included an <inherits...> statement for the module that I want to add.
But when I do grails compile-gwt-modules, I get an exception: ERROR? Unable to find 'com/google/gwt/search/Search.gwt.xml' on your classpath; could be a typo, or maybe you forgot to include a classpath entry for source?
So, how do I add the jar to my classpath for compiling?
I did copy it to the /lib directory. But that didn't help.
Found an answer to my classpath question above.
The jar (gwt-search.jar, in my case) needs to be in lib/gwt/
@jay.kirc But it report: Resource not found: rpc; (could a file be missing from the public path or a <servlet> tag misconfigured in module net.openonlinejudge.web.gwt.Admin.gwt.xml ?)
It seems you left off a step. You forgot to mention generating the async interface, namely,
generate-gwt-rpc
Great Tutorial. I still have a problem with the gwt-user.jar - where do i have to place it, so that the gwt client finds it. I wanted to look at the Repository (http://code.google.com/p/derjanandhisblog/source/browse) - but i m only getting 404 s and the message "No files in this directory." Thanks for your help in advance. Bye
The source code should be available again.
I get an error installing the plugin:
Welcome to Grails 1.1.1 - http://grails.org/ Licensed under Apache Standard License 2.0 Grails home is set to: /opt/grails-1.1.1
Base Directory: /Users/jwalsh/Dev/hnny/nb/Enrollment Running script /opt/grails-1.1.1/scripts/InstallPlugin?.groovy Environment set to development Reading remote plugin list ... Reading remote plugin list ...
Installing plug-in gwt-0.4-SNAPSHOT Executing gwt-0.4-SNAPSHOT plugin post-install script ... Plugin gwt-0.4-SNAPSHOT installed Plug-in provides the following new scripts:grails clean-gwt-modules grails compile-gwt-modules grails compile-i18n grails create-gwt-module grails create-gwt-page grails generate-gwt-rpc grails run-gwt-client Found events script in plugin gwt java.lang.NullPointerException? Error loading event script from file [/Users/jwalsh/.grails/1.1.1/projects/Enrollment/plugins/gwt-0.4-SNAPSHOT/scripts/Events.groovy] null
Any ideas?
I am trying to understand how Grails handles GWT-RPC services in more depth.
The most important question I have is: What does the "static expose" do exactly? Is it the name of the service implementation class or the interface?
Additionally:
Under $MY_GRAILS_APP/grails-app, there is a services directory. I can put the IMPLEMENTATION of my GWT-RPC services there in either Groovy or Java, right? Can I put those classes in a package? Can I freely mix and match Groovy/Java?
Then under $MY_GRAILS_APP/src, there are three directories: groovy, gwt and java. The "service" interface and the "async" interface go under which directory? Can I put them in a package? It seems that the server code has to share these with the client code. What are the rules around that?
Are some of these interfaces automatically generated?
@omsrobert "static expose = 'gwt' " simply marks a service as accessible via GWT-RPC. The service itself should implement the client-side service interface (not the async one). You can also append a package after 'gwt', for example 'gwt:org.example.client'. The package just tells the plugin which package the generated service interfaces should go in.
Note that the interface generation is no longer automatic. You need to call "grails generate-gwt-rpc".
The service implementation should be Groovy if it goes in the "grails-app/services" directory. You can put it in a package, and that is definitely recommended. Java code should only go under "src/java".
The service interfaces (async included) go under "src/java". The module and client-only code, i.e. the UI stuff, go under "src/gwt". Anything shared between the server and client should go under "src/java". Any classes can be put in a package.
Currently I'm rewriting the tutorial. More on this soon.
Note: after creating the BookService? (with GWT Application.java still empty), you must run:
$ grails generate-gwt-rpc
Otherwise BookServiceAsync? and stubs will not be created
Was wondering if anyone got the 0.4.1 gwt-plugin working with GWT 1.71 on Snow Leopard? I get: Starting the GWT hosted mode client. ...
Was not sure where to add the flag for the plug-in?Thanks!
Could you confirm this plugin really work ? {gwt-windows-1.7.1 + grails 1.1.1 + gwt 0.4.1} I spend hour on Linux or Windows to tests it. In the past, it was working fine. I've done the tutorial several times but it doesn't work at all. Please, could you update your code in the repository.
Thanks
I'm currently rewriting the tutorial. The code for the new version has been moved to github (http://github.com/derjan1982/grails-gwt-tutorial). The code there runs with GWT 1.7.1 (Mac), Grails 1.1.1 and a current snapshot of the plugin. Since the new version makes use of some 0.5 features of the plugin, it won't be tested with 0.4.1. I hope the next version will be release soon. There is also a Google Group for GWT / Grails topics, which should be your prefered way to ask such questions (http://groups.google.com/group/grails-gwt).
I'm little bit confuse because I've strange settings path error in my Window bug. if you're interested, I could provide you an example of GWT plugin usage. As soon as, my job has been done. Thanks you for your answer, nice job. (same account as victor.benarbia)
"java? You must use a 32-bit Java runtime to run GWT Hosted Mode. java? Leopard: Use the Java 1.5 runtime. java? Snow Leopard: Use the Java 1.6 runtime and add the -d32 flag. java? Java Result: 1"
An issue have been logged for this: http://jira.codehaus.org/browse/GRAILSPLUGINS-1679
I simply modify the /scripts/GwtInternal?.groovy to add the -d32 flag myself as a work around at the moment.
Using SmartGWT with Grails GWT plugin, see : http://code.google.com/p/grails-gwt-smart/wiki/HOWGrailsBecomeSmartGwt http://code.google.com/p/smartgwt/
I followed all the steps in your article. But the application is not running in hosted mode. I need to run the grails compile-gwt-modules then run the gwt-client command to see the result. Is the plugin updated for grails 2.0 and gwt2.4.
Thanks for this guide. It helped me get started using grails 2.0 and gwt 2.4.