Widgets are components¶
Widgets are what we call “components”, which are a central part of the event system. They are what allows widgets to have properties and react to things happening in other parts of the application. But let’s not get ahead of ourselves; the event system is dicsussed in the next chapter.
For the moment, it’s enough to know that the Widget
class is kind of JsComponent
.
This means that these widgets live in JavaScript: when their actions and methods
are called, they run in JavaScript. But the cool
thing is that you can still use these objects in Python, by setting their properties,
invoking their actions, and reacting to their state. This is possible because
of so-called proxy objects.
We mentioned earlier that the PyWidget
can be used to
create widgets that live in Python: these are a kind of PyComponent
:
their actions and methods are executed in Python. And yes, they can be used
from JS by setting properties, invoking action and reacting to state.
Being able to write components (and widgets) that operate either in Python or JavaScript is a very powerful feature of Flexx. However, it can easily be a source of confusion. Therefore it’s good to understand the difference between these kinds of classes.
PyComponent and JsComponent¶
A Flexx application consists of components that exist in either Python or JavaScript, and which can communicate with each-other in a variety of ways.
The PyComponent
and
JsComponent
classes derive from the
Component
class (which is a topic of the next chapter).
The most important things to know about PyComponent
and JsComponent
:
- They are both associated with a
Session
. - They have an
id
attribute that is unique within their session, and auid
attribute that is globally unique. - They live (i.e. their methods run) in Python and JavaScript, respectively.
- A
PyComponent
can only be instantiated in Python, aJsComponent
can be instantiated in both Python and JavaScript. - A
PyComponent
always has a corresponding proxy object in JavaScript. - A
JsComponent
may have a proxy object in Python; these proxy objects are created automatically when Python references the component.
In practice, you’ll use PyComponents
to implement Python-side behavior,
and JsComponents
(e.g. Widgets) for the behavior in JavaScript. Flexx
allows a variety of ways by which you can tie Python and JS together, but
this can be a pitfall. It’s important to think well about what parts of your
app operate in JavaScript and what in Python. Patterns which help you do this
are discussed later in the guide.
(And the plain Component
class? It can be
used (both in Python and JS), but is unaware of anything “on the other side”.
It’s use in Flexx is therefore limited.)
Proxy components¶
The proxy components allow the “other side” to inspect properties, invoke actions and connect to events. The real component is aware of what events the proxy reacts to, and will only communicate these events.
The example below may be a bit much to digest. Don’t worry about that. In most cases things should just work.
from flexx import flx
class Person(flx.JsComponent): # Lives in Js
name = flx.StringProp(settable=True)
age = flx.IntProp(settable=True)
@flx.action
def increase_age(self):
self._mutate_age(self.age + 1)
class PersonDatabase(flx.PyComponent): # Lives in Python
persons = flx.ListProp()
@flx.action
def add_person(self, name, age):
with self: # new components need a session
p = Person(name=name, age=age)
self._mutate_persons([p], 'insert', 99999)
@flx.action
def new_year(self):
for p in self.persons:
p.increase_age()
In the above code, the Person
objects live in JavaScript, while a
database object that keeps a list of them lives in Python. In practice,
the Person
components will e.g. have a visual representation in the
browser. The database could also have been a JsComponent
, but let’s
assume that we need it in Python because it synchronizes to a mysql
database or something.
We can observe that the add_person
action (which executes in Python)
instantiates new Person
objects. Actually, it instantiates proxy objects that
automatically get corresponding (real) Person
objects in JavaScript.
The new_year
action executes in Python, which in turn invokes the increase_age
action of each person, which execute in JavaScript.
Actions and events cross the boundary¶
It’s important to realize that actions of a component can be invoked from anywhere.
In the above example, Person.set_name("Guido")
can be called from
Python (e.g. by the PersonDatabase
).
Similarly, you can use reactions to listen for changes on components, no matter whether these components live in Python or JavaScript. As an example, let’s implement a personcounter (reactions are covered later in this guide):
class PersonCounter(flx.JsComponent):
def init(self, db):
self.db = db
# now we can call self.db.add_person() from JavaScript!
@flx.reaction
def _count(self):
print("There are", len(self.db.persons), "persons")
# To instantiate (e.g. inside PersonDatabase.init())
PersonCounter(database)
# Note that we can also invoke the db's actions from here!
The root component and active components¶
Another useful feature is that each component has a root
attribute that
holds a reference to the component representing the root of the application.
E.g. if the root is a PersonDatabase
, all JsComponent
objects have a
reference to (a proxy of) this database.
Further, when a component is used as a context manager, it becomes an
“active component”. We’ve already seen how this is used to structure
child widgets. Sometimes you may want to know which components are active,
which you can do with loop.get_active_component
and loop.get_active_components
.
Next¶
Next up: The event system.