Update pc.form on_submit args (#1033)

This commit is contained in:
Nikhil Rao 2023-05-16 12:09:15 -07:00 committed by GitHub
parent 98f1b30735
commit e6a679d3a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 102 additions and 78 deletions

View File

@ -94,6 +94,13 @@ class Component(Base, ABC):
Raises: Raises:
TypeError: If an invalid prop is passed. TypeError: If an invalid prop is passed.
""" """
# Set the id and children initially.
initial_kwargs = {
"id": kwargs.get("id"),
"children": kwargs.get("children", []),
}
super().__init__(**initial_kwargs)
# Get the component fields, triggers, and props. # Get the component fields, triggers, and props.
fields = self.get_fields() fields = self.get_fields()
triggers = self.get_triggers() triggers = self.get_triggers()
@ -264,17 +271,15 @@ class Component(Base, ABC):
events=events, state_name=state_name, full_control=full_control events=events, state_name=state_name, full_control=full_control
) )
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:
The event triggers. The event triggers.
""" """
return EVENT_TRIGGERS | set(cls.get_controlled_triggers()) return EVENT_TRIGGERS | set(self.get_controlled_triggers())
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:
@ -488,7 +493,7 @@ class Component(Base, ABC):
# Add the hook code for the children. # Add the hook code for the children.
for child in self.children: for child in self.children:
code.update(child.get_hooks()) code |= child.get_hooks()
return code return code
@ -502,6 +507,20 @@ class Component(Base, ABC):
return None return None
return format.format_ref(self.id) return format.format_ref(self.id)
def get_refs(self) -> Set[str]:
"""Get the refs for the children of the component.
Returns:
The refs for the children.
"""
refs = set()
ref = self.get_ref()
if ref is not None:
refs.add(ref)
for child in self.children:
refs |= child.get_refs()
return refs
def get_custom_components( def get_custom_components(
self, seen: Optional[Set[str]] = None self, seen: Optional[Set[str]] = None
) -> Set[CustomComponent]: ) -> Set[CustomComponent]:
@ -565,7 +584,7 @@ class CustomComponent(Component):
library = f"/{constants.COMPONENTS_PATH}" library = f"/{constants.COMPONENTS_PATH}"
# The function that creates the component. # The function that creates the component.
component_fn: Callable[..., Component] component_fn: Callable[..., Component] = Component.create
# The props of the component. # The props of the component.
props: Dict[str, Any] = {} props: Dict[str, Any] = {}

View File

@ -48,8 +48,7 @@ class Checkbox(ChakraComponent):
# The spacing between the checkbox and its label text (0.5rem) # The spacing between the checkbox and its label text (0.5rem)
spacing: Var[str] spacing: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -16,8 +16,7 @@ class CopyToClipboard(Component):
# The text to copy when clicked. # The text to copy when clicked.
text: Var[str] text: Var[str]
@classmethod def get_controlled_triggers(self) -> Set[str]:
def get_controlled_triggers(cls) -> Set[str]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -36,8 +36,7 @@ class Editable(ChakraComponent):
# The initial value of the Editable in both edit and preview mode. # The initial value of the Editable in both edit and preview mode.
default_value: Var[str] default_value: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -1,6 +1,6 @@
"""Form components.""" """Form components."""
from typing import Set from typing import Dict
from pynecone.components.component import Component from pynecone.components.component import Component
from pynecone.components.libs.chakra import ChakraComponent from pynecone.components.libs.chakra import ChakraComponent
@ -14,14 +14,19 @@ class Form(ChakraComponent):
as_: Var[str] = "form" # type: ignore as_: Var[str] = "form" # type: ignore
@classmethod def get_controlled_triggers(self) -> Dict[str, Dict]:
def get_triggers(cls) -> Set[str]: """Get the event triggers that pass the component's value to the handler.
"""Get the event triggers for the component.
Returns: Returns:
The event triggers. A dict mapping the event trigger to the var that is passed to the handler.
""" """
return super().get_triggers() | {"on_submit"} # Send all the input refs to the handler.
return {
"on_submit": {
ref[4:]: Var.create(f"{ref}.current.value", is_local=False)
for ref in self.get_refs()
}
}
class FormControl(ChakraComponent): class FormControl(ChakraComponent):
@ -52,7 +57,7 @@ class FormControl(ChakraComponent):
input=None, input=None,
help_text=None, help_text=None,
error_message=None, error_message=None,
**props **props,
) -> Component: ) -> Component:
"""Create a form control component. """Create a form control component.

View File

@ -55,8 +55,7 @@ class Input(ChakraComponent):
{"/utils/state": {ImportVar(tag="set_val")}}, {"/utils/state": {ImportVar(tag="set_val")}},
) )
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -64,8 +64,7 @@ class NumberInput(ChakraComponent):
# "outline" | "filled" | "flushed" | "unstyled" # "outline" | "filled" | "flushed" | "unstyled"
variant: Var[str] variant: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -55,8 +55,7 @@ class PinInput(ChakraComponent):
# "outline" | "flushed" | "filled" | "unstyled" # "outline" | "flushed" | "filled" | "unstyled"
variant: Var[str] variant: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -23,8 +23,7 @@ class RadioGroup(ChakraComponent):
# The default value. # The default value.
default_value: Var[Any] default_value: Var[Any]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -43,8 +43,7 @@ class RangeSlider(ChakraComponent):
# The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together. # The minimum distance between slider thumbs. Useful for preventing the thumbs from being too close together.
min_steps_between_thumbs: Var[int] min_steps_between_thumbs: Var[int]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -48,8 +48,7 @@ class Select(ChakraComponent):
# The size of the select. # The size of the select.
size: Var[str] size: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -61,8 +61,7 @@ class Slider(ChakraComponent):
# Maximum width of the slider. # Maximum width of the slider.
max_w: Var[str] max_w: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -41,8 +41,7 @@ class Switch(ChakraComponent):
# The color scheme of the switch (e.g. "blue", "green", "red", etc.) # The color scheme of the switch (e.g. "blue", "green", "red", etc.)
color_scheme: Var[str] color_scheme: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -42,8 +42,7 @@ class TextArea(ChakraComponent):
# "outline" | "filled" | "flushed" | "unstyled" # "outline" | "filled" | "flushed" | "unstyled"
variant: Var[str] variant: Var[str]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -80,8 +80,7 @@ class Upload(Component):
# Create the component. # Create the component.
return super().create(zone, on_drop=upload_file, **upload_props) return super().create(zone, on_drop=upload_file, **upload_props)
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Get the event triggers that pass the component's value to the handler. """Get the event triggers that pass the component's value to the handler.
Returns: Returns:

View File

@ -17,10 +17,10 @@ class Cond(Component):
cond: Var[Any] cond: Var[Any]
# The component to render if the cond is true. # The component to render if the cond is true.
comp1: Component comp1: Component = Fragment.create()
# The component to render if the cond is false. # The component to render if the cond is false.
comp2: Component comp2: Component = Fragment.create()
@classmethod @classmethod
def create( def create(

View File

@ -4,6 +4,7 @@ from __future__ import annotations
from typing import Any, Callable, List from typing import Any, Callable, List
from pynecone.components.component import Component from pynecone.components.component import Component
from pynecone.components.layout.fragment import Fragment
from pynecone.components.tags import IterTag from pynecone.components.tags import IterTag
from pynecone.vars import BaseVar, Var, get_unique_variable_name from pynecone.vars import BaseVar, Var, get_unique_variable_name
@ -15,7 +16,7 @@ class Foreach(Component):
iterable: Var[List] iterable: Var[List]
# A function from the render args to the component. # A function from the render args to the component.
render_fn: Callable render_fn: Callable = Fragment.create
@classmethod @classmethod
def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach: def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach:

View File

@ -35,8 +35,7 @@ class Avatar(ChakraComponent):
# "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full" # "2xs" | "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full"
size: Var[str] size: Var[str]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -43,8 +43,7 @@ class Image(ChakraComponent):
# The image srcset attribute. # The image srcset attribute.
src_set: Var[str] src_set: Var[str]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -52,8 +52,7 @@ class AlertDialog(ChakraComponent):
# If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert** # If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
use_inert: Var[bool] use_inert: Var[bool]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -58,8 +58,7 @@ class Drawer(ChakraComponent):
# Variant of drawer # Variant of drawer
variant: Var[str] variant: Var[str]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -60,8 +60,7 @@ class Menu(ChakraComponent):
# The CSS positioning strategy to use. ("fixed" | "absolute") # The CSS positioning strategy to use. ("fixed" | "absolute")
strategy: Var[str] strategy: Var[str]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -52,8 +52,7 @@ class Modal(ChakraComponent):
# A11y: If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert** # A11y: If true, the siblings of the modal will have `aria-hidden` set to true so that screen readers can only see the modal. This is commonly known as making the other elements **inert**
use_inert: Var[bool] use_inert: Var[bool]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -75,8 +75,7 @@ class Popover(ChakraComponent):
# The interaction that triggers the popover. hover - means the popover will open when you hover with mouse or focus with keyboard on the popover trigger click - means the popover will open on click or press Enter to Space on keyboard ("click" | "hover") # The interaction that triggers the popover. hover - means the popover will open when you hover with mouse or focus with keyboard on the popover trigger click - means the popover will open on click or press Enter to Space on keyboard ("click" | "hover")
trigger: Var[str] trigger: Var[str]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -62,8 +62,7 @@ class Tooltip(ChakraComponent):
# If true, the tooltip will wrap its children in a `<span/>` with `tabIndex=0` # If true, the tooltip will wrap its children in a `<span/>` with `tabIndex=0`
should_wrap_children: Var[bool] should_wrap_children: Var[bool]
@classmethod def get_triggers(self) -> Set[str]:
def get_triggers(cls) -> Set[str]:
"""Get the event triggers for the component. """Get the event triggers for the component.
Returns: Returns:

View File

@ -3,7 +3,6 @@
from __future__ import annotations from __future__ import annotations
import json import json
import re
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Union
from plotly.graph_objects import Figure from plotly.graph_objects import Figure
@ -97,14 +96,10 @@ class Tag(Base):
prop = json.loads(to_json(prop))["data"] # type: ignore prop = json.loads(to_json(prop))["data"] # type: ignore
# For dictionaries, convert any properties to strings. # For dictionaries, convert any properties to strings.
else: elif isinstance(prop, dict):
if isinstance(prop, dict): prop = format.format_dict(prop)
# Convert any var keys to strings.
prop = {
key: str(val) if isinstance(val, Var) else val
for key, val in prop.items()
}
else:
# Dump the prop as JSON. # Dump the prop as JSON.
try: try:
prop = format.json_dumps(prop) prop = format.json_dumps(prop)
@ -113,11 +108,6 @@ class Tag(Base):
f"Could not format prop: {prop} of type {type(prop)}" f"Could not format prop: {prop} of type {type(prop)}"
) from e ) from e
# This substitution is necessary to unwrap var values.
prop = re.sub('"{', "", prop)
prop = re.sub('}"', "", prop)
prop = re.sub('\\\\"', '"', prop)
# Wrap the variable in braces. # Wrap the variable in braces.
assert isinstance(prop, str), "The prop must be a string." assert isinstance(prop, str), "The prop must be a string."
return format.wrap(prop, "{", check_first=False) return format.wrap(prop, "{", check_first=False)

View File

@ -15,6 +15,7 @@ from pynecone import constants
from pynecone.utils import types from pynecone.utils import types
if TYPE_CHECKING: if TYPE_CHECKING:
from pynecone.components.component import ComponentStyle
from pynecone.event import EventChain, EventHandler, EventSpec from pynecone.event import EventChain, EventHandler, EventSpec
WRAP_MAP = { WRAP_MAP = {
@ -411,6 +412,33 @@ def format_ref(ref: str) -> str:
return f"ref_{clean_ref}" return f"ref_{clean_ref}"
def format_dict(prop: ComponentStyle) -> str:
"""Format a dict with vars potentially as values.
Args:
prop: The dict to format.
Returns:
The formatted dict.
"""
# Import here to avoid circular imports.
from pynecone.vars import Var
# Convert any var keys to strings.
prop = {key: str(val) if isinstance(val, Var) else val for key, val in prop.items()}
# Dump the dict to a string.
fprop = json_dumps(prop)
# This substitution is necessary to unwrap var values.
fprop = re.sub('"{', "", fprop)
fprop = re.sub('}"', "", fprop)
fprop = re.sub('\\\\"', '"', fprop)
# Return the formatted dict.
return fprop
def json_dumps(obj: Any) -> str: def json_dumps(obj: Any) -> str:
"""Takes an object and returns a jsonified string. """Takes an object and returns a jsonified string.

View File

@ -101,6 +101,9 @@ class Var(ABC):
value = json.loads(to_json(value))["data"] # type: ignore value = json.loads(to_json(value))["data"] # type: ignore
type_ = Figure type_ = Figure
if isinstance(value, dict):
value = format.format_dict(value)
try: try:
name = value if isinstance(value, str) else json.dumps(value) name = value if isinstance(value, str) else json.dumps(value)
except TypeError as e: except TypeError as e:

View File

@ -62,8 +62,7 @@ def component2() -> Type[Component]:
# A test list prop. # A test list prop.
arr: Var[List[str]] arr: Var[List[str]]
@classmethod def get_controlled_triggers(self) -> Dict[str, Var]:
def get_controlled_triggers(cls) -> Dict[str, Var]:
"""Test controlled triggers. """Test controlled triggers.
Returns: Returns:
@ -307,8 +306,8 @@ def test_get_controlled_triggers(component1, component2):
component1: A test component. component1: A test component.
component2: A test component. component2: A test component.
""" """
assert component1.get_controlled_triggers() == dict() assert component1().get_controlled_triggers() == dict()
assert set(component2.get_controlled_triggers()) == {"on_open", "on_close"} assert set(component2().get_controlled_triggers()) == {"on_open", "on_close"}
def test_get_triggers(component1, component2): def test_get_triggers(component1, component2):
@ -318,8 +317,8 @@ def test_get_triggers(component1, component2):
component1: A test component. component1: A test component.
component2: A test component. component2: A test component.
""" """
assert component1.get_triggers() == EVENT_TRIGGERS assert component1().get_triggers() == EVENT_TRIGGERS
assert component2.get_triggers() == {"on_open", "on_close"} | EVENT_TRIGGERS assert component2().get_triggers() == {"on_open", "on_close"} | EVENT_TRIGGERS
def test_create_custom_component(my_component): def test_create_custom_component(my_component):