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

- Zusammenfassung
Die Zusammenfassung informiert den Nutzer kompakt über die Anzahl der aktiven Suchkriterien. - 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
- 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.
- Die Zusammenfassung zeigt die Anzahl an aktiven Suchkriterien.

Suchen

Die Suche lässt sich auf zwei Wegen auslösen.
- Wenn ein Eingabefeld fokussiert ist, dann lässt sich über die Eingabe Taste die Suche auslösen.
- Über die Suchen Taste.
- 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:
- Die Suchkriterien werden zurückgesetzt. D.h. die eingegebenen Werte werden entfernt bzw. auf den Default zurück gesetzt.
- 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.
- 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.

- 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.
- Tooltip: Ein Hinweis gibt den Nutzern zu verstehen von welchen Eingabefeldern die Auswahl ggf. beeinflusst wird.
- 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.
- Modal Dialog Größe: Verwende für den Dialog mindestens einen Modal Dialog der Größe M.
- 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.
- 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.
- 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.
- Modal Dialog Größe: Verwende für den Dialog mindestens einen Modal Dialog der Größe M.
- 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.
- Modal Dialog Größe: Verwende für den Dialog mindestens einen Modal Dialog der Größe M.
- Checkboxen: Einzelne Tabellenspalten lassen sich über Checkboxen ein und ausblenden. Es muss dabei mindestens eine Tabellenspalte eingeblendet sein.
- 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.
- Modal Dialog Größe: Verwende für den Dialog mindestens einen Modal Dialog der Größe M.
- 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;
}
}