{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import param\n", "import panel as pn\n", "\n", "js_files = {\n", " 'mdc': 'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.js'\n", "}\n", " \n", "css_files = [\n", " 'https://unpkg.com/material-components-web@latest/dist/material-components-web.min.css'\n", "]\n", "\n", "pn.extension(js_files=js_files, css_files=css_files)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ " When building custom applications and dashboards it is frequently useful to extend Panel with custom components, which are specific to a particular application. Panel provides multiple mechanisms to extend and compose diffferent components or even add entirely new components. In this user guide we will go over these approaches and compare the benefits and drawbacks.\n", "\n", "## Viewer components\n", "\n", "The simplest way to extend Panel is to implement so called `Viewer` components. These components simply wrap other Panel object and make it possible to compose them as a unit just like any native Panel component. The core mechanism that makes this possible is the implementation of a ``__panel__`` method on the class, which Panel will call when displaying the component.\n", "\n", "Below we will declare a composite `EditableRange` component made up of two `FloatInput` widgets. The class creates the widgets and then sets up callbacks to sync the parameters on the underlying widgets with the parameters on the `Viewer` component and then implements the ``__panel__`` method, which returns the Panel layout to be rendered when displaying the component:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from panel.viewable import Viewer\n", "\n", "class EditableRange(Viewer):\n", " \n", " value = param.Range(doc=\"A numeric range.\")\n", " \n", " width = param.Integer(default=300)\n", " \n", " def __init__(self, **params):\n", " self._start_input = pn.widgets.FloatInput()\n", " self._end_input = pn.widgets.FloatInput(align='end')\n", " super().__init__(**params)\n", " self._layout = pn.Row(self._start_input, self._end_input)\n", " self._sync_widgets()\n", " \n", " def __panel__(self):\n", " return self._layout\n", " \n", " @param.depends('value', 'width', watch=True)\n", " def _sync_widgets(self):\n", " self._start_input.name = self.name\n", " self._start_input.value = self.value[0]\n", " self._end_input.value = self.value[1]\n", " self._start_input.width = self.width//2\n", " self._end_input.width = self.width//2\n", " \n", " @param.depends('_start_input.value', '_end_input.value', watch=True)\n", " def _sync_params(self):\n", " self.value = (self._start_input.value, self._end_input.value)\n", " \n", "range_widget = EditableRange(name='Range', value=(0, 10))\n", "\n", "pn.Column(\n", " '## This is a custom widget',\n", " range_widget\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Implementing a component by subclassing the `Viewer` baseclass gives the component a number of useful affordances:\n", " \n", "* It renders itself in a notebook (like all other Panel components)\n", "* It can be placed in a Panel layout component (such as a `Row` or `Column`)\n", "* It has `show` and `servable` methods\n", "\n", "This approach is very helpful when we want to wrap multiple existing Panel components into a easily reusable unit." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## ReactiveHTML components\n", "\n", "The `ReactiveHTML` provides bi-directional syncing of arbitrary HTML attributes and DOM properties with parameters on the subclass. This kind of component must declare a HTML template written using Javascript template variables (`${}`) and optionally Jinja2 syntax:\n", "\n", "- `_template`: The HTML template to render declaring how to link parameters on the class to HTML attributes.\n", "\n", "Additionally the component may declare some additional attributes providing further functionality \n", "\n", "- `_child_config` (optional): Optional mapping that controls how children are rendered.\n", "- `_dom_events` (optional): Optional mapping of named nodes to DOM events to add event listeners to.\n", "- `_scripts` (optional): Optional mapping of Javascript to execute on specific parameter changes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### HTML templates\n", "\n", "A ReactiveHTML component is declared by providing an HTML template on the `_template` attribute on the class. Parameters are synced by inserting them as template variables of the form `${parameter}`, e.g.:\n", "\n", "```html\n", " _template = '