Skip to content

Listbox

oldmangan edited this page Oct 7, 2026 · 1 revision

A list of rich items that can hold HTML and live widgets, with selection, actions, a filter, and drag-and-drop within and between lists. It is the column type of the planned Kanban. Added in #45.

from wapyt import ListAction, Listbox, ListboxConfig, ProgressBar, ProgressBarConfig

todo = Listbox(ListboxConfig(
    label="To do", list_id="todo", group="board", draggable=True, filterable=True,
    items=[
        {"id": "t1", "title": "Write spec", "subtitle": "Due Friday", "badge": 2},
        {"id": "t2", "title": "Review PR", "meta": "#45"},
    ],
    actions=[ListAction("edit", "Edit", "mdi-pencil"),
             ListAction("delete", "Delete", "mdi-delete", danger=True)],
), container=self.layout.getCell("todo"))

# A live widget inside an item
ProgressBar(ProgressBarConfig(value=40, compact=True), container=todo.item_body("t1"))

def moved(e):                                   # fires on the list the item lands in
    if not api.move(e["id"], e["to_list"], e["to_index"]):
        lists[e["to_list"]].move_item(e["id"], e["from_index"], to=lists[e["from_list"]])

todo.on_move(moved)
todo.on_action(lambda e: open_editor(e["id"]) if e["action"] == "edit" else None)

Mount it with container= (an element or a layout cell), root=, or Layout.add_listbox(id, config).

Items

Items are dicts with an id (id_field, default "id"). Without a template these keys are drawn: title, subtitle, meta, icon (MDI class), badge, and disabled (shown faded, not selectable or draggable). Any other keys are kept and come back in events.

A template draws the item from HTML with {key} placeholders:

ListboxConfig(template='<b>{title}</b> <span class="note">{notesHtml}</span>', items=[
    {"id": "x1", "title": "Ship <MenuBar>", "notesHtml": "<em>done</em> in #44"},
])

Values are escaped, so data cannot inject markup. A key ending in Html is inserted as-is, for HTML your app built and escaped itself (the same rule as ResourceBoard).

Widgets inside items

item_body(id) returns an element inside the item to mount any wapyt widget into (a ProgressBar, a Toolbar, a small Form) or your own elements. The body is never redrawn: the widgets stay alive through update_item and through drags, even into another list.

Buttons, inputs, links and anything else interactive inside an item keep working: pressing them never selects the item or starts a drag. Add the class wapyt-listbox-nodrag to any other element that should behave the same way.

ListAction(id, label, icon, danger) puts icon buttons on every item (shown on hover and keyboard focus, always on touch screens); they fire on_action.

Drag and drop

  • draggable=True lets items be dragged to reorder. drag_handle=True drags only by a grip on the left.
  • Lists with the same group exchange items. accept=False refuses drops from others; copy=True copies items out and leaves them in place (a template library). A copied item keeps its id; ids only need to be unique within one list.
  • Mouse and touch: a drag starts after a few pixels of movement. A placeholder shows where the item will land, the list scrolls near its edges, and Escape cancels.
  • Keyboard: Space picks the focused item up (Ctrl+Space then selects instead), ↑ / ↓ move it, ← / → move it to the neighbouring list of the group, Space or Enter drops it, Escape puts it back. Each step is announced to screen readers.
  • The app has the final say. A drop happens at once and on_move fires on the list the item lands in. If your server refuses, put it back with move_item, which fires nothing.
  • Dragging pauses while the filter box has text.

Options

ListboxConfig
items, id_field The items and their id key
template HTML for each item's content; see above
selection "none", "single" (default) or "multi" (Ctrl / Shift clicks)
draggable, drag_handle Enable dragging; drag by a grip only
group, list_id Lists that exchange items; this list's name in on_move (defaults to label)
accept, copy Take drops from the group (default on); copy items out instead of moving them
actions ListAction buttons on each item
filterable, filter_placeholder A filter box above the list
empty_text, label Empty state; accessible name

Events and methods

Event Payload
on_select {ids, id, items}; id is set when exactly one is selected
on_activate {id, item} on double-click or Enter
on_action {action, id, item}
on_move {id, item, from_list, to_list, from_index, to_index, copy}
on_drag_start {id, item}

Methods: set_items, get_items (current order), get_item, add_item(item, index), update_item(id, fields) (keeps body widgets), remove_item, item_body(id), move_item(id, index, to=None), get_selected_ids, select, clear_selection, set_filter, destroy.

Keyboard

One tab stop per list. ↑ / ↓ / Home / End move between items, Enter activates, Space selects (or picks up when draggable). Tab moves into the focused item's action buttons.

Not yet

Virtual rendering for very long lists (thousands of items).

Clone this wiki locally