Integrating an existing GWT widget

Integrating an existing, third party GWT widget usually just involves creating a regular Vaadin component with a client-side connector that uses the third-party widget, probably using a shared state, and possibly also with RPC - just as described in separate articles on these topics. Usually, the only addition is the need to modify your widgetset declaration to inherit the third-party widget’s GWT module.

In the following, we’ll integrate the SimplePlot widget from the GFlot library (which in turn is a GWT adaptation of the pure JavaScript plotting library Flot) to create a simple line plot component. We’ll start with modifying our widgetset’s gwt.xml to inherit the GFlot GWT module, so if you’re familiar with the rest of the process, you can basically stop once that is done.

But first a note on package structure: this particular example uses the com.example package domain, and is set up to be a add-on project, with the actual component in the addon package, and the demo that uses the component in the vaadingflot package. The addon package contains the widgetset gwt.xml definition, the server-side component (LinePlot), as well as all the code related to the client-side in the client.ui subpackage.

Once you have a working project, go ahead and download the GFlot jar, and add it to WEB-INF/lib, then update the widgetset gwt.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 1.7.0//EN"
         "http://google-web-toolkit.googlecode.com/svn/tags/1.7.0/distro-source/core/src/gwt-module.dtd">
<module>
  <inherits name="com.vaadin.DefaultWidgetSet" />
  <inherits name="ca.nanometrics.gflot.GFlot" />
</module>

It inherits the default Vaadin widgetset as well as the GFlot GWT module.

Now we’re ready to integrate the SimplePlot widget from the GFlot package. Since we’re familiar with the GFlot API, we know we want to add 'series' to the plot - we’ll create a shared state for this purpose, and a DataSeries class to represent the actual series within the state:

package com.example.addon.client.ui;

import java.util.ArrayList;
import java.util.List;

import com.vaadin.client.ComponentState;

public class LinePlotState extends AbstractComponentState {

  public List<DataSeries> series = new ArrayList<DataSeries>();

  public static class DataSeries {
    public String label;
    public String color;
    public List<Float> data;
  }
}

Lets make the server-side component next:

package com.example.addon;

import java.util.Arrays;

import com.example.addon.client.ui.LinePlotState;
import com.example.addon.client.ui.LinePlotState.DataSeries;
import com.vaadin.ui.AbstractComponent;

public class LinePlot extends AbstractComponent {

  public LinePlot() {
  }

  @Override
  public LinePlotState getState() {
    return (LinePlotState) super.getState();
  }

  public void addSeries(String label, String color, Float[] fs) {
    DataSeries ds = new DataSeries();
    ds.label = label;
    ds.color = color;
    ds.data = Arrays.asList(fs);
    getState().series.add(ds);
  }
}

We override getState() in order to narrow the return type to our own LinePlotState, and then implement a simple addSeries() that creates a DataSeries instance and adds it to the state. The state will be automatically transmitted to the client when needed, so the plots will remain intact over browser reloads for instance.The API for our component could obviously be expanded, but lets leave it like this for this example.

Since the GWT widget we’re going to use is already made for us (in the GFlot library), the only thing left for us to do is implement the client-side connector:

package com.example.addon.client.ui;

import ca.nanometrics.gflot.client.DataPoint;
import ca.nanometrics.gflot.client.SeriesHandler;
import ca.nanometrics.gflot.client.SimplePlot;

import com.example.addon.LinePlot;
import com.example.addon.client.ui.LinePlotState.DataSeries;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.communication.StateChangeEvent;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.client.ui.Connect;

@Connect(LinePlot.class)
public class LinePlotConnector extends AbstractComponentConnector {

  @Override
  public LinePlotState getState() {
    return (LinePlotState) super.getState();
  }

  @Override
  public SimplePlot getWidget() {
    return (SimplePlot) super.getWidget();
  }

  @Override
  protected Widget createWidget() {
    return GWT.create(SimplePlot.class);
  }

  @Override
  public void onStateChanged(StateChangeEvent stateChangeEvent) {
    super.onStateChanged(stateChangeEvent);

    getWidget().getModel().clear();
    for (DataSeries ds : getState().series) {

      SeriesHandler s = getWidget().getModel().addSeries(ds.label,
              ds.color);
      for (int i = 0; i < ds.data.size(); i++) {
        s.add(new DataPoint(i, ds.data.get(i)));
      }
    }
    getWidget().redraw();
  }
}

We override both getState() and getWidget() to narrow the return type to our liking, then make createWidget() return an instance of the GFlot widget we’re going to use, SimplePlot.

Last, we override onStateChange() which is called whenever the shared state has been changed. Here we make use of the SimplePlot API to add the series contained in the shared state (for simplicity, we clear the SimplePlot first, then add all the series in our state).

That’s it! The full source is available as an attachment to this article.