Search code examples
javavaadinvaadin8vaadin-grid

How to calculate the total in a Vaadin 8 grid footer with filtering


I know I have to use grid.getDataProvider() to get the ListDataProvider (assuming I sent a List to grid.setItems()). In other to calculate the footer total I do:

Collection myItems = ((ListDataProvider)grid.getDataProvider()).getItems();
for(MyItem myItem : myItems)
   total += myItem.getValue();
footer.getCell(footerCell).setText(format(total));

But this fails if I add a footer since it calculates over all items in my grid. So for example if I add:

((ListDataProvider)grid.getDataProvider()).addFilter(myFilter);

The code at the top fails since the footer isn't the filtered total but the full grid total.

That being said someone suggested I use:

grid.getDataCommunicator().fetchItemsWithRange(...);

However this is a protected method. Assuming I create my own subclass I don't even understand how that method works.

But even then that seems overly complex and something that should be simple, especially if there's an ability to add filtering in grids.

Therefore my big question is how do I calculate the footer total in a Vaadin 8 Grid if I filter the Grid?


Solution

  • To recalculate the total you can use a DataProviderListener which is triggered when the filter changes. In its implementation you can fetch the items from DataProvider with a Query, as the fetch method also takes into consideration the filter you defined.

    The below example is mainly based on the Vaadin grid sampler and the idea is to display a list of monthly quotes and their total. The filter will allow you to see the data starting from a certain month (it's silly but it gets you started):

    import com.vaadin.data.provider.ListDataProvider;
    import com.vaadin.data.provider.Query;
    import com.vaadin.ui.ComboBox;
    import com.vaadin.ui.Grid;
    import com.vaadin.ui.VerticalLayout;
    import com.vaadin.ui.components.grid.FooterRow;
    import com.vaadin.ui.components.grid.HeaderRow;
    import com.vaadin.ui.themes.ValoTheme;
    
    import java.util.List;
    import java.util.Random;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    
    public class FilteredGrid extends VerticalLayout {
        public FilteredGrid() {
            // list data provider with some random data
            Random random = new Random();
            List<Quote> quotes = IntStream.range(1, 11).mapToObj(month -> new Quote(month, random.nextInt(10))).collect(Collectors.toList());
            ListDataProvider<Quote> provider = new ListDataProvider<>(quotes);
    
            // month number filter combo
            ComboBox<Integer> monthFilterCombo = new ComboBox<>("Starting with", IntStream.range(1, 10).boxed().collect(Collectors.toList()));
            monthFilterCombo.setEmptySelectionCaption("All");
            monthFilterCombo.addStyleName(ValoTheme.COMBOBOX_SMALL);
            monthFilterCombo.addValueChangeListener(event -> {
                if (event.getValue() == null) {
                    provider.clearFilters();
                } else {
                    provider.setFilter(quote -> quote.getMonth() > event.getValue());
                }
            });
    
            // grid setup
            Grid<Quote> grid = new Grid<>(Quote.class);
            grid.setDataProvider(provider);
    
            // header and footer
            HeaderRow header = grid.appendHeaderRow();
            header.getCell("month").setComponent(monthFilterCombo);
            FooterRow footer = grid.prependFooterRow();
            footer.getCell("month").setHtml("<b>Total:</b>");
            provider.addDataProviderListener(event -> footer.getCell("value").setHtml(calculateTotal(provider)));
    
            // add grid to UI
            setSizeFull();
            grid.setSizeFull();
            addComponent(grid);
    
            // trigger initial calculation
            provider.refreshAll();
        }
    
        // calculate the total of the filtered data
        private String calculateTotal(ListDataProvider<Quote> provider) {
            return "<b>" + String.valueOf(provider.fetch(new Query<>()).mapToInt(Quote::getValue).sum()) + "</b>";
        }
    
        // basic bean for easy binding
        public class Quote {
            private int month;
            private int value;
    
            public Quote(int month, int value) {
                this.month = month;
                this.value = value;
            }
    
            public int getMonth() {
                return month;
            }
    
            public void setMonth(int month) {
                this.month = month;
            }
    
            public int getValue() {
                return value;
            }
    
            public void setValue(int value) {
                this.value = value;
            }
        }
    }
    

    Result:

    Vaadin filtered Grid