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:

1   from ludic.catalog.forms import InputField
2   
3   InputField(
4       label="First Name",
5       placeholder="Your First Name",
6       name="sample-input-field",
7   )

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

1   from ludic.catalog.forms import InputField
2   
3   InputField(
4       name="sample-input-field",
5   )

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:

1   from ludic.catalog.forms import SelectField, Option
2   
3   SelectField(
4       Option("Option 1"),
5       Option("Option 2", selected=True),
6       Option("Option 3"),
7       label="Select Option",
8       name="sample-select-field",
9   )

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:

1   from ludic.catalog.forms import TextAreaField
2   
3   TextAreaField(
4       name="sample-text-area",
5       label="Description",
6   )

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?

1   from ludic.catalog.forms import ChoiceField
2   
3   ChoiceField(
4       name="sample-choice-field",
5       label="Are you a vegetarian?",
6       choices=[("yes", "Yes"), ("no", "No")],
7       selected="no",
8   ),

Form

#

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

 1   from ludic.catalog.buttons import ButtonSuccess
 2   from ludic.catalog.forms import Form, InputField, TextAreaField
 3   from ludic.catalog.layouts import Cluster
 4   
 5   Form(
 6       InputField(
 7           name="sample-input-field",
 8           label="First Name",
 9           placeholder="Your First Name",
10       ),
11       TextAreaField(
12           name="sample-text-area",
13           label="Description",
14       ),
15       Cluster(ButtonSuccess("Submit")),
16   )

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:

 1   from ludic.catalog.buttons import ButtonSuccess
 2   from ludic.catalog.forms import Form, InputField, Option, SelectField
 3   from ludic.catalog.layouts import Cluster
 4   
 5   Form(
 6       Cluster(
 7           InputField(placeholder="First Name"),
 8           SelectField(
 9               Option("Age", disabled=True, selected=True),
10               Option("0-18"),
11               Option("18+"),
12           ),
13           ButtonSuccess("Submit"),
14       )
15   )

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:

 1   from typing import Annotated
 2   
 3   from ludic.attrs import Attrs
 4   from ludic.catalog.forms import FieldMeta, create_fields
 5   
 6   class PersonAttrs(Attrs):
 7       first_name: Annotated[str, FieldMeta()]
 8       last_name: Annotated[str, FieldMeta()]
 9   
10   person = PersonAttrs(first_name="John", last_name="Doe")
11   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:

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

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

 1   from typing import Annotated
 2   
 3   from ludic.attrs import Attrs
 4   from ludic.web.parsers import ValidationError
 5   from ludic.catalog.forms import FieldMeta
 6   
 7   def parse_email(email: str) -> str:
 8       if len(email.split("@")) != 2:
 9           raise ValidationError("Invalid email")
10       return email
11   
12   class CustomerAttrs(Attrs):
13       id: str
14       name: Annotated[
15           str,
16           FieldMeta(label="Email", parser=parse_email),
17       ]

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