Reimplementing Columns Management
This commit is contained in:
91
src/myfasthtml/controls/Sortable.py
Normal file
91
src/myfasthtml/controls/Sortable.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Sortable control for drag-and-drop reordering of list items.
|
||||
|
||||
Wraps SortableJS to enable drag-and-drop on any container, posting
|
||||
the new item order to the server via HTMX after each drag operation.
|
||||
Requires SortableJS to be loaded via create_app(sortable=True).
|
||||
"""
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from fasthtml.components import Script
|
||||
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.instances import MultipleInstance
|
||||
|
||||
logger = logging.getLogger("Sortable")
|
||||
|
||||
|
||||
class Sortable(MultipleInstance):
|
||||
"""
|
||||
Composable control that enables SortableJS drag-and-drop on a container.
|
||||
|
||||
Place this inside a render() method alongside the sortable container.
|
||||
Items in the container must have a ``data-sort-id`` attribute identifying
|
||||
each item. After a drag, the new order is POSTed to the server via the
|
||||
provided command.
|
||||
|
||||
Args:
|
||||
parent: Parent instance that owns this control.
|
||||
command: Command to execute after reordering. Its handler must accept
|
||||
an ``order: list`` parameter receiving the sorted IDs.
|
||||
_id: Optional custom ID suffix.
|
||||
container_id: ID of the DOM element to make sortable. Defaults to
|
||||
``parent.get_id()`` if not provided.
|
||||
handle: Optional CSS selector for the drag handle within each item.
|
||||
If None, the entire item is draggable.
|
||||
group: Optional SortableJS group name to allow dragging between
|
||||
multiple connected lists.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
parent,
|
||||
command: Command,
|
||||
_id: Optional[str] = None,
|
||||
container_id: Optional[str] = None,
|
||||
handle: Optional[str] = None,
|
||||
group: Optional[str] = None):
|
||||
super().__init__(parent, _id=_id)
|
||||
self._command = command
|
||||
self._container_id = container_id
|
||||
self._handle = handle
|
||||
self._group = group
|
||||
|
||||
def render(self):
|
||||
container_id = self._container_id or self._parent.get_id()
|
||||
opts = self._command.ajax_htmx_options()
|
||||
|
||||
js_opts = ["animation: 150"]
|
||||
if self._handle:
|
||||
js_opts.append(f"handle: '{self._handle}'")
|
||||
if self._group:
|
||||
js_opts.append(f"group: '{self._group}'")
|
||||
|
||||
existing_values = ", ".join(f'"{k}": "{v}"' for k, v in opts["values"].items())
|
||||
|
||||
js_opts.append(f"""onEnd: function(evt) {{
|
||||
var items = Array.from(document.getElementById('{container_id}').children)
|
||||
.map(function(el) {{ return el.dataset.sortId; }})
|
||||
.filter(Boolean);
|
||||
htmx.ajax('POST', '{opts["url"]}', {{
|
||||
target: '{opts["target"]}',
|
||||
swap: '{opts["swap"]}',
|
||||
values: {{ {existing_values}, order: items.join(',') }}
|
||||
}});
|
||||
}}""")
|
||||
|
||||
js_opts_str = ",\n ".join(js_opts)
|
||||
|
||||
script = f"""(function() {{
|
||||
var container = document.getElementById('{container_id}');
|
||||
if (!container) {{ return; }}
|
||||
new Sortable(container, {{
|
||||
{js_opts_str}
|
||||
}});
|
||||
}})();"""
|
||||
|
||||
logger.debug(f"Sortable rendered for container={container_id}")
|
||||
return Script(script)
|
||||
|
||||
def __ft__(self):
|
||||
return self.render()
|
||||
Reference in New Issue
Block a user