Простой пример фильтрации элементов в сетке в Vaadin Flow 14 - PullRequest
3 голосов
/ 17 февраля 2020

Я хочу подавить отображение некоторых элементов в виджете Grid в Vaadin Flow 14.

Например, если пользователь вводит год в виджете IntegerField, я хочу, чтобы сетка показывать только элементы со связанной датой до этого года. Элементы с датой на или после этой даты должны исчезнуть. Если пользователь изменяет номер года, фильтрация должна быть повторно применена, чтобы в сетке отображалось соответственно меньше или больше элементов.

Я видел обсуждаемые фильтры на Grid, но не могу обернуть голову вокруг различных движущихся частей. Возможно, кто-то может показать простой пример?

Ответы [ 2 ]

2 голосов
/ 17 февраля 2020

Фильтрация сеток довольно проста и довольно проста, как только вы ее освоите.

Во-первых, поймите, что объект Grid на самом деле не фильтруется. DataProvider, поддерживающий Grid, фильтруется. Grid автоматически обновляет свой собственный дисплей, чтобы соответствовать изменениям поставщика данных.

Ниже приведен пример приложения. Когда он появляется впервые, мы видим четыре элемента в виджете Grid. Эти элементы являются просто java.time.LocalDate объектами.

before filtering

Когда пользователь вводит номер года в IntegerField, мы примените фильтр к объекту ListDataProvider, поддерживающему нашу сетку. Если пользователь удаляет номер года из этого IntegerField, мы удаляем все фильтры от нашего поставщика данных.

after filtering

Установка и очистка фильтра (-ов) вступает в силу немедленно . Эта точка заставила меня я думал, что должен каким-то образом «применить» фильтры после вызова setFilter или addFilter. Но, нет, Grid автоматически обновляет свой дисплей - без дополнительного кода с моей стороны. Тест предикатов, определенный как часть вашего фильтра, автоматически применяется к каждому элементу в наборе данных объекта ListDataProvider. Элементы, прошедшие тест, отображаются, а элементы, не прошедшие тест, не отображаются.

Установка и очистка фильтров выполняется в прослушивателе изменения значений в виджете IntegerField. Когда пользователь вводит число (и покидает поле, например, нажимая клавишу Tab или Return), автоматически вызывается наш объект-слушатель с пропущенным объектом события. Обратите внимание, что мы тестируем нулевой ключ, извлекаемый из IntegerField. Ноль означает, что пользователь очистил существующую запись, оставив поле пустым.

Где мы определили наш ListDataProvider объект для поддержки нашего Grid объекта? Этот провайдер данных был создан для нас автоматически, когда мы передали List методу Grid::setItems.

Кстати, у вас может быть Grid автоматически отображать столбцы, используя соглашения об именах в стиле JavaBeans (методы получения / установки). Но здесь, в этом примере, мы явно определили наши столбцы, их значения генерируются путем вызова переданных нами ссылок на метод LocalDate ::.

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 );
    }
}

Вы можете установить несколько фильтров, вызвав addFilter вместо setFilter. Предикаты эффективно объединяются как И, а не как ИЛИ. Таким образом, все тесты предикатов должны пройти для отображения элемента. Звоните clearFilters, чтобы удалить все фильтры. Вызов setFilter очищает все существующие фильтры и устанавливает один фильтр.

1 голос
/ 17 февраля 2020

Я собираюсь представить свой способ фильтрации Grid с использованием ListDataProvider. Цель моего подхода состоит в том, чтобы иметь возможность добавлять поля фильтра для любого столбца, который вы хотите , и фильтры можно комбинировать друг с другом , а также каждый фильтр значение может быть изменено на что-то другое в любое время . С моим решением вы также сможете использовать другие поля ввода, кроме TextField. Поэтому можно также использовать ComboBox для перечислений и т. Д. c.

. Суть в том, что при использовании метода setFilter объекта ListDataProvider любой другой фильтр игнорируется, поэтому несколько фильтров не будут работать одновременно. Но когда вы используете addFilter исключительно, вы никогда не сможете изменить определенный вами фильтр, только добавьте еще один фильтр поверх этого. Вы можете позволить пользователю очистить все фильтры с помощью кнопки, которая предназначена для этого, но я бы хотел этого избежать.

Вот как я избегаю этой загадки: каждый раз, когда любое значение фильтра изменяется, я сбрасываю текущий фильтр, используя setFilter. Но внутри этого нового фильтра я буду проверять значения ВСЕХ полей фильтра, а не только значение поля, значение которого только что изменилось. Другими словами, у меня всегда активен только один единственный фильтр, но этот фильтр учитывает все определенные значения фильтра.

Вот полный и рабочий код для представления с сеткой (см. Изображения ниже) с использованием тот же пример, что и оригинальный ответ ОП. Вы можете фильтровать эту сетку по строковому представлению, году, месяцу или dayOfWeek одновременно.

@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;
        });
    }
}

Нефильтрованная сетка: grid with filters, no active filter


Отфильтровано по году: grid with filters, filtered by year


Отфильтровано по году и месяцу: grid with filters, filtered by year and month

...