Support for <button> items in action menus

Closes #2782

Animated demo: https://github.com/simonw/datasette/pull/2781#issuecomment-4703303274
This commit is contained in:
Simon Willison 2026-06-14 15:58:37 -07:00
commit 4ce2888e79
7 changed files with 179 additions and 20 deletions

View file

@ -288,6 +288,14 @@ element that wraps the HTML for that row. Datasette uses this attribute to find
the element to remove after a delete, or replace after an edit. Any edit or
delete controls should be rendered inside that same element.
The ``_action_menu.html`` template renders the action menus used by database,
table, query and row pages. Plugin-provided actions can be link dictionaries
with ``href`` and ``label`` keys, or button dictionaries using ``{"type":
"button", "label": "...", "attrs": {...}}`` for JavaScript-backed interactions.
Both shapes can include an optional ``description`` key. Custom
``_action_menu.html`` templates should preserve support for both link and button
action items.
.. _custom_pages:
Custom pages

View file

@ -1909,7 +1909,80 @@ Action hooks
Action hooks can be used to add items to the action menus that appear at the top of different pages within Datasette. Unlike :ref:`menu_links() <plugin_hook_menu_links>`, actions which are displayed on every page, actions should only be relevant to the page the user is currently viewing.
Each of these hooks should return return a list of ``{"href": "...", "label": "..."}`` menu items, with optional ``"description": "..."`` keys describing each action in more detail.
Each of these hooks should return a list of menu items, with optional ``"description": "..."`` keys describing each action in more detail.
The most common action item is a link to another page:
.. code-block:: python
{
"href": datasette.urls.path("/-/custom-action"),
"label": "Custom action",
"description": "Run this action on a separate page.",
}
Plugins can also return button actions for JavaScript-backed interactions:
.. code-block:: python
{
"type": "button",
"label": "Open custom dialog",
"description": "Show a dialog without leaving this page.",
"attrs": {
"aria-label": "Open custom dialog",
"data-plugin-action": "open-custom-dialog",
},
}
These are rendered as ``<button type="button" class="button-as-link action-menu-button" role="menuitem" tabindex="-1">``. The optional ``attrs`` dictionary is added to the button, and is useful for ``data-*`` attributes that your plugin's JavaScript can use to attach event handlers.
Here is a minimal plugin example that adds a button to a table page and loads JavaScript to handle clicks on that button:
.. code-block:: python
from datasette import hookimpl
@hookimpl
def table_actions(datasette, database, table):
return [
{
"type": "button",
"label": "Show table name",
"description": "Open a JavaScript-powered plugin action.",
"attrs": {
"aria-label": "Show table name",
"data-plugin-action": "show-table-name",
"data-database": database,
"data-table": table,
},
}
]
@hookimpl
def extra_js_urls(datasette):
return [
datasette.urls.static_plugins(
"datasette_show_table",
"show-table.js",
)
]
The ``static/show-table.js`` file in that plugin could look like this:
.. code-block:: javascript
document.addEventListener("click", (event) => {
const button = event.target.closest(
"button[data-plugin-action='show-table-name']"
);
if (!button) {
return;
}
alert(`${button.dataset.database}.${button.dataset.table}`);
});
They can alternatively return an ``async def`` awaitable function which, when called, returns a list of those menu items.