diff --git a/atlas/WIDGETS.md b/atlas/WIDGETS.md new file mode 100644 index 00000000..b5468524 --- /dev/null +++ b/atlas/WIDGETS.md @@ -0,0 +1,255 @@ +# Atlas Widgets + +This document describes the reusable search widgets provided by Atlas, how they work, and how to use them safely. + +## Scope + +These widgets are Django inclusion tags implemented in `atlas/templatetags/case_widgets.py` and rendered through partial templates in `atlas/templates/atlas/partials/`. + +Current widgets: +- `case_search_widget` +- `finding_search_widget` +- `displayset_search_widget` + +They are HTMX-first widgets with JavaScript fallback to `fetch`. + +## Load The Tags + +In any template that uses these widgets: + +```django +{% load case_widgets %} +``` + +## 1) case_search_widget + +### Tag Signature + +```django +{% case_search_widget + collection=None + cases=None + recent_cases=None + input_id='case-search-input' + target_id='case-search-results' + selection_mode='single' + action_url=None + show_select_button=False +%} +``` + +### Parameters + +- `collection`: + Optional collection object. When set, search requests include `collection=` and rows render an `Add` action. +- `cases`: + Optional initial case queryset/list for first render. +- `recent_cases`: + Optional initial recent list. If omitted, widget auto-populates with up to 5 recent cases authored by the current user. +- `input_id`: + DOM id for the search input. Must be unique per page. +- `target_id`: + DOM id for results container. Must be unique per page. +- `selection_mode`: + Present in tag API but currently not used by rendering logic. +- `action_url`: + Present in tag API but currently not used by rendering logic. +- `show_select_button`: + When `True`, rows include a `Select` button and clicking rows emits selection events. + +### How It Works + +- Input triggers search on: `keyup changed delay:400ms`. +- HTMX call target: `atlas:case_search` (`/atlas/collection/case_search`). +- The widget includes all local hidden state via: + - `hx-include="closest .case-search-widget"` +- Search results are swapped into the configured `target_id`. +- If JavaScript cannot use HTMX, fallback `fetch` performs the same GET and swaps `innerHTML`. + +### Events Emitted + +The widget dispatches a bubbling custom event from the widget container: + +- Event name: `case:selected` +- Detail payload: + - `casePk` (string) + - `caseTitle` (string) + +Example listener: + +```javascript +document.body.addEventListener('case:selected', function (e) { + const pk = e.detail && e.detail.casePk; + const title = e.detail && e.detail.caseTitle; + if (!pk) return; + // consume selection +}); +``` + +### Result Row Contract + +Rows are rendered with: +- `data-case-pk` +- visible title in `h6` + +Actions per mode: +- `collection` provided: `Add` form + `View` +- no collection, `show_select_button=True`: `Select` + `View` +- no collection, `show_select_button=False`: `View` + +### Common Usage Patterns + +#### A. Case picker behavior (select destination/import target) + +```django +{% case_search_widget + collection=None + input_id='move-series-case-search-input' + target_id='move-series-case-search-results' + show_select_button=True +%} +``` + +Then consume `case:selected` and write `casePk` to your hidden form field. + +#### B. Add to collection flow + +```django +{% case_search_widget + collection=collection + cases=cases + recent_cases=recent_cases + input_id='case-search-input-collection-'|add:collection.pk|stringformat:'s' + target_id='case-search-results-collection-'|add:collection.pk|stringformat:'s' +%} +``` + +No event handling is required; rows provide server-posted `Add` action. + +### Endpoint Notes + +The widget uses `atlas:case_search` for HTMX/fetch updates. That view supports: +- `q` search text +- `collection` optional collection id +- `show_select` optional flag to preserve select-button rendering on refresh + +## 2) finding_search_widget + +### Tag Signature + +```django +{% finding_search_widget + case=None + findings=None + input_id='finding-search-input' + target_id='finding-search-results' +%} +``` + +### Parameters + +- `case`: + Optional case instance; when present, search is filtered to findings linked to series in that case. +- `findings`: + Optional initial findings list. +- `input_id`, `target_id`: + DOM ids. Must be unique per page. + +### How It Works + +- HTMX endpoint: `atlas:finding_search_partial` / `atlas:finding_search`. +- Search query is sent as `q`. +- Case filter (if set) is sent as `case=`. +- Widget shows a case filter badge and a clear (`x`) button. +- Script mirrors active case into dataset fields used by markup insertion workflows: + - `input.dataset.currentCase` + - nearby `textarea.dataset.currentCase` + +### Result Row Contract + +Rows are rendered with: +- `data-finding-pk` +- `data-seriesfinding-pk` +- `data-label` + +These attributes are consumed by `atlas/static/atlas/js/markup_inserter.js` to insert tokens like: +- `[[seriesfinding:|