feat: Add documentation for Atlas widgets including case_search_widget, finding_search_widget, and displayset_search_widget

This commit is contained in:
Ross
2026-04-28 14:04:48 +01:00
parent 56a644c5c1
commit f7cc51c221
+255
View File
@@ -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=<pk>` 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=<pk>`.
- 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:<pk>|<label>]]`
- `[[finding:<pk>|<label>]]`
## 3) displayset_search_widget
### Tag Signature
```django
{% displayset_search_widget
case=None
displaysets=None
input_id='displayset-search-input'
target_id='displayset-search-results'
%}
```
### Parameters
- `case`:
Optional case instance; when present, search is filtered to display sets in that case.
- `displaysets`:
Optional initial list.
- `input_id`, `target_id`:
DOM ids. Must be unique per page.
### How It Works
- HTMX endpoint: `atlas:displayset_search_partial` / `atlas:displayset_search`.
- Query sent as `q`; case scoping sent as `case=<pk>`.
- Includes case badge + clear button behavior similar to finding widget.
- Also mirrors case context into input/textarea datasets for markup insertion workflows.
### Result Row Contract
Rows are rendered with:
- `data-ds-pk`
- `data-label`
Used by `atlas/static/atlas/js/markup_inserter.js` to insert:
- `[[displayset:<pk>|<label>]]`
## Integration Guidelines
- Keep each widget instance ids unique:
- unique `input_id`
- unique `target_id`
- Prefer one event listener per feature container or page for `case:selected`.
- Do not depend on button text (`Select`, `View`) in JS. Use data attributes and events.
- If your workflow needs selection, set `show_select_button=True` on `case_search_widget`.
- If your workflow is add-to-collection, pass `collection` and rely on row `Add` forms.
## Known Constraints
- `selection_mode` and `action_url` are currently API placeholders for `case_search_widget`; they are not applied by templates/views yet.
- Widget scripts are inline with each widget include. If many instances are rendered, listeners are instance-scoped but still duplicated per include.
## Quick Troubleshooting
- No results update:
- verify `input_id` and `target_id` are unique and present in DOM.
- verify endpoint routes exist and user is authenticated.
- Selection not received:
- ensure `show_select_button=True` and listener is bound (for example on `document.body`).
- inspect `case:selected` payload in DevTools.
- Case scoping not applied in finding/displayset widgets:
- ensure `case` is passed to the tag.
- verify `case=<pk>` appears in network requests.
## Source Files
- `atlas/templatetags/case_widgets.py`
- `atlas/templates/atlas/partials/case_search_widget.html`
- `atlas/templates/atlas/partials/case_search_results.html`
- `atlas/templates/atlas/partials/_case_search_item.html`
- `atlas/templates/atlas/partials/finding_search_widget.html`
- `atlas/templates/atlas/partials/_finding_search_results.html`
- `atlas/templates/atlas/partials/displayset_search_widget.html`
- `atlas/templates/atlas/partials/_displayset_search_results.html`
- `atlas/views.py` (`case_search`, `finding_search`, `displayset_search`)
- `atlas/static/atlas/js/markup_inserter.js`