Capsulate Frontend from Backend

Hello I want to put own items into my grid instead of loading them from the database (for analysing purposes). Is that possible in Vaadin?

Right now we have a grid filled with data like that:
grid.setItems(this.buildDataProvider(listOfAllFields));

With:

private ConfigurableFilterDataProvider<Record, Void, Condition> buildDataProvider(Collection<Field<?>> listOfFields) {

        DataProvider<Record,Condition> dataProvider = new DataProviderFactory(this.dslContext).createQueryDataProvider(
            dslContext1 -> {
                return buildQuery(dslContext1, listOfFields);
            },
            rowCount -> {
                this.appHeading.setHeadingExplicitly("Projekte ({0})", rowCount);
            }
        );

        return dataProvider.withConfigurableFilter();
    }

This DataProvider gets a query. In the end we get a simple table. But it is not possible just to create items with attributes corresponding to the table headers. When I do, It

I also tried to write a mocked DSLContext or an AbstractDataProvider to override functions. But I always get errors like:
There was an exception while trying to navigate to '' with the root cause 'java.lang.IllegalArgumentException: Field ("prf2"."project"."id") is not contained in Row ()' or
Internal error Please notify the administrator. Take note of any unsaved data, and click here or press ESC to continue.

It is super hard for me to understand this behaviour. What I really wanted is to generate items randomly and plot them in my table. But somehow I have to write a DataProvider and this makes it exponentially complicated for such an easy task.

I think there is a super simple solution and I am not able to see it.

Sounds like you are using the constructor of the grid that’s automatically creating columns. Stop doing that and build the columns yourself using grid.addColumn(lambda)… this allows full flexibility based on the data you are providing. There is also no need to use the data provider - simply using setItems with a list is also possible.

Actually I am new to Vaadin. This project was written by other developers and there are many clean code principles that weren’t followed by them. This class is about 1000 lines long and everything all at once. So yes, I think you could be right, but as far as I can see they didn’t use a grid-constructor. They build the grid within the constructor. Or didn’t you meant that?

But all in all it is super hard to understand, especially when you never saw anything of Vaadin before and how it works. I don’t know why I am not able to put own items instead of the DataProvider. Just get the error instantly.

Is there a way to see, what went wrong, when I get Internal error Please notify the administrator. Take note of any unsaved data, and click here or press ESC to continue.?

Because there is nothing logged in console. It is super hard to understand, if you never worked with Vaadin before

You don’t need to load items from a database, but if you’re using a lazy-loading data provider (such as your example), you need to make sure the data provider is fulfilling its contract. If the number of items returned by the fetch callback doesn’t align with the value returned from the count callback, you’re likely to see exceptions like yours.

If you’re ok with just using in-memory items, you should just use a List or a Stream and supply that in the grid.setItems method. That’s generally much more straightforward to use, and a lot of the functionality such as sorting just works out of the box without any additional code.

Already tried that too. But another big problem is, that the previous query did many joins to display these data. So I can’t stream Items. There is no type which containing all fields of these multiple entites which were joined before. And I really don’t want to create a new type just for displaying data. I still don’t understand why it is not possible just to put and 2d-array of data into that grid. It is way harder than React for me.

And I really don’t want to create a new type just for displaying data.

That sounds like the right way to go, though. Still, you don’t necessarily need a custom Bean class, you can use a dynamic class like a HashMap: How do I use HashMap in Grid and Binder instead of POJO - Vaadin Cookbook

I still don’t understand why it is not possible just to put and 2d-array of data into that grid.

That kind of API would prevent lazy loading, or at the very least require a completely new implementation for it.

That’s exactly how we do it in Java - we love our typesafety.

Yes but the query joins two entities. So there is no type for that data in my table which holds all fields. So for just displaying hardcoded data for analyzing this is exploding the work. But yeah in the end I did that by defining a new kind of type with combined fields and filled the records with values for these fields:

private ConfigurableFilterDataProvider<Record, Void, Condition> buildDataProviderMock() {
        Field<?>[] fields = {
            Tables.PROJECT.NAME,
            Tables.PROJECT.PROJECT_NO,
            Tables.PROJECT.YEAR,
            Tables.PROJECT.USER_ID,
            Tables.PROJECT.PLANNED_START_DATE,
            Tables.PROJECT.PLANNED_END_DATE,
            Tables.PROJECT.ACTUAL_END_DATE,
            Tables.PROJECT.COMPANY_ID,
            Tables.PROJECT.CREATED_ON,
            Tables.PROJECT.CREATED_BY,
            Tables.PROJECT.CUSTOMER_ID,
            Tables.PROJECT.STATUS,
            Tables.PROJECT_SUM.SUM_INCOMING_PLANNED,
            Tables.PROJECT_SUM.SUM_INCOMING_ACTUAL,
            Tables.PROJECT_SUM.SUM_OUTGOING_PLANNED,
            Tables.PROJECT_SUM.SUM_OUTGOING_ACTUAL,
            Tables.COMPANY.SHORT_NAME,
            Tables.DATEV_PARTNER.NAME.as("partner_name"),
            Tables.PROJECT.CUSTOMER_PROJECT_NO,
            Tables.PROJECT.CUSTOMER_CONTACT,
            Tables.USER.USERNAME
        };
        Result<Record> customResult = dslContext.newResult(fields);

        int projects = 53;
        for (int i = 0; i < projects; i++) {
            Random random = new Random();
            int randomInt = random.nextInt(10000);
            Record record = dslContext.newRecord(fields);
            record.setValue(Tables.PROJECT.NAME, "Dummy-Project-" + randomInt);
            record.setValue(Tables.PROJECT.PROJECT_NO, "23090" + randomInt);
            record.setValue(Tables.PROJECT.YEAR, 2024);
            record.setValue(Tables.PROJECT.USER_ID, 0);
            record.setValue(Tables.PROJECT.PLANNED_START_DATE, LocalDate.of(2024, 1,  1));
            record.setValue(Tables.PROJECT.PLANNED_END_DATE, LocalDate.of(2024, 1, 1));
            record.setValue(Tables.PROJECT.ACTUAL_END_DATE, LocalDate.of(2024, 1, 1));
            record.setValue(Tables.PROJECT.COMPANY_ID, 1);
            record.setValue(Tables.PROJECT.CREATED_ON, LocalDateTime.of(2024, 1, 1, 0, 0));
            record.setValue(Tables.PROJECT.CREATED_BY, "Dummy-user-" + randomInt);
            record.setValue(Tables.PROJECT.CUSTOMER_ID, 1);
            record.setValue(Tables.PROJECT.STATUS, "active");
            record.setValue(Tables.PROJECT_SUM.SUM_INCOMING_PLANNED, BigDecimal.valueOf(123456));
            record.setValue(Tables.PROJECT_SUM.SUM_INCOMING_ACTUAL, BigDecimal.valueOf(123456));
            record.setValue(Tables.PROJECT_SUM.SUM_OUTGOING_PLANNED, BigDecimal.valueOf(123456));
            record.setValue(Tables.PROJECT_SUM.SUM_OUTGOING_ACTUAL, BigDecimal.valueOf(123456));
            record.setValue(Tables.COMPANY.SHORT_NAME, "HO");
            record.setValue(Tables.DATEV_PARTNER.NAME.as("partner_name"), "Dummy-customer");
            record.setValue(Tables.PROJECT.CUSTOMER_PROJECT_NO, "12345");
            record.setValue(Tables.PROJECT.CUSTOMER_CONTACT, "Dummy-user");
            record.setValue(Tables.USER.USERNAME, "Dummy-user");
            customResult.add(record);
        }


        DataProvider<Record,Condition> dataProvider = new AbstractDataProvider<Record,Condition>() {
            @Override
            public boolean isInMemory() {
                return false;
            }

            @Override
            public int size(Query<Record, Condition> query) {
                return customResult.size();
            }

            @Override
            public Stream<Record> fetch(Query<Record, Condition> query) {
                return customResult.stream();
            }
            };

        return dataProvider.withConfigurableFilter();
    }

It worked. At least a bit. But there are more problems: No of these filters work anymore and a for-loop over more than 50 projects I get again: Internal error Please notify the administrator. Take note of any unsaved data, and click here or press ESC to continue.
How can I (as an administrator) see the error logs. I see nothing in my terminal xO

You are not applying the values from the Query. The Grid is asking you to return, say, the items 25-75 out of your dataset and you are returning the entire set with customResult.stream().

Oh yes thank you! Now I have pagination and don’t get the error anymore, when I want to generate more than 50 projects:

DataProvider<Record,Condition> dataProvider = new AbstractDataProvider<Record,Condition>() {
            @Override
            public boolean isInMemory() {
                return false;
            }

            @Override
            public int size(Query<Record, Condition> query) {
                // Filter the records based on the condition
                Stream<Record> filteredRecords = customResult.stream();
                if (query.getFilter().isPresent()) {
                    Condition condition = query.getFilter().get();
                    filteredRecords = filteredRecords.filter(record -> {
                        // Apply your condition filtering logic here
                        // Example: return condition.evaluate(record);
                        return true; // Replace with actual condition check
                    });
                }
                return (int) filteredRecords.count();
            }

            @Override
            public Stream<Record> fetch(Query<Record, Condition> query) {
                // Apply filtering based on the condition
                Stream<Record> filteredRecords = customResult.stream();
                if (query.getFilter().isPresent()) {
                    Condition condition = query.getFilter().get();
                    filteredRecords = filteredRecords.filter(record -> {
                        // Apply your condition filtering logic here
                        // Example: return condition.evaluate(record);
                        return true; // Replace with actual condition check
                    });
                }
                // Apply pagination
                return filteredRecords
                        .skip(query.getOffset())
                        .limit(query.getLimit());
            }
        };

I didn’t implemented filtering method, since this would be way too much. Especially for just displaying “mocked” data.