256 lines
7.4 KiB
Markdown
256 lines
7.4 KiB
Markdown
# 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`
|