Files

7.4 KiB

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:

{% load case_widgets %}

1) case_search_widget

Tag Signature

{% 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:

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)

{% 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

{% 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

{% 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

{% 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