Strange behavior adding bean to table

Hey all, i’ve got a pretty strange problem with adding a new bean to a table. I took a screen cap video, as I didn’t really know how to describe what it’s doing (see attached).

When inspecting the DOM in Chrome, I don’t see the fields there, though they do appear for a second and then go away after adding a new item. If I refresh the browser, the fields become visible again.

Relevant code:

CustomBeanContainer<AccountHolder, AccountHolder> accountContainer = new CustomBeanContainer(AccountHolder.class, AccountDomainManager.INSTANCE);
Table table = new Table();
Button add = new Button();

accountContainer.setBeanIdResolver(bean -> bean);
accountContainer.addNestedContainerBean("account");
accountContainer.addNestedContainerBean("accountClient");

CustomFieldFactory fieldFactory = new CustomFieldFactory() {
    @Override
    public Field createField(final Container container, final Object itemId, final Object propertyId, Component uiContext) {
        Field<?> field;

        if(propertyId.equals("accountClient.accessRole")) {
            field = new NativeSelect();
            ((NativeSelect)field).setContainerDataSource(accessRoleContainer);
            ((NativeSelect)field).setImmediate(true);
            ((NativeSelect)field).setNullSelectionAllowed(false);
            ((NativeSelect)field).setItemCaptionPropertyId("accessRoleName");
            ((NativeSelect)field).setItemCaptionMode(AbstractSelect.ItemCaptionMode.PROPERTY);
        }
        else
            field = super.createField(container, itemId, propertyId, uiContext);

        if (propertyId.equals("account.ID")) {
            return null;
        }
        if (propertyId.equals("account.email")) {
            ((TextField) field).setImmediate(true);
            field.addValueChangeListener(event ->
            {
                // If the email doesn't exist in the account table, return
                Account account = AccountDomainManager.INSTANCE.get((String)field.getValue());
                if(account == null)
                    return;

                // If the account is equal to the one already in the container, return
                int count = 0;
                for (Object i : accountContainer.getItemIds()) {
                    if(account.getEmail().equalsIgnoreCase(((AccountHolder) i).getAccount().getEmail()))
                        count++;
                }
                if(count > 1)
                    return;

                // Set the account holder to the current line being modified.
                AccountHolder accountHolder = accountContainer.getItem(itemId).getBean();

                if (accountHolder == null)
                    return;

                // If the two are the same, no need to update.
                if (account.equals(accountHolder.getAccount()))
                    return;

                account.addAccountClient(accountHolder.getAccountClient());
                accountHolder.setAccount(account);

                table.markAsDirty();
            });
        }
        if(field instanceof TextField) {
            ((TextField)field).setNullRepresentation("");
        }
        field.setWidth("100%");
        field.setBuffered(false);
        field.addValidator(new BeanValidator(AccountHolder.class, (String) propertyId));
        fieldValidation.put(new FieldComparison(propertyId, itemId), field);
        return field;
    }
};

// table
table.setWidth("100%");
table.setContainerDataSource(accountContainer);
table.setImmediate(true);
table.setEditable(true);
table.setBuffered(false);
table.setSelectable(true);
table.setPageLength(10);
table.setTableFieldFactory(fieldFactory);
table.setVisibleColumns("account.ID", "account.email", "account.firstName", "account.lastName", "accountClient.accessRole", "accountClient.active");
table.setColumnHeader("account.ID", "ID");
table.setColumnHeader("account.email", "email");
table.setColumnHeader("account.firstName", "first name");
table.setColumnHeader("account.lastName", "last name");
table.setColumnHeader("accountClient.accessRole", "access role");
table.setColumnHeader("accountClient.active", "active");
table.setColumnWidth("account.ID", 80);
table.setColumnWidth("account.email", 250);
table.setConverter("account.ID", new StringToPlainIntegerConverter());

add.setCaption("Add");
add.addClickListener(event -> {
    Account newAccount = new Account();
    AccountClient newAccountClient = new AccountClient(newAccount);
    newAccountClient.setClientID(ContractUI.getActiveClientID());
    newAccountClient.setActive(true);
    newAccount.addAccountClient(newAccountClient);
    //accountContainer.addBeanAt(0, new AccountHolder(newAccount, newAccount.getAccountClient(ContractUI.getActiveClientID())));
    accountContainer.addBean(new AccountHolder(newAccount, newAccount.getAccountClient(ContractUI.getActiveClientID())));
    //table.markAsDirty();
});
add.setImmediate(true);
add.setWidth("65px");
add.setHeight("25px");

18817.swf (737 KB)

I don’t really know what is going on in there, the behaviour looks rather buggy. The rows indeed collapse for some odd reason. That probably doesn’t normally occur when adding items, so it probably has something to do with the fact that they are empty items.

Trying to think what’s unusual there, the null editors for IDs are maybe one odd thing. Maybe return a “new Label(“X”)” there or something to try to see if that field could be the cause? Or return a TextField for all fields, or something else to narrow it down.

Usability-wise, you might not anyway want to allow adding multiple new items before the last added item is filled and valid.

Just attempted to not use the custom field factory at all (just the default), and it was still happening.

I’m not sure what else I can even look at at this point. This seems so basic.

Returning a null normally just tells the client side that it can display whatever the data in a div, which I read was lighter weight than a label or text field for read only values like that.

Also, for usability, your right I really should limit it to a single new entry at a time. Thanks.

Forgot to mention it in my post, but using Vaadin 7.4.2.

Going to revert to 7.3.x and see if it’s still present.

Alright, I reverted to 7.3.2 and it was still happening.

A bit more detail of what i’ve noticed when trying to debug this:

  • It only happens when the area the new bean will be added is visible in the table. If the location is out of view it works fine.
  • By setting breakpoints, i’ve determined that it does in fact send the correct DOM to the client at one point, then the client sends another request that the server responds to which messes everything up.

There’s at least
#13596
and
#13534
that apparently cause empty rows, but they might be unrelated. It’s probably some specific condition in which it occurs; just adding items in editable table probably isn’t the only factor. Perhaps it has something to do with the sizing of the table, or sizes of the fields (you’re setting them all 100% wide…), or something else like that. Or with the nested bean, although that’s a container detail and shouldn’t matter.

I made a
simple item-adding test
with beans. I can’t see the row collapse problem there, but it looks like the table flickers heavily when new items are added when the table is in a scrolled position, and there’s two requests like you described.

This just keeps getting stranger… I got it to not disappear, but only by setting the bean’s ID to a not null value.

I took a guess that possibly it was because it was the first column in the table, it didn’t like that it was null, so I reordered the columns so active is the first (which is not null by default), but that doesn’t seem to have worked. I also tried the table with an undefined, and fixed width, still nothing.

I also tried removing the nested beans to see if that’d work, and had the columns in the parent bean visible. Also didn’t work (unless I explicitly set the ID as stated above).

Any ideas why it would do that?


Found the issue… it’s only with the Reindeer theme (I haven’t moved to Valo quite yet). I made a demo application to try and pinpoint the issue and everything was working as expected, then I changed the theme from Valo to Reindeer, and it was working as described above.


I attached my demo application for review. I am terrible at the whole CSS thing, so I don’t think i’ll be able to figure out where exactly the bug is there no matter how much time I spend.

EDIT: I spoke too soon, I had that in read only mode, and it was just that Reindeer will collapse a read only column with no data in it while valo won’t. Once I made the table editable, everything is working as it should in my demo. Gotta be somewhere else then.

Gah attachment didn’t stick, here it is…
18906.zip (44.6 KB)

Ok. I’d guess is that there could be some problem in calculating row heights when the only textual column has no height. Maybe there’s some kind of timing problem, so that the heights are calculated before the fields are rendered, or something. Perhaps it has something to do with the flicker problem, which seems to be more pervasive.

In any case, perhaps you can work around the issue either by having (even invisible) text content for the ID column or by setting fixed heights for v-table-row, v-table-cell-wrapper, or v-table-cell-content in your theme.

Something like (with fixedrowheighttable style):

.v-table-fixedrowheighttable .v-table-row {
  height: 44px;
}

I feel like an idiot for how much time wasted on this. I overrode the equals method in the bean that that table was displaying, and I had a bug in the code there that it would sometimes return false even if the objects were the same. That obviously would cause some issues other places too, I just hadn’t noticed them yet because I was so fixated on this issue.

Thank you for the help in resolving this. It is much appreciated.

So for anyone who sees this in the future, words of warning: be careful overriding equals, lots of unintended consequences if you’re not careful.