Skip to content

Commit

Permalink
Add preventDefault and stopPropagation to event spec
Browse files Browse the repository at this point in the history
  • Loading branch information
rmorshea committed Aug 6, 2019
1 parent 8add37f commit 5af2c05
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 33 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ parts/
sdist/
var/
wheels/
pip-wheel-metadata
*.egg-info/
.installed.cfg
*.egg
Expand Down
6 changes: 5 additions & 1 deletion docs/event-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,11 @@ vdom will:

```python
{
'event_type': '{hash}_{target_name}'
'{eventName}': {
'hash': string,
'stopPropagation': boolean,
'preventDefault': boolean
}
}
```

Expand Down
94 changes: 67 additions & 27 deletions vdom/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,30 +46,6 @@ def escape(s):
_validate_err_template = "Your object didn't match the schema: {}. \n {}"


def create_event_handler(event_type, handler):
"""Register a comm and return a serializable object with target name"""

target_name = '{hash}_{event_type}'.format(hash=hash(handler), event_type=event_type)

def handle_comm_opened(comm, msg):
@comm.on_msg
def _handle_msg(msg):
data = msg['content']['data']
event = json.loads(data)
return_value = handler(event)
if return_value:
comm.send(return_value)

comm.send('Comm target "{target_name}" registered by vdom'.format(target_name=target_name))

# Register a new comm for this event handler
if get_ipython():
get_ipython().kernel.comm_manager.register_target(target_name, handle_comm_opened)

# Return a serialized object
return target_name


def to_json(el, schema=None):
"""Convert an element to VDOM JSON
Expand Down Expand Up @@ -102,6 +78,69 @@ def to_json(el, schema=None):
return json_el


def eventHandler(handler=None, preventDefault=False, stopPropagation=False):
"""Create an event handler with special attributes
Typically when defining event handlers you can simply pass them to a VDOM Component,
however you can't prevent the event's default behaviors or stop its propagation up
through the DOM this way.
Parameters:
preventDefault: stop the event's default behavior
stopPropagation: halt the event's propagation up the DOM
Examples:
>>> @eventHandler(preventDefault=True, stopPropagation=True)
... def on_hover(event):
pass
>>> drag_drop_spot = VDOM("div", event_handler={"onDragOver": hover})
"""

def setup(handler):
return EventHandler(handler, preventDefault, stopPropagation)

if handler is not None:
return setup(handler)
else:
return setup


class EventHandler(object):

def __init__(self, handler, prevent_default=False, stop_propagation=False):
self._handler = handler
self._prevent_default = prevent_default
self._stop_propagation = stop_propagation
# Register a new comm for this event handler
if get_ipython():
comm_manager = get_ipython().kernel.comm_manager
comm_manager.register_target(hash(self), self._on_comm_opened)

def serialize(self):
return {
"hash": hash(self),
"preventDefault": self._prevent_default,
"stopPropagation": self._stop_propagation,
}

def __call__(self, event):
return self._handler(event)

def __hash__(self):
return hash(self._handler)

def _on_comm_opened(self, comm, msg):
comm.on_msg(self._on_comm_msg)
comm.send('Comm target "{hash}" registered by vdom'.format(hash=hash(self)))

def _on_comm_msg(self, msg):
data = msg['content']['data']
event = json.loads(data)
return_value = self(event)
if return_value:
comm.send(return_value)


class VDOM(object):
"""A basic virtual DOM class which allows you to write literal VDOM spec
Expand Down Expand Up @@ -191,9 +230,10 @@ def to_dict(self):
vdom_dict = {'tagName': self.tag_name, 'attributes': attributes}
if self.event_handlers:
event_handlers = dict(self.event_handlers.items())
for key, value in event_handlers.items():
value = create_event_handler(key, value)
event_handlers[key] = value
for key, handler in event_handlers.items():
if not isinstance(handler, EventHandler):
handler = EventHandler(handler)
event_handlers[key] = handler.serialize()
vdom_dict['eventHandlers'] = event_handlers
if self.key:
vdom_dict['key'] = self.key
Expand Down
44 changes: 39 additions & 5 deletions vdom/tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from ..core import create_component, create_element, to_json, VDOM, convert_style_key
from ..core import create_component, create_element, to_json, VDOM, convert_style_key, eventHandler
from ..helpers import div, p, img, h1, b, button
from jsonschema import ValidationError, validate
import os
Expand Down Expand Up @@ -68,7 +68,7 @@ def test_css():
}


def test_event_handler():
def test_event_handler_normal_function():
def handle_click(event):
print(event)

Expand All @@ -77,7 +77,41 @@ def handle_click(event):
assert el.to_html() == '<button>click me</button>'
assert el.to_dict() == {
'attributes': {},
'eventHandlers': {'onClick': '{hash}_onClick'.format(hash=hash(handle_click))},
'eventHandlers': {
'onClick': {
'hash': hash(handle_click),
'stopPropagation': False,
'preventDefault': False,
},
},
'children': ['click me'],
'tagName': 'button',
}


def test_event_handler_decorator():

@eventHandler(stopPropagation=True, preventDefault=True)
def handle_click(event):
pass

assert handle_click.serialize() == {
"hash": hash(handle_click._handler),
"preventDefault": True,
"stopPropagation": True,
}

el = button('click me', onClick=handle_click)

assert el.to_dict() == {
'attributes': {},
'eventHandlers': {
'onClick': {
'hash': hash(handle_click),
'stopPropagation': True,
'preventDefault': True,
},
},
'children': ['click me'],
'tagName': 'button',
}
Expand Down Expand Up @@ -199,13 +233,13 @@ def test_create_element_deprecated():

def test_component_disallows_children():
void = create_component('void', allow_children=False)
with pytest.raises(ValueError, message='<void /> cannot have children'):
with pytest.raises(ValueError, match='<void /> cannot have children'):
void(div())


def test_component_disallows_children_kwargs():
void = create_component('void', allow_children=False)
with pytest.raises(ValueError, message='<void /> cannot have children'):
with pytest.raises(ValueError, match='<void /> cannot have children'):
void(children=div())


Expand Down

0 comments on commit 5af2c05

Please sign in to comment.