In [1]:
%gui asyncio
from flexx import event
In flexx.event, events are represented with dictionary objects that
provide information about the event (such as what button was pressed,
or the new value of a property). A custom Dict class is used that inherits from dict and allows attribute access,
e.g. ev.button as an alternative to ev['button'].
In [2]:
class MyObject(event.Component):
@event.reaction('!foo')
def on_foo(self, *events):
print('received the foo event %i times' % len(events))
ob = MyObject()
In [3]:
for i in range(3):
ob.emit('foo', {})
Note how the reaction is connected using a "connection string", which (in this case) indicates we connect to the type "foo" event of the object. The connection string allows some powerful mechanics, as we will see later in this tutorial. Here, we prefixed the connection string with "!", to supress a warning that Flexx would otherwise give, because it does not know about the "foo" event.
Also note how the reaction accepts multiple events at once. This means that in situations where we only care about something being changed, we can skip "duplicate" events. In situation where each individual event needs processing, use for ev in events: ....
Reactions can also be used as normal methods:
In [4]:
ob.on_foo()
A reaction can also connect to multiple events:
In [5]:
class MyObject(event.Component):
@event.reaction('!foo', '!bar')
def on_foo_or_bar(self, *events):
for ev in events:
print('received the %s event' % ev.type)
ob = MyObject()
ob.emit('foo', {}); ob.emit('foo', {}); ob.emit('bar', {})
Out[5]:
In [6]:
class MyObject(event.Component):
foo = event.IntProp(2, settable=True)
@event.reaction('foo')
def on_foo(self, *events):
print('foo changed from', events[0].old_value, 'to', events[-1].new_value)
ob = MyObject()
In [7]:
ob.set_foo(7)
Out[7]:
In [8]:
print(ob.foo)
Properties can also be set during initialization.
In [9]:
ob = MyObject(foo=12)
Properties are readonly. This may seem like a limitation at first, but it helps make apps more predictable, especially as they become larger. Properties can be mutated using actions. In the above example, a setter action was created automatically because we specified setter=True in the definition of the property.
In [10]:
class MyObject(event.Component):
foo = event.IntProp(2, settable=True)
@event.action
def increase_foo(self):
self._mutate_foo(self.foo + 1)
@event.reaction('foo')
def on_foo(self, *events):
print('foo changed from', events[0].old_value, 'to', events[-1].new_value)
ob = MyObject()
In [11]:
ob.increase_foo()
Out[11]:
The action above mutates the foo property. Properties can only be mutated by actions. This ensures that the state of a component (and of the whole app) is consistent during the handling of reactions.
In [12]:
class MyObject(event.Component):
@event.emitter
def mouse_down(self, js_event):
''' Event emitted when the mouse is pressed down. '''
return dict(button=js_event['button'])
@event.reaction('mouse_down')
def on_bar(self, *events):
for ev in events:
print('detected mouse_down, button', ev.button)
ob = MyObject()
In [13]:
ob.mouse_down({'button': 1})
ob.mouse_down({'button': 2})
Implicit reactions make it easy to write concise code that needs to keep track of state. To create an implicit reaction, simply provide no connection strings. The reaction will now automatically track all properties that the reaction is accessing. This even works dynamically, e.g. when accessing a property on each element in a list property, the reaction will automatically "reconnect" when the list changes.
In [14]:
class MyObject(event.Component):
foo = event.IntProp(2, settable=True)
@event.reaction
def on_foo(self):
print('foo changed is now', self.foo)
ob = MyObject()
In [15]:
ob.set_foo(99)
Out[15]:
In [16]:
class MyObject(event.Component):
@event.reaction('!foo:bb')
def foo_handler1(self, *events):
print('foo B')
@event.reaction('!foo:cc')
def foo_handler2(self, *events):
print('foo C')
@event.reaction('!foo:aa')
def foo_handler3(self, *events):
print('foo A')
ob = MyObject()
In [17]:
ob.emit('foo', {})
Out[17]:
In [18]:
ob.disconnect('foo:bb')
ob.emit('foo', {})
Out[18]:
In [19]:
class Root(event.Component):
children = event.TupleProp([], settable=True)
@event.reaction('children', 'children*.count')
def update_total_count(self, *events):
total_count = sum([child.count for child in self.children])
print('total count is', total_count)
class Sub(event.Component):
count = event.IntProp(0, settable=True)
root = Root()
sub1, sub2, sub3 = Sub(count=1), Sub(count=2), Sub(count=3)
root.set_children([sub1, sub2, sub3])
Out[19]:
Updating the count property on any of its children will invoke the callback:
In [20]:
sub1.set_count(100)
Out[20]:
We also connected to the children property, so that the handler is also invoked when the children are added/removed:
In [21]:
root.set_children([sub2, sub3])
Out[21]:
Naturally, when the count on new children changes ...
In [22]:
sub4 = Sub()
root.set_children([sub3, sub4])
Out[22]:
In [23]:
sub4.set_count(10)
Out[23]:
In [24]:
sub1.set_count(1000) # no update, sub1 is not part of root's children
Out[24]:
Note that the above example is a great candidate for using an implicit reaction. Try it!
In [ ]: