123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- """Form components."""
- from __future__ import annotations
- from typing import Any, Dict
- from jinja2 import Environment
- from reflex.components.component import Component
- from reflex.components.libs.chakra import ChakraComponent
- from reflex.components.tags import Tag
- from reflex.constants import EventTriggers
- from reflex.event import EventChain
- from reflex.utils import imports
- from reflex.utils.format import format_event_chain, to_camel_case
- from reflex.utils.serializers import serialize
- from reflex.vars import BaseVar, Var, get_unique_variable_name
- FORM_DATA = Var.create("form_data")
- HANDLE_SUBMIT_JS_JINJA2 = Environment().from_string(
- """
- const handleSubmit{{ handle_submit_unique_name }} = useCallback((ev) => {
- const $form = ev.target
- ev.preventDefault()
- const {{ form_data }} = {...Object.fromEntries(new FormData($form).entries()), ...{{ field_ref_mapping }}}
- {{ on_submit_event_chain }}
- if ({{ reset_on_submit }}) {
- $form.reset()
- }
- })
- """
- )
- class Form(ChakraComponent):
- """A form component."""
- tag = "Box"
- # What the form renders to.
- as_: Var[str] = "form" # type: ignore
- # If true, the form will be cleared after submit.
- reset_on_submit: Var[bool] = False # type: ignore
- # The name used to make this form's submit handler function unique
- handle_submit_unique_name: Var[str]
- @classmethod
- def create(cls, *children, **props) -> Component:
- """Create a form component.
- Args:
- *children: The children of the form.
- **props: The properties of the form.
- Returns:
- The form component.
- """
- if "handle_submit_unique_name" not in props:
- props["handle_submit_unique_name"] = get_unique_variable_name()
- return super().create(*children, **props)
- def _get_imports(self) -> imports.ImportDict:
- return imports.merge_imports(
- super()._get_imports(),
- {"react": {imports.ImportVar(tag="useCallback")}},
- )
- def _get_hooks(self) -> str | None:
- if EventTriggers.ON_SUBMIT not in self.event_triggers:
- return
- return HANDLE_SUBMIT_JS_JINJA2.render(
- handle_submit_unique_name=self.handle_submit_unique_name,
- form_data=FORM_DATA,
- field_ref_mapping=serialize(self._get_form_refs()),
- on_submit_event_chain=format_event_chain(
- self.event_triggers[EventTriggers.ON_SUBMIT]
- ),
- reset_on_submit=self.reset_on_submit,
- )
- def _render(self) -> Tag:
- render_tag = (
- super()
- ._render()
- .remove_props(
- "reset_on_submit",
- "handle_submit_unique_name",
- to_camel_case(EventTriggers.ON_SUBMIT),
- )
- )
- if EventTriggers.ON_SUBMIT in self.event_triggers:
- render_tag.add_props(
- **{
- EventTriggers.ON_SUBMIT: BaseVar(
- _var_name=f"handleSubmit{self.handle_submit_unique_name}",
- _var_type=EventChain,
- )
- }
- )
- return render_tag
- def _get_form_refs(self) -> Dict[str, Any]:
- # Send all the input refs to the handler.
- form_refs = {}
- for ref in self.get_refs():
- # when ref start with refs_ it's an array of refs, so we need different method
- # to collect data
- if ref.startswith("refs_"):
- form_refs[ref[5:-3]] = Var.create(
- f"getRefValues({ref[:-3]})", _var_is_local=False
- )
- else:
- form_refs[ref[4:]] = Var.create(
- f"getRefValue({ref})", _var_is_local=False
- )
- return form_refs
- def get_event_triggers(self) -> Dict[str, Any]:
- """Get the event triggers that pass the component's value to the handler.
- Returns:
- A dict mapping the event trigger to the var that is passed to the handler.
- """
- return {
- **super().get_event_triggers(),
- EventTriggers.ON_SUBMIT: lambda e0: [FORM_DATA],
- }
- class FormControl(ChakraComponent):
- """Provide context to form components."""
- tag = "FormControl"
- # If true, the form control will be disabled.
- is_disabled: Var[bool]
- # If true, the form control will be invalid.
- is_invalid: Var[bool]
- # If true, the form control will be readonly
- is_read_only: Var[bool]
- # If true, the form control will be required.
- is_required: Var[bool]
- # The label text used to inform users as to what information is requested for a text field.
- label: Var[str]
- @classmethod
- def create(
- cls,
- *children,
- label=None,
- input=None,
- help_text=None,
- error_message=None,
- **props,
- ) -> Component:
- """Create a form control component.
- Args:
- *children: The children of the form control.
- label: The label of the form control.
- input: The input of the form control.
- help_text: The help text of the form control.
- error_message: The error message of the form control.
- **props: The properties of the form control.
- Raises:
- AttributeError: raise an error if missing required kwargs.
- Returns:
- The form control component.
- """
- if len(children) == 0:
- children = []
- if label:
- children.append(FormLabel.create(*label))
- if not input:
- raise AttributeError("input keyword argument is required")
- children.append(input)
- if help_text:
- children.append(FormHelperText.create(*help_text))
- if error_message:
- children.append(FormErrorMessage.create(*error_message))
- return super().create(*children, **props)
- class FormHelperText(ChakraComponent):
- """A form helper text component."""
- tag = "FormHelperText"
- class FormLabel(ChakraComponent):
- """A form label component."""
- tag = "FormLabel"
- # Link
- html_for: Var[str]
- class FormErrorMessage(ChakraComponent):
- """A form error message component."""
- tag = "FormErrorMessage"
|