Einfach- und Mehrfachfilter mit List.js
Monday, 13. February 2023
List.js ist ein netter Helfer, um Listen mittels Javascript durchsuch- und filterbar zu machen. Ich nutze es auf sustaphones für genau das.
List.js’ Anwendung ist einfach zu verstehen. Du hast im HTML irgendeine List mit der id target. Was darin gefiltert werden soll bekommt Klassen, z.B. <span class="vendor">Der Wert</span>
, deren Namen in ein options-Array gepackt und danach dem zu erstellendem List-Objekt übergeben werden:
var options = { valueNames: [ 'devicename', 'vendor'] }; listObj = new List('target', options);
Wenn jetzt auf listObj
Funktionen wie .search
oder .filter
ausgeführt werden, passt das auch die HTML-Liste im Browser an, also was der Nutzer sieht. Beim Suchen via einem Texteingabefeld (hier mit der ID search-field) ist das trivial:
document.querySelector('#search-field').addEventListener('keyup', function(e) { listObj.search(this.value); });
Aber das Filtern ist etwas komplizierter.
Einfachfilter
Für die verschiedenen Filter nehmen wir jeweils ein Select-Element:
<select data-target="vendor"> <option>All</option> <option>Vendor 1</option> <option>Vendor 2</option> </select>
Nun können wir auf ein Ändern der Auswahl reagieren und die Liste filtern:
var filters = {}; document.querySelectorAll('select').forEach(function(select) { select.addEventListener('change', function(e) { if (e.target.selectedIndex === 0) { // Der erste Eintrag wurde gewählt, also soll der Filter deaktiviert werden: delete filters[select.dataset.target]; } else { // Ansonsten setzen wir den Filter für "vendor" auf die gewählte Option: filters[select.dataset.target] = e.target.value; } // Jetzt ist der Filter konfiguriert, wir können die Liste durchgehen und die Filter anwenden: listObj.filter(function(item) { for (var i=0; i < Object.keys(filters).length; i++) { // Und hier ist die Hauptabfrage: Wenn der Wert ungleich ist geben wir false zurück, dadurch // wird das Element aus der Liste entfernt if (item.values()[Object.keys(filters)[i]].trim() != Object.values(filters)[i]) { return false; } } return true; }); }); });
Mehrfachfilter
Mit dem oberen System können mehrere Filter gleichzeitig an sein, aber was ist, wenn mehrere Werte eines einzelnen Filters gleichzeitig angezeigt werden sollen? Das geht mit der obigen Lösung nicht – das Select-Element kann nur eine Option auswählen und der JS-Code macht einen Abgleich mit !=, erwartet also einen einzelnen String.
Aber beides können wir ändern.
Zuerst setzen wir das Select-Element auf Mehrfachauswahl:
<select data-target="vendor" multiple> <option>All</option> <option>Vendor 1</option> <option>Vendor 2</option> </select>
Auf dem Desktop können jetzt Leute mit STRG + Klick mehrere Elemente auswählen, bei Mobilbrowsern hat das Select-Popup nun Checkboxen statt Radioboxen.
Und beim JS-Code bauen wir uns ein Set und schauen, ob das jeweilige Element da drin ist:
var filters = {}; document.querySelectorAll('select').forEach(function(select) { select.addEventListener('change', function(e) { if (e.target.selectedOptions.length == 0 || (e.target.selectedOptions.length == 1 && e.target.selectedIndex === 0)) { // Die Prüfung auf das erste "All" ist die zweite Bedingung. Ansonsten deaktivieren // wir den Filter wenn die Auswahl leer ist delete filters[select.dataset.target] } else { // Hier kommen alle aktive Elemente aus dem Select in ein Set (eine Liste ohne doppelte Werte) filters[select.dataset.target] = new Set(Array.from( e.target.selectedOptions).map(({ value }) => value) ); } listObj.filter(function(item) { for (var i=0; i < Object.keys(filters).length; i++) { // Bei der Filterung müssen wir jetzt nur prüfen, ob das Element im Set enthalten ist if (! Object.values(filters)[i].has(item.values()[Object.keys(filters)[i]].trim())) { return false; } } return true; }); }); });
Hiermit sollten die Filter schon wie gewünscht funktionieren, sodass nach mehreren Werten für eine Kategorie gefiltert werden kann.
Bei mir musste ich noch ein bisschen was drumrumstricken. So sehen diese Select-Element mit multiple
etwas komisch aus, sodass ich sie erst nach Klick auf einen Button einblenden lasse. Und dieser Button brauchte dann einen Indikator um anzuzeigen, dass sein Filter aktiv ist. Aber die beste Lösung für solche Dinge wird in jedem Design anders sein, daher lasse ich das hier weg.