Table selection does not happen on HorizontalLayout from ColumnGenerator

I looked for this problem on the internet and here in the forum, but did not find any answer.

Below is a test application to make clear what’s the problem:

  • a click into the
    Table
    does not set the row selection when an XXXLayout of a “generated column” is at the click location.

With “generated column” I mean a table cell that has been created by a
ColumnGenerator
(see source below).

It works fine when just
Label
instances are generated, but as soon as e.g. a
HorizontalLayout
instance is generated, containing that
Label
, the selection is not set anymore when clicking into that row.

The example shows two
Table
instances, the one returning a
HorizontalLayout
from
ColumnGenerator
, the other a
Label
.
Try clicking on the “Name” column in each of them.
The left
Table
will not select the clicked row when clicked into the ‘Name’ text, the right one will always select the clicked row.

public class TableSelectionClickPropagation extends VerticalLayout
{
    public TableSelectionClickPropagation() {
        setMargin(true);
        setSpacing(true);
        final AbstractOrderedLayout ground = new VerticalLayout();
        addComponent(ground);
        
        ground.addComponent(new Label("<h1>Try clicking into the text of 'Name' column on both tables</h1>", ContentMode.HTML));
        addTableWithLabel(ground, true);
        ground.addComponent(new Label("<h1>Only the second table sets a selection on click in that column</h1>", ContentMode.HTML));
        addTableWithLabel(ground, false);
    }

    private void addTableWithLabel(AbstractOrderedLayout layout, boolean createCellLayout) {
        final Label selection = new Label("(Current selection: none)");
        final Table table = createTable(selection, createCellLayout);
        layout.addComponent(table);
        layout.addComponent(selection);
        layout.setComponentAlignment(selection, Alignment.BOTTOM_LEFT);
    }

    private Table createTable(final Label selection, final boolean createCellLayout)    {
        final Table table = new Table("Layout in Name Cell: "+createCellLayout);
        table.setSelectable(true);
        table.setPageLength(10);
        
        final Container container = new IndexedContainer();
        container.addContainerProperty("name", String.class, null);
        container.addContainerProperty("id", Integer.class, null);
        for (int i = 1; i <= 20; i++)    {
            final Object id = new Integer(i);
            final Item item = container.addItem(id);
            item.getItemProperty("name").setValue("Name "+i);
            item.getItemProperty("id").setValue(new Integer(i));
        }
        
        table.setContainerDataSource(container);
        
        final ColumnGenerator columnGenerator = new ColumnGenerator() {
            @Override
            public Object generateCell(Table source, Object itemId, Object columnId) {
                final Label label = new Label("Item is: "+itemId);
                if (createCellLayout == false)
                    return label;    // click works!
                
                final AbstractOrderedLayout ground = new HorizontalLayout();
                ground.addComponent(label);
                return ground;    // click doesn't work!
            }
        };
        table.addGeneratedColumn("name", columnGenerator);
        
        table.setVisibleColumns(new Object [] { "name", "id" });
        
        table.addValueChangeListener(new ValueChangeListener() {
            @Override
            public void valueChange(ValueChangeEvent event) {
                selection.setValue("Table selection event property value (== itemId): "+event.getProperty().getValue());
            }
        });
        
        
        return table;
    }

}

Any advice how to get around this problem?

In real world I have an icon (
Label
) and a
TextField
in my
HorizontalLayout
.

17226.png

The workaround is very simple:

        Table table = new Table("Table with two generated columns", container);
        table.setSelectable(true);
        table.addGeneratedColumn("label", new ColumnGenerator() {
            @Override
            public Object generateCell(Table source, Object itemId, Object columnId) {
                return new Label(source.getItem(itemId).getItemProperty("name"));
            }
        });
        table.addGeneratedColumn("layout", new ColumnGenerator() {
            @Override
            public Object generateCell(final Table source, final Object itemId, Object columnId) {
                Label label = new Label(source.getItem(itemId).getItemProperty("name"));
                HorizontalLayout layout = new HorizontalLayout(label);
                layout.addLayoutClickListener(new LayoutClickListener() {
                    
                    @Override
                    public void layoutClick(LayoutClickEvent event) {
                        source.setValue(itemId);
                    }
                });
                return layout;
            }
        });

This works fine for single selection. For multi-select it is different. To see the problem, add following code to my example above:

private Table createTable(final Label selection, final boolean createCellLayout)    {
        ....
        table.setMultiSelect(true);

The symptom is:

  • selections are added whenever clicking into the
    LayoutClickListener
    (behaves like holding down Ctl-key), and
  • selection is released whenever clicking into a normal cell that has no
    ColumnGenerator
    , regardless whether you press Ctl or Shift.

When you set a breakpoint to

Table.changeVariables()

and click the lower table, you see that what normally happens on a ClickEvent is different from calling
setValue(itemId)
.
The list state (“selectedRanges”, “clearSelection”, “selected”, according to Shift- and Ctl-key) seems to exist on browser client side only … I find no way to simulate that event on server side.

Try the following addon https://vaadin.com/directory/#addon/tableclickforwarder:vaadin

As much as I’ve seen in the debugger, the
LayoutClickEvent
contains the Shift- and Ctl-key states, so at least one can write some logic that manages the Table’s multiple selection.

I’ll come back to here as soon as possible and try the add-on.

Here is the changed source that integrates the
TableClickForwarder

add-on
:

        final ColumnGenerator columnGenerator = new ColumnGenerator() {
            @Override
            public Object generateCell(final Table source, final Object itemId, Object columnId) {
                final Label label = new Label("Item is: "+itemId);
                if (createCellLayout == false)
                    return label;    // click works!
                
                final AbstractOrderedLayout ground = new HorizontalLayout();
                ground.addComponent(label);
                
                new TableClickForwarder(ground);
                
                return ground;    // click still doesn't work!
            }
        };

But still the click does not work.

I looked at Teppo’s example code, he does different things, so maybe my code sucks … but how can I do a

item.getItemProperty("layout").setValue(hl); from a
ColumnGenerator
?
I mean, I am not allowed to touch the item’s property, there is a bean behind it.

By the way, there are two
TableClickForwarder
classes in my CLASSPATH:

  • org.tepi.table.extension.clickforwarder.TableClickForwarder
  • build.classes.org.tepi.table.extension.clickforwarder.TableClickForwarder

Both are in the JAR at the Vaadin Maven repository.


I haven’t yet tried to implement this with Shift- and Ctl-key states from the
LayoutClickEvent
, but this is next I will do here, and I will let you know how the solution looks when it works.

The multi-selection of Vaadin Table is managed on the browser-client.
That JS automaton is stateful, meaning there is an anchor item that helps to manage Ctr- and Shift-click actions.

My workaround experiment is another such automaton, but on server-side.
And it’s stateful too, holding an anchor item for Ctr- and Shift-click actions.

  • Any time I click into a cell
    with
    a HorizontalLayout, the server-side automaton jumps into action.
  • Any time I click in a cell
    without
    a HorizontalLayout, the client-side automaton jumps into action.

It is clear that, because they both have their private states, they interfere with each other.
So I tried to synchronize the server-side automaton with the state available on server-side, which is the set of selected items, but the JS anchor item is not available on server-side.

The
TableClickForwarder
at least helps to avoid inappropriate text selection when
LayoutClickListener
selects multiple items.

Sorry, no solution from my side until now.