{"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1510423051", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1510423051, "node_id": "IC_kwDOBm6k_c5aBzoL", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-04-16T16:12:14Z", "updated_at": "2023-04-20T05:14:39Z", "author_association": "NONE", "body": "# Javascript Plugin Docs (alpha)\r\n\r\n## Motivation\r\n\r\nThe Datasette JS Plugin API allows developers to add interactive features to the UI, without having to modify the Python source code. \r\n\r\n## Setup\r\n\r\nNo external/NPM dependencies are needed.\r\n\r\nPlugin behavior is coordinated by the Datasette `manager`. Every page has 1 `manager`.\r\n\r\nThere are 2 ways to add your plugin to the `manager`.\r\n\r\n1. Read `window.__DATASETTE__` if the manager was already loaded.\r\n\r\n```js\r\nconst manager = window.__DATASETTE__;\r\n```\r\n\r\n2. Wait for the `datasette_init` event to fire if your code was loaded before the manager is ready. \r\n\r\n```js\r\ndocument.addEventListener(\"datasette_init\", function (evt) {\r\n const { detail: manager } = evt;\r\n \r\n // register plugin here\r\n});\r\n```\r\n\r\n3. Add plugin to the manager by calling `manager.registerPlugin` in a JS file. Each plugin will supply 1 or more hooks with\r\n\r\n- unique name (`YOUR_PLUGIN_NAME`)\r\n- a numeric version (starting at `0.1`), \r\n- configuration value, the details vary by hook. (In this example, `getColumnActions` takes a function)\r\n\r\n```js\r\nmanager.registerPlugin(\"YOUR_PLUGIN_NAME\", {\r\n version: 0.1,\r\n makeColumnActions: (columnMeta) => {\r\n return [\r\n {\r\n label: \"Copy name to clipboard\",\r\n // evt = native click event\r\n onClick: (evt) => copyToClipboard(columnMeta.column),\r\n }\r\n ];\r\n },\r\n });\r\n```\r\n\r\nThere are 2 plugin hooks available to `manager.registerPlugin`:\r\n\r\n- `makeColumnActions` - Add items to the cog menu for headers on datasette table pages\r\n- `makeAboveTablePanelConfigs` - Add items to \"tabbed\" panel above the `` on pages that use the Datasette table template.\r\n\r\nWhile there are additional properties on the `manager`, but it's not advised to depend on them directly as the shape is subject to change.\r\n\r\n4. To make your JS file available as a Datasette plugin from the Python side, you can add a python file resembling [this](https://github.com/simonw/datasette/pull/2052/files#diff-c5ecf3d22075a60d04a4e95da2e15c612cf1bc84e38d777b67ba60dbd156e293) to your plugins directory. Note that you could host your JS file anywhere, it doesn't have to be served from the Datasette statics folder.\r\n\r\nI welcome ideas for more hooks, or feedback on the current design!\r\n\r\n## Examples\r\n\r\nSee the [example plugins file](https://github.com/simonw/datasette/blob/2d92b9328022d86505261bcdac419b6ed9cb2236/datasette/static/table-example-plugins.js) for additional examples.\r\n\r\n## Hooks API Guide\r\n\r\n### `makeAboveTablePanelConfigs`\r\n\r\nProvide a function with a list of panel objects. Each panel object should contain\r\n\r\n1. A unique string `id`\r\n2. A string `label` for the tab\r\n3. A `render` function. The first argument is reference to an HTML [Element](https://developer.mozilla.org/en-US/docs/Web/API/Element). \r\n\r\nExample:\r\n\r\n```js\r\n manager.registerPlugin(\"panel-plugin-graphs\", {\r\n version: 0.1,\r\n makeAboveTablePanelConfigs: () => {\r\n return [\r\n {\r\n id: 'first-panel',\r\n label: \"My new panel\",\r\n render: node => {\r\n const description = document.createElement('p');\r\n description.innerText = 'Hello world';\r\n node.appendChild(description);\r\n }\r\n }\r\n ];\r\n },\r\n });\r\n```\r\n\r\n### `makeColumnActions`\r\n\r\nProvide a function that returns a list of action objects. Each action object has\r\n\r\n1. A string `label` for the menu dropdown label\r\n2. An onClick `render` function.\r\n\r\nExample:\r\n\r\n```js\r\n manager.registerPlugin(\"column-name-plugin\", {\r\n version: 0.1,\r\n getColumnActions: (columnMeta) => {\r\n \r\n // Info about selected column. \r\n const { columnName, columnNotNull, columnType, isPk } = columnMeta;\r\n\r\n return [\r\n {\r\n label: \"Copy name to clipboard\",\r\n onClick: (evt) => copyToClipboard(column),\r\n }\r\n ];\r\n },\r\n });\r\n```\r\n\r\nThe getColumnActions callback has access to an object with metadata about the clicked column. These fields include:\r\n\r\n- columnName: string (name of the column)\r\n- columnNotNull: boolean\r\n- columnType: sqlite datatype enum (text, number, etc)\r\n- isPk: Whether this is the primary key: boolean\r\n\r\nYou can use this column metadata to customize the action config objects (for example, handling different summaries for text vs number columns).\r\n\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/pull/2052#issuecomment-1510423215", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2052", "id": 1510423215, "node_id": "IC_kwDOBm6k_c5aBzqv", "user": {"value": 9020979, "label": "hydrosquall"}, "created_at": "2023-04-16T16:12:59Z", "updated_at": "2023-04-16T16:12:59Z", "author_association": "NONE", "body": "## Research notes\r\n\r\n- I stuck to the \"minimal dependencies\" ethos of datasette (no React, Typescript, JS linting, etc).\r\n- Main threads on JS plugin development\r\n - Main: sketch of pluggy-inspired system: https://github.com/simonw/datasette/issues/983\r\n - Main: provide locations in Datasette HTML that are designed for multiple plugins to safely cooperate with each other (starting with the panel, but eventually could extend to \"search boxes\"): https://github.com/simonw/datasette/issues/1191\r\n - Main: HTML hooks for JS plugin authors: https://github.com/simonw/datasette/issues/987\r\n- Prior threads on JS plugins in Datasette for future design directions\r\n - Idea: pass useful strings to JS plugins: https://github.com/simonw/datasette/issues/1565\r\n - Idea: help with plugin dependency loading: https://github.com/simonw/datasette/issues/1542 . (IMO - the plugin providing the dependency can emit an event once it's done. Other plugins can listen for it, or ask the manager to inform them when the dependency is available). \r\n - Idea: help plugins to manage state in shareable URLs (plugins shouldn't have to interact with the URL directly, should have some basic insulation from clobbering each others' keys): https://github.com/simonw/datasette/issues/1144\r\n- Articles on plugins reviewed\r\n - https://css-tricks.com/designing-a-javascript-plugin-system/\r\n- Plugin/Extension systems reviewed (mostly JS).\r\n - Yarn: https://yarnpkg.com/advanced/plugin-tutorial\r\n - Tappable https://github.com/webpack/tapable (used by Auto, webpack)\r\n - Pluggy: https://pluggy.readthedocs.io/en/stable/\r\n - VSCode: https://code.visualstudio.com/api/get-started/your-first-extension\r\n - Chrome: https://developer.chrome.com/docs/extensions/reference/\r\n - Figma/Figjam Widget: https://www.figma.com/widget-docs/\r\n - Datadog Apps: [Programming Model](https://github.com/DataDog/apps/blob/master/docs/en/programming-model.md)\r\n - Storybook: https://storybook.js.org/docs/react/addons/addons-api", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1651082214, "label": "feat: Javascript Plugin API (Custom panels, column menu items with JS actions)"}, "performed_via_github_app": null}