Introduction
GWT 2 has introduced the concept of declarative interfaces, that is, the interface can be described via an XML document, rather than built using code. The idea isn't new, it's seen in many new technologies such as Macromedia Flex, ASP.Net and JavaFX, however to the GWT developer it means the end of writing reams of boiler plate code.
In this post I show you how to create a simple Image Gallery widget that has a declarative interface and then how to create bigger interfaces using these widgets.
The Basic Widget
Before we do anything, we need to define the basic ImageGalleryWidget, in our case, it looks as so :
public class ImageGalleryWidget extends Composite { private GalleryImage[] images; private int currentImageIndex = 0; public ImageGalleryWidget() { } public void setTitle(String value) { } public void setImages(GalleryImage[] images) { this.images = images; currentImageIndex = 0; } public static class GalleryImage { private final String caption; private final String url; public GalleryImage(String url, String caption) { this.caption = caption; this.url = url; } public String getUrl() { return url; } public String getCaption() { return caption; } } }
As you can see, our ImageGallery has two properties: a title, and an array of images to display. Each image has the URL of the actual file, and a caption. You'll also notice that our widget doesn't extend Widget but rather Composite, this is important as we'll see later.
Defining the Interface
GWT allows you to use the declerative method to define interfaces of individual components, in this case the interface for a particular widget. Before we go any further, it would be a good idea to show the UI for our image gallery:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'> <g:VerticalPanel spacing="3"> <g:Cell horizontalAlign="CENTER" cellSpacing="40" width="100%"> <g:Label ui:field="title"/> </g:Cell> <g:Cell> <g:Image ui:field="image"/> </g:Cell> <g:Cell width="500px"> <g:HorizontalPanel spacing="5" width="100%"> <g:Cell> <g:Button text="Prev" ui:field="btnPrev"/> </g:Cell> <g:Cell> <g:Label ui:field="caption"/> </g:Cell> <g:Cell> <g:Button text="Next" ui:field="btnNext"/> </g:Cell> </g:HorizontalPanel> </g:Cell> </g:VerticalPanel> </ui:UiBinder>
First lets look at the namespace declarations. The first binds the prefix ui to urn:ui:com.google.gwt.uibinder which identifies GWT specific elements to the parser such as the UiBinder element; it will be required on every declared interface file. The next namespace urn:import:com.google.gwt.user.client.ui imports elements from the com.google.gwt.user.client.ui package, (the standard GWT Widget package) to be used in the interface. You can see some of these objects in use such as g:VerticalPanel. We'll come back to imported elements later.
Inside the UiBinder element you define your interface by specifying widgets to be instantiated. In this case we're creating a standard VerticalPanel widget with Cells for each row. In the top cell we're adding a label, the next we're adding an Image, and the bottom cell holds a HorizontalPanel which contains the Prev and Next buttons. As you'll appreciate, this interface file is a lot easier to read and modify than if it had been defined in standard Java.
Certain elements in our file have a special ui:field attribute defined. This is a special declarative attribute which tells GWT which field within the associated Java class (we'll get to that shortly), should hold a reference to that component.
Our widget is called ImageGalleryWidget and therefore the declarative XML file should be saved in the same package with the name ie. ImageGalleryWidget.ui.xml
Binding To A Java Class
The XML defines the UI, however a UI doesn't exist by itself, we need a way of referencing elements so that we can set and retrieve properties. In order to do this we need to add elements to the ImageGalleryWidget :
public class ImageGalleryWidget extends Composite { interface ImageGalleryUiBinder extends UiBinder<VerticalPanel, ImageGalleryWidget> {} private static ImageGalleryUiBinder uiBinder = GWT.create(ImageGalleryUiBinder.class); @UiField Image image; @UiField Label caption, title; @UiField Button btnNext, btnPrev; // .... Rest Ignored ... }
The first line defines an interface which will act as the binder, that is, it will set the field elements annotated by UiField. An implementation of this interface will be created by the GWT compiler. The second line is a standard GWT deferred binding create statement need to instantiate the generated binder class.
The next thing we need to do is perform the binding magic to apply values to our UiFields. This is done using the binder as so :
public ImageGalleryWidget() { initWidget(uiBinder.createAndBindUi(this)); }
Here, the binder instance, created using deferred binding, is told to bind the interface file elements to the UiField elements in this class. The return from this call is a VerticalPanel, which if you remember, is the root panel from our UI XML. If this was a standard Widget, we'd only be able to set an Element, however, because this is a Composite Widget, we're able to set a child widget using initWidget method.
NOTE: The name of the field in the class being bound to MUST be exactly the same as the ui:field entry, including case.
Now we have UI element bindings, we can treat this as if the UI had been created using the standard Java approach. Here, for example, are the setTitle and setImages methods :
public void setTitle(String value) { title.setText(value); } public void setImages(GalleryImage[] images) { this.images = images; currentImageIndex = 0; displayImage(); } private void displayImage() { image.setUrl(images[currentImageIndex].getUrl()); image.setTitle(images[currentImageIndex].getCaption()); caption.setText(images[currentImageIndex].getCaption()); }
Adding Event Handlers
As it stands, our image gallery would simply display the first image in the array, what we need to do now is listen for Click events on the Prev and Next buttons in order to display other images.
In standard GWT we'd have to add anonymous ClickHandlers, but with the declarative interface we can use features of the generated UiBinder to do the work for us. For example, here's the click handlers for the buttons:
@UiHandler("btnPrev") public void handlePrev(ClickEvent event) { if (currentImageIndex != 0) { currentImageIndex--; displayImage(); } } @UiHandler("btnNext") public void handleNext(ClickEvent event) { if (currentImageIndex != images.length -1) { currentImageIndex++; displayImage(); } }
In both cases, the UiHandler annotation tells GWT which UiField field this handler is for. The name of the method itself doesn't matter, it can be anything you like. In order to set the handler, GWT will look at the event and discover the name of the Handler class. It will then look for a method call add
A Quick Test
Before we go any further, it would be good to test this Widget. Here's an example of using the Widget, using photos from Flickr
public class UIBinderExample implements EntryPoint { private static final GalleryImage[] images = { new GalleryImage("http://farm4.static.flickr.com/3309/3622157565_fd079ac983.jpg","Bishop's Moat"), new GalleryImage("http://farm2.static.flickr.com/1257/928632149_71d88ac137.jpg","Family Of Goats"), new GalleryImage("http://farm1.static.flickr.com/61/193636096_1f34d7a78d.jpg","Wooden Boat - Sailing - Port Townsend")}; public void onModuleLoad() { ImageGalleryWidget widget = new ImageGalleryWidget(); widget.setImages(images); widget.setTitle("Sample Flickr Gallery"); RootPanel.get().add(widget); } }
Adding Style
At the moment the widget doesn't look very good; let's improve that by adding some CSS styles. To do this we first add a ui:Style element to our UI definition.
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'> <ui:style> .caption {font-weight:bold;width:100%} .button {color:red} .image {width:375px; height:500px} </ui:style> <!-- Rest Ignored --> </ui:UiBinder>
We can then use those styles by setting the styleName property of the relevant elements, ie :
<g:Cell> <g:Image styleName="{style.image}" ui:field="image"/> </g:Cell>
Reusing this Widget
GWT allows you to build up your UI using different definition files. What we'll do now is reuse this initial Widget in a new UI that is itself declared using XML. In order to do this we'll define a UI that uses two versions of this Image Gallery, and looks like so
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:d='urn:import:com.maddison.client.widgets'> <g:HorizontalPanel spacing="5" width="100%"> <g:Cell> <d:ImageGalleryWidget title="Gallery One" ui:field="galleryOne"/> </g:Cell> <g:Cell> <d:ImageGalleryWidget title="Gallery Two" ui:field="galleryTwo"/> </g:Cell> </g:HorizontalPanel> </ui:UiBinder>
The important part of this file is the urn:import:com.maddison.client.widgets namespace declaration. This states that widgets marked with the d namespace can be found in the com.maddison.client.widgets package. Properties on this Widget (for example the title) are set by calling a setXXX method on the given widget.
To show another feature of the declarive API, lets save this file as MainAppInterface.ui.xml. Our application will be defined in a Composite widget that looks like so :
public class MyApp extends Composite { @UiTemplate("MainAppInterface.ui.xml") interface MyAppUiBinder extends UiBinder<HorizontalPanel, MyApp> {} private static MyAppUiBinder uiBinder = GWT.create(MyAppUiBinder.class); private static final GalleryImage[] images = { new GalleryImage("http://farm4.static.flickr.com/3309/3622157565_fd079ac983.jpg","Bishop's Moat"), new GalleryImage("http://farm2.static.flickr.com/1257/928632149_71d88ac137.jpg","Family Of Goats"), new GalleryImage("http://farm1.static.flickr.com/61/193636096_1f34d7a78d.jpg","Wooden Boat - Sailing - Port Townsend")}; @UiField ImageGalleryWidget galleryOne, galleryTwo; public MyApp() { initWidget(uiBinder.createAndBindUi(this)); galleryOne.setImages(images); galleryTwo.setImages(images); } }
You'll notice the UiTemplate annotation above the MyAppUiBinder interface. Normally GWT will look for a UI definition file that has the same name as the parent class, however, in this case we've told GWT that the interface file is actually called MainAppInterface.ui.xml by using the annotation.
Final Test
Our final application simply has to instantiate the MyApp interface like so:
public class UIBinderExample implements EntryPoint { public void onModuleLoad() { RootPanel.get().add(new MyApp()); } }
Conclusion
Hopefully this post has shown how to use some features of the GWT declarative UI, by no means does it show everything that's possible!
A complete Eclipse project with all the code from this post can be found here