GWT UIBinder and Custom Widget


December 5, 2013 Maithilish

3.2. FinsShell

FinsShell

In this section, we explorer two import concepts – GWT UiBinder and Custom Widgets and use them to compose FinsShell, which holds other layout components of our application The most effective way to create new widgets is to extend the Composite class as explained in Creating Custom Widgets section of GWT Developer’s Guide. FinsShell is a custom widget which extends Composite and is the container for other parts of the layout. FinsShell is added to RootPanel in onLoadModule() method of Entry Point instead of adding individual parts directly to the EntryPoint.

Out of various Panels provided by GWT, DockLayoutPanel is most suitable as we can dock Logo to the north, Status to the south, Menu bar to the west and contents to the center. Dimensions of menu and content panel becomes fixed in size if they placed directly in DockLayoutPanel. To make them adjustable, menu is added to the west and contents to the center of a SplitLayoutPanel which is added to the center of the DockLayoutPanel. With this design, we have fixed top and bottom sections and flexible middle section.
FinsShell and various content pages which goes into content area are placed in a separate package and for this create a new package in.fins.client.content. Add FinsShell.java and corresponding UIBinder file FinsShell.ui.xml with following contents to the newly created package in.fins.client.content.
in.fins.client.content/FinsShell.ui.xml

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	xmlns:g="urn:import:com.google.gwt.user.client.ui">
	
	<g:DockLayoutPanel unit='PCT'>
		<g:north size='5'>
			<g:LayoutPanel>
				<g:layer left="90%" width="10%" top="25%" height="75%">
					<g:Label ui:field="logoLabel" text="Fins" 
							styleName="fins-Logo-Label" />
				</g:layer>
			</g:LayoutPanel>
		</g:north>
		<g:south size='3'>
			<g:LayoutPanel>
				<g:layer>
					<g:Label>Status Placeholder</g:Label>
				</g:layer>
			</g:LayoutPanel>
		</g:south>
		<g:center>
			<g:SplitLayoutPanel ui:field="split">
				<g:west size="150">
					<g:Label>Menu Placeholder</g:Label>
				</g:west>
				<g:center>
					<g:Label>Content Placeholder</g:Label>
				</g:center>
			</g:SplitLayoutPanel>
		</g:center>
	</g:DockLayoutPanel>

</ui:UiBinder> 

in.fins.client.content/FinsShell.java

package in.fins.client.content;

import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ResizeComposite;
import com.google.gwt.user.client.ui.Widget;

public class FinsShell extends ResizeComposite {

	interface FinsShellBinder extends UiBinder<Widget, FinsShell> {
	}

	private static UiBinder<Widget, FinsShell> binder = GWT
			.create(FinsShellBinder.class);

	@UiField
	Label logoLabel;

	public FinsShell() {
		initWidget(binder.createAndBindUi(this));
	}
}

We have placed Logo and Status label in a LayoutPanel as we want them at certain positions within their docks. LayoutPanel allows its children to be positioned using arbitrary constraints, almost like AbsolutePanel. As far as possible, it is good to avoid fixed or absolute constraints to position the widgets as such constraints are specified in percentage which ensures that widgets are adjusted when browser is resized.
SplitLayoutPanel is added to the center of dock. HTML widgets are added to west and center of this split panel as placeholders, for MenuBar and ContentPanel, which are replaced by actual widgets later.
We are using fins-Logo-Label style to decorate the logo text. Create a new folder in in.fins package and name it as public. It has to be a folder as it is not possible to create a package named public. Add Fins.css to in/fins/public directory with following contents.
in.fins.public/Fins.css

.fins-Logo-Label {
 color: cornflowerblue;
 font-size: 18px;
 font-weight: bold;
}

To add css to module use stylesheet element in fins.gwt.xml which includes css through Automatic Resource Inclusion mechanism.
in.fins/fins.gwt.xml

<stylesheet src="Fins.css"/>

Add FinsShell to EntryPoint with the following modifications to Fins.java
in.fins.client.content/Fins.java

import in.fins.client.content.FinsShell;
import com.google.gwt.user.client.ui.RootLayoutPanel;

....

@Override
public void onModuleLoad() {
    RootLayoutPanel rp = RootLayoutPanel.get();
    FinsShell finsShell = new FinsShell();
    rp.add(finsShell);
}

GWT offers two types of panel, Basic Panels and Layout Panels. Layout Panels were introduced in GWT 2.0 for fast and predictable application-level layout and ideally they preferred over Basic Panels. In entry point class Fins we have used RootLayoutPanel instead of RootPanel, FinsShell extends ResizeComposite instead of Composite and Layout Panels like DockLayoutPanel, LayoutPanel are used instead of basic panels like DockPanel etc. All of these belongs to Layout Panels group. But it is not possible to build complex UI entirely with Layout Panels, we have to mix and match Basic Panels and Layout panels. For now, you may ignore these subtle differences. To know more about Panels refer Developer’s Guide – Layout Using Panels.
In FinsShell constructor we have called initWidget method of Composite. This method sets the widget to be wrapped by the Composite. Let us explain this with following code snippet.
public class WrapperWidget extends Composite{
      public WrapperWidget(){
          initWidget(new Label("I will getting wrapped");
      }
}
In the above example, the Composite WrapperWidget wraps the Label. With this, we can place WrapperWidget in any panel or even wrap it by other composites. Let us extend the example bit further.
public class WrapperWidget extends Composite{
      public WrapperWidget(){
          VerticalPanel vPanel = new VerticalPanel();
          TextField field = new TextField();
          Button button = new Button("Finish");
          vPanel.add(field);
          vPanel.add(button);
          initWidget(vPanel);
      }
}
TextField and Button are added to VerticalPanel and initWidget call wraps VerticalPanel. Widgets may be added to VerticalPanel either before or after calling initWidget, but initWidget has to be called before calling any other method of the Composite or using it with other composite or panels.
Coming back to our code, can you guess which widget is getting wrapped by FinsShell. The right answer is DockLayoutPanel and to get that we have to understand the logic behind UIBinder.
UiBinder involves two files, template file and its owner class. Template file is a xml file with extension ui.xml where UI objects are laid out, and an Owner class owns a template file. UiBinder generates objects declared in the template file and binds them to fields in owner class. In the owner class, following code initializes the UiBinder.
interface FinsShellBinder extends UiBinder<Widget, FinsShell> {}

UiBinder<Widget, FinsShell> binder = GWT.create(FinsShellBinder.class);
UiBinder instances are factories that generate a UI structure and binds it to an owning Java class. The UiBinder<U, O> interface declares two parameter types:

  • U is the type of root element declared in the ui.xml file, returned by the createAndBindUi call
  • O is the owner type whose @UiFields are to be filled in.
Ui create and wrap
Figure 3.2. Ui create and wrap


When Binder.createAndBindUi is called, FinsShellBinder parses FinsShell.ui.xml and creates an UI structure as shown in dotted area in the figure. Binder injects the widgets to @UiFields defined in owner class FinsShell. Then createAndBindUi returns the root widget which is DockLayoutPanel in this case.
With this, Fins is ready to display the broad contours of the application.