Write a component with special UiBinder markup? - gwt

UiBinder is used to lay out GWT components in a declarative way, with XML markup, as opposed to programmatically, with Java code.
A new XML element in a UiBinder tree means a new instance of that class should be created. Thus, this example from the GWT docs instantiates a new HorizontalPanel and two Labels:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<g:HorizontalPanel>
<g:Label>Keep your ducks</g:Label>
<g:Label>in a row</g:Label>
</g:HorizontalPanel>
</ui:UiBinder>
There's also this other example, with a DockLayoutPanel:
<g:DockLayoutPanel unit='EM'>
<g:north size='5'>
<g:Label>Top</g:Label>
</g:north>
<g:center>
<g:Label>Body</g:Label>
</g:center>
<g:west size='10'>
<g:HTML>
<ul>
<li>Sidebar</li>
<li>Sidebar</li>
<li>Sidebar</li>
</ul>
</g:HTML>
</g:west>
</g:DockLayoutPanel>
In this case, the elements are 'north', 'west', 'center', but those are not new instances of classes, but a configuration of the new DockLayoutPanel.
How do I write a component that, like DockLayoutPanel, accepts custom UiBinder XML elements ?
Where in the source of class DockLayoutPanel, or in its configuration files, is it marked as using special markup, and what to do with the inner content of the special markup elements ?
What other widgets accept special UiBinder markup ?

Seems like you'll have to introduce a custom UiBinder parser for your custom widget.
There's the com.google.gwt.uibinder.elementparsers.DockLayoutPanelParser class which has the following static final map defined:
private static final Map<String, String> DOCK_NAMES = new HashMap<String, String>();
static {
DOCK_NAMES.put("north", "addNorth");
DOCK_NAMES.put("south", "addSouth");
DOCK_NAMES.put("east", "addEast");
DOCK_NAMES.put("west", "addWest");
DOCK_NAMES.put("lineStart", "addLineStart");
DOCK_NAMES.put("lineEnd", "addLineEnd");
DOCK_NAMES.put("center", "add");
}
Haven't searched for all of them but am guessing that any widget that has a custom parser like DockLayoutPanel can process whichever inner XML elements you program it to process.

Related

Why does GWT Designer with UiBinder mess up this simple dialog?

I'm using Eclipse 3.7, GWT 2.5, Java 1.6, and the latest GPE. I've used UiBinder quite a bit, and now we're looking into using GWT Designer for the next version of our app. I created a basic web app project and let it generate its sample code. Then did New->GWT Designer->GWT UiBinder->Dialog Box. Then I added a button to spawn the popup, and a size and title for the dialog. Here's the widget code:
public class TestPopup extends DialogBox {
private static final Binder binder = GWT.create(Binder.class);
#UiField FlowPanel flowPanel;
interface Binder extends UiBinder<Widget, TestPopup> {
}
public TestPopup() {
setWidget(binder.createAndBindUi(this));
setTitle("Test Popup Title");
setPixelSize(400, 300);
}
}
and the corresponding *ui.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<!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'>
<ui:style>
.panel {
padding: 5px;
border: 5px solid cornflowerblue;
background-color: Menu;
}
</ui:style>
<g:FlowPanel styleName="{style.panel}" ui:field="flowPanel" width="100%" height="100%"/>
</ui:UiBinder>
and this is the result. Can anyone explain how these tools can hack a simple configuration into this:
This is a known issue. The DecoratorPanel and DecoratedPopupPanel Widgets have problems like this when you attempt to set their size specifically. Don't specify the size of the DialogBox. Instead, specify the size of the DialogBox's child Widget and the DialogBox should match it fine.

Generic widget in UiBinder

I just created widget:
public class myWidget<T> extends FlowPanel {
private T value;
public T getValue()
{
return value;
}
public myWidget(T[] values) {
for (T value : values)
{
//do action
}
}
How can I add it using UiBinder? Is it possible at all?
Yes you can. You have to import the package which contains the myWidget class into an XML namespace. Say your package is called com.test.widgets, the declarative layout looks like this:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:my='urn:import:com.test.widgets'>
<my:myWidget>
<g:Label>A label</g:Label>
<g:Label>A second label</g:Label>
</my:myWidget>
</ui:UiBinder>
Note the import xmlns:my='urn:import:com.test.widgets' and the usage <my:myWidget>.
To have your widget usable in Uibinder it must implement at least IsWidget interface. Being a widget already, it of course already implements IsWidget.
Therefore, any non-widget could also be used as a child widget element in uibinder by having it implement IsWidget.
The IsWidget interface requires the non-widget to implement the method asWidget(). Therefore, such a non-widget would have to act as a widget container.
Implementing IsWidget will only allow the class to be used as a child widget element.
Let's say your class is
com.zzz.client.ui.HelloKitty
In order for it be able to have child widget elements, it must implement HasWidgets.
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:z='urn:import:com.zzz.client.ui'>
<g:VerticalPanel>
<z:HelloKitty>
<g:button ..../>
<g:textbox>asdf</g:textbox>
</z:HelloKitty>
<g:VerticalPanel>
</ui:UiBinder>
Or, it could also just implement HasOneWidget.
In order to allow the class to have text between its uibinder tags, it must implement HasText.
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:z='urn:import:com.zzz.client.ui'>
<g:VerticalPanel>
<z:HelloKitty>qwerty</z:HelloKitty>
<g:VerticalPanel>
</ui:UiBinder>
In order to accept valid HTML between its tags, I believe you should have it implement HasHTML.
None of the answers seem to focus on the generic part. As mentioned by others, you can easily add a generic widget in an UiBinder template by omitting the generic types:
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:my='urn:import:com.test.widgets'>
<my:myWidget />
</ui:UiBinder>
But, what about if you want to reference this widget in your Java code? Should you omit the generic type there too and incur the wrath of the compiler's warning?
Thankfully, no. UiBinder is pretty loose when it comes to types and since generic types are just hints, you can get away with the following in the Java code backing the above UiBinder template:
#UiField(provided = true)
myWidget<Date> myWidget = new myWidget(new Date(), new Date());
Alternatively, you can also use an #UiFactory method, as mentioned in the documentation.

GWT UiBinder: How to make a custom AbsolutePanel which uses the "at" element?

Trying to extend AbsolutePanel, my UiBinder won't allow the <g:at> element which is normally ok for straight AbsolutePanels. How do I make my AbsolutePanel subclass able to use the <g:at> element? And more generally, can I make custom UiBinder keywords for my own custom Widgets like "at", "west", "layer" etc.?
You can use #UiChild to declare special functions in your widgets accessible in UiBinders.
for example,
class MyPanel extends AbsolutePanel {
#UiChild
public void addAt(Widget w, String parameter1, String parameter2) {
....
Then, in your uiBinder, you can say
<custom:MyPanel>
<custom:at parameter1="HI" parameter2="Anything you like!">
<g:AnySingleWidget />
</custom:at>
</custom:MyPanel>
See #UiChild at http://google-web-toolkit.googlecode.com/svn/javadoc/latest/com/google/gwt/uibinder/client/UiChild.html

dynamic value to uibinder in gwt

I am new to GWT and trying to making a page which is trying to inherit a composite widget but the value of the composite widget is dynamic.
My main page is somehting like:
.....
.....
<g:Button>Open composite widget</g:button>
.....
.....
which is opening an another popup panel which is something like:
.....
<table>
<tr><td>Top: <myCompany:userMeasurementUnit/> </td></tr>
<tr><td>Bottom: <myCompany:userMeasurementUnit/> </td></tr>
<tr><td>Left: <myCompany:userMeasurementUnit/> </td></tr>
<tr><td>Right: <myCompany:userMeasurementUnit/> </td></tr>
</table>
the above should show us
top (cm)
bottom (cm)
left (cm)
right (cm)
But I don't know how to pass the values from main page to custom widget i.e usermeasurementunit
<myCompany:userMeasurementUnit/>
My usermeasurementunit is something like:
UIBinder:
<htmlpanel tag="span" ui:field="unit"/>
and the composit widget is
usermeasurementunit extends Composite {
public usermeasurementunit(){
initwidget...
}
public onload() {
...
}
}
Now I want to pass any measurement unit cm or inches or meters upon clicking button. I tried it using the event bus but it didnt help because when I click the button popuppanel is not on the screen and its not catching the event. If any one of you can help me regarding this I would be really thankful as I am really struggling with this thing.
kind regards
First of all, you need to understand the object instantiation flow in GWT.
They call it "delayed binding", not "dynamic binding".
Uibinder xml file is a layout template. And the JAva source bean behind it is known in general programming terms as the "code-behind".
The role or purpose of the uibinder layout template is to off-load the laying-out (on the internet many non-English speaking programmers write "lay-outing" which, though syntax-wise amusing, is the same thing) so that the code-behind could be focused on controlling the layout's responses.
It's akin to the MVP attitude. View implementation separated from presentation control. You can write the code-behind error free without even knowing exactly the positions where those fields are laid out. You could even simply supply a template where the ui elements are not properly laid out so as to concentrate on your code-behind first. Perhaps after that. one uibinder template for mobile while another for desktop - but they can share the same code-behind.
The values displayed effected by the uibinder template is determined once-and-for-all during uibind. There is no dynamic binding of a uibinder field to the ever changing value of an object/variable declared in the code-behind.
To dynamically change/propagate the values of a uibinder field after uibind, you have to deliberately set its value in the code-behind or write a listener to detect its change.
public class Graceland {
#UiField
Label pressure;
#UiField
Button reset;
public void setPressure(int value) {
pressure.setText(value);
}
#UiHandler("reset")
void nameDoesNotMatter(ClickEvent ev) {
pressure.setText(default);
}
}
GWT.create() generates the Java source for the template during compile time. GWT.create is not a run-time function.
#UiField and #UiHandler are bound to the uifield in the template during uibind.
The role of uibind() is mostly not run-time but compile time too. Though, its finality is realised during run-time, all the javascript code and respective values to construct the objects are generated during compile time and executed once and only once during uibind at run-time.
Therefore, the intention is not to be able to completely replace the dynamic role of the code-behind but simply to free it from the task of laying-out, so that we the programmer could have a clean piece of code-behind being smudged as little as possible with the spaghetti source of the layout.
However, if you wish to "dynamically" affect the value of a uibinder field during bind time,then Ui:with is your friend.
package z.mambazo;
public class Graceland {
....
String initialPressure(){
/* "dynamically" obtain the pressure from the
* pressure gauge in the petroleum distillation stack
* during uibind
*/
}
}
Graceland.ui.xml:
<ui:UiBinder blah...blah>
<ui:with type="z.mambazo" field="zulu"/>
<g:VerticalPanel>
<g:Label
ui:field="pressure"
text="the temperature is :{zulu.initialPressure}"/>
</g:VerticalPanel>
</ui:UiBinder>
The ui:with bean does not have to be the template's code-behind. Either the ui:with bean has an no-argument constructor or you have to supply ui:with tag with attributes corresponding to the constructor arguments.
You have to take note that in order to use ui:with, the init value must be declared in the value attribute not in the tag text.
<g:Label
ui:field="pressure"
text="the temperature is : {zulu.initialPressure}"/>
Not
<g:Label ui:field="pressure">
the temperature is : {zulu.initialPressure}
</g:Label>
The second way, would simply reproduce the text as is.
However, you could also do it this way:
<g:HtmlPanel>
the temperature is :
<g:Label ui:field="pressure"
text="{zulu.initialPressure}"/>
</g:HtmlPanel>
Also, be reminded that all GWT UI Java code, even the interim generated ones, are all translated into browser Javascript. So, whatever class you reference with ui:with must be in Java source code not Java byte code. And those source code must not at any time down the calling chain call byte code.
What you need are shared resources. Here is an example:
MeasurementConstants.java:
package com.acme.client;
public class MeasurementConstants {
private final String measurementUnit;
public MeasurementConstants(String measurementUnit) {
this.measurementUnit = measurementUnit;
}
public String measurementUnit() {
return measurementUnit;
}
}
UiBinderMeasurement.java:
package com.acme.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiFactory;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Widget;
public class UiBinderMeasurement extends Composite {
private static UiBinderI18nUiBinder uiBinder = GWT
.create(UiBinderI18nUiBinder.class);
private MeasurementConstants constants;
interface UiBinderI18nUiBinder extends UiBinder<Widget, UiBinderMeasurement> {
}
public UiBinderMeasurement(MeasurementConstants constants) {
this.constants = constants;
initWidget(uiBinder.createAndBindUi(this));
}
#UiFactory
public MeasurementConstants getConstants() {
return constants;
}
}
UiBinderMeasurement.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">
<ui:with type="com.acme.client.MeasurementConstants" field="constants"></ui:with>
<g:HTMLPanel>
<table>
<tr><td><g:Label text="Top ({constants.measurementUnit}):" /> </td></tr>
<tr><td><g:Label text="Bottom ({constants.measurementUnit}):" /> </td></tr>
<tr><td><g:Label text="Left ({constants.measurementUnit}):" /> </td></tr>
<tr><td><g:Label text="Right ({constants.measurementUnit}):" /> </td></tr>
</table>
</g:HTMLPanel>
</ui:UiBinder>
Now you can call it like this:
new UiBinderMeasurement(new MeasurementConstants("cm"))

How to nest a composite widget into the main ui.xml file using uiBinder?

I have a task to add a composite widget into the main ui.xml file, but it's not working for some reason. Here is my widget code:
public class MyClass extends Composite {
#UiTemplate("MyClass .ui.xml")
interface MyClassUiBinder extends UiBinder<Widget, MyClass > {
}
private static MyClassUiBinder uiBinder = GWT.create(MyClassUiBinder.class);
#UiField Label label;
public MyClass() {
initWidget(uiBinder.createAndBindUi(this));
} ...
Then in my main viewImpl.ui.xml class:
I declare the class package:
... xmlns:u="urn:com... client.view">
and then the widget itself:
<g:HTMLPanel ui:field="mainPanel" styleName="{style.mainPanel}">
<table align="center">
<tr>
<td align="center">
<u:MyClass/>
</td>
</tr>
I also tried setting up a ui:field declaration in the viewImpl class, but I got an error thrown at compile time:
ERROR] In #UiField myClass, template field and owner field types don't match: com.google.gwt.dom.client.Element is not assignable to com... client.view.MyClass
When I took the #UiField declaration out of the viewImpl and the ui.xml it compiled, but the widget wasn't displaying when the page loaded.
I created another composite widget class and tried to duplicate this with just a button widget.
Using firebug I see that the element has been added to the main ui.xml page, but it also is not displaying, so my binding isn't totally complete somehow.
What am I missing here?
I found the problem, GWT was telling me that I didn't do my declaration properly, but the error wasn't as descriptive as I would have liked.
In the main.ui.xml file I used:
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
xmlns:g="urn:import:com.google.gwt.user.client.ui"
xmlns:u="urn:com.abc.client.view">
However, the last line should have had the word import in it:
xmlns:u="urn:import.com.abc.client.view">
This is where I found the answer.
I hope this helps someone, this cost me a lot of time!
You didn't show more UI binder markup around where you are inserting the MyClass widget, but if you have your widget inside some raw HTML you need to have all this HTML in an HTMLPanel.