Skip to content

Filter Panel

Das Filter Panel wird zum schnellen und gezielten Filtern von Tabellen genutzt.

Overview

Intro

Das Filter Panel ermöglicht die kontextbezogene Suche nach Objekten in Masteransichten. Das Muster lässt sich sowohl für die einfache als auch komplexe Variante vom  Master-Detail Muster anwenden und ist unabhängig von der Gesamtmenge der zugrundeliegenden Objekte. Zum Verwalten von komplexen Masteransichten mit vielen Suchkriterien stehen optional vordefinierte und individualisierbare Ansichten zur Verfügung.

Beispiel eines Filter Panels mit allen Funktionen

Verwendung

👍 Do👎 Don't
...wenn nach Objekten in einer Tabellenseite gesucht werden soll....wenn nach Objekten in einer Tabelle in einem anderen Kontext als der Tabellenseite gesucht werden soll wie z.B. in einer Tabellensektion einer Objektseite.

Guidelines

Aufbau

Im aufgeklappten Zustand (siehe Auf- und Zuklappen unter Verhalten) lassen sich alle Funktionen des Filter Panels in den folgenden Bereichen nutzen:

Im aufgeklappten Zustand

Ansichten (optional):

Wenn das Filter Panel mit mehreren Ansichten ausgeliefert werden soll, oder eine Individualisierung des Filter Panels vorgesehen ist, dann lässt sich durch die Ansichtenauswahl die aktuelle Ansicht wählen und verwalten.

Suchkriterien

Verwende immer den einfachsten Eingabetyp zur Konfiguration der Suchkriterien und vermeide unnötige Komplexität im Filter Panel.

Zulässige Eingabetypen:

INFO

Für Range Selector und Combo Box mit Mehrfachauswahl: immer je zwei Spalten einplanen.

Funktionsleiste

In der Funktionsleiste sind folgende Tasten verortet:

  • Suchen
  • Zurücksetzen
  • Anpassen (optional)

Sekundäre Funktionsleiste (Optional)

In der sekundären Funktionsleiste befindet sich die Funktionen zum aufgeklappt oder zugeklappt.

Im zugeklappten Zustand

  1. Zusammenfassung
    Die Zusammenfassung informiert den Nutzer kompakt über die Anzahl der aktiven Suchkriterien.
  2. Sekundäre Funktionsleiste

    In der sekundären Funktionsleiste befinden sich Funktionen zum auf- und zuklappen.

Responsive Design

Das Filter Panel passt sich an die verfügbare Breite seines Containers dynamisch an, solange diese >= Medium ist. Sollte der Container nur noch <= Small sein, dann wird das Filter Panel standardmäßig zugeklappt dargestellt. Die Aufgeklappte Ansicht erstreckt sich über die gesamte Verfügbare Bildschirmhöhe und überlagert die Trefferliste.

Verhalten

Auf- und Zuklappen des Filter Panels

  1. In der sekundären Funktionsleiste des Filter Panels lässt sich über einen Link zwischen den beiden Zuständen „zugeklappt“ und „aufgeklappt“ hin und her wechseln.
  2. Die Zusammenfassung zeigt die Anzahl an aktiven Suchkriterien.

Suchen

Die Suche lässt sich auf zwei Wegen auslösen.

  1. Wenn ein Eingabefeld fokussiert ist, dann lässt sich über die Eingabe Taste die Suche auslösen.
  2. Über die Suchen Taste.
  3. Während die Suche vom System ausgeführt wird ist ein Indikator zu sehen.

INFO

Hinweis: Löst der Nutzer oder das System eine Suche ohne Suchkriterien aus, dann werden einfach die ersten Treffer geladen.

Zurücksetzen

Die Zurücksetzen Taste löst mehrere Funktionen aus:

  1. Die Suchkriterien werden zurückgesetzt. D.h. die eingegebenen Werte werden entfernt bzw. auf den Default zurück gesetzt.
  2. Wenn das Anpassen von Ansicht möglich ist, dann wird diese ebenfalls zurück gesetzt. D.h. das nachträglich hinzugefügte Suchkriterien entfernt werden und nachträglich entfernte Suchkriterien werden wiederhergestellt. Sollte die Reihenfolge verändert worden sein, dann wird diese ebenfalls wiederhergestellt. Das gleiche gilt auch für Tabellenspalten und deren Reihenfolge. Das Verhalten ist somit identisch mit dem erneuten auswählen einer Ansicht.
  3. Die Trefferliste wird zurückgesetzt indem eine Suche mit den Ausgangswerten ausgelöst wird.

Voneinander abhängige Suchkriterien (Optional)

INFO

Die beschriebenen Funktionalitäten der voneinander abhängigen Suchkriterien müssen produktspezifisch implementiert werden und sind nicht Teil der technischen Komponente. Es handelt sich um ein Muster für die Implementierung.

Bei Bedarf können die Auswahlmöglichkeiten von Dropdown , Comboboxen und Lookup Felder durch andere Suchkriterien auf eine Teilmenge eingegrenzt werden.

  1. Abhängiges Suchkriterium: Die Auswahlmöglichkeiten für Suchkriterien vom Typ Dropdown , Combobox und Lookup können durch eine oder mehrere andere Suchkriterien (Eingrenzende Parameter) zu einer Teilmenge eingegrenzt werden.
  2. Tooltip: Ein Hinweis gibt den Nutzern zu verstehen von welchen Eingabefeldern die Auswahl ggf. beeinflusst wird.
  3. Eingrenzende Parameter: Die vom Nutzer ausgewählte Werte von Suchkriterien vom Typ Dropdown , Combobox oder Lookup können als Eingangsparameter davon abhängige Suchkriterien beeinflussen. Wenn die Nutzer ihre Auswahl in diesen Suchkriterien ändern, dann werden die davon abhängigen Suchkriterien geleert.

Anpassen (Optional)

Die Standard Variante des Filter Panels erlaubt die Anpassung der Suchkriterien, sowie der Trefferliste durch den Nutzer.

Suchkriterien ein- und ausblenden

Suchkriterien lassen sich ein- und ausblenden, indem man den Dialog dazu über die Anpassen Taste aufruft.

  1. Modal Dialog Größe: Verwende für den Dialog mindestens einen  Modal Dialog  der Größe M.
  2. Zähler: Einzelne Suchkriterien lassen sich über den Stepper  ein- und ausblenden. Sollten weitere Suchkriterien der selben Art eingeblendet werden, dann reihen diese sich an der Position der vorherigen ein.
  3. Gruppen: Suchkriterien können gruppiert werden, damit die Nutzer sich leichter orientieren können. Sobald ein Suchkriterium einer Kategorie eingeblendet ist, muss die Gruppe beim öffnen vom Dialog ausgeklappt dargestellt werden.
  4. Checkbox Einzelne Suchkriterien lassen sich auch mehrmals einblenden, so lange es sich nicht um Multiselect-Combobox handelt. In diesem Fall wird im Modalen Dialog anstelle eines Steppers eine einfache Checkbox eingebaut mit der das Feld ein- und ausgeblendet werden kann. Wenn einzelne Suchfelder mehrmals eingebunden sind, werden diese mittels „oder“ Operator in der Suche verbunden

Neue Suchkriterien reihen sich am Ende ein.

Suchkriterien Reihenfolge ändern

Die Reihenfolge der Suchkriterien lässt sich ändern, indem man den Dialog dazu über die Anpassen Taste aufruft.

Die Positionen von mehrfach auftauchenden Suchkriterien lassen sich nicht einzeln verändern. Gleiche Suchkriterien stehen immer nebeneinander und tauchen daher im Dialog zum ändern der Reihenfolge nur ein Mal auf.

  1. Modal Dialog Größe: Verwende für den Dialog mindestens einen  Modal Dialog  der Größe M.
  2. Sortieren: Die  Buttons  zum umsortieren der Reihenfolge müssen oberhalb und außerhalb der  Tabelle verortet sein. Das gewährleistet das diese beim scrollen innerhalb der  Tabelle  nicht aus dem sichtbaren Bereich verschwinden und eine gute Tastaturbedienbarkeit gegeben bleibt.

Tabellenspalten ein- und ausblenden

Tabellenspalten einblenden

Tabellenspalten lassen sich ein- und ausblenden indem man den Dialog dazu über die Anpassen Taste aufruft.

  1. Modal Dialog Größe: Verwende für den Dialog mindestens einen  Modal Dialog der Größe M.
  2. Checkboxen: Einzelne Tabellenspalten lassen sich über  Checkboxen  ein und ausblenden. Es muss dabei mindestens eine Tabellenspalte eingeblendet sein.
  3. Gruppen (Optional): Tabellenspalten können ggf. gruppiert dargestellt werden werden, damit die Nutzer sich leichter orientieren können. Sobald ein Tabellenspalte einer Kategorie eingeblendet ist, muss die Gruppe beim Öffnen vom Dialog ausgeklappt dargestellt werden.

Tabellenspalten, die neu eingeblendet werden, reihen sich am Ende ein.

Tabellenspalten Reihenfolge ändern

Die Reihenfolge der Tabellenspalten lässt sich ändern, indem man den Dialog dazu über die Anpassen Taste aufruft.

  1. Modal Dialog Größe: Verwende für den Dialog mindestens einen  Modal Dialog  der Größe M.
  2. Sortieren: Die  Buttons  zum umsortieren der Reihenfolge müssen oberhalb und außerhalb der Tabelle verortet sein. Das gewährleistet, dass diese beim Scrollen innerhalb der  Tabelle nicht aus dem sichtbaren Bereich verschwinden und eine gute Tastaturbedienbarkeit gegeben bleibt.

Ansichten (Optional)

Ansichten personalisieren

Um Anpassungen zu speichern, muss die Funktion zur Ansichten Verwaltung genutzt werden.

Diese Personalisierung ist in folgenden Szenarien wertvoll für die Nutzer:

  • Nutzer müssen verschiedene Filter Panel Konfigurationen speichern und laden, damit Sie ihre relevanten Daten finden können.
  • Nutzer müssen verschiedene Tabellen-Konfigurationen speichern und laden können, um die Daten in verschiedenen Ansichten zu sehen.
  • Nutzer müssen Einstellungen für eine gesamte Seite, inkl. das Filter Panel und Tabellen Konfigurationen speichern und laden können.

Partitionierte Ansichten (Optional)

INFO

Die beschriebenen Funktionalitäten der partitionierten Ansichten müssen produktspezifisch implementiert werden und sind nicht Teil der technischen Komponente. Es handelt sich um ein Muster für die Implementierung.

Mit partitionierten Ansichten lassen sich Suchkriterien und Tabellenspalten für spezielle Use Cases gruppieren. In der Praxis müssen nicht immer alle Suchkriterien mit allen kombinierbar sein. Häufig fordern Stakeholder und Nutzer jedoch immer mehr Suchkriterien über die Zeit was zu Performance-Problemen führen kann.

Durch das Partitionieren können im Hintergrund eigene Datenbank-Views implementiert werden um die Performance stabil zu halten. Siehe dazu folgender Vergleich zwischen normalen Ansichten und dem Ansatz diese zu partitionieren.

Barrierefreiheit

Die Tastaturbedienung erfolgt wie bei der  Ansichtenverwaltung , Eingabefeldern und  Buttons . Zusätzlich lässt sich durch das Drücken der "Eingabetaste" eine Suche auslösen.

Develop Vue

Die Implementierung der Webcomponenten in Vue.js kann in folgendem Showcase innerhalb des AKDB Netzwerks betrachtet werden: https://mate-ds-vue-components-showcase-25.core-platform.kubt.akdb.net/?path=/docs/komponenten-filterpanel--docs

Develop Flow

Der Showcase ist innerhalb des AKDB Netzwerks zu finden unter: https://mate-ds-flow-components-showcase-25.core-platform.kubt.akdb.net/?src=pattern%252Ffilter-panel

Code Examples

Install

Maven

xml
<dependency>
    <groupId>de.mate_ds</groupId>
    <artifactId>mate-filter-panel-flow</artifactId>
</dependency>

Import

java
import de.mate_ds.flow.component.filterpanel.FilterPanel;
import de.mate_ds.flow.component.filterpanel.FilterPanelDialogBuilders.FilterPanelReorderDialogBuilder;
import de.mate_ds.flow.component.filterpanel.FilterPanelDialogBuilders.FilterPanelVisibilityDialogBuilder;
import de.mate_ds.flow.component.filterpanel.FilterPanelDialogBuilders.ItemVisibilityProvider;

Variants

Micro

Basic filter panel without header, suitable for simple search scenarios.

java
TextField username = new TextField("Username");
TextField firstName = new TextField("First name");
TextField dateOfBirth = new DatePicker("Date of birth");

FilterPanel filterPanel = new FilterPanel();
filterPanel.add(username, firstName, dateOfBirth);
filterPanel.addSearchClickEventListener(e -> { /* trigger search */ });
filterPanel.addResetClickEventListener(e -> {
    username.clear();
    firstName.clear();
    dateOfBirth.clear();
});
filterPanel.setHeaderHidden(true);

Minimal

java
TextField username = new TextField("Username");
TextField firstName = new TextField("First name");
TextField dateOfBirth = new DatePicker("Date of birth");

Span criteriaCount = new Span("0");

FilterPanel filterPanel = new FilterPanel();
filterPanel.getHeaderCollapsed().add(new Span("Active search criteria"), criteriaCount);
filterPanel.addSearchClickEventListener(e -> {
    int count = 0;
    count += username.isEmpty() ? 0 : 1:
    count += firstName.isEmpty() ? 0 : 1:
    count += dateOfBirth.isEmpty() ? 0 : 1:
    criteriaCount.setText(String.valueOf(count));

    /* trigger search */
});
filterPanel.addResetClickEventListener(e -> {
    username.clear();
    firstName.clear();
    dateOfBirth.clear();
    criteriaCount.setText("0");
});

Standard

java
 FilterPanel filterPanel = new FilterPanel();
VerticalLayout layout = new VerticalLayout(filterPanel);
layout.setSpacing(true);

Grid<User> grid = new Grid<>();
grid.setItems(users);
layout.add(grid);

Generator crit1 = new Generator("Username", true, n -> filterPanel.add(new TextField(n)));
Generator crit2 = new Generator("First name", true, n -> filterPanel.add(new TextField(n)));
Generator crit3 = new Generator("Date of Birth", true, n -> filterPanel.add(new DatePicker(n)));
List<Generator> allCriteria = List.of(crit1, crit2, crit3);

Generator col1 = new Generator("Username", n -> grid.addColumn(User::getUsername).setHeader(n));
Generator col2 = new Generator("First name", n -> grid.addColumn(User::getFirstName).setHeader(n));
Generator col3 = new Generator("Last name", n -> grid.addColumn(User::getLastName).setHeader(n));
List<Generator> allColumns = List.of(col1, col2, col3);

MenuBar views = new MenuBar();
filterPanel.getHeaderExpanded().add(new Span("Ansicht"), views);

List<View> allViews = new ArrayList<>(List.of(new View("Standard", List.of(crit1, crit2, crit3), List.of(col1, col2, col3))));

AtomicReference<View> currentView = new AtomicReference<>(allViews.get(0));

SerializableRunnable updateCriteria = () -> {
    filterPanel.getChildren().filter(c -> c instanceof HasValue).forEach(filterPanel::remove);
    currentView.get().getCriteria().forEach(Generator::generate);
};
SerializableRunnable updateColumns = () -> {
    grid.removeAllColumns();
    currentView.get().getColumns().forEach(Generator::generate);
};

MenuItem viewsItem = views.addItem("");
AtomicReference<SerializableRunnable> onViewsUpdated = new AtomicReference<>();
onViewsUpdated.set(() -> {
    viewsItem.setText(currentView.get().getName() + (currentView.get().isDirty() ? " (bearbeitet)" : ""));
    SubMenu viewsMenu = viewsItem.getSubMenu();
    allViews.forEach(v -> {
        MenuItem item = viewsMenu.addItem(v.getName() + (v.isDirty() ? " (bearbeitet)" : ""), e -> {
            currentView.set(v);
            updateCriteria.run();
            updateColumns.run();
            onViewsUpdated.get().run();
        });
        item.setCheckable(true);
        if (v.equals(currentView.get())) {
            item.setChecked(true);
        }
    });
    viewsMenu.addItem(new Hr());
    viewsMenu.addItem("Speichern", e -> {
        currentView.get().setDirty(false);
        onViewsUpdated.get().run();
    });
    viewsMenu.addItem("Speichern unter", e -> {
        FilterPanelSaveAs data = new FilterPanelSaveAs();
        data.setName(currentView.get().getName() + " (Kopie)");
        new FilterPanelSaveAsDialogBuilder(data, saved -> {
            if (allViews.stream().anyMatch(v -> v.getName().equals(saved.getName()))) {
                View newView = currentView.get().clone(saved.getName());
                allViews.add(newView);

                currentView.set(newView);
                onViewsUpdated.get().run();
                return true;
            }
            return false;
        }).build();
    });
});

updateCriteria.run();
updateColumns.run();
onViewsUpdated.get().run();

filterPanel.addSearchClickEventListener(e -> Notification.show("Search clicked"));
filterPanel.addResetClickEventListener(e -> {
Notification.show("Reset clicked");
    filterPanel.getChildren().filter(c -> c instanceof HasValue).map(c -> (HasValue) c).forEach(HasValue::clear);
});

MenuBar configuration = new MenuBar();
SubMenu anpassen = configuration.addItem("Anpassen").getSubMenu();
anpassen.addItem("Suchkriterien ein- und ausblenden", e -> {
    FilterPanelVisibilityDialogBuilder.ofCriteria(allCriteria, c -> c, c -> {
        currentView.get().setCriteria(c.stream().map(item -> {
            item.getItem().setVisibility(item.getVisibility());
            return item.getItem();
        }));
        updateCriteria.run();
        return true;
    }).build().open();
});
anpassen.addItem("Suchkriterien Reihenfolge ändern", e -> {
FilterPanelReorderDialogBuilder.ofCriteria(currentView.get().getCriteria(), Generator::getName, c -> {
currentView.get().setCriteria(c);

updateCriteria.run();
return true;
}).build().open();
});
anpassen.addItem("Tabellenspalten ein- und ausblenden", e -> {
FilterPanelVisibilityDialogBuilder.ofColumns(allColumns, c -> c, c -> {
currentView.get().setColumns(c.stream().map(item -> {
item.getItem().setVisibility(item.getVisibility());
return item.getItem();
}));

updateColumns.run();
return true;
}).build().open();
});
anpassen.addItem("Tabellenspalten Reihenfolge ändern", e -> {
FilterPanelReorderDialogBuilder.ofColumns(currentView.get().getColumns(), Generator::getName, c -> {
currentView.get().setColumns(c);

updateColumns.run();
return true;
}).build().open();
});
filterPanel.getAction().add(configuration);

Context

User

java
 public static class User {

    private String username;
    private String firstName;
    private String lastName;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((username == null) ? 0 : username.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        User other = (User) obj;
        if (username == null) {
            if (other.username != null)
                return false;
        }
        else if (!username.equals(other.username))
            return false;
        return true;
    }
}

Generator

java
private static class Generator implements ItemVisibilityProvider {

    private final String  name;
    private final boolean counter;
    private int           visibility = 1;

    private final SerializableConsumer<String> consumer;

    public Generator(String name, SerializableConsumer<String> consumer) {
        this(name, false, consumer);
    }

    public Generator(String name, boolean counter, SerializableConsumer<String> consumer) {
        this.name = name;
        this.counter = counter;
        this.consumer = consumer;
    }

    public void generate() {
        IntStream.range(0, visibility).forEach(i -> consumer.accept(name));
    }

    public String getName() {
        return name;
    }

    @Override
    public String getLabel() {
        return name;
    }

    public void setVisibility(int visibility) {
        this.visibility = visibility;
    }

    @Override
    public boolean isCounter() {
        return counter;
    }

    @Override
    public int getVisibility() {
        return visibility;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Generator other = (Generator) obj;
        if (name == null) {
            if (other.name != null)
                return false;
        }
        else if (!name.equals(other.name))
            return false;
        return true;
    }

}