GWT widgets are the visible elements of a GWT application and so it's quite important to understand how they work. In a previous post I explained the rendering process but here we get a little less abstract by actually creating a simple GWT component. On the way we'll also look at using Java 1.5 Enums to great effect in order to reduce the amount of runtime errors our browser app may have.
I've called the component we're going to build the HtmlList, and as it's name might suggest it's simply displays a list (ordered, or unordered), in the browser. List items will have actions associated and will run these actions when the item is click. Finally list items will highlight as the mouse pointer moves over them.
Before we start, I'm making the assumption that you've got a GWT application running inside Eclipse. If you haven't, then simply use the GWT projectCreator and applicationCreator commands to create it, (my test application is called com.maddison.testapp.client.TestApp).
Creating the module
Since our HtmlList is going to be the next GWT killer component we obviously want to package it in a way which people can use it in their own applications, to do this we need to package it in a module. Modules are one of the building blocks of GWT applications, you can think of them in much the same way a library of classes. To create the module we do the following:
- Create a standard Java package that will hold our module, I'm going to call mine com.maddison.killerwidgets
- Create the client package which will hold the actual GWT code, this will be the same package but with client on the end, for example, mines called com.maddison.killerwidgets.client
- Now we can add the module descriptor which is a simple XML file that tells the GWT compiler all about our module.. Create a standard text file called KillerWidgets.gwt.xml in the com.maddison.killerwidgets package. For now use the following simple module definition, we'll be adding more to it later:
<module> <inherits name='com.google.gwt.user.User'/> </module>
To recap, at the end of these steps, you should have a package structure that looks like so:
Creating the basic Widget
All GWT widgets are decended from com.google.gwt.user.client.ui and therefore our component class needs to extend this. Create a class in com.maddison.killerwidgets.client called HtmlList that extends com.google.gwt.user.client.ui.Widget. This will create possibly the dullest widget in the world since nothing will actually rendered.
Since all GWT widgets are simply Java classes, the first thing we need to do is give HtmlList a constructor. Remember that one of our requirements was the list could be rendered as either an ordered list (OL element), or an un-ordered list (UL element) and thus we need some way to tell the component which it's going to be. A simple way would be to pass in a boolean that if true creates an ordered list and if false, creates an unordered list .i.e. :
public HtmlList(boolean orderedList) { }
This approach works, but it's not nice for two reasons:
- A user of the widget only sees that the constructor takes a boolean and then must understand what the boolean means. A call to this constructor would be new HtmlList(true), which isn't very clear to anybody who's never seen our fantastic component before
- The other is one of extendibility. Once our component is popular we may want to release version 2.0 which allows a list to be created on say a SPAN element. If we've used a boolean we only have two possible values and the interface to our component would have to be modified, breaking any existing code
The power of the Enum
Our HtmlList will use a Java 1.5 Enum (Enumerated Type) as the parameter to the constructor to indicate the type of list required. A simple Enum would look something like this:
public enum ListType { UNORDERED, ORDERED }
Resulting in a constructor that looks as follows:
public HtmlList(ListType listType) { switch(listType){ case UNORDERED: // Create UL Element break; case ORDERED: // Create OL Element break; } }
That's better since a client would look something like new HtmlList(ListType.ORDERED) and a code reviewer can tell what type of list would be created without having to resort to JavaDocs; it's better, it can still be improved. Lets say for example in version 2.0 I do add the ability to host a list inside a SPAN element. I add the type to the ListType but in my rush to get Version 2.0 out the door I forget to update the constructor. There's no information to tell the compiler a switch case is missing and so it'll quite happly compile the Application and we ship it (although hopefully your Testing/QA proceedures are a bit firmer than that!)
What we want to do is force a developer to HAVE to add element creation if they update the Enum. This can be achieved by extending the ListType enumerated type to have methods as in the following code:
public static enum ListType { UNORDERED { public Element createElement() { return Document.get().createULElement(); } }, ORDERED { public Element createElement() { return Document.get().createULElement(); } }; public abstract Element createElement(); }
Most people are pretty comfortable with the basic Enum of values but probably haven't seen this approach before, which is a shame. Java 1.5 Enumerated types aren't just replacements for static Integers, they're fully blown classes in their own right. What the above Enum says is that each Enum value MUST have (because of the abstract keyword) a createElement method which returns an Element. The constructor for this now looks as follows.
public HtmlList(ListType listType) { setElement(listType.createElement()); }
This is now 100% safe, the compiler has the information to know that an Enum value must have a createElement and so it just won't compile the application if it's missing. Catching errors like this at compile time is much better than catching them later on in the development cycle, and quite a few of the improvements in Java 1.5 were designed around improving compile time checking.
If using Enums in this way is new to you, then I would certainly urge you to look at the Sun description of Enums, and if you have a copy of Joshua Blocs Effective Java (second edition) - and if not, why not? every Java developer SHOULD own a copy - check out Chapter 6 where he explains how these Enum types can be used to even greater effect.
Whilst this Enum stuff isn't something that's GWT specific, it does show the power of using GWT over straight JavaScript. Since Java is a statically typed language we get extra help from the compiler in ways that just wouldn't be possible with a JavaScript compiler.
After that digression, it's time update our component with the new Enum so that it looks as follows:
public class HtmlList extends Widget { public static enum ListType { UNORDERED { public Element createElement() { return Document.get().createULElement(); } }, ORDERED { public Element createElement() { return Document.get().createULElement(); } }; public abstract Element createElement(); } public HtmlList(ListType listType) { setElement(listType.createElement()); } }
There are two things in this code that need explaining. The first is the createElement method that uses the GWT Document object, which is really GWT's abstraction layer over the browser HTML DOM. The second is the setElement method. This method is important as it tells GWT what element should represent this component in the HTML DOM. This method MUST be called when the Widget is created.
Adding Items
At this point we have a component that's created either an OL or UL element, not very exciting so lets add some list items. Our original requirments for list items was that they perform some action when clicked. There are two ways we could model this, one is to fire an event when an item is clicked, and let the component client respond to the event. The other way is to pass in a GWT Command object when adding the item, and call the Command.execute method when the item is clicked. Both of these are a matter of taste, but for simplicity we'll use the Command object pattern; which means adding the following addItem method to HtmlList:
public void addItem(String text, Command command) { LIElement liElement = Document.get().createLIElement(); liElement.setInnerText(text); getElement().appendChild(liElement); listItems.put(liElement, command); sinkEvents(Event.ONCLICK); }
Now things start to get a little juicy. The first line of this method creates the HTML LI element we've already seen this Document object at work previously. The second line sets the text of this LI element and the third line retrieves the element of this component (the element passed to setElement), and appends the LI element to it.
The next line stores the Command object away for later retrieval in a Map called listItems. We currently don't have this map defined, so add it to the top of the class as follows :
public class HtmlList extends Widget { private final MaplistItems = new HashMap ();
Java Tip: Although this is a HashMap implementation we're using, the actual field type is the generic interface Map. It's important to use the generic interface so that the implementation can be changed at a later date; I see a quite a bit of code where the actual implementation class is used as the field type!
The Map will be keyed on the actual LIElement which is safe to do because the GWT Element method contains an implementation of equals and hashCode
Meanwhile back in the addItem method we come to the final line sinkEvents(Event.ONCLICK). When out GWT Widget is created, it simply represents a normal HTML list, it won't receive any events. If this were a JavaScript object we would need to add the onClick event handler, and this is what the sinkEvents method is doing, it's telling GWT that we would like to know of any browser onClick events that happen on our (OL or UL) element.
Handling the event
When an event that we've declared in our sinkEvents happens, GWT will call the onBrowser event method, so we need to add the following method to HtmlList :
@Override public void onBrowserEvent(Event event) { switch(event.getTypeInt()) { case Event.ONCLICK: Element target = event.getTarget(); if (listItems.containsKey(target)) DeferredCommand.addCommand(listItems.get(target)); break; } }
Java Tip:Always use the @Override annotation, this is information to the compiler that this method should override the Widget.onBrowserEvent which provides extra help from the compiler against such nasty things as typing errors (which I appear to be prone too!).
The event object contains the getTypeInt method which informs us of the event that's happened. At the moment you'll notice that I'm handingly this in a Switch statement, this is because, not only is it easier to read and maintain (as apposed to using a large if-else if block), but we're also going to be adding more events to this later. If the event is a CLICK, we retrieve the target element and check to see if we have this elemented keyed in our command Map. Don't forget that events in Javascript (and thus GWT), bubble up so parent's will receive event notifications on child elements, in this case the LI element.
DeferredCommand
Once we've retrieved the Command object we add it to the special GWT DeferredCommand object which needs a little further explaination. It must be remembered that JavaScript is a single threaded language, if some event code is running all other code is blocked from running until it's finished. Now obviously we have no idea what Command the client is going to perform in the passed in Command object, it could be a short operation, or it could be a long running operation. If it's a long running operation and we simply called Command.execute() we wouldn't be able to return from the onBrowserEvent until after this operation has finished, and therefore we'd really lock up the UI. The DeferredCommand object tip toes around this problem by calling the Command.execute() method after 1ms which means our onBrowserEvent can return and later on the Command will be called back to use the JavaScript thread. Remembering that JavaScript is single threaded is VERY important and easily forgotten when your actually coding in a multi-threaded language such as Java. Further information on using DefferedCommand and other related objects can be found in the Google GWT help.
A quick test
At this point we should have a component that displays a list and runs a command when something is clicked. Before we go on and add more bells and whistles, it would be good to check that this widget works so far. To do this we'll just add it to the test application that the GWT applicationCreator.cmd command created, in my case that's TestApp and now looks like this:
public void onModuleLoad() { HtmlList list = new HtmlList(ListType.ORDERED); list.addItem("Test 1", new Command() { public void execute() { Window.alert("You Selected Test 1"); } }); list.addItem("Test 2", new Command() { public void execute() { Window.alert("You Selected Test 2"); } }); RootPanel.get().add(list); }
This should be self explainatary, but really we're just creating an instance of an Ordered HtmlList and adding two items, both of which display an alert box if the item is clicked. Finally the HtmlList is added to the Body element of the browsers HTML document. Although this will compile inside Eclipse it won't compile with the GWTCompiler. That's because HtmlList is in another module (KillerWidgets), and thus we need to import it into our TestApp module; this is done in the TestApp.gwt.xml module definition. The following shows the KillerWidgets module being imported, the bold line is the one which was added :
<!-- Other module inherits --> <inherits name="com.maddison.killerwidgets.KillerWidgets"/> <!-- Specify the app entry point class. --> <entry-point class='com.maddison.testapp.client.TestApp'/>
Now the application is complete and we can use the GWT created TestApp.launch file to start it; simply right click on it and choose RunAs TestApp. The application as it stands so far is given here, clicking on a link displays the alert message:
Adding some Style
As you've noticed, when the cursor is moved over the list items it's still a text cursor, but we'll try and change that by injecting some style. Regardless of how fantastic GWT is, the platform it's deploying to is still at the end of the day Javascript+HTML+CSS, and therefore all GWT components use CSS rules in order to dictate how they should appear in the browser. In GWT stylesheets etc are all 'deployed' from the public directory of the module so lets add that now, by creating a directory and ListStyle.css file so that the package structure looks as follows:
And the contents of the ListStyle.css should look as follows:
.html-list { list-style:inside; width:100px; } .html-list li { cursor: hand; }
Here any LI elements that are children of an element of class .html-list will have a hand cursor. In order that this style sheet stays with our module we need to update the KillerWidgets module definition to include the stylesheet element, i.e:
<module> <inherits name='com.google.gwt.user.User'/> <stylesheet src='ListStyle.css' /> </module>
And next we need to hook up the .html-list style to our component which is done using the setStylePrimaryName method of the GWT widget class. This makes our HtmlList constructor look as follows:
public HtmlList(ListType listType) { setElement(listType.createElement()); setStylePrimaryName("html-list"); }
And when compiled gives us the following much more exciting application:
Adding further style
The last requirement of our component is that it should highlight the element the cursor is over. We'll implement this by applying a particular style whe onMouseOver is fired and remove this style when onMouseOut is fired. For this to work we obviously need a style, so I've added the following class to ListStyle.css:
.highlightOn { background-color:#cccccc; }
Next we have to tell GWT that we're interested in further events and we do that by updating the sinkEvents line so that it looks as follows:
sinkEvents(Event.ONMOUSEOVER | Event.ONMOUSEOUT | Event.ONCLICK);
And then extra cases to the onBrowserEvent switch statement to handle these new events:
case Event.ONMOUSEOUT: event.getTarget().setClassName(null); break; case Event.ONMOUSEOVER: event.getTarget().setClassName("highlightOn"); break;
This should be self explanitary, but really when an LI element triggers the ONMOUSEOVER event, it has the CSS style class highlighOn set in it's class attribute . Setting the className to null removes the style and resets the element back to what it was before
Finally compiling the test application gives the following:
Conclusion
This post has turned out to be a LOT bigger than I had planned, but hopefully it's given you a taste of how simple it is to build a GWT component; certainly this is just the tip of the iceberg
The full code for this post can be downloaded here
11 comments:
Hi David,
This is by far the most helpful custom-widget tutorial I have seen. +1 for getting the top PageRank ;)
Cheers
Mike
GREAT post.
btw, in the enum in the ORDERED type it
should be createOLElement.
Great entry! I'm not sure about the java tip, to use the generic interface Map is true. Because the compiler can optimize more if you actually create it as an HashMap. But in the end that's a design decision.
For the highlighting you should use addDependentStyleName - this is what it's for.
Great great great tutorial!
I learned a lot!
It would be even more great if you have some time to upgrade it gwt 2.0.
It seems that event.getTarget() is deprecated now and can't find out how to implement the suggested alternative event.getRelativeTarget()..
Could you give a hand?
Thanks!
Can you explain how to use the component in a new project?
How do I create the jar?
How to configure the build path?
Thanks Giancarlo
Thanks, very helpful and well written.
Thanks for this simple and explanatory tutorial. Knowing something is important and being able to teach is more important..
Thanks a lot..
David, thanks for taking the time to write such a helpful tutorial.
Very good post. Thanks
Thank you so much David for this wonderful article !!!
Post a Comment