Skip to content

Commit

Permalink
add 'key' to VDOM spec
Browse files Browse the repository at this point in the history
all makes event handler targets deterministic
based on keys and child indices
  • Loading branch information
rmorshea committed Apr 17, 2021
1 parent d04faf9 commit c3236fe
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 233 deletions.
15 changes: 3 additions & 12 deletions docs/source/core-concepts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,27 +77,18 @@ have to re-render the layout and see what changed:
from idom.core.layout import LayoutEvent


event_handler_id = "on-click"


@idom.component
def ClickCount():
count, set_count = idom.hooks.use_state(0)

@idom.event(target_id=event_handler_id) # <-- trick to hard code event handler ID
def on_click(event):
set_count(count + 1)

return idom.html.button(
{"onClick": on_click},
{"onClick": lambda event: set_count(count + 1)},
[f"Click count: {count}"],
)


async with idom.Layout(ClickCount()) as layout:
async with idom.Layout(ClickCount(key="something")) as layout:
patch_1 = await layout.render()

fake_event = LayoutEvent(event_handler_id, [{}])
fake_event = LayoutEvent("something.onClick", [{}])
await layout.dispatch(fake_event)
patch_2 = await layout.render()

Expand Down
37 changes: 21 additions & 16 deletions src/idom/core/component.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from __future__ import annotations

import abc
import inspect
from functools import wraps
from typing import TYPE_CHECKING, Any, Callable, Dict, Tuple, Union

from typing import Any, Callable, Dict, Tuple, Union

if TYPE_CHECKING: # pragma: no cover
from .vdom import VdomDict # noqa
from .vdom import VdomDict


ComponentConstructor = Callable[..., "AbstractComponent"]
ComponentRenderFunction = Callable[..., Union["AbstractComponent", "VdomDict"]]
ComponentRenderFunction = Callable[..., Union["AbstractComponent", VdomDict]]


def component(function: ComponentRenderFunction) -> Callable[..., "Component"]:
Expand All @@ -21,42 +21,47 @@ def component(function: ComponentRenderFunction) -> Callable[..., "Component"]:
"""

@wraps(function)
def constructor(*args: Any, **kwargs: Any) -> Component:
return Component(function, args, kwargs)
def constructor(*args: Any, key: str = "", **kwargs: Any) -> Component:
return Component(function, key, args, kwargs)

return constructor


class AbstractComponent(abc.ABC):

__slots__ = [] if hasattr(abc.ABC, "__weakref__") else ["__weakref__"]
__slots__ = ["key"]
if not hasattr(abc.ABC, "__weakref__"):
__slots__.append("__weakref__") # pragma: no cover

key: str

@abc.abstractmethod
def render(self) -> "VdomDict":
def render(self) -> VdomDict:
"""Render the component's :ref:`VDOM <VDOM Mimetype>` model."""


class Component(AbstractComponent):
"""An object for rending component models."""

__slots__ = (
"_function",
"_args",
"_kwargs",
)
__slots__ = "_function", "_args", "_kwargs"

def __init__(
self,
function: ComponentRenderFunction,
key: str,
args: Tuple[Any, ...],
kwargs: Dict[str, Any],
) -> None:
self.key = key
self._function = function
self._args = args
self._kwargs = kwargs

def render(self) -> Any:
return self._function(*self._args, **self._kwargs)
def render(self) -> VdomDict:
model = self._function(*self._args, **self._kwargs)
if isinstance(model, AbstractComponent):
model = {"tagName": "div", "children": [model]}
return model

def __repr__(self) -> str:
sig = inspect.signature(self._function)
Expand Down
26 changes: 3 additions & 23 deletions src/idom/core/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,15 @@
)

from anyio import create_task_group
from mypy_extensions import TypedDict


EventsMapping = Union[Dict[str, Union["Callable[..., Any]", "EventHandler"]], "Events"]


class EventTarget(TypedDict):
target: str
preventDefault: bool # noqa
stopPropagation: bool # noqa


def event(
function: Optional[Callable[..., Any]] = None,
stop_propagation: bool = False,
prevent_default: bool = False,
target_id: Optional[str] = None,
) -> Union["EventHandler", Callable[[Callable[..., Any]], "EventHandler"]]:
"""Create an event handler function with extra functionality.
Expand All @@ -49,12 +41,8 @@ def event(
Block the event from propagating further up the DOM.
prevent_default:
Stops the default actional associate with the event from taking place.
target_id:
A unique ID used to locate this handler in the resulting VDOM. This is
automatically generated by default and is typically not set manually
except in testing.
"""
handler = EventHandler(stop_propagation, prevent_default, target_id=target_id)
handler = EventHandler(stop_propagation, prevent_default)
if function is not None:
handler.add(function)
return handler
Expand Down Expand Up @@ -142,8 +130,6 @@ def __repr__(self) -> str: # pragma: no cover
class EventHandler:
"""An object which defines an event handler.
Get a serialized reference to the handler via :meth:`Handler.serialize`.
The event handler object acts like a coroutine when called.
Parameters:
Expand All @@ -159,19 +145,16 @@ class EventHandler:
"__weakref__",
"_coro_handlers",
"_func_handlers",
"target_id",
"prevent_default",
"stop_propogation",
"stop_propagation",
)

def __init__(
self,
stop_propagation: bool = False,
prevent_default: bool = False,
target_id: Optional[str] = None,
) -> None:
self.target_id = target_id or str(id(self))
self.stop_propogation = stop_propagation
self.stop_propagation = stop_propagation
self.prevent_default = prevent_default
self._coro_handlers: List[Callable[..., Coroutine[Any, Any, Any]]] = []
self._func_handlers: List[Callable[..., Any]] = []
Expand Down Expand Up @@ -216,6 +199,3 @@ def __contains__(self, function: Any) -> bool:
return function in self._coro_handlers
else:
return function in self._func_handlers

def __repr__(self) -> str: # pragma: no cover
return f"{type(self).__name__}({self.serialize()})"
Loading

0 comments on commit c3236fe

Please sign in to comment.