{# Filter als Adressen (Etappe 3, Task 4, Spec §8). **Jeder Zustand ist eine Adresse**, und jeder Weg aus einer Liste heraus nimmt die uebrigen Filter mit. Genau dort gehen sie sonst still verloren: Ein Klick auf ein Jahr hob bis Task 3 den Statusfilter auf, ohne dass jemand ihn aufgehoben haette. Darum entsteht JEDE Adresse an einer Stelle: `filteradresse()` in `vorlage.py`, hier als Funktion aufrufbar. Sie liefert rohen Text, der beim Einsetzen ins Attribut genau EINMAL escapt wird; ein Jinja-Makro koennte das nicht, weil sein Ergebnis `Markup` ist und ein `|ziel` dahinter die Kennzeichnung verliert (der Link zeigte dann auf einen Parameter namens «amp;jahr», gemessen am 2026-09-03). #} {# Alle geltenden Filter als verborgene Felder — ausser dem, den das Formular selbst setzt. **Nicht einzeln aufgezaehlt.** Genau so hat das Wechselformular der Werkzeugleiste `status` und `jahr` verloren: Wer eine Zeile mit `{% if %}` schreibt, denkt an den Wert, den er gerade vermisst (Code-Review 2026-09-03). Die Liste kommt darum aus `FILTERNAMEN`, und ein weiterer Filter reist ohne Aenderung an dieser Datei mit. #} {% import "bausteine/bausatz.html" as b %} {% macro verborgene(f, ohne=()) -%} {#- **`ohne` ist eine LISTE, kein Name.** Die Filterleiste setzt drei Felder auf einmal; mit einem einzelnen Namen stuende jedes uebrige zweimal im Formular — einmal als verborgenes Feld mit dem alten Wert, einmal als Auswahl mit dem neuen. Der Browser sendet beide, und der Server liest das erste. Ein String bricht darum ab, statt sich als Teilzeichenkette zu verhalten («status» steckt in «statusfilter»). -#} {%- if ohne is string %}{{ OHNE_IST_EINE_LISTE_VON_NAMEN_KEIN_EINZELNER_NAME }}{% endif -%} {%- for name in FILTERNAMEN if name not in ohne and f.get(name) %}{% endfor -%} {%- endmacro %} {# Was gerade gilt, mit einem Weg heraus je Filter. «Alles zuruecksetzen» steht daneben, weil sonst drei Klicks noetig sind. **Jede Aussage steht genau einmal.** Den Status zeigt die Kachel, das Jahr sein Auswahlfeld; beide zeigen, was gilt, und beide führen zurück. Eine Kapsel daneben wäre eine zweite Aussage über dieselbe Sache, und die Sichtprobe zu Task 4 zeigte genau das: «2026» im Facettentitel, «2026» markiert in der Liste und «2026 ×» darunter. Was keine eigene Anzeige hat, ist die Suche. **Die Zeile erscheint trotzdem, sobald ZWEI Filter gelten**: Dann ist «Alles zuruecksetzen» der kuerzeste Weg, und es braucht einen Ort. #} {% macro kapseln(pfad, f, kapseln_werte, ohne_filter) -%} {#- `kapseln_werte` kommt aus `_filterkapseln` in `main.py`: `{zeigen, kapseln}` — je Filter, der gilt UND keine eigene Anzeige hat, ein `{name, wort, hilfe}`. **`zeigen` ist nicht dasselbe wie «es gibt Kapseln».** Gelten zwei Filter und tragen beide ihre eigene Anzeige, steht hier keine einzige Kapsel — «Alles zuruecksetzen» aber schon, denn drei Klicks sind der Weg, den es sonst braucht. **Warum die Liste in Python entsteht.** Sie zaehlte hier bis Task 2 der Etappe 4 drei Filter einzeln auf, mit je einem `mit_…`-Schalter — und genau diese Bauform hat am Wechselformular zwei Filter verloren (Code-Review 2026-09-03). Jeder Filter braucht ausserdem sein eigenes Wort («Offen», «2026», ««Lift»»), und das ist eine Entscheidung, keine Formatierung: Sie gehoert dorthin, wo die Filter herkommen. -#} {%- if kapseln_werte.zeigen %}
{%- for k in kapseln_werte.kapseln %}{{ k.wort }} {{ k.hilfe }}{% endfor %} {#- **«Alles zurücksetzen» heisst hier so, weil es mehr tut**: Es löscht auch das Suchwort. Der Weg aus dem leeren Filter (der Knopf in den Leerzuständen der Listen) führt seit dem 05.09.2026 überall auf dieselbe Adresse — `_ohne_filter`, also ohne Filter, aber MIT der gewählten Darstellung. Zwei der drei zeigten vorher auf den nackten Pfad und warfen die Sortierung still mit weg; ein Wort, zwei Wirkungen (Code-Review 05.09.2026). Seit dem 05.09.2026 trägt er in allen Listen dasselbe Wort (vorher drei Wörter für eine Handlung), und seit U30 ist es dieses hier, «Alles zurücksetzen», statt «Filter zurücksetzen» wie im Immotool: Die Laienprüfung vom 22.10.2026 las «Filter zurücksetzen» unter einer Suche ohne Filter als «nicht für mich», und der Knopf löscht die Suche mit (Produktentscheid 22.10.2026). -#} Alles zurücksetzen
{%- endif %} {%- endmacro %} {# Die Werkzeugzeile: Suche, Filter und EIN Knopf in einer Zeile (Rückmeldung des Auftraggebers, 04.09.2026: «Kann man Schnellsuche und Filter auf dieselbe Ebene setzen?»). Bis dahin waren es zwei Formulare untereinander, jedes mit eigenem Knopf: erst die Filterleiste mit «Anwenden», darunter das Suchfeld mit «Suchen». Wer beides wollte, lud die Seite zweimal — und die zweite Ladung trug die erste Wahl nur deshalb mit, weil jedes Formular die Werte des anderen als verborgene Felder mitschleppte. **Ein Formular für alles.** Das Suchfeld ist ein Feld unter Feldern, der Knopf schickt sie zusammen ab, und Enter im Suchfeld tut dasselbe wie der Knopf. Die verborgenen Felder tragen nur noch, was die Zeile NICHT selbst setzt: Darstellung (Sortierung, Ansicht) und Filter ohne eigenes Feld. **Der Knopf heisst «Anwenden», nicht «Suchen».** Er tut beides, und ein Wort, das nur die Hälfte nennt, ist eine Zusage, die die andere Hälfte nicht einlöst. **Die Reihenfolge ist die der Anleitung** (`docs/gestaltung.md`, «Tabellen»): Schnellsuche links, dann die Filter, der Knopf am Ende. Die Seite fällt beim Abschicken weg, und das ist richtig: Wer den Filter wechselt, fängt vorn an. Seite 3 einer anderen Menge ist kein Ort. **Der geltende Wert steht IMMER in der Auswahl.** Die Werte kommen aus einer eigenen Erhebung, und die kann ihn nicht kennen: Bei den Meldungen liefert die API nur die Jahre, in denen etwas liegt, und fällt ein Mandant aus, ist die Liste leer. Fehlte er, setzte «Anwenden» den Filter still zurück — die Auswahl wäre dann keine Anzeige des Zustands mehr, sondern eine, die ihn ändert. #} {% macro auswahl(f, feld) -%} {%- set werte = feld.eintraege|map(attribute='wert')|map('string')|list %}

{%- endmacro %} {# Der Rahmen: Suchfeld, Auswahlfelder, Knopf. **Welche Felder es gibt, entscheidet Python** (`_filterfelder` in `main.py`) — aus demselben Grund wie bei den Kapseln: Die Liste treibt HIER beides, die Felder UND die Namen, die nicht zusätzlich als verborgenes Feld mitreisen dürfen. Zwei Aufzählungen von Hand wären zwei Gelegenheiten, sie auseinanderlaufen zu lassen — und ein Filter, der zweimal im Formular steht, sendet zwei Werte, von denen der Server den ersten liest. `q` steht darum ebenfalls in der Ausschlussliste: Das Suchfeld setzt es selbst. **Ein Feld ohne einen einzigen Wert erscheint nicht.** Es wäre eine Auswahl, in der es nichts zu wählen gibt; `_filterfelder` lässt es weg. Die Zeile steht trotzdem — das Suchfeld gibt es auf jeder Liste, auch wenn gerade kein Filter etwas zu wählen hat. #} {% macro leiste(pfad, f, felder, was='') -%} {%- if not felder and not was %}{{ EINE_WERKZEUGZEILE_OHNE_FELD_UND_OHNE_SUCHE_IST_EIN_KNOPF_OHNE_WIRKUNG }}{% endif %}
{#- **`q` steht nur dann NICHT verborgen da, wenn das Suchfeld es setzt.** Unbedingt ausgeschlossen ginge eine geltende Suche beim Klick auf «Anwenden» still verloren, sobald jemand die Zeile ohne `was` aufruft (Code-Review 2026-09-04). -#} {{- verborgene(f, ohne=(['q'] if was else []) + felder|map(attribute='name')|list) }} {%- if was %} {#- **Das Suchfeld ist ein Feld wie die anderen**, mit sichtbarer Beschriftung und derselben Hülle: Ohne sie stand es in der Zeile allein ohne Überschrift, während «Kategorie» und «Jahr» daneben eine trugen — dieselbe Zeile mit zwei Bauformen darin (Sichtprobe 2026-09-04). Der Platzhalter sagt dann, WONACH man sucht. -#}

{%- endif %} {%- for feld in felder %}{{ auswahl(f, feld) }}{% endfor %}

{{ b.knopf("Anwenden", "zweit", klasse="klein") }}

{%- endmacro %}