search: make the navigation form work from every page (AUD-151)
The nav form carried hx-get="/search" hx-target="#search-results", but #search-results exists only on /search. On every other page htmx found no target and silently discarded the response: a search box that looks like it works and does nothing. - SearchForm is now a plain GET with no htmx attributes (navigation and the landing page), so it navigates to /search?q=… from any page and with JavaScript disabled. - SearchFormLive keeps the in-place swap on the search page, where the target does exist. For HX-Request the server now returns the block's *contents* (SearchResultsBody), so the fragment no longer nests a second element with id="search-results" inside the live region. - Empty query shows a prompt instead of an empty list. A zero-match query says "Nothing found" and offers the six areas plus the evidence index. - The result count is role="status" inside the existing aria-live container, so a screen reader reads "4 results for …" rather than a run of titles. Tests (internal/server, internal/site): - the nav form carries no htmx attribute on nine pages, and no built page carries an hx-target whose element is missing (walks all of dist/); - search submitted from /, /edge/ and /platform/ lands on a page with results with no HX-Request header, i.e. the JavaScript-disabled path; - empty query, zero match and the announced count; - a content change followed by a build is visible in dist/search.db. That is the deploy path: deploy/deploy.sh runs `make dist` before it stages a release, and search.Build removes and rewrites the index every build. DoD: search from home, /edge and /platform lands on results; works with JavaScript disabled; zero-match query shows a message; content change followed by a deploy is reflected in search; no htmx request targets a missing element. (cherry picked from commit a56c795768e1e9b10204aae04c3612d7a96775a7)
This commit is contained in:
@@ -101,17 +101,43 @@ templ Breadcrumbs(crumbs []Crumb) {
|
||||
}
|
||||
}
|
||||
|
||||
// SearchForm is a GET form: it works with JavaScript disabled and is upgraded by
|
||||
// HTMX when scripts are available.
|
||||
// SearchForm is the plain GET form used in the navigation and the landing page.
|
||||
// It navigates to /search and carries no htmx attributes, because #search-results
|
||||
// only exists on the search page: an hx-target that is not in the document makes
|
||||
// htmx discard the response, which is a form that looks like it works and does
|
||||
// nothing. A plain GET works from every page and with JavaScript disabled.
|
||||
templ SearchForm(q string, class string) {
|
||||
<form class={ "search", class } action="/search" method="get" role="search"
|
||||
hx-get="/search" hx-target="#search-results" hx-swap="innerHTML" hx-push-url="true">
|
||||
<label class="visually-hidden" for={ "q-" + class }>Search the site</label>
|
||||
<input id={ "q-" + class } type="search" name="q" value={ q } placeholder="Search — try “zigbee”, “signage”, “routing”" autocomplete="off"/>
|
||||
<form class={ "search", class } action="/search" method="get" role="search">
|
||||
@searchField(q, class)
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
}
|
||||
|
||||
// SearchFormLive is the search page's own form. HTMX swaps the results block in
|
||||
// place, which is safe here and only here: #search-results is part of the page.
|
||||
templ SearchFormLive(q string, class string) {
|
||||
<form
|
||||
class={ "search", class }
|
||||
action="/search"
|
||||
method="get"
|
||||
role="search"
|
||||
hx-get="/search"
|
||||
hx-target="#search-results"
|
||||
hx-swap="innerHTML"
|
||||
hx-push-url="true"
|
||||
>
|
||||
@searchField(q, class)
|
||||
<button type="submit">Search</button>
|
||||
</form>
|
||||
}
|
||||
|
||||
// searchField is the label and input both forms share. The id is derived from the
|
||||
// form class, so two forms on one page never collide.
|
||||
templ searchField(q string, class string) {
|
||||
<label class="visually-hidden" for={ "q-" + class }>Search the site</label>
|
||||
<input id={ "q-" + class } type="search" name="q" value={ q } placeholder="Search — try “zigbee”, “signage”, “routing”" autocomplete="off"/>
|
||||
}
|
||||
|
||||
// Stage is the presentation panel. Everything about how it is fed comes from
|
||||
// the binding: route mode swaps in pre-rendered fragments, stream mode listens
|
||||
// on an event stream. The markup is identical either way.
|
||||
|
||||
Reference in New Issue
Block a user