Hi;
I think I may have found a bug with how the table handles PropertyFormatters in fields embedded in the table.
First a little bit of background:
In my application; admins are able to mass-upload user data from an excel sheet. After the excel sheet is uploaded and parsed; I stuff the results in the form of UserInfo beans in a container. A table sits on top of this container and displays the results of the upload-parse process; at which point admins can correct possible errors in the excel sheet prior to persisting the new users to the backend via Hibernate.
Because the excel sheet can contain errors; I need to display validators in the table to flag missing required fields; invalid email addresses and so forth.
Now, by default; Vaadin tables are in readOnly mode and so the data being rendered isn’t being validated in any way, shape or form. To get around that, my table has a TableFieldFactory which tracks the current row and property field that an admin is working on. If they match; the TableFieldFactory returns an editable field. If they don’t; then it returns a TextField which is .setReadOnly(true). This allows me to still render the table in “readOnly” mode; but because each cell is now an actual textField; I can add validators and so display errors for all rows; even the one’s that an admin isn’t currently adding.
All this works fine.
However; some of my “static TextFields” are actually backed by nested beans on the User object (specifically; users can’t be assigned to Accounts; which are themselves complex objects).
In editable form; the account property is rendered as a ComboBox; so the admin can select a different account from what was originally entered in the excel sheet.In readOnly form however; I can’t just return the ComboBox with setReadOnly(true) - I found when I did that; first of all the comboBox looks weird; second of all; it kills performance on the table.
So instead; in “readOnly” mode; I return a TextField with setReadOnly(true) for the nested beans; and the TextField gets given a PropertyFormatter so that it’s able to render the nested bean as a meaningfull text representation. Like so:
if(propertyId.equals(UserField.ACCOUNT.getPropertyName()))
{
TextField accountField= new TextField(UserField.ACCOUNT.getCaption());
accountField.setPropertyDataSource(new PropertyFormatter(container.getItem(itemId).getItemProperty(propertyId))
{
@Override
public String format(Object value) {
return ((Account)value).getAccountCode();
}
@Override
public Object parse(String formattedValue) throws Exception {
// TODO Auto-generated method stub
return null;
}
});
accountField.setWidth(FIELD_WIDTH);
accountField.setRequired(true);
accountField.setReadOnly(true);
return accountField;
}
(parse returns null because this will never get called; the field is in readOnly mode; if the user clicks it; a different field will get rendered and the user will get the full fledgded combobox instead).
This should work in principle; however; I’m finding that while the PropertyFormatter.format() method IS getting called; the Table is still outputting the .toString() method on the underlying nested Bean (in this case; the Account object of the user).
After looking at the table code; I discovered why:
For each cell in the table; the Table component calls this method:
protected Object getPropertyValue(Object rowId, Object colId,
Property property) {
if (isEditable() && fieldFactory != null) {
final Field f = fieldFactory.createField(getContainerDataSource(),
rowId, colId, this);
if (f != null) {
f.setPropertyDataSource(property);
return f;
}
}
return formatPropertyValue(rowId, colId, property);
}
So you can see it calls my fieldFactory to create the TextField; with the attached PropertyFormatter as a propertyDataSource. The problem is that right AFTER that; it sets the propertyDataSource to just the basic property; which basically undoes all my work with the PropertyFormatter: the field is now, once again; backed directly by the nestedBean; rather than the PropertyFormatter.
I believe, instead, the code for this should be:
protected Object getPropertyValue(Object rowId, Object colId,
Property property) {
if (isEditable() && fieldFactory != null) {
final Field f = fieldFactory.createField(getContainerDataSource(),
rowId, colId, this);
[b]
if (f != null)
{
if(f.getPropertyDataSource == null)
{
f.setPropertyDataSource(property);
}
return f;
}
[/b]
}
return formatPropertyValue(rowId, colId, property);
}
This fixes the problem - if the field already has a PropertyDataSource; that propertyDataSource is used; if it isn’t; then it’s backed by the simple object itself.