Brings LiveView from Phoenix framework into Django
Project description
Reactor, a LiveView library for Django
Reactor enables you to do something similar to Phoenix framework LiveView using Django Channels.
Installation and setup
Setup up your django-channels project before hand.
Install reactor:
pip install django-reactor
Add reactor
to your INSALLED_APPS
. Register the URL patterns of reactor in your your file where is the ASGI application, usually <youproject>/routing.py
, something like this:
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from reactor.urls import websocket_urlpatterns # <- import this
application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(URLRouter(
websocket_urlpatterns, # <- add it here
...
))
})
In the templates where you want to use reactive components you have to load the reactor static files. So do something like:
{% load reactor %}
<!doctype html>
<html>
<head>
....
{% reactor_header %}
</head>
...
</html>
Put them as early as possible, they use <script defer>
so they will be downloaded in parallel with the page load and when the page is loaded will be executed.
Simple counter example
In your app create a template x-counter.html
:
{% load reactor %}
<x-counter id="{{ this.id }}" state="{{ this.serialize|tojson }}">
{{ this.amount }}
<button onclick="send(this, 'inc')">+</button>
<button onclick="send(this, 'dec')">-</button>
<button onclick="send(this, 'set_to', {amount: 0})">reset</button>
</x-counter>
Anatomy of a template: each component should is a custom web component that inherits from HTMLElement. They should have an id
so the backend knows which instance is this one and a state
attribute with the necessary information to recreate the full state of the component on first render and in case of reconnection to the back-end.
Render things as usually, so you can use full Django template language, trans
, if
, for
and so on. Just keep in mind that the instance of the component is referred as this
.
Forwarding events to the back-end: Notice that for event binding in-line JavaScript is used on the event handler of the HTML elements. How this works? When the increment button receives a click event send(this, 'inc')
is called, send
is a reactor function that will look for the parent custom component and will dispatch to it the inc
message, or the set_to
message and its parameters {amount: 0}
. The custom element then will send this message to the back-end, where the state of the component will change and then will be re-rendered back to the front-end. In the front-end morphdom
(just like in Phoenix LiveView) is used to apply the new HTML.
Now let's write the behavior part of the component
from reactor import Component
class XCounter(Component):
# reference the template from above
template_name = 'x-counter.html'
# A component is instantiated during normal rendering and when the component
# connects from the front-end. Then __init__ is called passing `context` of
# creation (in case of HTML rendering is the context of the template, in
# case of a WebSocket connection is the scope of django channels) Also the
# `id` is passed if any is provided, otherwise a `uuid4` is generated on
# the fly.
# This method is called after __init__ passing the initial state of the
# Component, this method is responsible taking the state of the component
# and construct or reconstruct the component. Sometimes loading things from
# the database like in the tood-demo app https://bit.ly/2ZlMvP3
def mount(self, amount=0, **kwargs):
self.amount = amount
# This method is used to capture the essence of the state of a component
# state, so it can be reconstructed at any given time on the future.
# By passing what ever is returned by this method to `mount`
def serialize(self):
return dict(id=self.id, amount=self.amount)
# This are the event handlers they always start with `receive_`
def receive_inc(self, **kwargs):
self.amount += 1
def receive_dec(self, **kwargs):
self.amount -= 1
def receive_set_to(self, amount, **kwargs):
self.amount = amount
Let's now render this counter, expose a normal view that renders HTML, like:
def index(request):
return render(request, 'index.html')
And the index template being:
{% load reactor %}
<!doctype html>
<html>
<head>
....
{% reactor_header %}
</head>
<body>
{% 'x-counter' %}
<!-- or passing an initial state -->
{% 'x-counter' amount=100 %}
</body>
</html>
Components can be nested, there is no problem with that. You just need to create one inside another.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.