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