SearchArea recipe
Source: 02_SearchArea/ · three flavours of SearchArea shown side-by-side in a Pivot so you can compare them.
How it looks
A search box, a facet sidebar, and a column of result cards. The real SearchArea queries the workspace; the runnable Tesserae preview below sketches the same layout with static data.
What it teaches
- Default —
SearchArea()with nothing customised. Hits the workspace's default search endpoint and renders the standard result cards. - With facets — Pre-filter the query with
OnSearch(sr => sr.SetBeforeTypesFacet("..."))and enable the facet sidebar with.WithFacets(). The right starting point for "search inside a single node type". - Custom row — Adjust the search-result row with
Renderer(r => r.CustomizeResult(result => ...)). You receive the row the node type built, with the search's own marks already on it, and return the same row or another one. Use this when you need a denser list, custom row commands, or to surface fields the default row doesn't show. - Appending a button next to the search box with
SearchBox(b => b.AppendToSearchBox(...)).
Three flavours
// 1. Default
SearchArea()
// 2. Filter to one node type + facet sidebar
SearchArea()
.OnSearch(sr => sr.SetBeforeTypesFacet(nameof(MyNodeType)))
.WithFacets()
// 3. Customize the result row
SearchArea()
.Renderer(r => r.CustomizeResult(result => result
.SetText(null)
.InlineCommands(Button().SetIcon(UIcons.Plus).OnClick(() => Select(result.Result)))))
.SearchBox(b => b.AppendToSearchBox(Button("Action").SetIcon(UIcons.Bolt)))
Adding filters to the search box
The product's main search box understands @file, @webpage, filetype:, ext: and src: (see Search Box Filters). A SearchArea's box starts without them. SearchBox(...) hands you its AdvancedOmniBox, which takes the built-in set and filters of your own:
SearchArea()
.OnSearch(sr => sr.SetBeforeTypesFacet(nameof(Ticket)))
.SearchBox(b => b
.WithBasicSearchFilters()
.RegisterSnapFilter(
new OmniBox.SnapHandler("open", "Open tickets".t(), new[] { "open" }, Icon(UIcons.Inbox), "Only show open tickets".t()),
sr => sr.SetValueFacet("Ticket.Status", keepOtherTypes: false, invertedBehaviour: false, applyBefore: true, values: new[] { "Open" }))
.RegisterFilterSnap(
new OmniBox.FilterSnapHandler("priority", "Priority".t(), new[] { "priority", "prio" }, new[] { "P1", "P2", "P3" }, Icon(UIcons.Flag), "Filter by priority".t()),
(sr, value) => sr.SetValueFacet("Ticket.Priority", keepOtherTypes: false, invertedBehaviour: false, applyBefore: true, values: new[] { value })))
| Method | Adds | The callback receives |
|---|---|---|
WithBasicSearchFilters() |
The five built-in filters from OmniBoxSearchFilters |
n/a |
RegisterSnapFilter(SnapHandler, Action<SearchRequest>) |
An @word chip with no value |
The request being built |
RegisterFilterSnap(FilterSnapHandler, Action<SearchRequest, string>) |
A word:value chip |
The request and the chip's value |
- The callback runs when the search is built, once per chip in the box. Narrow the request there, usually with
SetBeforeTypesFacetorSetValueFacet(..., applyBefore: true, ...). - A
FilterSnapHandlertakes its values as a fixedstring[]or as aFunc<string, Task<string[]>>that receives what the user has typed so far. Use the async form for values that come from the server, as the built-insrc:filter does. - Handlers carry the icon component they render, so build a new handler for each box rather than sharing one instance.
- The id (the first constructor argument) is how a chip is matched back to its callback. Keep it unique within one box.
UI.Neighbors(...) and SimilarSearchArea.SearchBox(...) accept the same Action<AdvancedOmniBox>. None of this changes the main search box, whose filter set is fixed in OmniBoxSearchFilters.
See also
- Migrating to
OmniResult— the recipes for whatWithCardCustomizer/WithCustomizedRenderer/WithRendererused to do. Omni Result— every slot and option on the rowCustomizeResulthands you.- The Connector Recipes cover how to ingest the data this search area queries.