Add pc.form component (#929)
This commit is contained in:
parent
d0e383d23c
commit
948ec90fc4
@ -93,6 +93,7 @@ export const applyEvent = async (event, router, socket) => {
|
||||
}
|
||||
|
||||
if (event.name == "_set_value") {
|
||||
event.payload.ref.current.blur();
|
||||
event.payload.ref.current.value = event.payload.value;
|
||||
return false;
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ editable = Editable.create
|
||||
editable_input = EditableInput.create
|
||||
editable_preview = EditablePreview.create
|
||||
editable_textarea = EditableTextarea.create
|
||||
form = Form.create
|
||||
form_control = FormControl.create
|
||||
form_error_message = FormErrorMessage.create
|
||||
form_helper_text = FormHelperText.create
|
||||
|
@ -251,7 +251,6 @@ class Component(Base, ABC):
|
||||
events = [
|
||||
EventSpec(
|
||||
handler=e.handler,
|
||||
local_args=(EVENT_ARG,),
|
||||
args=get_handler_args(e, arg),
|
||||
)
|
||||
for e in events
|
||||
@ -312,13 +311,10 @@ class Component(Base, ABC):
|
||||
)
|
||||
|
||||
# Add component props to the tag.
|
||||
props = {attr: getattr(self, attr) for attr in self.get_props()}
|
||||
|
||||
# Special case for props named `type_`.
|
||||
if hasattr(self, "type_"):
|
||||
props["type"] = self.type_ # type: ignore
|
||||
if hasattr(self, "as_"):
|
||||
props["as"] = self.as_ # type: ignore
|
||||
props = {
|
||||
attr[:-1] if attr.endswith("_") else attr: getattr(self, attr)
|
||||
for attr in self.get_props()
|
||||
}
|
||||
|
||||
# Add ref to element if `id` is not None.
|
||||
ref = self.get_ref()
|
||||
|
@ -4,7 +4,7 @@ from .button import Button, ButtonGroup
|
||||
from .checkbox import Checkbox, CheckboxGroup
|
||||
from .copytoclipboard import CopyToClipboard
|
||||
from .editable import Editable, EditableInput, EditablePreview, EditableTextarea
|
||||
from .formcontrol import FormControl, FormErrorMessage, FormHelperText, FormLabel
|
||||
from .formcontrol import Form, FormControl, FormErrorMessage, FormHelperText, FormLabel
|
||||
from .iconbutton import IconButton
|
||||
from .input import Input, InputGroup, InputLeftAddon, InputRightAddon
|
||||
from .numberinput import (
|
||||
|
@ -39,6 +39,9 @@ class Button(ChakraComponent):
|
||||
# | "purple" | "pink" | "linkedin" | "facebook" | "messenger" | "whatsapp" | "twitter" | "telegram"
|
||||
color_scheme: Var[str]
|
||||
|
||||
# The type of button.
|
||||
type_: Var[str]
|
||||
|
||||
|
||||
class ButtonGroup(ChakraComponent):
|
||||
"""A group of buttons."""
|
||||
|
@ -1,10 +1,29 @@
|
||||
"""Form components."""
|
||||
|
||||
from typing import Set
|
||||
|
||||
from pynecone.components.component import Component
|
||||
from pynecone.components.libs.chakra import ChakraComponent
|
||||
from pynecone.vars import Var
|
||||
|
||||
|
||||
class Form(ChakraComponent):
|
||||
"""A form component."""
|
||||
|
||||
tag = "Box"
|
||||
|
||||
as_: Var[str] = "form" # type: ignore
|
||||
|
||||
@classmethod
|
||||
def get_triggers(cls) -> Set[str]:
|
||||
"""Get the event triggers for the component.
|
||||
|
||||
Returns:
|
||||
The event triggers.
|
||||
"""
|
||||
return super().get_triggers() | {"on_submit"}
|
||||
|
||||
|
||||
class FormControl(ChakraComponent):
|
||||
"""Provide context to form components."""
|
||||
|
||||
|
@ -10,7 +10,7 @@ from plotly.graph_objects import Figure
|
||||
from plotly.io import to_json
|
||||
|
||||
from pynecone.base import Base
|
||||
from pynecone.event import EventChain
|
||||
from pynecone.event import EVENT_ARG, EventChain
|
||||
from pynecone.utils import format, types
|
||||
from pynecone.vars import Var
|
||||
|
||||
@ -75,16 +75,14 @@ class Tag(Base):
|
||||
|
||||
# Handle event props.
|
||||
elif isinstance(prop, EventChain):
|
||||
local_args = ",".join(([str(a) for a in prop.events[0].local_args]))
|
||||
|
||||
if prop.full_control:
|
||||
# Full control component events.
|
||||
event = format.format_full_control_event(prop)
|
||||
else:
|
||||
# All other events.
|
||||
chain = ",".join([format.format_event(event) for event in prop.events])
|
||||
event = f"Event([{chain}])"
|
||||
prop = f"({local_args}) => {event}"
|
||||
event = f"{{{EVENT_ARG}.preventDefault(); Event([{chain}])}}"
|
||||
prop = f"({EVENT_ARG}) => {event}"
|
||||
|
||||
# Handle other types.
|
||||
elif isinstance(prop, str):
|
||||
|
@ -92,9 +92,6 @@ class EventSpec(Base):
|
||||
# The handler on the client to process event.
|
||||
client_handler_name: str = ""
|
||||
|
||||
# The local arguments on the frontend.
|
||||
local_args: Tuple[Var, ...] = ()
|
||||
|
||||
# The arguments to pass to the function.
|
||||
args: Tuple[Tuple[Var, Var], ...] = ()
|
||||
|
||||
|
@ -83,9 +83,6 @@ def run(
|
||||
frontend_port = get_config().port if port is None else port
|
||||
backend_port = get_config().backend_port if backend_port is None else backend_port
|
||||
|
||||
# set the upload url in pynecone.json file
|
||||
build.set_pynecone_upload_endpoint()
|
||||
|
||||
# If --no-frontend-only and no --backend-only, then turn on frontend and backend both
|
||||
if not frontend and not backend:
|
||||
frontend = True
|
||||
|
@ -160,6 +160,9 @@ def setup_frontend(root: Path, disable_telemetry: bool = True):
|
||||
dest=str(root / constants.WEB_ASSETS_DIR),
|
||||
)
|
||||
|
||||
# set the upload url in pynecone.json file
|
||||
set_pynecone_upload_endpoint()
|
||||
|
||||
# Disable the Next telemetry.
|
||||
if disable_telemetry:
|
||||
subprocess.Popen(
|
||||
|
@ -294,8 +294,15 @@ def format_event(event_spec: EventSpec) -> str:
|
||||
for name, val in event_spec.args
|
||||
]
|
||||
)
|
||||
quote = '"'
|
||||
return f"E(\"{format_event_handler(event_spec.handler)}\", {wrap(args, '{')}, {wrap(event_spec.client_handler_name, quote) if event_spec.client_handler_name else ''})"
|
||||
event_args = [
|
||||
wrap(format_event_handler(event_spec.handler), '"'),
|
||||
]
|
||||
if len(args) > 0:
|
||||
event_args.append(wrap(args, "{"))
|
||||
|
||||
if event_spec.client_handler_name:
|
||||
event_args.append(wrap(event_spec.client_handler_name, '"'))
|
||||
return f"E({', '.join(event_args)})"
|
||||
|
||||
|
||||
def format_full_control_event(event_chain: EventChain) -> str:
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pynecone"
|
||||
version = "0.1.29"
|
||||
version = "0.1.30"
|
||||
description = "Web apps in pure Python."
|
||||
license = "Apache-2.0"
|
||||
authors = [
|
||||
|
@ -25,19 +25,18 @@ def mock_event(arg):
|
||||
({"a": 1, "b": 2, "c": 3}, '{{"a": 1, "b": 2, "c": 3}}'),
|
||||
(
|
||||
EventChain(events=[EventSpec(handler=EventHandler(fn=mock_event))]),
|
||||
'{() => Event([E("mock_event", {}, )])}',
|
||||
'{(_e) => {_e.preventDefault(); Event([E("mock_event")])}}',
|
||||
),
|
||||
(
|
||||
EventChain(
|
||||
events=[
|
||||
EventSpec(
|
||||
handler=EventHandler(fn=mock_event),
|
||||
local_args=(EVENT_ARG,),
|
||||
args=((Var.create_safe("arg"), EVENT_ARG.target.value),),
|
||||
)
|
||||
]
|
||||
),
|
||||
'{(_e) => Event([E("mock_event", {arg:_e.target.value}, )])}',
|
||||
'{(_e) => {_e.preventDefault(); Event([E("mock_event", {arg:_e.target.value})])}}',
|
||||
),
|
||||
({"a": "red", "b": "blue"}, '{{"a": "red", "b": "blue"}}'),
|
||||
(BaseVar(name="var", type_="int"), "{var}"),
|
||||
|
@ -45,27 +45,25 @@ def test_call_event_handler():
|
||||
event_spec = handler()
|
||||
|
||||
assert event_spec.handler == handler
|
||||
assert event_spec.local_args == ()
|
||||
assert event_spec.args == ()
|
||||
assert format.format_event(event_spec) == 'E("test_fn", {}, )'
|
||||
assert format.format_event(event_spec) == 'E("test_fn")'
|
||||
|
||||
handler = EventHandler(fn=test_fn_with_args)
|
||||
event_spec = handler(make_var("first"), make_var("second"))
|
||||
|
||||
# Test passing vars as args.
|
||||
assert event_spec.handler == handler
|
||||
assert event_spec.local_args == ()
|
||||
assert event_spec.args == (("arg1", "first"), ("arg2", "second"))
|
||||
assert (
|
||||
format.format_event(event_spec)
|
||||
== 'E("test_fn_with_args", {arg1:first,arg2:second}, )'
|
||||
== 'E("test_fn_with_args", {arg1:first,arg2:second})'
|
||||
)
|
||||
|
||||
# Passing args as strings should format differently.
|
||||
event_spec = handler("first", "second") # type: ignore
|
||||
assert (
|
||||
format.format_event(event_spec)
|
||||
== 'E("test_fn_with_args", {arg1:"first",arg2:"second"}, )'
|
||||
== 'E("test_fn_with_args", {arg1:"first",arg2:"second"})'
|
||||
)
|
||||
|
||||
first, second = 123, "456"
|
||||
@ -73,11 +71,10 @@ def test_call_event_handler():
|
||||
event_spec = handler(first, second) # type: ignore
|
||||
assert (
|
||||
format.format_event(event_spec)
|
||||
== 'E("test_fn_with_args", {arg1:123,arg2:"456"}, )'
|
||||
== 'E("test_fn_with_args", {arg1:123,arg2:"456"})'
|
||||
)
|
||||
|
||||
assert event_spec.handler == handler
|
||||
assert event_spec.local_args == ()
|
||||
assert event_spec.args == (
|
||||
("arg1", format.json_dumps(first)),
|
||||
("arg2", format.json_dumps(second)),
|
||||
@ -94,9 +91,9 @@ def test_event_redirect():
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_redirect"
|
||||
assert spec.args == (("path", "/path"),)
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:"/path"}, )'
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:"/path"})'
|
||||
spec = event.redirect(Var.create_safe("path"))
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:path}, )'
|
||||
assert format.format_event(spec) == 'E("_redirect", {path:path})'
|
||||
|
||||
|
||||
def test_event_console_log():
|
||||
@ -105,9 +102,9 @@ def test_event_console_log():
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_console"
|
||||
assert spec.args == (("message", "message"),)
|
||||
assert format.format_event(spec) == 'E("_console", {message:"message"}, )'
|
||||
assert format.format_event(spec) == 'E("_console", {message:"message"})'
|
||||
spec = event.console_log(Var.create_safe("message"))
|
||||
assert format.format_event(spec) == 'E("_console", {message:message}, )'
|
||||
assert format.format_event(spec) == 'E("_console", {message:message})'
|
||||
|
||||
|
||||
def test_event_window_alert():
|
||||
@ -116,9 +113,9 @@ def test_event_window_alert():
|
||||
assert isinstance(spec, EventSpec)
|
||||
assert spec.handler.fn.__qualname__ == "_alert"
|
||||
assert spec.args == (("message", "message"),)
|
||||
assert format.format_event(spec) == 'E("_alert", {message:"message"}, )'
|
||||
assert format.format_event(spec) == 'E("_alert", {message:"message"})'
|
||||
spec = event.window_alert(Var.create_safe("message"))
|
||||
assert format.format_event(spec) == 'E("_alert", {message:message}, )'
|
||||
assert format.format_event(spec) == 'E("_alert", {message:message})'
|
||||
|
||||
|
||||
def test_set_value():
|
||||
@ -130,8 +127,8 @@ def test_set_value():
|
||||
("ref", Var.create_safe("ref_input1")),
|
||||
("value", ""),
|
||||
)
|
||||
assert format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:""}, )'
|
||||
assert format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:""})'
|
||||
spec = event.set_value("input1", Var.create_safe("message"))
|
||||
assert (
|
||||
format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:message}, )'
|
||||
format.format_event(spec) == 'E("_set_value", {ref:ref_input1,value:message})'
|
||||
)
|
||||
|
@ -330,6 +330,7 @@ def test_setup_frontend(tmp_path, mocker):
|
||||
assert str(web_folder) == prerequisites.create_web_directory(tmp_path)
|
||||
|
||||
mocker.patch("pynecone.utils.prerequisites.install_frontend_packages")
|
||||
mocker.patch("pynecone.utils.build.set_pynecone_upload_endpoint")
|
||||
|
||||
build.setup_frontend(tmp_path, disable_telemetry=False)
|
||||
assert web_folder.exists()
|
||||
|
Loading…
Reference in New Issue
Block a user