Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GWT Presentation Layer: Who does what?

I'm learning GWT and trying to wrap my head around all the UI options. I'm having trying to make sense of when/where/how to use Widgets, UIBinder, GWT Designer and custom Widgets. Specifically:

  • What does GWT Designer generate as output? UIBinder XML? Is it safe to say that GWT Designer can be used when you don't want to hand-code UIBinder XML, but that they both serve the same exact purpose?
  • When to use UIBinder over Widgets? Is it that Widgets get translated into UIBinder XML, but have a lot of more code (event-handling, etc.) in them? In that case, I would assume the advantage of UIBinder XML is less code and thus faster performance? Any other factors that should be considered when choosing between the two?
  • Do you write lots of UIBinder XML "snippets" or just pack them all into one big monolithic XML file?
  • For making my own custom components, what's the difference between extending com.google.gwt.user.client.ui.* vs. com.google.gwt.user.client.ui.Composite?
  • Division of labor: Widgets/UIBinder vs. Layouts vs CSS files: who does what?

To me, I feel like all of these things do the same stuff, and perhaps this is just GWT's way of providing you with multiple ways to accomplish presentation? If not, please correct me on these items.

like image 876
IAmYourFaja Avatar asked Sep 07 '12 12:09

IAmYourFaja


2 Answers

  • I only use Widgets over UiBinder when the widget is extremely simple. For example, I'm just adding two widgets to a Panel. If there is any CSS involved, I always go with UiBinder because it is much easier to work with styles.

  • Widgets do not get translated into UiBinder XML. All GWT code becomes JavaScript that adds and manipulates DOM elements, so everything gets translated to an executable language, not to a templating system.

  • I write lots of UiBinder snippets. I try to follow good rules about abstraction and composition that you can find all over the web.

  • The MVP pattern is a must if you have any non-trivial logic because testing a GWT-free presenter is very quick and easy with JUnit, whereas GWT tests have much more overhead and are much slower.

  • I like to keep as much styling in CSS files as possible because it's a standard good practice to separate concerns, you can compress and bundle your CSS files, and lots of other reasons that are the same as why in a normal HTML page you put CSS in separate files instead of on the page directly.

  • I never use the GWT designer. I always prefer to have clean code rather than the crazy junk usually spit out by any UI code generator.

  • 99% of the time, my widgets extend Composite because either I'm using UiBinder or I'm adding things to a Panel. Even if I only have a single widget, I find it easier to extend Composite and add my one widget to a SimplePanel. I rarely extend Widget because then you have to make a call to Document.get().createFooElement() to create a DOM Element, but I typically find Widgets, which can be added to Panels, easier and safter to work with than Elements

How I Construct Widgets

Each widget implements an interface that extends IsWidget. Everyone who wants to use the Widget should depend on the interface, not on the underlying class. This presents a single, JSNI-free abstraction.

If the widget is very simple, I will have a single class that extends Composite and implements the interface. Either the widget will be very simple and add a few items to a Panel or it will use UiBinder.

If the widget has non-trivial logic that I would like to test, I use the MVP pattern. There will be a presenter class that implements the 'public' interface of the widget, a view interface that extends IsWidget that the presenter depends on, and a view widget that implements the view interface.

A benefit of having a single 'public' interface for the widget is that you can change from implementing the interface with a single Composite class to using MVP if the logic becomes complex, and no one using the widget needs to change at all.

I use Gin to wire all the interfaces and implementations together.

Example

This is best explained with some code. Let's say I have a chart that I want to use on several pages, so I decide to make a reusable widget for it. There is some non-trivial logic around processing the RPC response before displaying it, so I want to thoroughly unit test it. I'd go with something like this:

public interface FinancialChart extends IsWidget {
  void setTickerSymbol(String tickerSymbol);
}

class FinancialChartPresenter extends Composite implements FinancialChart {
  private final FinancialChartView view;      
  private final DataServiceAsync service;

  @Inject(FinancialChartView view, DataServiceAsync service) {
    this.view = view;
    this.service = service;
  }

  @Override public Widget asWidget() {
    return view.asWidget();
  }

  @Override public void setTickerSymbol(String tickerSymbol) {
    service.getData(tickerSymbol, new AsyncCallback<FinancialData>() {
      @Override public void onFailure(Throwable t) {
        // handle error
      }

      @Override public void onSuccess(FinancialData data) {
        SimpleData simpleData = // do some parsing with presentation-specific
          // logic, e.g. make dramatic increases or decreases in price have a
          // a different color so they stand out.  End up with something simple
          // that's essentially some (x, y) points that the dumb view can plot
          // along with a label and color for each point.
        view.drawGraph(simpleData);
      }
  }
}

interface FinancialChartView extends IsWidget {
  void drawGraph(SimpleData simpleData);
}

class FinancialChartWidget extends Composite implements FinancialChartView {
  @Override public void drawGraph(SimpleData simpleData) {
    // plot the points on a chart.  set labels.  etc.
  }
}

class SomethingWithFinancialChartWidget extends Composite
    implements SomethingWithFinancialChart {
  interface Binder extends UiBinder<Widget, SomethingWithFinancialChartWidget> {}

  @UiField(provided = true) final FinancialChart chart;

  @Inject SomethingWithFinancialChartWidget(Binder binder, FinancialChart chart) {
    this.chart = chart;
    initWidget(binder.createAndBindUi(this));
  }
}

// In SomethingWithFinancialChartWidget.ui.xml
<ui:HTMLPanel>
  <!-- lots of stuff -->
  <mynamespace:FinancialChart ui:field="chart" />
  <!-- lots more stuff -->
</ui:HTMLPanel>

class MyPackagesGinModule extends AbstractGinModule {
  @Override protected void configure() {
    bind(FinancialChart.class).to(FinancialChartPresenter.class);
    bind(FinancialChartView.class).to(FinancialChartWidget.class);
  }
}

This allows me to write very simple, thorough, and fast JUnit tests for the FinancialViewPresenter because it has no GWT dependencies that require JSNI, which has to run in a browser as part of a much slower GWT test case. You can create a mock FinancialChartView.

One thing to note here is that since SomethingWithFinancialChartWidget is depending on the interface FinancialChart, it cannot instantiate that object because it is just an interface. That is why chart is set up as @UiField(provided = true) in the Java code of SomethingWithFinancialChartWidget. Gin set up the binding from the FinancialChart interface to a concrete class so it can provide an implementation to the @Inject constructor of SomethingWithFinancialChartWidget, and then setting this.chart gives the UiBinder the object it needs.

There are many files that get created for all the interfaces and implementations in MVP, but the abstraction is absolutely worth it because they enable easy unit testing of presenters and allow you to change how the top-level interface, FinancialChart in this example, is implemented, e.g. change from a single Composite class to MVP, with no client needing to change.

I'm sure there are some implementation details that may not be super clear or things I glossed over, e.g. GWT tests, so please post comments and I can edit my answer to update and clarify.

like image 102
smjZPkYjps Avatar answered Sep 17 '22 12:09

smjZPkYjps


Let's take some distance with your questions and answer the overall concern at once. But first, my favorite catchphrase:

There's no magic!

UiBinder is a tool that takes an XML definition and generates Java code out of it. That is to say (and that's true for everything constructed through a call to GWT.create(): there's no magic!) that you could have written the same by hand, in Java code. In other words, UiBinder is only a tool to get you more productive, by requiring less code to achieve the same goal. But as it generates code that you could have written by hand, it won't give better performance at runtime (not worse either); it however makes it easier to use patterns that result in better performance, namely HTMLPanel: maintaining HTMLPanel-based code in pure-Java is a nightmare, and the complete opposite with UiBinder.

UiBinder will:

  • generate an implicit ClientBundle, with an implicit CssResource for each <ui:style>, implicit an ImageResource for each <ui:image> and an implicit DataResource for each <ui:data>
  • generate an implicit Messages interface for I18N out of <ui:msg>, <ui:ph> et al.
  • inject objects into fields of a partner object (known as the owner and passed as argument to the createAndBindUi method) based on ui:field attributes in the XML and @UiField annotations in the Java code; or possibly retrieve them from the partner object instead, if the annotation has provided=true
  • create objects using various specialized parsers, or @UiConstructor-annotated constructors, or @UiFactory-annotated methods of the partner object, or as a last resort using a GWT.create() call
  • bind @UiHandler-annotated methods of the partner object as event handlers for widgets in the XML template (based on the ui:field attributes)
  • and last but not least: assemble all those constructed objects together, and set some of their properties (out of attributes in the XML).

The most common use of UiBinder is for those objects I talked about above to be widgets. Assembling them then means adding widgets into other container widgets.
This is why you generally see UiBinder being used inside a Composite to provide the value for the initWidget() method: UiBinder will create and put together all the widgets needed and return the top-most one, to be used as the root for the composite widget.

You'll have understood already: you won't use UiBinder over widgets, and widgets aren't translated to UiBinder XML (quite the contrary actually).

Now to your more specific questions:

Do you write lots of UIBinder XML "snippets" or just pack them all into one big monolithic XML file?

UiBinder is kind of an implementation detail. Most of the time, you'll build (reusable) widgets (composites) with UiBinder; it won't transpire to the outside world, which will only see a widget, not "something built with UiBinder".
That means you'll have lots of small/medium UiBinder templates (generally one per screen in your app, at the very least, or for a more general rule, one per complex UI component).

For making my own custom components, what's the difference between extending com.google.gwt.user.client.ui.* vs. com.google.gwt.user.client.ui.Composite?

Ümit answered that one, and the documentation too: https://developers.google.com/web-toolkit/doc/latest/DevGuideUiCustomWidgets

Division of labor: Widgets/UIBinder vs. Layouts vs CSS files: who does what?

There's unfortunately no simple answer to that one. It depends.

It depends the kind of application you're building, and the kind of UI you want. For the "global layout" of your app, you'll use either several RootPanel or one big HTMLPanel and do the layout with HTML and CSS (either in the HTML host page, or in a UiBinder template to serve as a shell for your app), or you'll use so-called layout panels.

For more localized layout (inside composite widgets), I'd recommend using HTMLPanel or FlowPanel; or possibly layout panels. If you need to handle scroll events, or if you need scrolling right inside a container widget, then use a ScrollPanel, otherwise just use overflow: scroll in CSS.

There are tradeoffs. You'll have to do experiments. And in the end, use what you think is good for you. There's no one-size-fits-all.


Finally, while it's its most common use case, note that UiBinder can be used without widgets, for example to create non-composite widgets (or UiObjects, but there's very few use cases for them; you'll see them used for MenuItems and TreeItems for instance), using setElement() rather than initWidget(), or create custom Cells.
And actually, there isn't much use cases for Composite either: you can simply implement IsWidget (returning the result of createAndBindUi, called once and cached in a private field) and it should work everywhere a widget is expected/accepted.

like image 22
Thomas Broyer Avatar answered Sep 17 '22 12:09

Thomas Broyer