Ludic Logo
Edit

Forms

These components located in ludic.catalog.forms are in an experimental mode. There is the possibility to automatically create form fields from annotations, but it is far from production-ready.

Input Field

#

The InputField component is the most basic form field. It is a wrapper around the input HTML element which also generates a label if not disabled. Here is what it looks like:

from ludic.catalog.forms import InputField

InputField(
    label="First Name",
    placeholder="Your First Name",
    name="sample-input-field",
)

You can also create the input field without the label by not passing the label attribute:

from ludic.catalog.forms import InputField

InputField(
    name="sample-input-field",
)

Select Field

#

The SelectField component is a wrapper around the select HTML element. It also generates a label if not disabled. Here is what it looks like:

from ludic.catalog.forms import SelectField, Option

SelectField(
    Option("Option 1"),
    Option("Option 2", selected=True),
    Option("Option 3"),
    label="Select Option",
    name="sample-select-field",
)

Text Area

#

The TextAreaField component is a wrapper around the textarea HTML element. It also generates a label if not disabled. Here is what it looks like:

from ludic.catalog.forms import TextAreaField

TextAreaField(
    name="sample-text-area",
    label="Description",
)

Similarly as for the input field, you can create the text area without the label.

Choices

#

The ChoiceField component is a wrapper around the <input type='radio'> HTML element. It also generates a label if not disabled. Here is what it looks like:

Are you a vegetarian?

from ludic.catalog.forms import ChoiceField

ChoiceField(
    name="sample-choice-field",
    label="Are you a vegetarian?",
    choices=[("yes", "Yes"), ("no", "No")],
    selected="no",
),

Form

#

The Form component is a wrapper around the form HTML element. Here is a sample form:

from ludic.catalog.buttons import ButtonSuccess
from ludic.catalog.forms import Form, InputField, TextAreaField
from ludic.catalog.layouts import Cluster

Form(
    InputField(
        name="sample-input-field",
        label="First Name",
        placeholder="Your First Name",
    ),
    TextAreaField(
        name="sample-text-area",
        label="Description",
    ),
    Cluster(ButtonSuccess("Submit")),
)

We use the Cluster component to create a form with multiple input fields and buttons. this component is described in the Layouts section. You can also have the input field and the button aligned horizontally using this layout component:

from ludic.catalog.buttons import ButtonSuccess
from ludic.catalog.forms import Form, InputField, Option, SelectField
from ludic.catalog.layouts import Cluster

Form(
    Cluster(
        InputField(placeholder="First Name"),
        SelectField(
            Option("Age", disabled=True, selected=True),
            Option("0-18"),
            Option("18+"),
        ),
        ButtonSuccess("Submit"),
    )
)

Generating Form Fields

#

In some cases, the attributes of a component or class-based endpoint can be used to create form fields automatically using the create_fields function. Here is an example:

from typing import Annotated

from ludic.attrs import Attrs
from ludic.catalog.forms import FieldMeta, create_fields

class PersonAttrs(Attrs):
    first_name: Annotated[str, FieldMeta()]
    last_name: Annotated[str, FieldMeta()]

person = PersonAttrs(first_name="John", last_name="Doe")
fields = create_fields(person, spec=PersonAttrs)

The create_fields function generates form fields from annotations. It generates only fields that are annotated with the FieldMeta dataclass, which looks like this:

@dataclass
class FieldMeta:
    label: str | Literal["auto"] | None = "auto"
    kind: Literal["input", "textarea", "checkbox"] = "input"
    type: Literal["text", "email", "password", "hidden"] = "text"
    attrs: InputAttrs | TextAreaAttrs | None = None
    parser: Callable[[Any], PrimitiveChildren] | None = None

The parser attribute validates and parses the field. Here is how you would use it:

from typing import Annotated

from ludic.attrs import Attrs
from ludic.web.parsers import ValidationError
from ludic.catalog.forms import FieldMeta

def parse_email(email: str) -> str:
    if len(email.split("@")) != 2:
        raise ValidationError("Invalid email")
    return email

class CustomerAttrs(Attrs):
    id: str
    name: Annotated[
        str,
        FieldMeta(label="Email", parser=parse_email),
    ]

Fields created with the create_fields function can be than extracted from submitted form data using the Parser class.

Made with Ludic and HTMX and 🐍 • DiscordGitHub