{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "6dd69519-afc9-4065-ad12-8253c708f5af", "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "import param\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "id": "a796364a-f1cd-411a-b7fd-d4354794474e", "metadata": {}, "source": [ "A `PyComponent`, unlike a `Viewer` class inherits the entire API of Panel components, including all layout and styling related parameters. It doesn't just imitate a Panel component, it actually **is** one and therefore is a good option to build some composite widget made up of other widgets, a layout with some special behavior or a pane that renders some type of object in a way that is useful but does not require novel functionality." ] }, { "cell_type": "code", "execution_count": null, "id": "9164b959-7dc5-4579-b65c-1b3827803ca0", "metadata": {}, "outputs": [], "source": [ "from panel.custom import PyComponent\n", "from panel.widgets import WidgetBase \n", "\n", "class CounterButton(PyComponent, WidgetBase):\n", "\n", " value = param.Integer(default=0)\n", "\n", " def __panel__(self):\n", " return pn.widgets.Button(\n", " name=self._button_name, on_click=self._on_click\n", " )\n", "\n", " def _on_click(self, event):\n", " self.value += 1\n", "\n", " @param.depends(\"value\")\n", " def _button_name(self):\n", " return f\"count is {self.value}\"\n", "\n", "CounterButton()" ] }, { "cell_type": "markdown", "id": "78fda29d-eedd-45e4-a02c-e0771ac6b182", "metadata": {}, "source": [ ":::{note}\n", "If you are looking to create new components using JavaScript, check out [`JSComponent`](JSComponent.md), [`ReactComponent`](ReactComponent.md), or [`AnyWidgetComponent`](AnyWidgetComponent.md) instead.\n", ":::\n", "\n", "## API\n", "\n", "### Attributes\n", "\n", "The `PyComponent` class inherits the entire Panel `Viewable` API including all sizing, layout and styling related parameters such as `width`, `height`, `sizing_mode` etc.\n", "\n", "### Methods\n", "\n", "- **`__panel__`**: Must be implemented. Should return the Panel component or object to be displayed. Will be lazily evaluated and cached on render.\n", "- **`servable`**: This method serves the component using Panel's built-in server when running `panel serve ...`.\n", "- **`show`**: Displays the component in a new browser tab when running `python ...`.\n", "\n", "## Usage\n", "\n", "### Styling with CSS\n", "\n", "You can style the component by styling the component(s) returned by `__panel__` using their `styles` or `stylesheets` attributes.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "68f36432-40e1-4098-a195-12474dbf4a83", "metadata": {}, "outputs": [], "source": [ "class StyledCounterButton(PyComponent):\n", "\n", " value = param.Integer()\n", "\n", " _stylesheets = [\n", " \"\"\"\n", " :host(.solid) .bk-btn.bk-btn-default\n", " {\n", " background: #0072B5;\n", " color: white;\n", " border: none;\n", " padding: 10px;\n", " border-radius: 4px;\n", " }\n", " :host(.solid) .bk-btn.bk-btn-default:hover {\n", " background: #4099da;\n", " }\n", " \"\"\"\n", " ]\n", "\n", " def _on_click(self, event):\n", " self.value += 1\n", "\n", " @param.depends(\"value\")\n", " def _button_name(self):\n", " return f\"Clicked {self.value} times\"\n", "\n", " def __panel__(self):\n", " return pn.widgets.Button(\n", " name=self._button_name,\n", " on_click=self._on_click,\n", " stylesheets=self._stylesheets\n", " )\n", "\n", "\n", "StyledCounterButton().servable()" ] }, { "cell_type": "markdown", "id": "3b134810-94f9-4ccf-a257-aa07cb48d6f3", "metadata": {}, "source": [ "See the [Apply CSS](../../how_to/styling/apply_css.md) guide for more information on styling Panel components.\n", "\n", "## Displaying A Single Child\n", "\n", "You can display Panel components (`Viewable`s) by defining a `Child` parameter.\n", "\n", "Let's start with the simplest example:\n" ] }, { "cell_type": "code", "execution_count": null, "id": "42f21642-ad17-4c21-b186-d894c90da554", "metadata": {}, "outputs": [], "source": [ "from panel.custom import Child\n", "\n", "class SingleChild(PyComponent):\n", "\n", " object = Child()\n", "\n", " def __panel__(self):\n", " return pn.Column(\"A Single Child\", self.param.object.rx())\n", "\n", "single_child = SingleChild(object=pn.pane.Markdown(\"A **Markdown** pane!\"))\n", "\n", "single_child.servable()" ] }, { "cell_type": "markdown", "id": "9ba02dcb-c83b-4212-ac1f-8e14e895d485", "metadata": {}, "source": [ "Calling `self.param.object.rx()` creates a reactive expression which updates when the `object` parameter is updated.\n", "\n", "Let's replace the `object` with a `Button`:" ] }, { "cell_type": "code", "execution_count": null, "id": "6e386a52-d256-47a9-9139-a0e81e7bad88", "metadata": {}, "outputs": [], "source": [ "single_child.object = pn.widgets.Button(name=\"Click me\")" ] }, { "cell_type": "markdown", "id": "9dbf41ea-b18f-4421-977a-291612cda6f7", "metadata": {}, "source": [ "Let's change it back" ] }, { "cell_type": "code", "execution_count": null, "id": "880176b1-5052-4da1-8fbb-cce9e39524ab", "metadata": {}, "outputs": [], "source": [ "single_child.object = pn.pane.Markdown(\"A **Markdown** pane!\")" ] }, { "cell_type": "markdown", "id": "10cc810a-6001-49d3-a5e1-798252e96a5d", "metadata": {}, "source": [ "If you provide a non-`Viewable` child it will automatically be converted to a `Viewable` by `pn.panel`:" ] }, { "cell_type": "code", "execution_count": null, "id": "d929e9b1-62da-4122-bc78-7129988de486", "metadata": {}, "outputs": [], "source": [ "SingleChild(object=\"A **Markdown** pane!\").servable()" ] }, { "cell_type": "markdown", "id": "417c59c4-4e0d-46d1-a762-98af457034d5", "metadata": {}, "source": [ "If you want to allow a certain type of Panel components only, you can specify the specific type in the `class_` argument." ] }, { "cell_type": "code", "execution_count": null, "id": "f02e8336-a3fa-4e58-b457-d71f5d84fd18", "metadata": {}, "outputs": [], "source": [ "class SingleChild(PyComponent):\n", "\n", " object = Child(class_=pn.pane.Markdown)\n", "\n", " def __panel__(self):\n", " return pn.Column(\"A Single Child\", self.param.object.rx())\n", "\n", "\n", "SingleChild(object=pn.pane.Markdown(\"A **Markdown** pane!\")).servable()" ] }, { "cell_type": "markdown", "id": "8ba7de66-21a3-4ab8-8eb2-0e6fd7d488fd", "metadata": {}, "source": [ "The `class_` argument also supports a tuple of types:\n", "\n", "```python\n", " object = Child(class_=(pn.pane.Markdown, pn.widgets.Button))\n", "```" ] }, { "cell_type": "markdown", "id": "000a0276-717b-4984-9d1e-e936ba8c1a05", "metadata": {}, "source": [ "## Displaying a List of Children\n", "\n", "You can also display a `List` of `Viewable` objects using the `Children` parameter type:" ] }, { "cell_type": "code", "execution_count": null, "id": "a4fa17c5-65d2-4c1a-a311-aad61a4bf433", "metadata": {}, "outputs": [], "source": [ "from panel.custom import Children\n", "\n", "\n", "class MultipleChildren(PyComponent):\n", "\n", " objects = Children()\n", "\n", " def __panel__(self):\n", " return pn.Column(objects=self.param['objects'], styles={\"background\": \"silver\"})\n", "\n", "\n", "MultipleChildren(\n", " objects=[\n", " pn.panel(\"A **Markdown** pane!\"),\n", " pn.widgets.Button(name=\"Click me!\"),\n", " {\"text\": \"I'm shown as a JSON Pane\"},\n", " ]\n", ").servable()\n" ] }, { "cell_type": "markdown", "id": "60ebdbd0-48c8-4f21-9a57-7b49e31523ae", "metadata": {}, "source": [ ":::note\n", "You can change the `item_type` to a specific subtype of `Viewable` or a tuple of `Viewable` subtypes.\n", ":::\n", "\n", "## References\n", "\n", "### Tutorials\n", "\n", "- [Reusable Components](../../tutorials/intermediate/reusable_components.md)\n", "\n", "### How-To Guides\n", "\n", "- [Combine Existing Widgets](../../how_to/custom_components/python/create_custom_widget.md)" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }