Skip to main content

A reusable FastHTML component for Sortable.js-enhanced ordered queue panels with drag-and-drop reorder, keyboard navigation, and HTMX-powered mutations.

Project description

cjm-fasthtml-sortable-queue

Install

pip install cjm_fasthtml_sortable_queue

Project Structure

nbs/
├── config.ipynb      # Configuration dataclass for sortable queue instances.
├── handlers.ipynb    # Tier 1 handler functions for queue reorder, remove, and clear operations.
├── html_ids.ipynb    # HTML element ID generators for sortable queue components.
├── keyboard.ipynb    # Self-contained keyboard system for sortable queue navigation and actions.
├── models.ipynb      # URL bundle and reorder utility functions for sortable queues.
├── rendering.ipynb   # Queue panel and item rendering with callback-based custom content.
├── router.ipynb      # Tier 2 convenience router with auto-wired reorder, remove, and clear endpoints.
└── sortable_js.ipynb # Sortable.js CDN loading and initialization script generation.

Total: 8 notebooks

Module Dependencies

graph LR
    config[config<br/>config]
    handlers[handlers<br/>handlers]
    html_ids[html_ids<br/>html_ids]
    keyboard[keyboard<br/>keyboard]
    models[models<br/>models]
    rendering[rendering<br/>rendering]
    router[router<br/>router]
    sortable_js[sortable_js<br/>sortable_js]

    handlers --> config
    handlers --> models
    handlers --> html_ids
    handlers --> rendering
    keyboard --> config
    keyboard --> html_ids
    keyboard --> models
    rendering --> config
    rendering --> models
    rendering --> html_ids
    router --> html_ids
    router --> config
    router --> handlers
    router --> models

14 cross-module dependencies detected

CLI Reference

No CLI commands found in this project.

Module Overview

Detailed documentation for each module in the project:

config (config.ipynb)

Configuration dataclass for sortable queue instances.

Import

from cjm_fasthtml_sortable_queue.config import (
    SortableQueueConfig,
    get_item_key
)

Functions

def get_item_key(
    config: SortableQueueConfig,  # Queue configuration
    item: dict,  # Queue item dict
) -> str:  # Unique key string for the item
    "Extract the unique key from a queue item using the config's key_fn or key_field."

Classes

@dataclass
class SortableQueueConfig:
    "Configuration for a sortable queue instance."
    
    prefix: str  # HTML ID prefix (e.g., "sq", "sd", "tss")
    key_field: str = 'id'  # Field name to extract item key (simple case)
    key_fn: Optional[Callable[[dict], str]]  # Custom key extraction (overrides key_field if set)
    item_class: str = 'queue-item'  # CSS class for queue items (keyboard selector + styling target)
    handle_class: str = 'drag-handle'  # CSS class for Sortable.js drag handle
    animation_ms: int = 150  # Sortable.js drag animation duration in milliseconds
    queue_title: str = 'Selected'  # Header title text

handlers (handlers.ipynb)

Tier 1 handler functions for queue reorder, remove, and clear operations.

Import

from cjm_fasthtml_sortable_queue.handlers import (
    handle_reorder,
    handle_reorder_by_direction,
    handle_remove,
    handle_clear
)

Functions

def handle_reorder(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints
    items: List[dict],  # Current item list
    new_key_order: List[str],  # Keys in new order (from form_data.getlist("item"))
    render_content: Callable[[dict, int], Any],  # Custom content callback
    **render_kwargs,  # Additional kwargs passed to render_sortable_queue
) -> tuple:  # (reordered_items, rendered_panel)
    "Reorder items by key order and return the updated list and rendered panel."
def handle_reorder_by_direction(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints
    items: List[dict],  # Current item list
    item_key: str,  # Key of item to move
    direction: str,  # "up" or "down"
    render_content: Callable[[dict, int], Any],  # Custom content callback
    **render_kwargs,  # Additional kwargs passed to render_sortable_queue
) -> tuple:  # (reordered_items, rendered_panel)
    "Move an item up or down and return the updated list and rendered panel."
def handle_remove(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints
    items: List[dict],  # Current item list
    remove_key: str,  # Key of item to remove
    render_content: Callable[[dict, int], Any],  # Custom content callback
    **render_kwargs,  # Additional kwargs passed to render_sortable_queue
) -> tuple:  # (updated_items, rendered_panel)
    "Remove an item by key and return the updated list and rendered panel."
def handle_clear(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints
    render_content: Callable[[dict, int], Any],  # Custom content callback
    **render_kwargs,  # Additional kwargs passed to render_sortable_queue
) -> tuple:  # (empty_list, rendered_panel)
    "Clear all items and return an empty list and rendered panel."

html_ids (html_ids.ipynb)

HTML element ID generators for sortable queue components.

Import

from cjm_fasthtml_sortable_queue.html_ids import (
    SortableQueueHtmlIds
)

Functions

def _safe_id(
    value: str,  # Raw value to sanitize
) -> str:  # HTML-safe ID fragment
    "Replace non-alphanumeric characters with hyphens for use in HTML IDs."

Classes

@dataclass
class SortableQueueHtmlIds:
    "HTML element ID generators for a sortable queue instance."
    
    prefix: str  # Instance prefix (e.g., "sq", "sd", "tss")
    
    def container(self) -> str:  # Outer container div ID
            """Queue panel container."""
            return f"{self.prefix}-queue-container"
    
        @property
        def list(self) -> str:  # Sortable <ul> ID
        "Queue panel container."
    
    def list(self) -> str:  # Sortable <ul> ID
            """Sortable queue list element."""
            return f"{self.prefix}-queue-list"
    
        @property
        def empty(self) -> str:  # Empty state div ID
        "Sortable queue list element."
    
    def empty(self) -> str:  # Empty state div ID
            """Empty state placeholder."""
            return f"{self.prefix}-queue-empty"
    
        @property
        def header(self) -> str:  # Header section ID
        "Empty state placeholder."
    
    def header(self) -> str:  # Header section ID
            """Queue header section."""
            return f"{self.prefix}-queue-header"
    
        @property
        def remove_btn(self) -> str:  # Hidden keyboard remove button ID
        "Queue header section."
    
    def remove_btn(self) -> str:  # Hidden keyboard remove button ID
            """Hidden button for keyboard-triggered remove."""
            return f"{self.prefix}-queue-remove-btn"
    
        @property
        def reorder_up_btn(self) -> str:  # Hidden keyboard reorder-up button ID
        "Hidden button for keyboard-triggered remove."
    
    def reorder_up_btn(self) -> str:  # Hidden keyboard reorder-up button ID
            """Hidden button for keyboard-triggered reorder up."""
            return f"{self.prefix}-queue-reorder-up-btn"
    
        @property
        def reorder_down_btn(self) -> str:  # Hidden keyboard reorder-down button ID
        "Hidden button for keyboard-triggered reorder up."
    
    def reorder_down_btn(self) -> str:  # Hidden keyboard reorder-down button ID
            """Hidden button for keyboard-triggered reorder down."""
            return f"{self.prefix}-queue-reorder-down-btn"
    
        @property
        def system_id(self) -> str:  # Keyboard system ID for hierarchy wiring
        "Hidden button for keyboard-triggered reorder down."
    
    def system_id(self) -> str:  # Keyboard system ID for hierarchy wiring
        "Keyboard system identifier for coordinator registration."
    
    def item(
        "Per-item HTML ID."
    
    def as_selector(
            self,
            html_id: str,  # An HTML ID string
        ) -> str:  # CSS selector string
        "Convert an HTML ID to a CSS selector."

keyboard (keyboard.ipynb)

Self-contained keyboard system for sortable queue navigation and actions.

Import

from cjm_fasthtml_sortable_queue.keyboard import (
    create_queue_keyboard_system
)

Functions

def create_queue_keyboard_system(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints for HTMX actions
    zone_focus_classes: tuple = (str(ring(0))),  # CSS classes when queue zone is active
    item_focus_classes: Optional[tuple] = None,  # CSS classes on focused item (default: bg-base-300)
    data_attributes: tuple = (),  # Data attributes to extract (e.g., ("record-id", "provider-id"))
    on_focus_change: Optional[str] = None,  # JS callback on item focus change
    hidden_input_prefix: Optional[str] = None,  # Prefix for hidden state inputs
    system_id: Optional[str] = None,  # Keyboard system ID (auto-generated from ids.system_id if not set)
    show_hints: bool = False,  # Show keyboard hints UI
) -> KeyboardSystem:  # Complete rendered keyboard system
    """
    Create a self-contained keyboard system for the sortable queue.
    
    Returns a rendered KeyboardSystem with a single FocusZone (LinearVertical
    navigation) and built-in actions for Delete/Backspace remove and
    Shift+Arrow reorder. Works standalone or as a child in a hierarchy
    via `coord.setParent(system_id, parent_id)`.
    """

Variables

_DEFAULT_ITEM_FOCUS_CLASSES

models (models.ipynb)

URL bundle and reorder utility functions for sortable queues.

Import

from cjm_fasthtml_sortable_queue.models import (
    SortableQueueUrls,
    reorder_by_keys,
    reorder_by_direction
)

Functions

def reorder_by_keys(
    items: List[dict],  # Current item list
    new_key_order: List[str],  # Keys in desired order (from Sortable.js form data)
    key_fn: Callable[[dict], str],  # Function to extract key from item
) -> List[dict]:  # Reordered item list
    "Reorder items to match the key order from Sortable.js form data."
def reorder_by_direction(
    items: List[dict],  # Current item list
    item_key: str,  # Key of item to move
    direction: str,  # "up" or "down"
    key_fn: Callable[[dict], str],  # Function to extract key from item
) -> List[dict]:  # Reordered item list
    "Move an item up or down by swapping with its neighbor."

Classes

@dataclass
class SortableQueueUrls:
    "URL endpoints for sortable queue HTMX operations."
    
    reorder: str  # POST — Sortable.js drag-end reorder
    remove: str  # POST — Remove item from queue
    clear: str  # POST — Clear all items

rendering (rendering.ipynb)

Queue panel and item rendering with callback-based custom content.

Import

from cjm_fasthtml_sortable_queue.rendering import (
    render_queue_item,
    render_sortable_queue
)

Functions

def render_queue_item(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints
    item: dict,  # Queue item data
    index: int,  # 0-based position in queue
    render_content: Callable[[dict, int], Any],  # Callback for custom item content
    extra_attrs: Optional[dict] = None,  # Additional HTML attributes per item
) -> Any:  # Li element
    "Render a single queue item with drag handle, position, custom content, and remove button."
def _render_default_empty() -> Any:  # Empty state element
    "Default empty state when no items are in the queue."
def render_sortable_queue(
    config: SortableQueueConfig,  # Queue configuration
    ids: SortableQueueHtmlIds,  # HTML ID generators
    urls: SortableQueueUrls,  # URL endpoints
    queue_items: List[dict],  # Ordered list of queue item dicts
    render_content: Callable[[dict, int], Any],  # Callback for custom item content
    render_empty: Optional[Callable[[], Any]] = None,  # Custom empty state (default provided)
    render_header_actions: Optional[Callable[[List[dict], SortableQueueUrls], Any]] = None,  # Custom header actions
    render_footer: Optional[Callable[[List[dict]], Any]] = None,  # Optional footer content
    extra_item_attrs: Optional[Callable[[dict], dict]] = None,  # Additional data-* attributes per item
    container_classes: tuple = (),  # Additional classes on container div
) -> Any:  # Queue panel element
    "Render the complete sortable queue panel."

router (router.ipynb)

Tier 2 convenience router with auto-wired reorder, remove, and clear endpoints.

Import

from cjm_fasthtml_sortable_queue.router import (
    init_sortable_queue_router
)

Functions

def init_sortable_queue_router(
    config: SortableQueueConfig,  # Queue configuration
    get_items: Callable[[str], List[dict]],  # (session_id) -> current items
    set_items: Callable[[str, List[dict]], None],  # (session_id, items) -> persist
    render_content: Callable[[dict, int], Any],  # Custom content callback
    on_mutate: Optional[Callable[[str, List[dict], str], tuple]] = None,  # (mutation_type, items, sess) -> OOB elements
    prefix: str = "/queue",  # Route prefix
    **render_kwargs,  # Additional kwargs passed to render_sortable_queue
) -> Tuple[APIRouter, SortableQueueUrls]:  # (router, urls) tuple
    """
    Initialize a router with reorder, remove, and clear endpoints.
    
    The `on_mutate` callback receives the mutation type ("reorder", "remove",
    "clear"), the updated items list, and the session ID. It should return a
    tuple of OOB elements to append to the response, or an empty tuple.
    """

sortable_js (sortable_js.ipynb)

Sortable.js CDN loading and initialization script generation.

Import

from cjm_fasthtml_sortable_queue.sortable_js import (
    SORTABLE_JS_CDN,
    sortable_js_headers,
    generate_sortable_init_script
)

Functions

def sortable_js_headers() -> tuple:  # Tuple of Script elements for app headers
    "CDN script tag for Sortable.js, suitable for FastHTML app headers."
def generate_sortable_init_script(
    handle_class: str = "drag-handle",  # CSS class for drag handle elements
    animation_ms: int = 150,  # Drag animation duration in milliseconds
) -> Script:  # Script element with Sortable.js initialization
    """
    Generate htmx.onLoad script that initializes Sortable.js on `.sortable` elements.
    
    Includes the disable-on-drag/re-enable-on-swap pattern to prevent
    double-firing during HTMX response processing.
    """

Variables

SORTABLE_JS_CDN = 'https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js'

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cjm_fasthtml_sortable_queue-0.0.4.tar.gz (24.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cjm_fasthtml_sortable_queue-0.0.4-py3-none-any.whl (25.4 kB view details)

Uploaded Python 3

File details

Details for the file cjm_fasthtml_sortable_queue-0.0.4.tar.gz.

File metadata

File hashes

Hashes for cjm_fasthtml_sortable_queue-0.0.4.tar.gz
Algorithm Hash digest
SHA256 aa1a3761b0ff2d8d53a39d386fbfc9cc9027815d630917b1fe27e3ea310abda2
MD5 d44bfd55e204c6233831a6d88dd33c46
BLAKE2b-256 e555416d809291436d15b76f3c58f1bb888ff52c3a16b1c4205ba8ac99ff4715

See more details on using hashes here.

File details

Details for the file cjm_fasthtml_sortable_queue-0.0.4-py3-none-any.whl.

File metadata

File hashes

Hashes for cjm_fasthtml_sortable_queue-0.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 fe1b335b9797d8e575129e34fd24ba9595aa30125093cbe0ac3184746e7e15cb
MD5 e41b39cb65382e28e6c48c6f84dace06
BLAKE2b-256 a5d360a35256e99d9e4547855554a30f132b0ae9f6518c683ad829e9cedefe89

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page