GWT ValueListBox Editor - gwt

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.

Related

How to set a backing list for a ListEditor

how already stated here i am currently trying to get into gwt editors.
I figured i was missing a backing list to hold the data i manipulate.
I tried to assign that backing list with a setValue call from the parent view. Now the compiler complains it is missing the getter for groupList.
I understand that by convention the groupList property is derived by naming the Editor groupListEditor. What would be the right way to attach the lists? It seems i need to somehow call setValue with a list or else it does not seem to work. What would be the right way to do it?
My Editor looks 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> {
}
#UiField
FlowPanel pWidget;
#UiField
PushButton bAdd;
#UiField
FlowPanel pList;
private class StringItemEditorSource extends EditorSource<GroupItemEditor> {
#Override
public GroupItemEditor create(final int index) {
GroupItemEditor subEditor = new GroupItemEditor();
pList.insert(subEditor, index);
subEditor
.addDeleteHandler(new EditorDeleteEvent.EditorDeleteHandler() {
public void onEditorDeleteEvent(EditorDeleteEvent event) {
remove(index);
}
});
return subEditor;
}
#Override
public void dispose(GroupItemEditor subEditor) {
subEditor.removeFromParent();
}
#Override
public void setIndex(GroupItemEditor editor, int index) {
pList.insert(editor, index);
}
}
private ListEditor<String, GroupItemEditor> editor = ListEditor
.of(new StringItemEditorSource());
public GroupListEditor() {
initWidget(uiBinder.createAndBindUi(this));
}
#UiHandler("bAdd")
void onBAddClick(ClickEvent event) {
Log.debug("Add button clicked");
add();
}
private void add() {
String s = "";
//TODO: Problem is there is no backing list, FIx this
editor.getList().add(s);
}
#Override
public ListEditor<String, GroupItemEditor> asEditor() {
return editor;
}
private void remove(final int index) {
editor.getList().remove(index);
}
}
The Editor is used as a sub-editor in a editor widget like this:
I tried to set the backing list:
public class ContainerEditorDialogPresenterWidget extends PresenterWidget<ContainerEditorDialogPresenterWidget.MyView> implements
ContainerEditorDialogUiHandlers {
private final PlaceManager placeManager;
#Inject
ContainerEditorDialogPresenterWidget(EventBus eventBus,
MyView view, PlaceManager placeManager) {
super(eventBus, view);
getView().setUiHandlers(this);
this.eventBus = eventBus;
this.placeManager = placeManager;
}
/**
* {#link LocalDialogPresenterWidget}'s PopupView.
*/
public interface MyView extends PopupView, ContainerEditView<ContainerDto>, HasUiHandlers<ContainerEditorDialogUiHandlers> {
}
private ContainerDto currentContainerDTO = null;
private DeviceDto currentDeviceDTO = null;
private final EventBus eventBus;
private SimpleBeanEditorDriver<ContainerDto, ?> driver;
public ContainerDto getCurrentContainerDTO() {
return currentContainerDTO;
}
public void setCurrentContainerDTO(ContainerDto currentContainerDTO) {
this.currentContainerDTO = currentContainerDTO;
}
public void setCurrentDeviceDTO(DeviceDto currentDeviceDTO) {
this.currentDeviceDTO = currentDeviceDTO;
}
#Override
public void onReveal() {
super.onReveal();
driver.edit(currentContainerDTO);
}
#Override
protected void onBind() {
super.onBind();
driver = getView().createEditorDriver();
}
#Override
public void updateContainer() {
ContainerDto dev = driver.flush();
eventBus.fireEvent(new ContainerUpdatedEvent(dev));
}
}
I tried to assign the backing list (just an empty string list) in the view:
public class ContainerEditorDialogView extends
PopupViewWithUiHandlers<ContainerEditorDialogUiHandlers> implements
ContainerEditorDialogPresenterWidget.MyView, Editor<ContainerDto> {
interface Binder extends UiBinder<PopupPanel, ContainerEditorDialogView> {
}
public interface Driver extends SimpleBeanEditorDriver<ContainerDto, ContainerEditorDialogView> {
}
#UiField
TextBox uuid;
#UiField
TextBox name;
#UiField
TextBox groups;
//#UiField
//TextBox device;
//public TextBox getDevice() {
// return device;
//}
#UiField
GroupListEditor groupListEditor;
#UiField
TextBox imei;
#UiField
TextBox type;
#UiField
TextBox user;
#UiField
Button okButton;
#UiField
Button cancelButton;
#Inject
ContainerEditorDialogView(Binder uiBinder, EventBus eventBus) {
super(eventBus);
initWidget(uiBinder.createAndBindUi(this));
ListEditor<String, GroupItemEditor> ed = null;
groupListEditor.asEditor().setValue(new ArrayList<String>());
}
#Override
public SimpleBeanEditorDriver<ContainerDto, ?> createEditorDriver() {
Driver driver = GWT.create(Driver.class);
driver.initialize(this);
return driver;
}
//Should this handled by a presenter?
#UiHandler("okButton")
void okButtonClicked(ClickEvent event) {
getUiHandlers().updateContainer();
hide();
}
#UiHandler("cancelButton")
void cancelButtonClicked(ClickEvent event) {
hide();
}
}
Thank you!
Update:
A version of the GroupListEditor without a separate ArrayList would look like this and is what i started with:
public class GroupListEditor extends Composite implements
IsEditor<ListEditor<String, GroupItemEditor>> {
private static StringListEditorUiBinder uiBinder = GWT
.create(StringListEditorUiBinder.class);
interface StringListEditorUiBinder extends
UiBinder<Widget, GroupListEditor> {
}
#UiField
FlowPanel pWidget;
#UiField
PushButton bAdd;
#UiField
FlowPanel pList;
private class StringItemEditorSource extends EditorSource<GroupItemEditor> {
#Override
public GroupItemEditor create(final int index) {
GroupItemEditor subEditor = new GroupItemEditor();
pList.insert(subEditor, index);
subEditor
.addDeleteHandler(new EditorDeleteEvent.EditorDeleteHandler() {
public void onEditorDeleteEvent(EditorDeleteEvent event) {
remove(index);
}
});
return subEditor;
}
#Override
public void dispose(GroupItemEditor subEditor) {
subEditor.removeFromParent();
}
#Override
public void setIndex(GroupItemEditor editor, int index) {
pList.insert(editor, index);
}
}
private ListEditor<String, GroupItemEditor> editor = ListEditor
.of(new StringItemEditorSource());
public GroupListEditor() {
initWidget(uiBinder.createAndBindUi(this));
}
#UiHandler("bAdd")
void onBAddClick(ClickEvent event) {
Log.debug("Add button clicked");
add();
}
private void add() {
String s = "Stuff";
editor.getList().add(s);
}
#Override
public ListEditor<String, GroupItemEditor> asEditor() {
return editor;
}
private void remove(final int index) {
editor.getList().remove(index);
}
}
Since i declare the Editor as
#UiField
GroupListEditor groupListEditor;
Should the required getter of ContainerDto not be named getGroupList() ?
You refer to DeviceDto, which is just carried around and should not interfere with the editor.
Since the GroupListEditor implements
IsEditor<ListEditor<String, GroupItemEditor>>
it should expect a List, right?
My ContainerDto has the field
protected ArrayList<String> groupList;
So that should be fine, i guess. This was my starting point before trying to manually call setValue.
When i that, i get this error, when clicking the "Add" button.
Caused by: java.lang.NullPointerException: null at
testproject.client.application.containers.editor.GroupListEditor.add(GroupListEditor.java:81)
at testproject.client.application.containers.editor.GroupListEditor.onBAddClick(GroupListEditor.java:76)
which refers to editor.getList().add(s) which means there is no list...
Update 2:
I changed the declaration of the UIField to:
#UiField
#Path("groupList")
GroupListEditor groupListEditor;
But i still get the NullPointerException when trying to add stuff to the list like before: editor.getList().add(s);
You don't have to manually call setList to pass the list to your SubEditor. That should be taken care of for you by the parent/container Editor.
However the Editor will use the UiField name to match the corresponding getter in your backing DTO.
In your above example this will only work if your DeviceDTO has a getGroupListEditor() getter that returns the type that your ListEditor expects (beacuse of #UiField GroupListEditor groupListEditor;).
If your DeviceDTO doesn't contain the corresponding getter you can do 3 things:
Rename your #UiField GroupListEditor groupListEditor; (i.e. groupItems)
Rename the getter in your DeviceDTO to getGroupListEditor
add Path('groupItems') to your #UiField GroupListEditor groupListEditor;
Solution 3 is usually the way to go:
#UiField
#Path('groupItems')
GroupListEditor groupListEditor;
Note: Change groupItems has to match the getter in your DeviceDTO that returns the group items.
Update:
This is how your DeviceDTO looks like and how you use Path to point your editor to the correct getter. As long as your groupList class variable is initialized to an empty ArrayList everything should work fine.
class DeviceDTO {
protected List<String> groupList = new ArrayList<String>();
public List<String> getGroupList() {
return groupList;
}
}
public class ContainerEditorDialogView extends
PopupViewWithUiHandlers<ContainerEditorDialogUiHandlers> implements
ContainerEditorDialogPresenterWidget.MyView, Editor<ContainerDto> {
....
#UiField
#Path("groupList")
GroupListEditor groupListEditor;
}

GWT's Editor Framework and GWTP

building on this answer, i try to integrate the GWT editors into a popup presenter widget. What is the right way to do that?
My view looks like this:
public class DeviceEditorDialogView extends
PopupViewWithUiHandlers<DeviceEditorDialogUiHandlers> implements
DeviceEditorDialogPresenterWidget.MyView {
interface Binder extends UiBinder<PopupPanel, DeviceEditorDialogView> {
}
public interface Driver extends SimpleBeanEditorDriver<DeviceDto, DeviceEditorDialogView> {
}
#Inject
DeviceEditorDialogView(Binder uiBinder, EventBus eventBus) {
super(eventBus);
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public SimpleBeanEditorDriver<DeviceDto, ?> createEditorDriver() {
Driver driver = GWT.create(Driver.class);
driver.initialize(this);
return driver;
}
}
and my presenter looks like this:
public class DeviceEditorDialogPresenterWidget extends PresenterWidget<DeviceEditorDialogPresenterWidget.MyView> implements
DeviceEditorDialogUiHandlers {
#Inject
DeviceEditorDialogPresenterWidget(EventBus eventBus,
MyView view) {
super(eventBus, view);
getView().setUiHandlers(this);
}
/**
* {#link LocalDialogPresenterWidget}'s PopupView.
*/
public interface MyView extends PopupView, DevicesEditView<DeviceDto>, HasUiHandlers<DeviceEditorDialogUiHandlers> {
}
private DeviceDto currentDeviceDTO = null;
private SimpleBeanEditorDriver<DeviceDto, ?> driver;
public DeviceDto getCurrentDeviceDTO() {
return currentDeviceDTO;
}
public void setCurrentDeviceDTO(DeviceDto currentDeviceDTO) {
this.currentDeviceDTO = currentDeviceDTO;
}
#Override
protected void onBind() {
super.onBind();
driver = getView().createEditorDriver();
}
//UiHandler Method: Person person = driver.flush();
}
Is this the right approach? What is missing? Currently nothing happens when i try to use it like this:
#Override
public void showDeviceDialog() {
deviceEditorDialog.setCurrentDeviceDTO(new DeviceDto());
addToPopupSlot(deviceEditorDialog);
}
showDeviceDialog is in the parent presenter and called when clicking a button in that parent Presenter, that instantiates the dialog with private final DeviceEditorDialogPresenterWidget deviceEditorDialog;
Thanks!
Here are a few key points that are missing from your code above:
Your DeviceEditorDialogView should implement Editor<DeviceDto>. This is required in order for the fields of DeviceEditorDialogView to be populated with data from you POJO.
Your DeviceEditorDialogView should have child editors that are mapped to fields in your POJO. For example, given the field deviceDto.modelName (type String), you could have a GWT Label named modelName in your DeviceEditorDialogView. This Label implements Editor<String> and will be populated with the modelName from your DeviceDto when you call driver.edit(deviceDto)
You should call driver.initialize(this) only once, in DeviceEditorDialogView's constructor
You should override onReveal() like this:
#Override
public void onReveal() {
super.onReveal();
driver.edit(currentDeviceDTO); // this will populate your view with the data from your POJO
}
This method will be called when the popup is displayed, just after your DeviceEditorDialogPresenterWidget has been addToPopupSlot

Custom Renderer in 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";
}
}
}

ListEditor with polymorphic types

I have searched around and been trying to figure out if I can use the editor framework with polymorphic types. I found this post at Using GWT Editors with a complex usecase which is close to what I am trying to do.
I am fairly new to the editor framework, so any help would be much appreciated.
For example, here is some of the code,
Data Transfer Objects:
public class Employee{
public List<Contact> contacts
}
public class Contact {
public class ContactEmail extends Contact {}
public class ContactAddress extends Contact {}
public class ContactPhoneNumber extends Contact {}
Editors:
public interface ContactBaseEditor<T extends Contact> extends Editor<T> {}
public class AddressEditor extends Composite implements Editor<ContactAddress>, ContactBaseEditor<ContactAddress>{}
public class EmailEditor extends Composite implements Editor<ContactEmail>, ContactBaseEditor<ContactEmail>{)
public class PhoneNumberEditor extends Composite implements Editor<ContactPhoneNumber>, ContactBaseEditor<ContactPhoneNumber>{}
ContactEditor class:
public class ContactEditor extends Composite implements IsEditor<ListEditor<Contact, ContactEditorWrapper>> {
private class ContactEditorSource extends EditorSource<ContactEditorWrapper> {
#Override
public ContactEditorWrapper create(final int index) {
ContactEditorWrapper contactEditor = new ContactEditorWrapper();
communicationContactsPanel.add(contactEditor);
return contactEditor;
}
#Override
public void dispose(ContactEditorWrapper subEditor) {
subEditor.removeFromParent();
}
#Override
public void setIndex(ContactEditorWrapper editor, int index) {
communicationContactsPanel.insert(editor, index);
}
}
private ListEditor<Contact, ContactEditorWrapper> editor = ListEditor.of(new ContactEditorSource());
public ListEditor<Contact, ContactEditorWrapper> asEditor() {
return editor;
}
}
ContactEditorWrapper:
class ContactEditorWrapper extends Composite implements ContactBaseEditor<Contact>, ValueAwareEditor<Contact> {
private SimplePanel panel = new SimplePanel();
#Path("") ContactBaseEditor<Contact> realEditor;
public ContactEditor() {
initWidget(panel);
}
#Override
public void setValue(Contact value) {
if (value instanceof Address) {
realEditor = new AddressEditor();
panel.setWidget((AddressEditor)realEditor);
}
else if (value instanceof Email) {
realEditor = new EmailEditor();
panel.setWidget((EmailEditor)realEditor);
}
else if (value instanceof PhoneNumber) {
realEditor = new PhoneNumberEditor();
panel.setWidget((PhoneNumberEditor)realEditor);
}
else {
realEditor = null;
}
}
}
Main Editor class:
public class AddEmployeeEditor extends Composite implements Editor<Employee> {
#UiField
ContactEditor contacts;
interface Driver extends SimpleBeanEditorDriver<Employee, AddEmployeeEditor> {
}
public AddEmployeeEditor(final Binder binder) {
driver = GWT.create(Driver.class);
driver.initialize(this);
List<Contact> list = new ArrayList<Contact>();
list.add(new Address());
list.add(new Email());
list.add(new PhoneNumber());
list.add(new PhoneNumber());
Employee employee = new Employee();
employee.setContacts(list);
driver.edit(employee);
}
}
Can anyone tell me if this will work, am I going in the right direction or ?
Thanks in advance,
Mac
I have updated the code above to now contain the ContactEditorWrapper class that Thomas advised.
That code has good chances to break: there's no guarantee that an editor returned by the EditorSource won't be used to edit another value in the list.
You should create a wrapper editor implementing ValueAwareEditor and with the actual editor as a child editor with #Path(""); and create the appropriate ContactBaseEditor in the setValue method.
class ContactEditor extends Composite implements ValueAwareEditor<Contact> {
private SimplePanel panel = new SimplePanel();
#Path("") ContactBaseEditor realEditor;
public ContactEditor() {
initWidget(panel);
}
#Override
public void setValue(Contact value) {
if (contact instanceof ContactAddress) {
realEditor = new AddressEditor();
}
else if (contact instanceof ContactEmail) {
realEditor = new EmailEditor();
}
else if (contact instanceof ContactPhoneNumber) {
realEditor = new PhoneNumberEditor();
}
else {
realEditor = null;
}
panel.setWidget(realEditor);
}
Note however that only the field/sub-editors from ContactBaseEditor will be edited, whichever the actual implementation being used. If there are additional fields/sub-editors in some ContactBaseEditor subclass, you'd have to implement ValueAwareEditor and handle things by-hand in the setValue and flush methods.

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("");
}-*/;