At my “day job” at Manheim Auctions we have been using GWT for a while now on “real world” projects. Along the way we have built a lot of tooling for GWT and we are now releasing a big chunk of it as Gwittir (like Glitter with a lisp). There are a number of tools included here, but the real core of it is made up of “lite” version of things you are accustomed to the the larger Java world. Here we will take a look at Beans Binding, and Animation to create a Flickr browser (running example at the link).

I kind of joke that this project is a roll up of a lot of the open source work I have done for the last couple years as it uses ROME and the MediaRSS module and is built with gwt-maven which I have talked about in this space before. You can grab the source for the example here. A long look at the code after the jump.

Most of you, I am sure, are at least somewhat familiar with GWT and what it is. One of the problems with GWT, though, is there are no clear ways to accomplish certain goals. Over the last year at Manheim we have been doing a good bit of GWT work, so we had to scratch our own itch in terms of some tooling. Hence, Gwittir.

Much like GWT starts with the basic Java cross-compiler and builds on top of it to provide a full toolkit, Gwittir starts with a Beans-style introspection system, then includes a lot of things built on top of it. In this post I am going to demonstrate using the Data Binding system and our Animation system. Much like our Introspection system is Beans-lite, these systems are “Lite” versions JSR-295 and Swing Timing Framework. Here I am going to build a Flickr browser with a proxy RPC service.

The first thing we need to do is create our “Bindable” data bean. The Bindable interface extends two others: SourcesPropertyChangeEvents and Introspectable. The first is our marker interface for PropertyChangeSupport and the later tells the compiler plugin to build runtime Introspection data for the bean. Since we are going to use this as a RPC return value, it will also implement IsSerializable.


public class Photo implements Bindable, IsSerializable {
    private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
            this);
    private String thumbnail;
    private String standard;
    private String title;

    public void addPropertyChangeListener(PropertyChangeListener l) {
        this.propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void addPropertyChangeListener(
        String propertyName, PropertyChangeListener l) {
        this.propertyChangeSupport.addPropertyChangeListener(propertyName, l);
    }

    public PropertyChangeListener[] getPropertyChangeListeners() {
        return this.propertyChangeSupport.getPropertyChangeListeners();
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        this.propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void removePropertyChangeListener(
        String propertyName, PropertyChangeListener l) {
        this.propertyChangeSupport.removePropertyChangeListener(
            propertyName, l);
    }

    public String getThumbnail() {
        return thumbnail;
    }

    public void setThumbnail(String thumbnail) {
        this.propertyChangeSupport.firePropertyChange(
            "thumbnail", this.thumbnail, this.thumbnail = thumbnail);
    }

    public String getStandard() {
        return standard;
    }

    public void setStandard(String standard) {
        this.propertyChangeSupport.firePropertyChange(
            "standard", this.standard, this.standard = standard);
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.propertyChangeSupport.firePropertyChange(
            "title", this.title, this.title = title);
    }
}

Next we will build our remote service that returns photos based on a tags query. For this I am using ROME, ROME-Fetcher and the MediaRSS module. I make a call to the Flickr feed service and turn the media:* tags into Photo objects:

public class FlickrServiceServlet  extends RemoteServiceServlet implements FlickrService{

    private FeedFetcherCache feedInfoCache = HashMapFeedInfoCache.getInstance();
    private FeedFetcher feedFetcher = new HttpURLFeedFetcher(feedInfoCache); 

    public List getPhotos(String[] tags) {
        String url = "http://api.flickr.com/services/feeds/photos_public.gne?lang=en-us&format=rss2&";
        if( tags != null ){
            url+="tags=";
            for( int i=0; i < tags.length; i++ ){
                url+=tags[i].trim()+",";
            }
            url+="&";
        }
        try{
            SyndFeed feed = this.feedFetcher.retrieveFeed(new URL( url ) );
            List photos = new ArrayList();
            for( SyndEntry entry : (List<SyndEntry>) feed.getEntries() ){
                Photo photo = new Photo();
                photo.setTitle(entry.getTitle());
                MediaModule media = (MediaModule) entry.getModule(MediaModule.URI);
                String originalUrl = media.getMetadata().getThumbnail()[0].getUrl().toString();
                String baseUrl = originalUrl.substring(0, originalUrl.length() -6);
                photo.setThumbnail(baseUrl+"_s.jpg");
                photo.setStandard(baseUrl+".jpg");
                photos.add( photo );
            }
            return photos;
        } catch (IOException e){
            throw new RuntimeException(e);
        } catch( FeedException e){
            throw new RuntimeException(e);
        } catch( FetcherException e){
            throw new RuntimeException(e);
        }
    }

}

That is pretty easy to. The next “prefab” step is to create another Bindable bean that encapsulates this service. This will simply watch for changes to the “tags” property and refresh the “photos” property asynchronously. This one doesn’t have to be serializable, since it is a client-side class.

public class FlickrSearchBean implements Bindable {
    private static final FlickrServiceAsync SERVICE = (FlickrServiceAsync) GWT.create( FlickrService.class );
    static {
        ((ServiceDefTarget) SERVICE).setServiceEntryPoint( GWT.getModuleBaseURL()+"FlickrService");
    }
    private PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
    private String[] tags;
    private List photos;
    private AsyncCallback callback = new AsyncCallback(){

        public void onFailure(Throwable t) {
            throw new RuntimeException( t );
        }

        public void onSuccess(Object result) {
            setPhotos( (List) result );
        }

    };

    public FlickrSearchBean(){
        SERVICE.getPhotos( null, callback);
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        this.propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void addPropertyChangeListener(
        String propertyName, PropertyChangeListener l) {
        this.propertyChangeSupport.addPropertyChangeListener(propertyName, l);
    }

    public PropertyChangeListener[] getPropertyChangeListeners() {
        return this.propertyChangeSupport.getPropertyChangeListeners();
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        this.propertyChangeSupport.removePropertyChangeListener(l);
    }

    public void removePropertyChangeListener(
        String propertyName, PropertyChangeListener l) {
        this.propertyChangeSupport.removePropertyChangeListener(
            propertyName, l);
    }

    public String[] getTags() {
        return tags;
    }

    public void setTags(String[] tags) {
        this.propertyChangeSupport.firePropertyChange("tags", this.tags, this.tags = tags);
        SERVICE.getPhotos(tags, callback);
    }

    public List getPhotos() {
        return photos;
    }

    public void setPhotos(List photos) {
        this.propertyChangeSupport.firePropertyChange("photos", this.photos, this.photos = photos);
    }

OK. The Beginnings are out of the way. Now lets look at building our UI and creating the bindings. I’m going to go through the EntryPoint code bit by bit so I can talk about it a little. First we create the Widgets we need. Here we are using Gwittir widgets and GWT panels. The Gwittir Widgets implement the BoundWidget interface, which is Bindable, but also exposes some standard methods. We will talk about these in just a moment. First lets look at the special widgets we are using that aren’t part of the core GWT.

public class FlickrEntryPoint implements EntryPoint {

    public void onModuleLoad() {
        TextBox box = new TextBox(false);
        Label label = new Label("Comma separated tags:");
        HorizontalPanel tags = new HorizontalPanel();
        tags.add( label );
        tags.add( box );
        ReflectedImageGroup group = new ReflectedImageGroup(75, 75, .2, .5);
        SoftScrollArea ssa = new SoftScrollArea();
        ssa.setWidth("800px");
        ssa.setHeight("150px");
        ssa.setWidget( group );
        ssa.addMouseListener( ssa.MOUSE_MOVE_SCROLL_LISTENER );

        Image larger = new Image();

        VerticalPanel vp = new VerticalPanel();
        vp.add( tags );
        vp.add( ssa );
        vp.add( larger );
        vp.setHorizontalAlignment( HasHorizontalAlignment.ALIGN_CENTER );
        vp.setWidth("100%");
        RootPanel.get().add(vp);
       

The ReflectedImageGroup is what gives us the tray of reflected images. Because of problems with cross-browser support, you have to give it dimensions when you build it. Those numbers in the constructor are “width, height, height of reflection, beginOpacityOfReflection.” Next we create the SoftScrollArea. This is a positioned element inside an overflow:hidden element, so we have to give it a known size for it to work right. The MOUSE_MOVE_SCROLL_LISTENER is a built in listener that does a Sinoidal scroll across the area based on mouse position. Finally we create the larger version of the image, put the whole thing in a VerticalPanel and place it in the Root.

The next feature demonstrated is the Renderer. All BoundWidgets support a Renderer. These allow you to get around what I consider the annoying bit of Swing where .toString() is used to render an object into whatever the widget needs to draw. Here I am going to replace the default ToStringRenderer with Renderers that will display the thumnail in the ReflectedImageGroup and the larger version in the Image instance:

       group.setRenderer(
            new Renderer() {
                public Object render(Object o) {
                    return ((Photo) o).getThumbnail();
                }
            });

        larger.setRenderer(
            new Renderer() {
                public Object render(Object o) {
                    return ((Photo) o).getStandard();
                }
            });    

This is doing a String adaptation, but it doesn’t have to be a Stirng, just whatever the BoundWidgets “value” property wants to support. You will note there is a lot of use of Object here. This is a failing of Gwittir right now, but we are waiting on Generics support in GWT to clean up the type safety on these.

Next we are going to set up bindings. Bindings have several features. This example doesn’t cover validation, but you can see Converters. Converters allow you to adapt values between two properties that are of dissimilar types. Here we have the String value of the TextBox and the String[] value of the tags on the FlickrSearchBean.

        FlickrSearchBean search = new FlickrSearchBean();
        Binding images = new Binding(group, "value", search, "photos");

        images.getChildren().add(
            new Binding(
                box, "value",
                new Converter() {
                public Object convert(Object original) {
                    if (original == null) {
                        return original;
                    } else {
                        return original.toString().split(",");
                    }
                }
            }, search, "tags",
                new Converter() {
                public Object convert(Object original) {
                    if (original == null) {
                        return original;
                    } else {
                        String[] strings = (String[]) original;
                        StringBuffer ret = new StringBuffer();

                        for (int i = 0; i < strings.length; i++) {
                            ret.append(strings[i]);

                            if (i < (strings.length - 1)) {
                                ret.append(",");
                            }
                        }

                        return ret.toString();
                    }
                }
            }));

At the top of this, you see the simplest form of a Binding. We are binding the “value” property of the ReflectedImageGroup to the “photos” property on the FlickrSearchBean. A lot of times this will be all you need, but there is a lot more there. Below that we add a child binding to that one. Children can be added and they will cascade from their parent. Here we create a binding with two converters. On the TextBox we use .split() to turn the String into Stirng[] and on the tags property we go the other way, building a comma delimited String value. This lets us bind the dissimilar types. There is one more step in the binding process which we will get to after the next listing. Fist, though, lets look at using the PropertyAnimator.


final OpacityWrapper largerOpacity = new OpacityWrapper( larger );
        largerOpacity.setOpacity(new Double(0.0));

        final PropertyAnimator animator = new PropertyAnimator( largerOpacity, "opacity",
                new Double(0.0), new Double(1.0),
                MutationStrategy.DOUBLE_CUBIC, 1000 );
        group.addPropertyChangeListener("selected", new PropertyChangeListener(){

            public void propertyChange(PropertyChangeEvent arg0) {
                largerOpacity.setOpacity(new Double(0.0));
            }

        });
        larger.addLoadListener( new LoadListener(){

            public void onError(Widget arg0) {
                largerOpacity.setOpacity( new Double(0.0) );
            }

            public void onLoad(Widget arg0) {
                animator.start();
            }

        });

Since UIObjects in GWT don’t support Opacity (because it is a cross-browser issue) we are creating an OpacityWrapper around the Image object. The Gwittir Opacity wrapper works in all the browsers, adapting the Opacity to the proper DirectX Filter in MSIE. First we add a change listener on “selected” on the ReflectedImageGroup to hide the image while we get the next one. Then on the Image class we add a LoadListener that invokes the PropertyAnimator.

Here we are creating an animator with a target object, the property to change, the start value, the end value, a MutationStrategy implementation, and the number of millisections the animation to take to complete. There are several default implementations buildled. Here I am using a cubit transform on a Double value.

The final step is to initialize the values and perform the bind.

        images.setLeft();
        images.getChildren().add( new Binding(larger, "value", group, "selected") );
        images.bind();

The setLeft() method tells the Binding to take all the values from the “left” objects, the first one in the constructor and set their values to those of the “right” objects. The convention we use is the “left” object is always the view for standard stuff, so .setLeft() is usually called. There is a .setRight() method available, however. Since the Image can’t have a “null” as its value, we wait to add it to the parent Binding until after setLeft. It will get changes from the “selected” property of the ReflectedImageGroup, but these will won’t be nulls when a selection is made. Finally we call bind.

In a larger application, you will also want to use .unbind(). This cleans up all the listeners on all the objects that the Binding has created, simplifying your cleanup code. In this one “screen” state application, we don’t really need that. More information about how to use a BindingAction for lifecycle management is available on the Wiki.

Hopefully this gives you a good taste of development with Gwittir. There are a lot more features which you can read about on the wiki, and a few big ones that we haven’t finished fully documenting. I hope you find this useful.