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)