App introduction¶
The app module implements the connection between Python and JavaScript.
It runs a web server and websocket server based on Tornado, provides
an asset (and data) management system, and provides the Model
class,
which allows definition of objects that have both a Python and JavaScript
representation, forming the basis for widgets etc.
Writing code with the Model class¶
An application will typically consists of a custom Model class (or Widget).
In the class, one can define Python behavior, JavaScript behavior, define
properties etc. Using the event system explained in flexx.event
, one
can listen for events on either side (Python or JavaScript). Check out
the examples and this simplistic code:
from flexx import app, event
class MyModel(app.Model):
def init(self):
...
# Listen for changes of foo in Python
@event.connect('foo')
def on_foo(self, *events):
...
class Both:
# A property that exists on both ends (and is synced)
@event.property
def foo(self, v=0):
return v
class JS:
# Listen for changes of foo in JS
@event.connect('foo')
def on_foo(self, *events):
...
The scope of modules¶
The above demonstrates how one can write code that is executed in JavaScript. In this code, you can make use of functions, classes, and values that are defined in the same module (as long as they can be transpiled / serialized).
For every Python module that defines code that is used in JS, a corresponding JS module is created. Flexx detects what variable names are used in the JS code, but not declared in it, and tries to find the corresponding object in the module. You can even import functions/classes from other modules.
from flexx import app
from foo import func1
def func2():
...
info = {'x': 1, 'y': 2}
class MyModel(app.Model):
class JS:
@event.connect('some.event')
def handler(self, *events):
func1(info)
func2()
In the code above, Flexx will include the definition of func2
and
info
in the same module that defines MyModel
, and include
func1
in the JS module foo
. If the JS in MyModel
would not
use these functions, neither definition would be included in the JavaScript.
A useful feature is that the RawJS
class from PyScript can be used
in modules to define objects in JS:
from flexx import app
my_js_module = RawJS('require("myjsmodule.js")')
class MyModel(app.Model):
class JS:
@event.connect('some.event')
def handler(self, *events):
my_js_module.foo.bar()
One can also assign __pyscript__ = True
to a module to make Flexx
transpile a module as a whole. A downside is that (at the moment) such
modules cannot use import.
Applications¶
A Model
class can be made into an application by passing it to
app.serve
. This registers the application, so that clients can connect
to the app based on its name (or using a custom name specified in
app.serve()
). One instance of this class is created
per connection. Multiple apps can be hosted from the same process simply
be specifying more app classes. To connect to the application
corresponding to the MyApp class, one should connect to
“http://domain:port/MyApp”.
An app can also be launched (via app.launch()
), which will invoke
a client webruntime which is connected to the returned app object. This
is the intended way to launch desktop-like apps.
An app can also be exported to HTML via app.export()
. One can
create a drectory structure that contains multiple exported apps that
share assets, or export apps as standalone html documents.
Starting the server¶
Use app.start()
to enter the mainloop for the server. For desktop
applications you can use app.run()
, which does what start()
does,
except the main loop exits when there are no more connections (i.e. the
server stops when the (last) window is closed).
Interactive use¶
Further, Flexx can be used interactively, from an IDE or from the Jupyter
notebook. Use app.init_interactive()
to launch a runtime in the same
way as app.launch()
, except one can now interactively (re)define models
and widgets, and make them appear in the runtime/browser.
In the IPython/Jupyter notebook, the user needs to run
init_notebook()
which will inject the necessary JS and CSS.
Simple widgets (e.g. buttons) will display just fine, but for other
widgets you might want to use SomeWidget(style='height:300px')
to
specify its size.
Asset management¶
When writing code that relies on a certain JS or CSS library, that library can be loaded in the client by associating it with the module that needs it. Flexx will then automatically load it when code from that module is used in JS:
# Associate asset
app.assets.associate_asset(__name__, 'mydep.js', js_code)
# Sometimes a more lightweight *remote* asset is prefered
app.assets.associate_asset(__name__, 'http://some.cdn/lib.css')
# Create Model (or Widget) that needs the asset at the client
class MyMode(app.Model):
....
It is also possible to provide assets that are not automatically loaded on the main app page, e.g. for sub-pages or web workers:
# Register asset
asset_url = app.assets.add_shared_asset('mydep.js', js_code)
Data management¶
Data can be provided per session or shared between sessions:
# Add session-specific data
link = your_model.session.add_data('some_name.png', binary_blob)
# Add shared data
link = app.assets.add_shared_data('some_name.png', binary_blob)
Futher, it is possible to send data from Python to JS within a model class:
class MyModel(app.Model):
def some_method(self):
self.send_data(binary_blob, meta_dict)
class JS:
def receive_data(self, array_buffer, meta_dict):
# This gets called when the data arrives.
... # handle the data
In this case, binary_blob
can also be a URL where the client should
download the binary data from.
Some background info on the server process¶
Each server process hosts on a single URL (domain+port), but can serve multiple applications (via different paths). Each process uses one tornado IOLoop, and exactly one Tornado Application object.
When a client connects to the server, it is served an HTML page, which contains the information needed to connect to a websocket. From there, all communication happens over this websocket.