Create a Form with HoloViz Panel#
Welcome to the “Create a Form” tutorial! In this guide, we will create an interactive and fully functional form using HoloViz Panel. By the end of this tutorial, you’ll have built a form with validation that captures user input and processes it effectively. This tutorial adopts the param.Parameterized
approach to ensure the form is extensible and maintainable.
Key Objectives#
Create an Interactive Form: Build a form to capture Name, Email, and an optional Message.
Validate User Input: Ensure the form inputs meet specified criteria.
Submit Data: Simulate sending the form data for further processing.
Implementation#
Below is a step-by-step breakdown of the code to create the form.
1. Import Dependencies#
import panel as pn
import param
We use panel
to build the interactive components and param
for structured data handling and validation.
2. Define Form Text and Icons#
We add some static elements to make the form visually appealing.
FORM_TEXT = """\
<h1>Join Newsletter</h1>
Get the latest updates and news about Panel.
"""
FORM_ICON = """\
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path>
<path d="M3 7l9 6l9 -6"></path>
</svg>
"""
3. Create a Parameterized Form State Class#
The FormState
class handles form inputs, validation, and state management.
class FormState(param.Parameterized):
name = param.String(default="", doc="The name of the user.")
email = param.String(default="", doc="The email of the user.")
message = param.String(default="", label="Message", doc="An optional message from the user")
is_not_valid = param.Boolean(default=False)
validation_errors = param.Dict()
validation_message = param.String()
def __init__(self, **params):
params["name"]=params.get("name", "")
super().__init__(**params)
def _validate(self):
errors = {}
if not self.name:
errors["name"] = "No *Name* entered."
if not self.email:
errors["email"] = "No *Email* entered."
elif "@" not in self.email or "." not in self.email:
errors["email"] = "Not a valid *Email*."
self.validation_errors = errors
self.is_not_valid = bool(errors)
self.validation_message = "**Error**: " + " ".join(errors.values())
def _to_dict(self):
return {"name": self.name, "email": self.email, "message": self.message}
def _reset_to_defaults(self):
self.param.update(name="", email="", message="")
def submit(self, event):
self._validate()
if not self.validation_errors:
pn.state.notifications.success(f"Form submitted: {self._to_dict()}", duration=2000)
self._reset_to_defaults()
4. Build the Form Layout#
We use pn.widgets
to create form inputs and buttons.
def create_form():
form_state = FormState()
header = pn.Row(
pn.pane.SVG(FORM_ICON, margin=0, height=80, sizing_mode="fixed"),
FORM_TEXT,
)
error_pane = pn.pane.Alert(
object=form_state.param.validation_message,
visible=form_state.param.is_not_valid,
alert_type="danger",
stylesheets=["p {margin-bottom: 0}"]
)
name_input = pn.widgets.TextInput.from_param(form_state.param.name, name="Name*", placeholder="User Name")
email_input = pn.widgets.TextInput.from_param(form_state.param.email, name="Email*", placeholder="Email Address")
message_input = pn.widgets.TextAreaInput.from_param(form_state.param["message"], placeholder="An optional message")
submit_button = pn.widgets.Button(name="Send", on_click=form_state.submit, button_type="primary")
return pn.Column(
header,
error_pane,
name_input,
email_input,
message_input,
submit_button,
sizing_mode="fixed",
width=500,
styles={"margin": "auto"}
)
5. Serve the Application#
Extend Panel with notifications, create the form, and mark it .servable()
.
pn.extension(notifications=True, design="bootstrap", sizing_mode="stretch_width")
form = create_form()
form.servable()
Finally you can serve the application with
panel serve app.py --dev --show
Summary#
In this tutorial, we built a fully functional form with the following features:
User Input Fields: Capturing Name, Email, and an optional Message.
Input Validation: Ensuring required fields are filled and valid.
Form Submission: Resetting the form upon successful submission and displaying notifications.
By structuring our app with param.Parameterized
, we ensured that the form is easy to maintain and extend in the future.
Full Code#
Here’s the complete code for reference:
import panel as pn
import param
import json
FORM_TEXT = """\
<h1>Join Newsletter</h1>
Get the latest updates and news about Panel.
"""
FORM_ICON = """\
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
<path d="M3 7a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-10z"></path>
<path d="M3 7l9 6l9 -6"></path>
</svg>
"""
class FormState(param.Parameterized):
name = param.String(default="", doc="The name of the user.")
email = param.String(default="", doc="The email of the user.")
message = param.String(default="", label="Message", doc="An optional message from the user")
is_not_valid = param.Boolean(default=False)
validation_errors = param.Dict()
validation_message = param.String()
def __init__(self, **params):
params["name"]=params.get("name", "")
super().__init__(**params)
def _validate(self):
errors = {}
if not self.name:
errors["name"] = "No *Name* entered."
if not self.email:
errors["email"] = "No *Email* entered."
elif "@" not in self.email or "." not in self.email:
errors["email"] = "Not a valid *Email*."
self.validation_errors = errors
self.is_not_valid = bool(errors)
self.validation_message = "**Error**: " + " ".join(errors.values())
def _to_dict(self):
return {"name": self.name, "email": self.email, "message": self.message}
def _reset_to_defaults(self):
self.param.update(name="", email="", message="")
def submit(self, event):
self._validate()
if not self.validation_errors:
pn.state.notifications.success(f"Form submitted: {self._to_dict()}", duration=2000)
self._reset_to_defaults()
def create_form():
form_state = FormState()
header = pn.Row(
pn.pane.SVG(FORM_ICON, margin=0, height=80, sizing_mode="fixed"),
FORM_TEXT,
)
error_pane = pn.pane.Alert(
object=form_state.param.validation_message,
visible=form_state.param.is_not_valid,
alert_type="danger",
stylesheets=["p {margin-bottom: 0}"]
)
name_input = pn.widgets.TextInput.from_param(form_state.param.name, name="Name*", placeholder="User Name")
email_input = pn.widgets.TextInput.from_param(form_state.param.email, name="Email*", placeholder="Email Address")
message_input = pn.widgets.TextAreaInput.from_param(form_state.param["message"], placeholder="An optional message")
submit_button = pn.widgets.Button(name="Send", on_click=form_state.submit, button_type="primary")
return pn.Column(
header,
error_pane,
name_input,
email_input,
message_input,
submit_button,
sizing_mode="fixed",
width=500,
styles={"margin": "auto"}
)
pn.extension(notifications=True, design="bootstrap", sizing_mode="stretch_width")
form = create_form()
form.servable()