diff --git a/examples/reference/panes/Placeholder.ipynb b/examples/reference/panes/Placeholder.ipynb new file mode 100644 index 0000000000..ffd48271f7 --- /dev/null +++ b/examples/reference/panes/Placeholder.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import asyncio\n", + "import panel as pn\n", + "\n", + "pn.extension()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Placeholder` pane serves as a placeholder for other Panel components. It can be used to display a message while a computation is running, for example.\n", + "\n", + "#### Parameters:\n", + "\n", + "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n", + "\n", + "* **`object`** (Any): The Panel object to display, if object is not already a Panel object it will be converted with the `panel(...)` function.\n", + "\n", + "___" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `Placeholder` pane can accept any Panel component as its argument, including other panes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "placeholder = pn.pane.Placeholder(\"Hello\")\n", + "placeholder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The benefit of using a `Placeholder` is that it allows you to replace the content of the pane without being restricted to a specific type of component. This means you can replace the placeholder with any other pane type, including plots, images, and widgets. You may either use the `update` method:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "placeholder.update(pn.widgets.TextInput(value=\"Hello again!\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or set the `object` directly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "placeholder.object = \"Hello once more!\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you'd like to temporarily replace the contents, you can use it as a context manager." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "placeholder = pn.pane.Placeholder(\"⏳ Idle\")\n", + "placeholder" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Upon execution of the cell below, the `Placeholder` pane will display `Starting...`, `Running...`, and `Complete!` in sequence, with a 1 second pause between each message, before finally displaying `Idle` again." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "with placeholder:\n", + " placeholder.update(\"🚀 Starting...\")\n", + " await asyncio.sleep(1)\n", + " placeholder.update(\"🏃 Running...\")\n", + " await asyncio.sleep(1)\n", + " placeholder.update(\"✅ Complete!\")\n", + " await asyncio.sleep(1)" + ] + } + ], + "metadata": { + "language_info": { + "name": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/panel/pane/__init__.py b/panel/pane/__init__.py index 877ad5963d..7b97a3a9bf 100644 --- a/panel/pane/__init__.py +++ b/panel/pane/__init__.py @@ -44,6 +44,7 @@ ) from .media import Audio, Video # noqa from .perspective import Perspective # noqa +from .placeholder import Placeholder # noqa from .plot import ( # noqa YT, Bokeh, Matplotlib, RGGPlot, ) @@ -84,6 +85,7 @@ "panel", "PDF", "Perspective", + "Placeholder", "Plotly", "PNG", "ReactiveExpr", diff --git a/panel/pane/placeholder.py b/panel/pane/placeholder.py new file mode 100644 index 0000000000..949f49c4dc --- /dev/null +++ b/panel/pane/placeholder.py @@ -0,0 +1,58 @@ +""" +Defines the Placeholder pane which serves as a placeholder for other Panel components. +""" + +from __future__ import annotations + +import param + +from ..pane.base import ReplacementPane + + +class Placeholder(ReplacementPane): + """ + The `Placeholder` pane serves as a placeholder for other Panel components. + It can be used to display a message while a computation is running, for + example. + + Reference: https://panel.holoviz.org/reference/panes/Placeholder.html + + :Example: + + >>> with Placeholder("⏳ Idle"): + ... placeholder.object = "🏃 Running..." + """ + + def __init__(self, object=None, **params): + super().__init__(object=object, **params) + self._past_object = object # used to restore object when Placeholder is exited + self._temporary = False + if object is not None: + self._replace_panel() + + @param.depends("object", watch=True) + def _replace_panel(self): + if not self._temporary: + self._past_object = self.object + self._update_inner(self.object) + + def __enter__(self): + self._temporary = True + return self + + def __exit__(self, exc_type, exc_value, traceback): + try: + self.object = self._past_object + finally: + self._temporary = False + return False + + def update(self, object): + """ + Updates the object on the Placeholder. + + Arguments + --------- + object: The object to update the Placeholder with. + """ + self.object = object diff --git a/panel/tests/pane/test_placeholder.py b/panel/tests/pane/test_placeholder.py new file mode 100644 index 0000000000..9f80e7c765 --- /dev/null +++ b/panel/tests/pane/test_placeholder.py @@ -0,0 +1,28 @@ +import param + +from panel.pane.placeholder import Placeholder +from panel.widgets import IntInput + + +class TestPlaceholder: + + def test_update_object(self): + placeholder = Placeholder("Idle") + placeholder.object = "Running..." + assert placeholder.object == "Running..." + placeholder.object = "New" + assert placeholder.object == "New" + + def test_enter_exit(self): + placeholder = Placeholder("⏳ Idle") + with placeholder: + placeholder.object = "🏃 Running..." + assert placeholder.object == "🏃 Running..." + placeholder.object = "🚶 Walking..." + assert placeholder.object == "🚶 Walking..." + assert placeholder.object == "⏳ Idle" + + def test_param_ref(self): + int_input = IntInput(name="IntInput", start=1, end=10, value=5) + placeholder = Placeholder(int_input.param.value) + assert isinstance(placeholder.object, param.Integer)