3. Shareable filters¶
Goal: all / open / closed tabs whose state survives a reload and travels in a link.
Declare a URL signal¶
from situ import Local, Server, Url
search: Local[str] = ""
filter: Url[str] = "all" # all | open | closed — lives in the query string
issues: Server[object]
and add the tabs at the top of the region:
<nav>
<a href="?filter=all" class="{{ 'selected' if filter == 'all' else '' }}">all</a>
<a href="?filter=open" class="{{ 'selected' if filter == 'open' else '' }}">open</a>
<a href="?filter=closed" class="{{ 'selected' if filter == 'closed' else '' }}">closed</a>
<span class="count">{{ issues | length }} shown</span>
</nav>
(The count moved into the <nav>; drop the old <p class="count"> line.)
A Url signal has no storage of its own — the query string is the store. You set it with a link; the server reads it back: context()'s view parameter (wired in part 1) carries the current URL signals with their declared defaults, so service.visible(which) already filters and the selected class renders server-side. There was nothing else to build.
Restart and click closed: the URL becomes /?filter=closed, the page navigates, the server renders the filtered list. Copy the URL into another tab — same view. Bookmark it, share it; that is the point of the site.
The trade-off, on purpose¶
Type something into the search box, then click a filter tab. The search box clears.
A Url change is a real navigation — the page reloads, and in-progress Local state (your search text) resets to its defaults. That is the deal this site offers: link semantics in exchange for page lifetime. Both behaviors are correct for different state — which is exactly why situ puts the choice on each signal, while most stacks decide it once for the whole app. Had you wanted the filter to keep the search text alive, filter could be Local; it would then stop being shareable.
One more property you get for free: when commands arrive in part 5, the current URL signals ride along on every POST automatically, so the server re-renders the region for the filter you are looking at.