.. _javascript_plugins: JavaScript plugins ================== Datasette can run custom JavaScript in several different ways: - Datasette plugins written in Python can use the :ref:`extra_js_urls() ` or :ref:`extra_body_script() ` plugin hooks to inject JavaScript into a page - Datasette instances with :ref:`custom templates ` can include additional JavaScript in those templates - The ``extra_js_urls`` key in ``datasette.yaml`` :ref:`can be used to include extra JavaScript ` There are no limitations on what this JavaScript can do. It is executed directly by the browser, so it can manipulate the DOM, fetch additional data and do anything else that JavaScript is capable of. .. warning:: Custom JavaScript has security implications, especially for authenticated Datasette instances where the JavaScript might run in the context of the authenticated user. It's important to carefully review any JavaScript you run in your Datasette instance. .. _javascript_datasette_init: The datasette_init event ------------------------ Datasette emits a custom event called ``datasette_init`` when the page is loaded. This event is dispatched on the ``document`` object, and includes a ``detail`` object with a reference to the :ref:`datasetteManager ` object. Your JavaScript code can listen out for this event using ``document.addEventListener()`` like this: .. code-block:: javascript document.addEventListener("datasette_init", function (evt) { const manager = evt.detail; console.log("Datasette version:", manager.VERSION); }); .. _javascript_datasette_manager: datasetteManager ---------------- The ``datasetteManager`` object ``VERSION`` - string The version of Datasette ``plugins`` - ``Map()`` A Map of currently loaded plugin names to plugin implementations ``registerPlugin(name, implementation)`` Call this to register a plugin, passing its name and implementation ``makeColumnField(context)`` Calls the ``makeColumnField()`` hook on registered plugins, returning the first custom insert/edit field control that matches the provided field context. This is used internally by Datasette's row insert and edit dialogs. ``selectors`` - object An object providing named aliases to useful CSS selectors, :ref:`listed below ` .. _javascript_plugin_objects: JavaScript plugin objects ------------------------- JavaScript plugins are blocks of code that can be registered with Datasette using the ``registerPlugin()`` method on the :ref:`datasetteManager ` object. The ``implementation`` object passed to this method should include a ``version`` key defining the plugin version, and one or more of the following named functions providing the implementation of the plugin: .. _javascript_plugins_makeJumpSections: makeJumpSections() ~~~~~~~~~~~~~~~~~~ This method should return a JavaScript array of objects defining additional sections to be added to the blank state of the ``/`` jump menu, before the user starts typing a search. Each object should have the following: ``id`` - string A unique string ID for the section, for example ``agent-chat`` ``render(node, context)`` - function A function that will be called with a DOM node to render the section into The ``context`` object has the following keys: ``navigationSearch`` The ```` custom element instance. This example shows how a plugin might add a button for starting a new chat: .. code-block:: javascript document.addEventListener('datasette_init', function(ev) { ev.detail.registerPlugin('agent-plugin', { version: 0.1, makeJumpSections: () => { return [ { id: 'agent-chat', render: node => { node.innerHTML = ''; node.querySelector('button').addEventListener('click', () => { location.href = '/-/agent/new'; }); } } ]; } }); }); .. _javascript_plugins_makeAboveTablePanelConfigs: makeAboveTablePanelConfigs() ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This method should return a JavaScript array of objects defining additional panels to be added to the top of the table page. Each object should have the following: ``id`` - string A unique string ID for the panel, for example ``map-panel`` ``label`` - string A human-readable label for the panel ``render(node)`` - function A function that will be called with a DOM node to render the panel into This example shows how a plugin might define a single panel: .. code-block:: javascript document.addEventListener('datasette_init', function(ev) { ev.detail.registerPlugin('panel-plugin', { version: 0.1, makeAboveTablePanelConfigs: () => { return [ { id: 'first-panel', label: 'First panel', render: node => { node.innerHTML = '

My custom panel

This is a custom panel that I added using a JavaScript plugin

'; } } ] } }); }); When a page with a table loads, all registered plugins that implement ``makeAboveTablePanelConfigs()`` will be called and panels they return will be added to the top of the table page. .. _javascript_plugins_makeColumnActions: makeColumnActions(columnDetails) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This method, if present, will be called when Datasette is rendering the cog action menu icons that appear at the top of the table view. By default these include options like "Sort ascending/descending" and "Facet by this", but plugins can return additional actions to be included in this menu. The method will be called with a ``columnDetails`` object with the following keys: ``columnName`` - string The name of the column ``columnNotNull`` - boolean True if the column is defined as NOT NULL ``columnType`` - string The SQLite data type of the column ``isPk`` - boolean True if the column is part of the primary key It should return a JavaScript array of objects each with a ``label`` and ``onClick`` property: ``label`` - string The human-readable label for the action ``onClick(evt)`` - function A function that will be called when the action is clicked The ``evt`` object passed to the ``onClick`` is the standard browser event object that triggered the click. This example plugin adds two menu items - one to copy the column name to the clipboard and another that displays the column metadata in an ``alert()`` window: .. code-block:: javascript document.addEventListener('datasette_init', function(ev) { ev.detail.registerPlugin('column-name-plugin', { version: 0.1, makeColumnActions: (columnDetails) => { return [ { label: 'Copy column to clipboard', onClick: async (evt) => { await navigator.clipboard.writeText(columnDetails.columnName) } }, { label: 'Alert column metadata', onClick: () => alert(JSON.stringify(columnDetails, null, 2)) } ]; } }); }); .. _javascript_plugins_makeColumnField: makeColumnField(context) ~~~~~~~~~~~~~~~~~~~~~~~~ This method, if present, can provide a custom form field for a column in Datasette's row insert and edit dialogs. It is designed for plugins that :ref:`register custom column types ` using the Python ``register_column_types()`` plugin hook. For example, a plugin that defines a ``file`` column type can use ``makeColumnField()`` to replace a plain text input with a file picker, and a plugin that defines a rich text column type can use it to enhance the field with an editor. Datasette calls ``makeColumnField(context)`` on each registered JavaScript plugin when it renders an editable insert/edit field. Plugins should inspect the ``context`` object and only return a control object if they can handle that field. Otherwise, use a bare ``return;``. The first plugin to return a truthy control object is used for that field. Plugins are called in registration order. If a plugin raises an exception, Datasette logs the error to the browser console and continues to the next plugin. The row dialog tracks the value that will be sent to the insert/update API. The ``context`` object describes the column and form environment; custom controls should read and write field values using the ``field`` helper object passed to ``render(field)``. Context object ^^^^^^^^^^^^^^ ``makeColumnField(context)`` is called with a context object describing the field. The current context object has these keys: ``mode`` - string ``"insert"`` or ``"edit"``. ``database`` - string or null The database name. ``table`` - string or null The table name. ``tableUrl`` - string or null The path to the table page, including any configured :ref:`base URL prefix `. ``column`` - string The column name. ``columnType`` - object or null The configured Datasette column type for this column, if one exists. This is ``null`` if no column type has been configured. If present, this object has exactly these keys: ``type`` - string The :ref:`registered column type name `, matching the ``name`` attribute of the Python ``ColumnType`` subclass. ``config`` - object Configuration for this specific column type assignment. This is ``{}`` if no configuration has been set. ``sqliteType`` - string or null The SQLite affinity for this column, if known. This is one of ``"TEXT"``, ``"INTEGER"``, ``"REAL"``, ``"BLOB"``, ``"NUMERIC"`` or ``null`` if Datasette could not determine the affinity. ``notNull`` - boolean True if the column is defined as ``NOT NULL``. ``isPk`` - boolean True if this column is part of the table's primary key. ``defaultExpression`` - string or null The SQLite default expression for the column, if available. This is ``null`` if the column has no SQLite default. For example, a column defined with ``DEFAULT (datetime('now'))`` will have ``"datetime('now')"`` here. This is the expression from the table schema, not the actual value SQLite will insert. ``form`` - ``HTMLFormElement`` or null The row insert/edit form element. ``dialog`` - ``HTMLDialogElement`` or null The modal dialog element. Returned control object ^^^^^^^^^^^^^^^^^^^^^^^ A plugin that wants to handle a field should return an object. Datasette currently recognizes these properties: ``useTextarea`` - boolean, optional If true, Datasette creates a ``