Custom Renderer in GWT - gwt

I'm trying to create a widget that will render its associated value in a format that is not the same as the native value. For example, if the value (in the database) is "abcde" I want to show "ab.cd.e" on the screen, and if the user types "abcde" I would also want to show "ab.cd.e". If the user types "ab.cd.e" then I would want to store just "abcde" in the database. I am doing this within the GWT editor framework. I have attempted to use the advice from this answer: Converting String to BigDecimal in GWT, but I can't get it to work. Here's what I have in the UiBinder file:
<g:TextBox ui:field='myTextBox' width='300px'/>
And in the associated Java unit:
#UiField
TextBox myTextBox;
...
initWidget(binder.createAndBindUi(this));
new MyValueBox(myTextBox);
And here's the definition of the MyValueBox widget:
public class MyValueBox extends ValueBox<String> {
//=========================================================================
public static class MyRenderer extends AbstractRenderer<String> {
private static MyRenderer _instance;
private static MyRenderer instance() {
if (_instance == null) {
_instance = new MyRenderer();
}
return _instance;
}
#Override
public String render(final String text) {
// validation is required before doing this!
return text.substring(0, 2) + "." + text.substring(2, 4) + "."
+ text.substring(4);
}
}
//=========================================================================
public static class MyParser implements Parser<String> {
private static MyParser _instance;
private static MyParser instance() {
if (_instance == null) {
_instance = new MyParser();
}
return _instance;
}
#Override
public String parse(final CharSequence text) throws ParseException {
return "parsed string";
}
}
//=========================================================================
public MyValueBox(final TextBox valueBox) {
super(valueBox.getElement(), MyRenderer.instance(), MyParser.instance());
}
}
As you can see, I'm trying to wrap the TextBox that was created using UiBinder, but I don't see any effect from this. I know that I'm missing something very simple, and that there is a much easier way to accomplish this, but I'm stumped. Thank you for any suggestions!
--Edit--
I eventually decided to use a CellWidget, which had the added advantage that I can use this code in a cell widget (e.g., a DataGrid), in addition to using it on a panel. I have documented my solution here: GWT: A Custom Cell Example

You are missing to declare your custom Widget in the UIBinder. You need to tie the package to the xml declaration, adding yours to the standard one (called 'g'):
<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:myurn='urn:import:mypackage'>
Then you should use your declared urn, and the name of your class when declaring your TextBox in the UIBinder:
<myurn:MyValueBox ui:field='myTextBox' width='300px'/>
======EDIT=====
You should extend ValueBoxBase instead of wrapping TextBox, that way you will get control over the Renderer and the Parser as you intend, now you will be able to use your custom box as a widget from within the UIBinder:
public class CustomText extends ValueBoxBase<String>
{
public CustomText() {
super(Document.get().createTextInputElement(),CustomRenderer.instance(),
CustomParser.instance());
}
private static class CustomRenderer extends AbstractRenderer<String>
{
private static CustomRenderer INSTANCE;
public static CustomRenderer instance() {
if (INSTANCE == null) {
INSTANCE = new CustomRenderer();
}
return INSTANCE;
}
#Override
public String render(String text)
{
return "rendered string";
}
}
private static class CustomParser implements Parser<String>
{
private static CustomParser INSTANCE;
public static CustomParser instance() {
if (INSTANCE == null) {
INSTANCE = new CustomParser();
}
return INSTANCE;
}
#Override
public String parse(CharSequence text) throws ParseException
{
return "parsed string";
}
}
}

Related

How to pass additional data to GWT sub-editors?

i have this issue:
I have a PresenterWidget which contains sub-editors.
There are "container" elements which should be editable by this widget. These containers can be assigned to groups. To do so, i would like to fetch a list of all available groups from the server. So the widget is set up like this (i use GWTP):
public class ContainerEditorDialogPresenterWidget extends PresenterWidget<ContainerEditorDialogPresenterWidget.MyView> implements
ContainerEditorDialogUiHandlers {
private final PlaceManager placeManager;
private List<GroupDTO> groupList = new ArrayList<GroupDTO>();
private final DispatchAsync dispatcher;
#Inject
ContainerEditorDialogPresenterWidget(EventBus eventBus,
MyView view, PlaceManager placeManager, DispatchAsync dispatcher) {
super(eventBus, view);
getView().setUiHandlers(this);
this.dispatcher = dispatcher;
fetchGroups();
}
...
public void fetchGroups(){
FetchGroupsAction action = new FetchGroupsAction();
dispatcher.execute(action, new AsyncCallbackImpl<FetchGroupsResult>() {
#Override
public void onSuccess(FetchGroupsResult result) {
groupList = result.getGroupDtos();
eventBus.fireEvent(new GroupListUpdatedEvent(groupList));
}
});
}
So i call fetchGroups in the constructor to get it as early as possible. Since it is an AynchCallback, i get the result back "at some time". I then try to pass the values to the sub-editor with a GroupListUpdatedEvent. In there i have a Editor declared like this:
public class GroupListEditor extends Composite implements
IsEditor<ListEditor<String, GroupItemEditor>> {
private static StringListEditorUiBinder uiBinder = GWT
.create(StringListEditorUiBinder.class);
interface StringListEditorUiBinder extends
UiBinder<Widget, GroupListEditor> {
}
//Gives us access to the event bus.
#Inject private EventBus eventBus;
...
public GroupListEditor() {
initWidget(uiBinder.createAndBindUi(this));
eventBus.addHandler(GroupListUpdatedEvent.TYPE, new GroupListUpdatedEvent.GroupListUpdatedHandler() {
#Override
public void onGroupListUpdatedEvent(GroupListUpdatedEvent event) {
Log.debug("onContainerUpdatedEvent caught");
allGroups = event.getGroupList();
if(allGroups != null) {
for (GroupDTO g : allGroups) {
lbAllGroups.addItem(g.getName(), g.getId().toString());
}
lbAllGroups.setVisibleItemCount(5);
Log.debug("Item list = " + lbAllGroups.getItemCount());
} else {
Log.debug("GROUP LIST is Null!");
}
}
});
}
When i try to register the handler, i get an exception. So i expect the eventBus is not injected properly. What do i miss, how can i use events and the event bus if i am not in a Presenter?
And: Is this the right way at all to populate Editors with "utility" data? I guess Editor should be related directly to the data they care for. But how do i handle this kind of supplemental data?
Thanks :)
Do you use #UiField in your ContainerEditorDialogPresenterWidgetView for your GroupListEditor ?
If so then Dependency Injection won't work because you basically manually create the GroupListEditor which leads to EventBus being NULL.
I would also use Constructor Injection instead of field injection.
GroupListEditor:
#Inject
public GroupListEditor(EventBus eventBus) {
this.eventBus = eventBus;
}
ContainerEditorDialogPresenterWidgetView:
public class ContainerEditorDialogPresenterWidgetView {
#UiField(provided=true)
GroupListEditor groupListEditor;
#Inject
public ContainerEditorDialogPresenterWidgetView(GroupListEditor groupListEditor);
this.groupListEditor = groupListEditor;
initWidget();
}
}
Alternatively you could get an instance of your GroupListEditor via the Ginjector directly.

GWT ValueListBox, Renderer and ProvidesKey

How to implement a GWT ValueListBox inside an Editor with a specific list of objects, my code:
...
#UiField(provided = true)
#Path("address.countryCode")
ValueListBox<Country> countries = new ValueListBox<Country>(
new Renderer<Country>() {
#Override
public String render(Country object) {
return object.getCountryName();
}
#Override
public void render(Country object, Appendable appendable)
throws IOException {
render(object);
}
},
new ProvidesKey<Country>() {
#Override
public Object getKey(Country item) {
return item.getCountryCode();
}
});
...
The Country class
public class Country {
private String countryName;
private String countryCode;
}
But, during the GWT compilation I'm getting this error:
Type mismatch: cannot convert from String to Country
The problem is that you are trying to edit the address.countryCode (looking at the path annotation) with editor for Country.
To make this work, you should change the path to address.country and do the assignment of the address.countryCode after editorDriver.flash(). Something like:
Address address = editorDriver.flush();
address.setCountryCode(address.getCountry().getCountryCode());
To support this, the Address class should have the Country object as property.
You may have assumed that the ValueListBox will work like classical select where the key is assigned to the property. Here the whole object is assigned. So in your case Country object can not be assigned to address.countryCode and vice-versa.
Btw. you can correct the renderer (like the code below) and take care of null objects as arguments in the Renderer and Key Provider.
new Renderer<Country>() {
...
#Override
public void render(Country object, Appendable appendable)
throws IOException {
appendable.append(render(object));
}
...
}

GWT ValueListBox Editor

I'm puzzled about how to use GWT's ValueListBox with an Editor. I'm getting this ERROR:
The method setValue(String) in the type TakesValueEditor<String>
is not applicable for the arguments (List<String>)
Here's the relevant code.
public class MyBean {
private List<String> dateFormats;
public List<String> getDateFormats() {
return dateFormats;
}
public void setDateFormats(List<String> dateFormats) {
this.dateFormats = dateFormats;
}
}
public interface MyBeanView extends IsWidget, Editor<MyBean> {
#Path("dateFormats")
IsEditor<TakesValueEditor<String>> getDateFormatEditor();
}
public class MyBeanViewImpl implements MyBeanView {
#UiField(provided=true) ValueListBox<String> dateFormats;
public MyBeanViewImpl() {
dateFormats = new ValueListBox<String>(PassthroughRenderer.instance(),
new ProvidesKey<String>() {
#Override
public Object getKey(String item) {
return item;
}
});
dateFormats.setAcceptableValues(Arrays.asList(new String[] {"YYYY"}));
// ... binder.createAndBindUi(this);
}
#Override
public IsEditor<TakesValueEditor<String>> getDateFormatEditor() {
return dateFormats;
}
}
Here's what's in ui.xml with xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<g:HTMLPanel>
Data Formats: <g:ValueListBox ui:field="dateFormats"> </g:ValueListBox>
</g:HTMLPanel>
I'm surely missing something obvious here. Much thanks.
The problem that you're running into has to do with trying to map the List<String> dateFormats from MyBean onto the ValueListBox<String> dateFormats editor. The datatypes are incompatible, since a ValueListBox<T> doesn't edit a List<T>, but instead a single instance of T chosen from a list provided by setAcceptableValues(). Given the example above, it would make sense for MyBean to have a String getDateFormat() property and rename the editor field to dateFormat.

How to respond to URLs with GWT's built-in MVP-framework?

I'm building a very simple calendar app to get familiar with the MVP-framework introduced with the 2.1 version of GWT.
What I want to achieve is being able to switch between a list of scheduled appointments and a list of the avialable time.
I have created the a CalendarPlace, CalendarActivity, CalendarView and CalendarViewImpl.
I know that to navigate to a different place i would call PlaceController.goTo(Place), so in my calendar app I would call:
clientFactory.getPlaceController.goTo(new CalendarPlace("freeTime");
The URL would be index.html#CalendarPlace:freeTime for the list of free time or
clientFactory.getPlaceController.goTo(new CalendarPlace("appointments");
for the list of scheduled appointments. The URL would be index.html#CalendarPlace:appointments
But the question is where do I respond to the different tokens? I guess the CalendarPlace would be the right place, but how would I do that?
Here is my source code(I took most of the boilerplate from the tutorial here:
CalendarPlace:
public class CalendarPlace extends Place {
private String calendarName;
public CalendarPlace(String token) {
this.calendarName = token;
}
public String getCalendarName() {
return calendarName;
}
public static class Tokenizer implements PlaceTokenizer<CalendarPlace> {
#Override
public CalendarPlace getPlace(String token) {
return new CalendarPlace(token);
}
#Override
public String getToken(CalendarPlace place) {
return place.getCalendarName();
}
}
}
CalendarActivity:
public class CalendarActivity extends AbstractActivity
implements
CalendarView.Presenter {
private ClientFactory clientFactory;
private String name;
public CalendarActivity(CalendarPlace place, ClientFactory clientFactory) {
this.name = place.getCalendarName();
this.clientFactory = clientFactory;
}
#Override
public void goTo(Place place) {
clientFactory.getPlaceController().goTo(place);
}
#Override
public void start(AcceptsOneWidget containerWidget, EventBus eventBus) {
CalendarView calendarView = clientFactory.getCalendarView();
calendarView.setName(name);
calendarView.setPresenter(this);
containerWidget.setWidget(calendarView.asWidget());
}
}
CalendarViewImpl:
public class CalendarViewImpl extends Composite implements CalendarView {
private VerticalPanel content;
private String name;
private Presenter presenter;
private OptionBox optionBox;
public CalendarViewImpl() {
//optionBox is used for navigation
//optionBox is where I call PlaceController.goTo() from
optionBox=new OptionBox();
RootPanel.get("bluebar").add(optionBox);
content=new VerticalPanel();
this.initWidget(content);
}
#Override
public void setPresenter(Presenter listener) {
this.presenter=listener;
}
#Override
public void setName(String calendarName) {
this.name = calendarName;
}
public void displayFreeTime() {
//called from somewhere to display the free time
}
public void getAppointments() {
//called from somewhere to display the appointments
}
}
In your CalendarActivity constructor you have access to the place, and therefore the token. Tuck it aside, and then in your start() method you can use it. Activities are meant to be lightweight objects, created for each new navigation.

How to set item renderer for ListView in ext-gwt?

I have a ListView in ext-gwt and I'm adding some custom data to it. How can I set an item renderer on the ListView? As of right now, whenever an item is added, there's just a small line representing each entry in the view.
Here's my basic code:
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.widget.ListView;
import com.foo.bar.FooModelData;
private final ListStore<FooModelData> listStore =
new ListStore<FooModelData>();
private final ListView<FooModelData> listView =
new ListView<FooModelData>();
public initializeView()
{
listView.setStore(listStore);
listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
}
public void addItem(FooModelData data) {
listStore.add(data);
}
public class FooModelData extends BaseModel
{
public ModelDataInstance(Foo foo, String style)
{
setFoo(foo);
setStyle(style);
}
public String getStyle()
{
return get("style");
}
public Foo getFoo()
{
return (Foo) get("foo");
}
public void setStyle(String style)
{
set("style", style);
}
public void setFoo(Foo foo)
{
set("foo", foo);
}
}
Thanks for all help!
GXT uses a templating implementation.
Using a simplified version of the Sencha explorer example you could use your data as follows (foo.name assumes foo is also a model:
public initializeView()
{
listView.setStore(listStore);
listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
listView.setTemplate(getTemplate());
}
private native String getTemplate() /*-{
return ['<tpl for=".">',
'<div class="{style}">{foo.name}</div>',
'</tpl>',
'<div class="x-clear"></div>'].join("");
}-*/;