问题
I want to suppress the display of some items in a Grid
widget in Vaadin Flow 14.
For example, if the user enters a year in a IntegerField
widget, I want the grid to show only items with an associated date before that year. Items with a date on or after that date should disappear. If the user changes the year number, the filtering should be re-applied with fewer or more items displayed appropriately in the grid.
I have seen filters on Grid
discussed but cannot wrap my head around the various moving parts. Perhaps someone could show a simple example?
回答1:
Filtering grids is quite slick, and fairly easy once you get the hang of it.
First, understand that the Grid object is not actually filtered. The DataProvider backing the Grid
is filtered. The Grid
automatically updates its own display to match changes to the data provider.
Below is an entire example app. When it first appears, we see four items it the Grid
widget. These items are simply java.time.LocalDate objects.
When the user enters a year number in the IntegerField
, we apply a filter to the ListDataProvider object backing our grid. If the user clears the year number from that IntegerField
, we clear all filters from our data provider.
Setting and clearing the filter(s) takes effect immediately. This point threw me, with me thinking that I must somehow “apply” the filters after calling setFilter
or addFilter
. But, no, the Grid
automatically updates its display — with no further code on my part. The predicate test defined as part of your filter is automatically applied to each item in the ListDataProvider object’s data set. Those items that pass the test are displayed, while items that fail the test are suppressed from display.
The setting and clearing of filters is done in a value-change listener on the IntegerField widget. When a user enters a number (and leaves the field, such as by pressing Tab or Return), our listener object automatically is called, with an event object passed. Notice that we test for a null being retrieved from the IntegerField
. A null means the user cleared the existing entry, leaving the field blank.
Where did we define our ListDataProvider
object to back our Grid
object? That data provider was instantiated automatically for us when we passed a List to the Grid::setItems method.
By the way, you can have a Grid
automatically display columns using the JavaBeans-style naming conventions (getter/setter methods). But here in this example we defined our columns explicitly, their values generated by calling the LocalDate ::
method references we pass.
package work.basil.example;
import com.vaadin.flow.component.AbstractField;
import com.vaadin.flow.component.dependency.CssImport;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.Route;
import java.time.LocalDate;
import java.time.Month;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* The main view contains a button and a click listener.
*/
@Route ( "" )
//@PWA ( name = "Project Base for Vaadin", shortName = "Project Base" )
@CssImport ( "./styles/shared-styles.css" )
@CssImport ( value = "./styles/vaadin-text-field-styles.css", themeFor = "vaadin-text-field" )
public class MainView extends VerticalLayout
{
private Grid < LocalDate > grid;
private IntegerField yearField;
public MainView ( )
{
yearField = new IntegerField( "Filter years before: " );
yearField.addValueChangeListener(
( AbstractField.ComponentValueChangeEvent < IntegerField, Integer > event ) -> {
Integer year = event.getValue();
// If the user cleared the field, its value is null. In such a case, clear all filters.
// If the user entered a year number into this field, specify a filter.
if ( Objects.isNull( year ) )
{
( ( ListDataProvider < LocalDate > ) grid.getDataProvider() ).clearFilters();
} else
{
( ( ListDataProvider < LocalDate > ) grid.getDataProvider() ).setFilter( ( LocalDate localDate ) -> localDate.getYear() < year );
}
}
);
grid = new Grid <>();
List < LocalDate > dates = List.of(
LocalDate.of( 2020 , Month.JANUARY , 23 ) ,
LocalDate.of( 2019 , Month.FEBRUARY , 24 ) ,
LocalDate.of( 2022 , Month.MARCH , 25 ) ,
LocalDate.of( 2011 , Month.APRIL , 26 )
);
grid.setItems( new ArrayList < LocalDate >( dates ) );
grid.addColumn( LocalDate :: toString );
grid.addColumn( LocalDate :: getYear );
grid.addColumn( LocalDate :: getDayOfWeek );
this.add( yearField , grid );
}
}
You can install multiple filters by calling addFilter
instead of setFilter
. The predicates are effective combined as AND rather than as OR. So all the predicate tests must pass for an item to be displayed. Call clearFilters
to remove any filters. Calling setFilter
clears any existing filters and installs a single filter.
回答2:
I am going to submit my way of filtering a Grid using a ListDataProvider
. The goal of my approach is to be able to add filter fields for any column you want, and that the filters can be combined with each other, and also that each filter value can be changed to something else at any time. With my solution, you will also be able to use other Input Fields than TextField. So can also use ComboBoxes for enums, etc.
The crux is that when you use the setFilter
method of the ListDataProvider, any other filter is disregarded so multiple filters wont work simultaneously. But when you use addFilter
exclusively you can never change a filter you defined, only add another filter on top of that. You could let the user clear all filters with a button that is dedicated for this but I would like to avoid that.
This is how I avoid this conundrum:
Every time that any filter value changes, I reset the current filter using setFilter
. But within that new Filter, I will check the values of ALL filter fields, and not only the value of the field whose value just changed. In other words, I always have only one single filter active, but that filter accounts for all defined filter-values.
Here is the full and working code for a View with a grid (see images below) using the same example as OP's original answer. You can filter this grid by string representation, year, month, or dayOfWeek simultaneously.
@Route(value = "Test", layout = MainView.class)
public class TestView extends VerticalLayout {
private Grid<LocalDate> grid;
private TextField toStringFilter, yearFilter;
private ComboBox<Month> monthFilter;
private ComboBox<DayOfWeek> dayOfWeekFilter;
public TestView() {
grid = new Grid<>(LocalDate.class, false);
List< LocalDate > dates = List.of(
LocalDate.of( 2020 , Month.JANUARY , 23 ) ,
LocalDate.of( 2019 , Month.FEBRUARY , 24 ) ,
LocalDate.of( 2022 , Month.MARCH , 25 ) ,
LocalDate.of( 2011 , Month.APRIL , 26 ) ,
LocalDate.of( 2022 , Month.APRIL , 23 )
);
grid.setItems( new ArrayList< LocalDate >( dates ) );
grid.addColumn( LocalDate :: toString ).setHeader("String Representation").setKey("tostring");
grid.addColumn( LocalDate :: getYear ).setHeader("Year").setKey("year");
grid.addColumn( LocalDate :: getMonth ).setHeader("Month").setKey("month");
grid.addColumn( LocalDate :: getDayOfWeek ).setHeader("Day Of Week").setKey("dayofweek");
prepareFilterFields();
add(grid);
}
private void prepareFilterFields() {
HeaderRow headerRow = grid.appendHeaderRow();
toStringFilter = gridTextFieldFilter("tostring", headerRow);
yearFilter = gridTextFieldFilter("year", headerRow);
monthFilter = gridComboBoxFilter("month", headerRow, Month::toString, Month.values());
dayOfWeekFilter = gridComboBoxFilter("dayofweek", headerRow, DayOfWeek::toString, DayOfWeek.values());
}
private <T> ComboBox<T> gridComboBoxFilter(String columnKey, HeaderRow headerRow, ItemLabelGenerator<T> itemLabelGenerator, T... items) {
ComboBox<T> filter = new ComboBox<>();
filter.addValueChangeListener(event -> this.onFilterChange());
filter.setItemLabelGenerator(itemLabelGenerator);
filter.setItems(items);
filter.setWidthFull();
filter.setClearButtonVisible(true);
headerRow.getCell(grid.getColumnByKey(columnKey)).setComponent(filter);
return filter;
}
private TextField gridTextFieldFilter(String columnKey, HeaderRow headerRow) {
TextField filter = new TextField();
filter.setValueChangeMode(ValueChangeMode.TIMEOUT);
filter.addValueChangeListener(event -> this.onFilterChange());
filter.setWidthFull();
headerRow.getCell(grid.getColumnByKey(columnKey)).setComponent(filter);
return filter;
}
private void onFilterChange(){
ListDataProvider<LocalDate> listDataProvider = (ListDataProvider<LocalDate>) grid.getDataProvider();
// Since this will be the only active filter, it needs to account for all values of my filter fields
listDataProvider.setFilter(item -> {
boolean toStringFilterMatch = true;
boolean yearFilterMatch = true;
boolean monthFilterMatch = true;
boolean dayOfWeekFilterMatch = true;
if(!toStringFilter.isEmpty()){
toStringFilterMatch = item.toString().contains(toStringFilter.getValue());
}
if(!yearFilter.isEmpty()){
yearFilterMatch = String.valueOf(item.getYear()).contains(yearFilter.getValue());
}
if(!monthFilter.isEmpty()){
monthFilterMatch = item.getMonth().equals(monthFilter.getValue());
}
if(!dayOfWeekFilter.isEmpty()){
dayOfWeekFilterMatch = item.getDayOfWeek().equals(dayOfWeekFilter.getValue());
}
return toStringFilterMatch && yearFilterMatch && monthFilterMatch && dayOfWeekFilterMatch;
});
}
}
Unfiltered Grid:
Filtered By Year:
Filtered By Year AND Month:
来源:https://stackoverflow.com/questions/60256269/simple-example-of-filtering-items-in-a-grid-in-vaadin-flow-14