Elemente auf einer Webseite filtern mit GenericElementFilter

Logo generic element filter

 

Kennst du das:

Du brauchst auf einer Seite ein paar Filter für Karten oder Listen, baust schnell etwas mit JavaScript zusammen, auf der nächsten Seite wieder, diesmal ein wenig anders, und nach einem Jahr hast du vier Varianten von fast demselben Filter im Projekt liegen.

Genau das hat mich irgendwann so genervt, dass ich mir eine eigene kleine Library gebaut habe: GenericElementFilter. Ich wollte kein großes Framework nur für ein paar Filter, sondern etwas Leichtes, das mit normalem HTML und ein bisschen JavaScript funktioniert.

In diesem Artikel zeige ich dir die Idee dahinter und am Ende auch, wie du die Library konkret einsetzt.

Warum Filter oft ausufern

Der Klassiker

  • Du brauchst eine einfache Filterfunktion für eine Liste von Elementen
  • Du verbaust ein paar if Abfragen auf Klassen oder Texte
  • Dann kommen zusätzliche Kriterien dazu, zum Beispiel Kategorie, Status, Zeitraum
  • Andere Seite, anderes Projekt, neues Filterproblem, neuer Code

Am Ende ist alles sehr speziell verdrahtet. Besonders nervig wird es, wenn

  • mehrere Filter gleichzeitig gelten sollen
  • es eine vernünftige Anzeige geben soll, wie viele Ergebnisse übrig sind
  • du auf mehreren Seiten das gleiche Muster brauchst

Eigentlich ist das Problem total generisch:

Form Elemente liefern einen Filterzustand,
HTML Elemente tragen Metadaten,
sichtbar bleibt nur, was passt.

Also habe ich mir die Frage gestellt: wie kann ich das einmal sauber lösen und dann immer wieder benutzen.

Die Grundidee: name bei den Filtern, data Attribute auf den Elementen

Die Basis von GenericElementFilter ist sehr einfach:

  • Jedes Filter Element bekommt ein name Attribut
  • Jedes gefilterte Element bekommt passende data Attribute

Beispiel für eine Liste von Veranstaltungen, die du nach Typ und Region filtern willst:

<div class="event-filters">
  <label>
    Art der Veranstaltung
    <select name="type">
      <option value="all">Alle</option>
      <option value="workshop">Workshop</option>
      <option value="talk">Vortrag</option>
      <option value="online">Online Format</option>
    </select>
  </label>

  <label>
    Region
    <select name="region">
      <option value="all">Alle Regionen</option>
      <option value="north">Nord</option>
      <option value="south">Süd</option>
      <option value="east">Ost</option>
      <option value="west">West</option>
    </select>
  </label>
</div>

<div class="event-list">
  <article class="event-card"
           data-type="workshop"
           data-region="south">
    …
  </article>

  <article class="event-card"
           data-type="talk"
           data-region="north">
    …
  </article>
</div>

Das Mapping ist klar:

  • name="type" passt zu data-type
  • name="region" passt zu data-region

In der Library gibt es außerdem das Konzept allValue, standardmäßig ist das der Wert "all". Dieser Wert bedeutet: dieser Filter ist im Moment deaktiviert und wird bei der Berechnung ignoriert. ([GitHub][1])

So entsteht eine einfache Regel:

  • Alle aktiven Filter werden kombiniert
  • Nur Elemente, die alle Kriterien erfüllen, bleiben sichtbar
  • Ein Filter mit allValue ist neutral

Wichtige Aspekte für gute Filter

Bevor ich zur Library komme, einmal kurz die Punkte, die mir wichtig waren und die GenericElementFilter abdeckt:

  1. Mehrere Filter gleichzeitig
    Es gibt eine AND Logik, also zum Beispiel
    Art der Veranstaltung und Region und Verfügbarkeit. ([GitHub][1])
  2. Einfache Deaktivierung
    Über allValue lässt sich jeder Filter mit einer Option wieder ausknipsen, ohne Spezialcode.
  3. Flexible HTML Struktur
    Es ist egal, ob du Karten, Listeneinträge, Tabellenzeilen oder etwas anderes filterst. Die Library schaut nur auf Selektoren und data Attribute. ([GitHub][1])
  4. A11y freundliche Statusmeldung
    Es gibt einen Status Text für sichtbare Elemente und eine getrennte Meldung für den Fall, dass nichts mehr sichtbar ist. Beide kannst du in deinem Markup platzieren und mit aria live auszeichnen. ([GitHub][1])
  5. Unterstützung für Tags
    Wenn du in einem data Attribut mehrere Werte speichern willst, gibt es einen matchMode "contains", der einzelne Tokens erkennt. Praktisch für Tags oder Schlagwörter. ([ulrischa][2])
  6. Animation über View Transitions
    Wenn der Browser die View Transition API unterstützt, kann der Filter Vorgang animiert werden. Dazu gleich mehr. ([GitHub][1])

Warum eine eigene Library und kein Framework

Ich hätte natürlich auch einfach React, Vue oder ein anderes Framework für solche Filter einsetzen können. Für das konkrete Problem war mir das aber zu schwergewichtig.

Ziele der Library:

  • klein und unabhängig
  • nutzbar in bestehenden CMS Templates
  • keine Abhängigkeiten zu einem Framework
  • Konfiguration entweder über JavaScript oder über data Attribute

Daher ist GenericElementFilter eine einzelne Klasse, die du mit einem Konstruktor verwendest oder über eine Hilfsfunktion bindAll, die auf Basis von data Attributen arbeitet. ([GitHub][1])

A11y und Status Meldungen

Für Barrierefreiheit war mir wichtig, dass Nutzerinnen und Nutzer mit Screenreader verstehen, was beim Filtern passiert.

Die Library unterstützt dafür zwei Dinge:

  • einen Status Text, der die Anzahl der sichtbaren Elemente ausgibt
  • eine keine Ergebnisse Meldung, die eingeblendet wird, wenn nichts mehr passt

In deinem HTML kann das so aussehen:

<p class="event-filter-status" aria-live="polite"></p>
<p class="event-no-results" hidden>
  Es wurden keine Veranstaltungen gefunden, die zu Ihrer Auswahl passen.
</p>

Und im JavaScript konfigurierst du das über statusSelector und noResultsSelector. Außerdem kannst du mit statusFormatter steuern, wie die Meldung genau klingt. ([GitHub][1])

Die Bibliothek kümmert sich darum, dass bei jeder Änderung des Filterzustands die passenden Texte gesetzt werden und das Element für die Meldung ohne Treffer ein oder ausgeblendet wird. Du musst nur noch im Markup aria live setzen, damit Screenreader das mitbekommen.

Wie die Animation über View Transitions funktioniert

Ein Filter ist ein klassischer Zustandwechsel: vorher viele Elemente, nachher nur ein Teil. Ohne Animation wirkt das oft wie ein abruptes Wegspringen.

Hier kommt die View Transition API ins Spiel.

In Kurzform:

  1. Die Library prüft, ob document.startViewTransition vorhanden ist.
  2. Wenn du in der Konfiguration einen viewTransitionPrefix angibst, wird der komplette Filter Vorgang in diese Transition gehüllt. ([GitHub][1])
  3. Du kannst in deinem CSS Transition Stile definieren, die auf diesen Prefix aufbauen.

Ein möglicher Konfigurationsausschnitt:

const filter = new GenericElementFilter(".event-card", {
  root: document.querySelector(".event-section"),
  filtersSelector: ".event-filters [name]",
  statusSelector: ".event-filter-status",
  noResultsSelector: ".event-no-results",
  allValue: "all",
  viewTransitionPrefix: "events",
});

In modernen Browsern läuft der Filter dann in einer View Transition. So kannst du zum Beispiel im CSS mit Selektoren arbeiten, die auf diesen Prefix Bezug nehmen, und das Ein und Ausblenden der Karten animieren. In Browsern ohne View Transition API passiert einfach ein normaler Wechsel ohne Animation. Du musst nichts speziell abfangen, das ist automatisch ein progressives Enhancement.

Die Demo Seite zeigt dir diese Mechanik in Aktion, inklusive verschiedener Filtervarianten und Zustände: GenericElementFilter Demo Seite. ([ulrischa][2])

Konkretes Beispiel mit Veranstaltungen

Stellen wir uns vor, du hast eine Seite mit Fortbildungen und willst nach Art der Veranstaltung und Region filtern.

HTML der Filter und Karten

<section class="event-section">
  <div class="event-filters">
    <label>
      Art der Veranstaltung
      <select name="type">
        <option value="all">Alle</option>
        <option value="workshop">Workshop</option>
        <option value="talk">Vortrag</option>
        <option value="online">Online Format</option>
      </select>
    </label>

    <label>
      Region
      <select name="region">
        <option value="all">Alle Regionen</option>
        <option value="north">Nord</option>
        <option value="south">Süd</option>
        <option value="east">Ost</option>
        <option value="west">West</option>
      </select>
    </label>
  </div>

  <p class="event-filter-status" aria-live="polite"></p>
  <p class="event-no-results" hidden>
    Es wurden keine Veranstaltungen gefunden, die zu Ihrer Auswahl passen.
  </p>

  <div class="event-list">
    <article class="event-card"
             data-type="workshop"
             data-region="south">
      <h3>Workshop Klima Kommunikation</h3>
      <p>Präsenzveranstaltung, Region Süd.</p>
    </article>

    <article class="event-card"
             data-type="talk"
             data-region="north">
      <h3>Vortrag Fließgewässer in Zeiten des Klimawandels</h3>
      <p>Präsenzveranstaltung, Region Nord.</p>
    </article>

    <article class="event-card"
             data-type="online"
             data-region="all">
      <h3>Online Reihe Umwelt Daten verständlich erklären</h3>
      <p>Online Format, Teilnahme aus allen Regionen möglich.</p>
    </article>
  </div>
</section>

Die Struktur ist simpel:

  • Filter oben, Karten unten
  • Filter bekommen name
  • Karten bekommen passende data Attribute

GenericElementFilter nutzen

Jetzt kommt die Library ins Spiel. Du bindest das Skript ein, zum Beispiel die gebaute Version aus dem Repository, und initialisierst dann den Filter:

<script src="GenericElementFilter.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
  const filter = new GenericElementFilter(".event-card", {
    root: document.querySelector(".event-section"),
    filtersSelector: ".event-filters [name]",
    statusSelector: ".event-filter-status",
    noResultsSelector: ".event-no-results",
    allValue: "all",
    statusFormatter: (count, total) => {
      if (count === total) {
        return `Alle ${total} Veranstaltungen werden angezeigt.`;
      }
      if (count === 0) {
        return "Es werden keine Veranstaltungen angezeigt.";
      }
      if (count === 1) {
        return "Eine Veranstaltung wird angezeigt.";
      }
      return `${count} Veranstaltungen werden angezeigt.`;
    },
    viewTransitionPrefix: "events",
  });

  // Optional: Reset Button anbinden, falls vorhanden
  const resetButton = document.querySelector(".event-filters-reset");
  if (resetButton) {
    resetButton.addEventListener("click", () => filter.reset());
  }
});
</script>

Was du damit erledigt hast:

  • Alle Filter arbeiten zusammen und filtern die Karten
  • Der Status Text wird automatisch aktualisiert
  • Die Meldung ohne Treffer wird automatisch ein und ausgeblendet
  • Wenn der Browser View Transitions kann, wird der Wechsel animiert

Wenn du eher mit data Attributen arbeiten willst, kannst du dich stark an den Beispielen im README und auf der Demo Seite orientieren. Dort siehst du auch Varianten mit Tabellenzeilen, Tag Filtern und dynamischen Inhalten. ([GitHub][1])


Wenn du tiefer einsteigen willst, schau dir direkt das Repository an:
GitHub GenericElementFilter

Und zum Ausprobieren lohnt sich die Demo:
GenericElementFilter Demo Seite



Als erster einen Kommentar schreiben.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert