Source code for htmx_nav.targeting
"""
HTMX request-targeting: the Target condition type, predicates built from
it, low-level HX-Target matching, and Target evaluation.
"""
from collections.abc import Callable
from typing import TypeAlias
from django.contrib.messages import get_messages
from django.http import HttpRequest
Target: TypeAlias = str | Callable[[HttpRequest], bool] | bool
"""Condition deciding whether a partial or swap applies to an HTMX request.
Examples:
.. code-block:: python
"main-content" # Matches HX-Target header
targeting("main-content", "modal")
not_targeting("sidebar")
True
False
"""
def _is_htmx_request(request: HttpRequest) -> bool:
"""Determine whether the request is an HTMX request based on HTTP headers."""
return request.headers.get("HX-Request", "") == "true"
def _htmx_target_header(request: HttpRequest) -> str | None:
"""Resolve the HX-Target header for this request."""
return request.headers.get("HX-Target")
def _dom_id(value: str) -> str:
"""Normalize selector strings like 'div#foo' or '#foo' to 'foo'."""
return value.rsplit("#", 1)[-1]
def _htmx_target_is(target: str | None, dom_id: str) -> bool:
"""Check if an HX-Target header matches a DOM ID across HTMX versions."""
if not target:
return False
return _dom_id(target) == _dom_id(dom_id)
[docs]
def htmx_target_is(request: HttpRequest, *dom_ids: str) -> bool:
"""Check if the request's ``HX-Target`` header matches any given DOM ID.
Args:
request: The incoming HTTP request.
*dom_ids: DOM element IDs to match against (e.g., ``"content"``, ``"#content"``).
Returns:
True if the request target matches any provided ID.
Example:
.. code-block:: python
if htmx_target_is(request, "tab-content", "modal-body"):
...
"""
target = _htmx_target_header(request)
return any(_htmx_target_is(target, dom_id) for dom_id in dom_ids)
[docs]
def targeting(*dom_ids: str) -> Callable[[HttpRequest], bool]:
"""Create a predicate checking if a request targets any specified DOM ID.
Args:
*dom_ids: Target DOM element IDs to match against.
Returns:
A callable taking ``HttpRequest`` and returning True if target matches.
Example:
.. code-block:: python
Swap(
"partials/tabs.html",
target_id="tabs",
include_if=targeting("tab-content", "tabs"),
)
"""
def predicate(request: HttpRequest) -> bool:
return htmx_target_is(request, *dom_ids)
return predicate
[docs]
def not_targeting(*dom_ids: str) -> Callable[[HttpRequest], bool]:
"""Create a predicate checking that a request does not target specified DOM IDs.
Args:
*dom_ids: DOM element IDs to exclude.
Returns:
A callable taking ``HttpRequest`` and returning True if target does not match.
Example:
.. code-block:: python
Swap(
"partials/sidebar.html",
target_id="sidebar",
include_if=not_targeting("main-content"),
)
"""
def predicate(request: HttpRequest) -> bool:
return not htmx_target_is(request, *dom_ids)
return predicate
def _eval_target(spec: Target, request: HttpRequest) -> bool:
"""Evaluate a ``Target`` specification against an HTTP request."""
if spec is True:
return True
if spec is False:
return False
if isinstance(spec, str):
return htmx_target_is(request, spec)
if callable(spec):
return bool(spec(request))
raise TypeError(f"Invalid Target value: {spec!r}")
[docs]
def has_messages(request: HttpRequest) -> bool:
"""Check whether the request has pending Django messages.
Args:
request: The incoming HTTP request.
Returns:
True if pending messages exist in the storage backend.
Example:
.. code-block:: python
Swap(
"partials/messages.html",
target_id="messages",
include_if=has_messages,
)
"""
messages = get_messages(request)
return bool(messages)