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"))
Related
Edit
Since no one has responded to my original question I think it is worthwhile adding a description of what I am attempting to accomplish, in addition to the existing description of how I have attempted to achieve my goal:
My objective is to create a DataGrid that will resize according to any change in size of its container. This is not difficult to do, but I have an additional requirement, which is to have Panel widgets above and below the DataGrid; these two Panel widgets will contain widgets that are fixed in size (e.g., a row of buttons or text input widgets). My expectation was that a HeaderPanel would be perfect for this, but this doesn't seem to work (as can be seen in my original question, below). So ... an alternative to my original question ("why doesn't this work") is: what is the best way to implement this requirement?
My original question:
I have a DataGrid in the content area of a HeaderPanel, but the detail lines in the DataGrid are not being displayed (the DataGrid column headings are showing, however). Is there an issue with using a DataGrid in the content area of a HeaderPanel? Or is this a simple misuse of the widgets? I'm adding the HeaderPanel to the RootLayoutPanel, which should provide the necessary resize notification (I think). Here is my UiBinder code:
<ui:UiBinder
xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
xmlns:c='urn:import:com.google.gwt.user.cellview.client'>
<g:HeaderPanel>
<g:SimplePanel/>
<g:ResizeLayoutPanel>
<c:DataGrid ui:field='dataGrid'/>
</g:ResizeLayoutPanel>
<g:HorizontalPanel>
<g:Button
ui:field='addRecordButton'
text='Add Record'/>
<g:Label ui:field='numberOfRecordsLabel'/>
</g:HorizontalPanel>
</g:HeaderPanel>
</ui:UiBinder>
and here is the Java code:
public class TempGWT implements EntryPoint {
#UiField
Button addRecordButton;
#UiField
DataGrid<Record> dataGrid;
#UiField
Label numberOfRecordsLabel;
private ArrayList<Record> _recordList;
interface TempGWTBinder extends UiBinder<Widget, TempGWT> {
}
private static class Record {
private String _field1;
}
#Override
public void onModuleLoad() {
_recordList = new ArrayList<Record>();
TempGWTBinder binder = GWT.create(TempGWTBinder.class);
Widget widget = binder.createAndBindUi(this);
Column<Record, String> field1Column = new Column<Record, String>(new TextInputCell()) {
#Override
public String getValue(final Record record) {
return record._field1;
}
};
dataGrid.addColumn(field1Column, "Field 1");
RootLayoutPanel.get().add(widget);
}
#UiHandler("addRecordButton")
public void onAddRecordButtonClick(final ClickEvent event) {
Record record = new Record();
record._field1 = "Record " + (_recordList.size() + 1);
_recordList.add(record);
dataGrid.setRowData(_recordList);
numberOfRecordsLabel.setText("Records:" + _recordList.size());
}
}
I've attempted to trace the execution and, although I'm not certain, it looks as though the following happens when I change the size of the browser window and the "resize" request is received by the DataGrid (I've skipped some of the "unimportant" methods):
DataGrid#onResize
HeaderPanel#forceLayout
ScrollPanel#onResize
The DataGrid object contains a HeaderPanel, which contains the headings for the DataGrid and a ScrollPanel. I don't know whether this is the key to the problem, but the ScrollPanel in the DataGrid's HeaderPanel contains a DataGrid$TableWidget object, and TableWidget does not implement RequiresResize; the ScrollPanel#onResize method only sends the resize to its child if the child implements RequiresResize.
The Tables and Frames section of the GWT Developer's Guide makes it clear that I just needed to use a width/height of 100% for the DataGrid! Like so:
<c:DataGrid
ui:field='dataGrid'
width='100%'
height='100%'/>
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.
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.
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.
I am using DockLayoutPanel as my main panel.
Dependent of the menu I click I change the center-part of the DLP.
For example I change either to form1.ui.xml or to form2.ui.xml.
Both of these forms have a "marker" implemented to display an error message:
<g:HTMLPanel ui:field="messageblock"/>
I am following the MVP Pattern (I use EventBus for communication) and so far everything works great. The only thing I can't figure out is how to replace the content of messageblock. Or to be more concret how to get access to messageblock from my MainPresenter. The main idea behind this stuff is to bundle the error-handling in one presenter...
I am looking for something like
final Panel panel = DockLayoutPanel.get("messageblock");
panel.add(subwidget);
I appreciate every hint...
You could either make the Display responsible for rendering the error (make some interface with a renderError(Error) method) or make the Display return an HTMLPanel that something else can render the error into (some interface with a HTMLPanel getErrorPanel() method). The latter is closest to what you're talking about. Have Form1 and Form2 both implement HasErrorPanel and then call getErrorPanel().add(subWidget).
Here is the conclusion I came to. Maybe it helps someone else.
It's based on what Riley Lark recommended - Thanks to Riley btw.
RegistrationPresenter here is responsible for the registration process and shows a registration form to the user. The error-message should be displayed as close as possible to the place where the error occurred.
Without error http://www.mikemitterer.at/fileadmin/stacktrace_imagehosting/screenshot-778.jpg
An error occurred:
Error popped up http://www.mikemitterer.at/fileadmin/stacktrace_imagehosting/screenshot-780.jpg
Here now a rough description how I implemented this behavior:
public class RegistrationPresenter implements Presenter {
public interface Display extends StatusDisplay, HasMessageBlock {
Widget asWidget();
void setData(RegistrationTO registration);
}
private final Display display;
private final EventBus eventBus;
...
as you can see it's Display implements HasMessageBlock:
public interface HasMessageBlock {
void showMessage(Message message);
void hideMessage();
}
There exists a UIBinder-Widget MessageBlock (MessageBlock.ui.xml + MessageBlock.java)
(messageblock will be turned to invisible in it's constructor)
<g:HTMLPanel styleName="errorblock" ui:field="messageblock">
<div id="errorMsg" class="flurid">
<div class="row">
<div class="column width_15/16">
<h3><ui:msg key="errorblock.headline">An error occurred...</ui:msg></h3>
</div>
<div class="column orientation-right islink width_1/16">
<g:Image resource='{res.xgray}' ui:field="image" />
</div>
...
The Registration-Widget now includes MessageBlock
<g:HTMLPanel styleName="registration" ui:field="panel">
<div class="uniForm maxgrid700">
<h1>
<ui:msg key="registration.headline">Registration</ui:msg>
</h1>
<c:MessageBlock ui:field="messageblock"/>
<div class="ctrlHolder">
<p class="label">
<em></em>
<ui:msg key="registration.name">Name:</ui:msg>
</p>
...
Now, if someone fires a Message
eventbus.fireEvent(new MessageEvent(new MessageImpl(Message.MESSAGETYPE.ERROR, "Server Error Message")));
every Presenter which has "HasMessageBlock" for it's Display can process/display the message:
eventBus.addHandler(MessageEvent.TYPE, new MessageEventHandler() {
#Override
public void execute(final MessageEvent event) {
display.showMessage(event.getMessage());
}
});