GWT Presentation Layer: Who does what?
Asked Answered
R

3

7

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.

Reservoir answered 7/9, 2012 at 12:37 Comment(2)
Great question, this kind of stuff is quite hard for a newcomer to get their head around. Would love to see an in-depth answer from a battle-weary GWT UI veteran.Quickfreeze
As someone who has worked with GWT in the past and does some JS-fu in his spare time, I strongly suggest NOT using GWT if you don't really have to. While it can help you in managing large-scale web apps it's far to heavy weight for small to medium sized web apps. Learning JS is pretty easy (even with its quirks) and may pay off in the long run. This may sound like a rant but that were my experiences with GWT. It's not to say that it's of no use but for most web apps it's overblown.Marmara
B
5
  • 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.

Braca answered 7/9, 2012 at 14:19 Comment(7)
Amazing, amazing answer @Michael Davidson - I wish I could upvote it more! A few followups, although with your code example I follow most of what you're explaining here: (1) you mentioned that you only prefer Widgets to UIBinder when the Widget is extremely simple; can I ask why (in other words, why are more complex Widgets more difficult to implement than using UIBinder? (2) This example appears to be with a Widget; can I assume that that if it were to be rewritten as a UIBinder then the code inside of FinancialChartWidget::drawGraph would be the frontend code inside the UIbinder?Reservoir
And (3) Does GWT guarantee me that if I develop my UI (and specifically, my CSS) inside, say, Firefox, that the CSS will work and that the UI will be the same for all permutations, for all browsers/versions? I ask because I guess I was hoping to steer clear of any CSS at all (I'm used to Swing layouts and placing components at specific x,y coordinates), so if I do invest the time to get my layout/CSS styles working for one browser, it would be nice to know that it will look/work the same everywhere it runs. Thanks again for such a great answer!Reservoir
And actually - sorry - (4) I'm not really sure what the right terminologies are here. It looks like you call a "View" something that extends IsWidget. I'm probably wrong, but I have been thinking of "widgets" as individual controls on the screen (a single button, a single drop down listbox, etc.). I've always (from my JSP days) thought of a "view" as the entire set of components/widgets for a given page. Is this different in GWT-land? How do you distinguish: Widgets, Composites, Layouts, Views and "Pages" (full screen layouts)? And I promise this is the last followup - thanks again!Reservoir
(1) I find UiBinder easier for complex widgets because it's more like HTML, which is what I'm ultimately trying to create. I think the declarative template makes it easier to see the structure than a whole bunch of 'addWidget' calls to various panels. You can also put styles in your UiBinder very easily, though I've been steering away from that and using CSS files instead.Braca
(2) You're correct that this example doesn't have FinancialChartWidget using UiBinder, but SomethingWithFinancialChartWidget does. However, if FinancialChartWidget were to use UiBinder, I would still keep logic in drawGraph. UiBinder, or at least how I always use it, is a way to layout the structure of the widget; not to contain any code or logic like a JSP page.Braca
(3) GWT does not make such a guarantee about CSS, but it does make that guarantee for JavaScript. I'm unaware of a tool to reduce or eliminate cross-browser CSS issues, but that would be very useful!Braca
(4) What I call a view comes from the standard MVP term. en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter does a better job explaining it than I could. The view just happens to be one of the three pieces in MVP that can be used to implement a widget. But as I mentioned, the nice part of having a single interface that everyone depends on is that you can use MVP or a single Composite class and your clients never know the difference and are insulated from change. I consider a 'widget' to be any self-contained control, which can often be large and complex.Braca
A
2

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.

Ardell answered 7/9, 2012 at 15:48 Comment(0)
T
1
  1. You are correct. GWT Designer outputs UIBinder XML code. UIBinder XML code is just a declarative syntax which translates to actual Java code (same as if you would create the elements in your java class).
  2. UIBinder and Widgets are somehow not comparable. You can create Widgets by using UiBinder together with normal Java code but you could implement the same widget with the same functionality only with java code. UiBinder just makes it more readable because it is declarative and you don't end up instantiating a lot of UI elements (i.e. TextBox textbox = new TextBox(); and container.add(textBox))
  3. I would use separate UiBinder snippets for components and widgets and just drop them into a parent UIBinder file (i.e. Presenter). However it probably doesn't make sense to put every single tiny component (i.e. a Label + TextBox for a Form) into a separate UIBinder snippet. You have to find a good balance (though in this example you could create a Label + TextBox component if you use it extensively using UiBinder).
  4. If you create a new component that consists of existing ones use Composite. If you create a unique new widget or component which can't be constructed by using existing ones use Widget or IsWidget.
  5. I usually do it this way:

.

  • Use as much UIBinder as possible. For example use LayoutPanels for the page structure.
  • Create a main ClientBundle and CSSResource for common styles which you use throughout your App.
  • Put <ui:style> declarations inside the corresponding UiBinder files when you need specific styles which you only use once.
  • Create widgets for reusable components (again you can follow the 3 points).

Finally Michael Davidson brought up some good points about MVP and testing. I strongly recommend to follow them.

Tantalus answered 7/9, 2012 at 14:36 Comment(1)
Thanks @Umit (+1) - a quick question about ClientBundle and CSSResource: Is a CSSResource the GWT-equivalent POJO of an external CSS file? How does it relate to a ClientBundle? How does GWT know to pull down the CSSResource and make it available to the generated JS code? Thanks again for the great advice here!Reservoir

© 2022 - 2024 — McMap. All rights reserved.