SmartGWT Custom FormItems
Building a custom FormItem for use in a DynamicForm is straight forward. However, various common practices prevent the same FormItem from being used in a ListGrid. I’ll present some simple examples and try to explain why they don’t work and show what changes make the items usable in a ListGrid.
First Example
public class BadClearTextItem extends TextItem {
public BadClearTextItem() {
FormItemIcon formItemIcon = new FormItemIcon();
setIcons(formItemIcon);
addIconClickHandler(new IconClickHandler() {
@Override
public void onIconClick(IconClickEvent event) {
System.out.println("getValue() is " + getValue());
clearValue();
}
});
}
}
This BadClearTextItem is provides nothing more than a standard TextItem with an icon to clear the current value. Give it a try by dropping into a test form. Now try this same item in a ListGrid with:
ListGridField field = new ListGridField(); // ...additional field configuration here field.setEditorType(new BadClearTextItem());
You should receive an error: Uncaught exception escaped : com.google.gwt.core.client.JavaScriptException(TypeError): self.clearValue is not a function.
Why? The first thing to know is that the setEditorType() method does not really take and hold an instance of a FormItem but rather grabs the properties from the passed template FormItem. When editing begins a new instance of a FormItem is created using the saved template and that editor is shown to the user. I hear another “Why?” question. One reason is you can have editing enabled for all rows (see setAlwaysShowEditors). Another reason is the same editor can be used for a filtering (see setShowFilterEditor).
So what this means is the call clearValue() implies a reference to the current instance of the TextItem (this) but that instance has not yet been rendered so the underlying JavaScriptObject is not yet a FormItem. The getValue() call did not throw an exception because the SGWT wrapper does not attempt to call the underlying FormItem JavaScriptObject if the item has never been rendered – it returns the local starting value, null, or the value set by setValue(). The value logged for getValue() is never correct because it continues to reference the instance created for the template, not the instance associated with the inline editor.
Let’s now look at a simple fix:
public class GoodClearTextItem extends TextItem {
public GoodClearTextItem() {
FormItemIcon formItemIcon = new FormItemIcon();
setIcons(formItemIcon);
addIconClickHandler(new IconClickHandler() {
@Override
public void onIconClick(IconClickEvent event) {
FormItem item = event.getItem();
System.out.println("getValue() is " + item.getValue());
item.clearValue();
}
});
}
}
Instead of using the implied this reference, I have pulled the item instance from the event. This disconnects the event handler code from the instance where it was created.
LESSON: Be aware that this is not always what you expect this to be.
Another example
Here’s an update to the previous working example that only clears the item on every other click. No, this isn’t realistic but is used to simply represent a common use pattern.
public class BadClearTextItem extends TextItem {
private int count = 0;
public BadClearTextItem() {
FormItemIcon formItemIcon = new FormItemIcon();
setIcons(formItemIcon);
addIconClickHandler(new IconClickHandler() {
@Override
public void onIconClick(IconClickEvent event) {
FormItem item = event.getItem();
System.out.println("count is " + count);
if (++count % 2 == 0) item.clearValue();
}
});
}
}
Drop this item into your test grid and give it try. At first glance it seems to work fine but try editing different rows and clicking the icon. Now take a look at your console output. Note that the value for count increments on every icon click even though you are editing in different rows. How can this be if SmartGWT creates a new instance of the FormItem for every edit session? The reason is the count property is always referencing the instance created for the setEditorType() call, not the instance created on-the-fly for editing. This private instance variable now acts like a static.
public class GoodClearTextItem extends TextItem {
public GoodClearTextItem() {
FormItemIcon formItemIcon = new FormItemIcon();
setIcons(formItemIcon);
setAttribute("count", 0);
addIconClickHandler(new IconClickHandler() {
@Override
public void onIconClick(IconClickEvent event) {
FormItem item = event.getItem();
int count = formItem.getAttributeAsInt("count");
System.out.println("count is " + count);
if (++count % 2 == 0) item.clearValue();
formItem.setAttribute("count", count);
}
});
}
}
This version of the FormItem now maintains a true instance variable for count. By using the getAttribute…() and setAttribute() methods we are sure to be setting the value on the desired instance in our constructor and in our event handlers.
LESSON: Instance variables are not always instance variables.
I hope this helps make building custom FormItems for use in ListGrids (and FilterBuilder) easier. Best of luck!