2010-11-26

UiBinder Support For ExtGWT (GXT)

I recently wrote about a new open source library I released that supports registering custom UiBinder ElementParser classes called gwt-customuibinder.  As mentioned in that blog post, I have also released an open source library that utilizes this approach to support the ExtGWT library from Sencha (GXT) in UiBinder XML files called gxt-uibinder.

The current version of GXT was created prior to GWT 2.0 UiBinder, so it does not implement the proper interfaces, such as HasWidgets, to behave properly in UiBinder.  This means that when a project needs to add GXT layouts and widgets, it all needs to be done programmatically in the "View" class rather than declaratively in XML.  This causes a huge burden on the developer.

With my recent work on adding custom UiBinder ElementParsers for GWT, I have been able to add new ElementParsers for the ExtGWT framework to make working with GXT widgets in UiBinder much more friendly.  This project has been released as gxt-uibinder.

Getting Started
First, you must inherit the module.  Since this library uses gwt-customuibinder to register custom ElementParser classes, this module must be inherited after all other <inherit> lines.

For example:
<module>

  <inherits name="com.google.gwt.user.User" />
  <inherits name="com.extjs.gxt.ui.GXT" />

  <inherits name="com.jhickman.web.gwt.gxtuibinder.GxtUiBinder" />

  <source path="client" />
  <source path="shared" />
</module>


Supported Components

As this is a fairly new project, not all components are supported.  The most important one, however, is the ContainerParser for subclasses of the Container component.

Container
Since most of the "panels" in GXT (such as LayoutContainer, ContentPanel, etc) extend Container, then all of these these will support nested children.

LayoutContainer
GXT LayoutContainer subclasses Container, so all of the above apply here as well.  The major difference, however, is the ability to apply different "Layouts".

Currently, there are 2 supported Layouts: FitLayout and BorderLayout.   Below shows examples of both.

FitLayout
<gxt:LayoutContainer layout="FitLayout">
  <Label text="Hi" />
</gxt:LayoutContainer>

BorderLayout
BorderLayout supports layout regions as child elements. This means that the children of a LayoutContainer using the BorderLayout must be one of <gxt:center>, <gxt:west>, <gxt:north>, <gxt:east>, and <gxt:south>. Each region can only appear once, but may appear in any order.

Also note that each region, except <gxt:center> must specify a size attribute, specified in pixels. These regions also support the folling attributes:
  • split
    • boolean value. true if resizable split panel is needed.
  • collapsible
    • boolean value. true if the panel should contain the collapse button.
  • margins
    • can be specified as a single integer (ie margins="5") to make all margins the same, or a comma separated list of 4 integers (ie margins="0,5,0,0"). The order is top, right, bottom, left

<gxt:LayoutContainer layout="BorderLayout">
  <gxt:north size="30">
    <gxt:ContentPanel>...</gxt:ContentPanel>
  </gxt:north>
  <gxt:center>
    <gxt:ContentPanel>...</gxt:ContentPanel>
  </gxt:center>
  <gxt:east size="50" split="true" collapsible="true">
    <gxt:ContentPanel>...</gxt:ContentPanel>
  </gxt:east>
</gxt:LayoutContainer>

ContentPanel
GXT ContentPanel subclasses LayoutContainer, so all of the above apply here as well.  The ContentPanel supports specifying top and bottom components.  This is typically used for status bars or toolbars.  This can be done by using the child elements: <gxt:topcomponent> and <gxt:bottomcomponent>.  These are optional children, and may appear only once each.

For example:
<gxt:ContentPanel heading="Admin Scripting Console">
  <gxt:topcomponent>
    <menu:MenuBar borders="true" /><!-- menu explained below -->
  </gxt:topcomponent>
</gxt:ContentPanel>

Menus


GXT menus are also supported.  Here is an example of its usage:

<gxt:ContentPanel heading="Some Panel">
  <gxt:topcomponent>
    <menu:MenuBar borders="true">
      <menu:MenuBarItem text="File">
        <menu:Menu>
          <menu:MenuItem text="New" ui:field="newMenuItem" />
          <menu:MenuItem text="Open" ui:field="openMenuItem" />
          <menu:MenuItem text="Save"  ui:field="saveMenuItem" />
          <menu:MenuItem text="Save As" ui:field="saveAsMenuItem" />
          <menu:CheckMenuItem checked="true" />
        </menu:Menu>
      </menu:MenuBarItem>
    </menu:MenuBar>
  </gxt:topcomponent>
  <gxt:LayoutContainer layout="BorderLayout">
    <gxt:center>
      <gxt:LayoutContainer layout="FitLayout">
        <gxt:Label>
      </gxt:LayoutContainer>
    </gxt:center>
  </gxt:LayoutContainer>
</gxt:ContentPanel>

Custom Event Handling

The GXT event model differs from GWT.  This means that the @UiHandler annotation cannot be directly used to apply widget event handlers back to the owning view class.

For this, I have created a new annotation that can be used for GXT widget event binding.   It's called @GxtUiHandler.

For example, let's say in the UiBinder XML, you have a GXT button that needs to execute.  In the XML, the ui:field is used as normal.

<gxt:Button text="submit" ui:field="submitButton" />

In the owning view Java class, a method to receive the event will have the @GxtUiHandler annotation applied as such:

@GxtUiHandler(uiField="submitButton", eventType=GxtEvent.Select)
public void submitForm(ButtonEvent event) {
  System.out.println("form Submitted");
}

Unlike the @UiHandler annotation, we have to tell the compiler what Event type it relates to.  This is the major difference between @GxtUiHandler and @UiHandler annotations.

Summary

This library is still in early development, so does not yet support all GXT widgets and properties, but it may have enough for any project to get started with using GXT in their UiBinder XML files.



2010-11-23

Custom UiBinder ElementParsers for GWT

The other day, I released two new open source projects for GWT development to make it easier to adapt GWT widget libraries to UiBinder.

GWT UiBinder was introduced in GWT 2.0 as a declarative approach to building user interfaces.  Rather than programmatically instantiating objects and adding objects to containers, a new XML structure was added.  This approach not only saves hours of development time, but it also reduces the complexities of many application layouts.

Unfortunately, UiBinder wasn't made to support registering custom ElementParsers, thus only supporting core GWT widgets.  This means that if you're planning on using widgets in UiBinder XML, they must conform to the GWT rules. For example, "containers" must implement the HasWidgets interface to support nested children.

After doing a little bit of research, I have found that some people have tried modifying GWT classes (such as UiBinderWriter) and use ClassLoader tricks (changing class path search order) to get their modified versions of the GWT code loaded first, thus giving them the ability to use custom ElementParsers. Typically, this involves editing the UiBinderWriter every time you add a new ElementParser.

I have also recently come across discussions on a new annotation called @ElementParserToUse. With this annotation, a developer would be able to add it to any widget class to specify a specific ElementParser to use. This is a great step towards registering custom ElementParsers.

Unfortunately, this approach does not help me as I'm currently using Ext GWT from Sencha (GXT).  The current version of GXT was created prior to GWT 2.0 UiBinder, thus not implementing the proper interfaces, so when adding GXT widgets into your app, you are basically required to use a programmatic approach to the UI layouts.

I wanted to use GXT for it's rich widgets, but didn't want to fall back to developing my user interface programmatically.  To solve this problem, I started making my own modifications to the UiBinder code generation and released my work as gwt-customuibinder.

Using gwt-customuibinder and Registering Custom ElementParsers

To use gwt-customuibinder in your project, the first thing to do is to <inherit> the module.  This is done like this:
<module>
  ...
  <inherits name="com.jhickman.web.gwt.customuibinder.CustomUiBinder" />
</module>

This inherit line must be the last one for your module (or at least after com.google.gwt.user.User) as it overrides the UiBinderGenerator configuration. If the inherit line comes before, then the GWT UiBinderGenerator will be used, thus not giving you the desired results.

To register custom ElementParsers, you must "extend" a GWT configuration property called "gwt.uibinder.elementparser". This means that there are no code changes needed to support new ElementParser classes.

Here is an example:
<module>
  <extend-configuration-property 
    name="gwt.uibinder.elementparser"
    value="com.foo.client.ui.SomeWidget:com.foo.uibinder.elementparsers.SomeWidgetParser" />
</module>

As you can see from this code snippet, we "extend-configuration-property" called "gwt.uibinder.elementparser".  The value of this property must be a colon separated widget:parser combination.

Note: If you use "set-configuration-property", it will override the previous definitions and not all of the custom ElementParsers will be available.

Using gwt-customuibinder and Registering Custom HandlerEvaluators

Another item I realized when adapting 3rd party widget libraries to GWTs UiBinder is that not all libraries support the same Event model GWT provides. In order to have an equivalent of @UiHandler in the view code, I needed the ability to register a custom HandlerEvaluator. This supports the creation of any handler method annotation needed for your widget library.

Just as registering custom ElementParser classes, we can extend a different configuration property called "gwt.uibinder.customHandlerEvaluator" and use the full classname as the value.

For example:
<module>
  <extend-configuration-property
    name="gwt.uibinder.customHandlerEvaluator"
    value="com.foo.rebind.SomeCustomHandlerEvaluator" />
</module>

The value specified here must implement the "com.jhickman.web.gwt.customuibinder.rebind.CustomHandlerEvaluator" interface.


Conclusion

Although I am glad to start this new open source project as it gives me something fun to work on, I am fully hoping that the project will become unnecessary once the GWT team adds the support for registering custom ElementParsers.


Example Project
The other project I released is called gxt-uibinder. Watch for upcoming blog entry describing that project.