# 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`
