Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TreeGrid connectors does not check for attached client side element in certain circumstances #2782

Open
stefanuebe opened this issue Feb 25, 2022 · 1 comment
Labels

Comments

@stefanuebe
Copy link
Contributor

Description of the bug

A TreeGrid, that has drag and drop activated, produces a client side exception, when two things happen in the same request:

  • the data provider is refreshed
  • the grid is removed from the component tree

At first this might sound like an unlikely combination, but we just had that in a huge project, where lot of things happen automatically based on certain triggers. This issue was one result.

The client side error is (shortened):

Uncaught TypeError: Cannot read properties of null (reading '$connector')

The relevant code is in TreeGrid, at line 293 in the method setDataProvider

dataProviderRegistration = dataProvider.addDataProviderListener(e -> {
            if (!(e instanceof DataChangeEvent.DataRefreshEvent)) {
                // refreshAll was called
                getElement().executeJs("$0.$connector && $0.$connector.reset()",
                        getElement());
            }
        });

The issue only happens in a TreeGrid, but regardless of any special setups. You can reproduce it with a simple String list and one normal column.

Expected behavior

The above mentioned JS snippet should also check, if $0 is defined.

Minimal reproducible example

public class TreeGridConnectorJSIssueView extends VerticalLayout {

    public TreeGridConnectorJSIssueView() {
        List<String> rootItems = new ArrayList<>(Arrays.asList("1", "2", "3"));

        TreeGrid<String> grid = new TreeGrid<>();
        grid.addColumn(String::toString); // can also be hierarchy
        grid.setItems(rootItems);

        // initializing a drop target in any way
//        grid.setDropMode(GridDropMode.ON_GRID);
//        grid.setDropMode(null);
        DropTarget.create(grid).setActive(false);

        add(new Button("Crash", event -> {
            grid.getDataProvider().refreshAll();
            remove(grid);
        }), grid);
    }
}

Versions

  • Vaadin / Flow version: Vaadin 14
  • Java version: 15
  • OS version: Windows 10
  • Browser version (if applicable): Chrome
  • Application Server (if applicable): Spring Boot Application Server
  • IDE (if applicable): IntelliJ
@stefanuebe
Copy link
Contributor Author

stefanuebe commented Feb 25, 2022

Working workaround for me was to simply c&p the method (incl. the content of Grid) and add the missing check:

public static class MyTreeGrid<T> extends TreeGrid<T> {
    public MyTreeGrid() {
    }

    public MyTreeGrid(Class<T> beanType) {
        super(beanType);
    }

    public MyTreeGrid(HierarchicalDataProvider<T, ?> dataProvider) {
        super(dataProvider);
    }

    private Registration dataProviderRegistration;
    private Registration dataProviderChangeRegistration;

    @Override
    public void setDataProvider(DataProvider<T, ?> dataProvider) {
        if (!(dataProvider instanceof HierarchicalDataProvider)) {
            throw new IllegalArgumentException(
                    "TreeGrid only accepts hierarchical data providers. "
                            + "An example of interface to be used: HierarchicalDataProvider");
        }
        if (dataProviderRegistration != null) {
            dataProviderRegistration.remove();
        }
        dataProviderRegistration = dataProvider.addDataProviderListener(e -> {
            if (!(e instanceof DataChangeEvent.DataRefreshEvent)) {
                // refreshAll was called
                getElement().executeJs("$0 && $0.$connector && $0.$connector.reset()",
                        getElement());
            }
        });

        Objects.requireNonNull(dataProvider, "data provider cannot be null");
        handleDataProviderChange(dataProvider);

        deselectAll();
        getDataCommunicator().setDataProvider(dataProvider, null);

        /*
         * The visibility of the selectAll checkbox depends on whether the
         * DataProvider is inMemory or not. When changing the DataProvider, its
         * visibility needs to be revalidated.
         */
        if (getSelectionModel() instanceof GridMultiSelectionModel) {
            GridMultiSelectionModel<T> model = (GridMultiSelectionModel<T>) getSelectionModel();
            model.setSelectAllCheckboxVisibility(
                    model.getSelectAllCheckboxVisibility());
        }
    }


    private void handleDataProviderChange(DataProvider<T, ?> dataProvider) {
        onDataProviderChange();

        if (dataProviderChangeRegistration != null) {
            dataProviderChangeRegistration.remove();
        }

        dataProviderChangeRegistration = dataProvider
                .addDataProviderListener(event -> onDataProviderChange());
    }
}

@caalador caalador transferred this issue from vaadin/flow Mar 4, 2022
@yuriy-fix yuriy-fix added bug Something isn't working Impact: Low Severity: Major labels Mar 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants