Giter Site home page Giter Site logo

reactor's Introduction

Reactor, a LiveView library for Django

Reactor enables you to do something similar to Phoenix framework LiveView using Django Channels.

TODO MVC demo app

What's in the box?

This is no replacement for VueJS or ReactJS, or any JavaScript but it will allow you use all the potential of Django to create interactive front-ends. This method has its drawbacks because if connection is lost to the server the components in the front-end go busted until connection is re-established. But also has some advantages, as everything is server side rendered the interface comes already with meaningful information in the first request response, you can use all the power of Django template and ORM directly in your component and update the interface in real-time by subscribing to events on the server. If connection is lost or a component crashes, the front-end will have enough information to rebuild their state in the last good known state and continue to operate with the connection is restored.

Installation and setup

Reactor requires Python >=3.9.

Install reactor:

pip install django-reactor

Reactor makes use of django-channels, by default this one uses an InMemory channel layer which is not capable of a real broadcasting, so you might wanna use the Redis one, take a look here: Channel Layers

Add reactor and channels to your INSTALLED_APPS before the Django applications so channels can override the runserver command.

INSTALLED_APPS = [
    'reactor',
    'channels',
    ...
]

...

ASGI_APPLICATION = 'project_name.asgi.application'

and modify your project_name/asgi.py file like:

import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project_name.settings')

import django
django.setup()

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from reactor.urls import websocket_urlpatterns

application = ProtocolTypeRouter({
    'http': get_asgi_application(),
    'websocket': AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})

Note: Reactor since version 2, autoloads any live.py file in your applications with the hope to find there Reactor Components so they get registered and can be instantiated.

In the templates where you want to use reactive components you have to load the reactor static files. So do something like this so the right JavaScript gets loaded:

{% load reactor %}
<!doctype html>
<html>
    <head>
        ... {% reactor_header %}
    </head>
    ...
</html>

Don't worry if you put this as early as possible, the scripts are loaded using <script defer> so they will be downloaded in parallel with the html, and when all is loaded they are executed.

Simple example of a counter

In your app create a template x-counter.html:

{% load reactor %}
<div {% tag_header %}>
  {{ amount }}
  <button {% on 'click' 'inc' %}>+</button>
  <button {% on 'click' 'dec' %}>-</button>
  <button {% on 'click' 'set_to' amount=0 %}>reset</button>
</div>

Anatomy of a template: each component should be a single root tag and you need to add {% tag_header %} to that tag, so a set of attributes that describe the component state are added to that tag.

Each component 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 re-connection 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 does this work? 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 in live.py:

from reactor.component import Component


class XCounter(Component):
    _template_name = 'x-counter.html'

    amount: int = 0

    async def inc(self):
        self.amount += 1

    async def dec(self):
        self.amount -= 1

    async def set_to(self, amount: int):
        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>
        {% component 'XCounter' %}

        <!-- or passing an initial state -->
        {% component 'XCounter' amount=100 %}
    </body>
</html>

Don't forget to update your urls.py to call the index view.

Persisting the state of the Counter in the URL as a GET parameter

Add:

...

class SearchList(Component):
  query: str = ""

  @classmethod
  def new(cls, reactor: ReactorMeta, **kwargs):
    # read the query parameter and initialize the object with that parameter
    kwargs.setdefault("query", reactor.params.get("query", ""))
    return cls(reactor=reactor, **kwargs)

  async def filter_results(self, query: str):
    self.query = query
    # update the query string in the browser
    self.reactor.params["query"] = query
...

This will make that everytime that method gets called the query string on the browser will get updated to include "query=blahblah". Never replace the self.reactor.params, mutate it instead.

Here is another example, suppose you have a list of items that can be expanded, like nodes in a tree view:

class Node(Component):
    name: str
    expanded: bool = False

    @classmethod
    def new(cls, reactor: ReactorMeta, id: str, **kwargs):
        kwargs["expanded"] = id in reactor.params.get("expanded.json", [])
        return cls(reactor=reactor, id=id, **kwargs)

    async def toggle_expanded(self):
        self.expanded = not self.expanded
        expanded = self.reactor.params.setdefault("expanded.json", [])
        if self.expanded:
            expanded.append(self.id)
        elif self.id in expanded:
            expanded.remove(self.id)

Here expanded.json is a list of the expanded nodes. Notice the .json this indicates that the value of this key should be encoded/decoded to/from JSON, so just put there JSON serializable stuff.

Settings:

Default settings of reactor are:

from reactor.schemas import AutoBroadcast

REACTOR = {
    "TRANSPILER_CACHE_SIZE": 1024,
    "USE_HTML_DIFF": True,
    "USE_HMIN": False,
    "BOOST_PAGES": False,
    "TRANSPILER_CACHE_NAME": "reactor:transpiler",
    "AUTO_BROADCAST": AutoBroadcast(
        # model-a
        model: bool = False
        # model-a.1234
        model_pk: bool = False
        # model-b.9876.model-a-set
        related: bool = False
        # model-b.9876.model-a-set
        # model-a.1234.model-b-set
        m2m: bool = False
        # this is a set of tuples of ('app_label', 'ModelName')
        # to subscribe for the auto broadcast
        senders: set[tuple[str, str]] = Field(default_factory=set)
    ),
}
  • TRANSPILER_CACHE_SIZE: this is the size of an LRU dict used to cache javascript event halder transpilations.
  • USE_HTML_DIFF: when enabled uses difflib to create diffs to patch the front-end, reducing bandwidth. If disabled it sends the full HTML content every time.
  • REACTOR_USE_HMIN: when enabled and django-hmin is installed will use it to minified the HTML of the components and save bandwidth.
  • AUTO_BROADCAST: Controls which signals are sent to Component.mutation when a model is mutated.

Back-end APIs

Template tags and filters of reactor library

  • {% reactor_header %}: that includes the necessary JavaScript to make this library work. ~10Kb of minified JS, compressed with gz or brotli.
  • {% component 'Component' param1=1 param2=2 %}: Renders a component by its name and passing whatever parameters you put there to the XComponent.new method that constructs the component instance.
  • {% on 'click' 'event_handler' param1=1 param2=2 %}: Binds an event handler with paramters to some event. Look at Event binding in the front-end
  • cond: Allows simple conditional presence of a string: {% cond {'hidden': is_hidden } %}.
  • class: Use it to handle conditional classes: <div {% class {'nav_bar': True, 'hidden': is_hidden} %}></div>.

Component live cycle

Initialization & Rendering

This happens when in a "normal" template you include a component.

{% component 'Component' param1=1 param2=2 %}

This passes those parameter there to Component.new that should return the component instance and then the component get's rendered in the template and is sent to the client.

Joins

When the component arrives to the front-end it "joins" the backend. Sends it's serialized state to the backend which rebuilds the component and calls Component.joined.

After that the component is rendered and the render is sent to the front-end. Why? Because could be that the client was online while some change in the backend happened and the component needs to be updated.

User events

When a component or its parent has joined it can send user events to the client. Using the on template tag, this events are sent to the backend and then the componet is rendered again.

Subscriptions

Every time a component joins or responds to an event the Componet._subscriptions set is reviewed to check if the component subscribes or not to some channel.

  • In case a mutation in a model occurs Component.mutation(channel: str, action: reactor.auto_broadcast.Action, instance: Model) will be called.
  • In case you broadcast a message using reactor.component.broadcast(channel, **kwargs) this message will be sent to any component subscribed to channel using the method Component.notification(channel, **kwargs).

Disconnection

If the component is destroyed using the Component.destroy or just desapears from the front-end it is removed from the backend. If the the websocket closes all components in that connection are removed from the backend and the state of those componets stay just in the front-end in the seralized form awaiting for the front-end to join again.

Component API

Each component is a Pydantic model so it can serialize itself. I would advice not to mess with the __init__ method. Instead use the class method new to create the instance.

Rendering
  • _template_name: Contains the path of the template of the component.
  • _exclude_fields: (default: {"user", "reactor"}) Which fields to exclude from state serialization during rendering

Subscriptions

  • _subscriptions: (default: set()) Defines which channels is this component subscribed to.
  • mutation(channel, action, instance) Called when autobroadcast is enabled and a model you are subscribed to changes.
  • notification(channel, **kwargs) Called when reactor.component.broadcast(channel, **kwargs) is used to send an arbitrary notification to components.

Actions

  • destroy(): Removes the component from the interface.
  • focus_on(selector: str): Makes the front-end look for that selector and run .focus() on it.
  • skip_render(): Prevents the component from being rendered once.
  • send_render(): Send a signal to request render the component ahead of time.
  • dom(_action: DomAction, id: str, component_or_template, **kwargs): Can append, prepend, insert befor or after certain HTMLElement ID in the dom, the component or template, rendered using the kwargs.
  • freeze(): Prevents the component from being rendered again.
  • deffer(f, *args, **kwargs): Send a message to the current event to be executed after the current function is executed.
  • reactor.redirect_to(to, **kwargs): Changes the URL of the front-end and triggers a page load for that new URL
  • reactor.replace_to(to, **kwargs): Changes the current URL for another one.
  • reactor.push_to(to, **kwargs): Changs the URL of the front-end adding a new history entry but does not fetch the new URL from the backend.
  • reactor.send(_channel: str, _topic: str, **kwargs): Sends a message over a channel.

Front-end APIs

  • reactor.send(element, name, args): Sends a reactor user event to element, where name is the event handler and args is a JS object containing the implicit arguments of the call.

Event binding in the front-end

Look at this:

  <button {% on "click.prevent" "submit" %}>Submit</button>

Syntax: {% on [] %} The format for event and modifiers is @<event>[.modifier1][.modifier2][.modifier2-argument1][.modifier2-argument2]

Examples:

  • {% on "click.ctrl" "decrement" %}>: Clicking with Ctrl pressed calls "decrement".
  • {% on "click" "increment" amount=1 %}>: Clicking calls "increment" passing amount=1 as argument.

Misc:

  • event: is the name of the HTMLElement event: click, blur, change, keypress, keyup, keydown...

  • modifier: can be concatenated after the event name and represent actions or conditions to be met before the event execution. This is very similar as how VueJS does event binding:

    Available modifiers are:

    • inlinejs: takes the next "event handler" argument as literal JS code.
    • prevent: calls event.preventDefault()
    • stop: calls event.StopPropagation()
    • ctrl, alt, shift, meta: continues processing the event if any of those keys is pressed
    • debounce: debounces the event, it needs a delay in milliseconds. Example: keypress.debounce.100.
    • key.<keycode>: continues processing the event if the key with keycode is pressed
    • enter: alias for key.enter
    • tab: alias for key.tab
    • delete: alias for key.delete
    • backspace: alias for key.backspace
    • space: alias for key.
    • up: alias for key.arrowup
    • down: alias for key.arrowdown
    • left: alias for key.arrowleft
    • right: alias for key.arrowright

Event arguments

Reactor sends the implicit arguments you pass on the on template tag, but also sends implicit arguments. The implicit arguments are taken from the form the element handling the event is in or from the whole component otherwise.

Examples:

Here any event inside that component will have the implicit argument x being send to the backend.

<div {% tag-header %}>
  <input name="x"/>
  <button {% on "click" "submit" %}>Send</button>
</div>

Here any submit_x will send x, and submit_y will send just y.

<div {% tag-header %}>
  <input name="x"/>
  <button {% on "click" "submit_x" %}>Send</button>
  <form>
    <input name="y"/>
    <button {% on "click.prevent" "submit_y" %}>Send</button>
  </form>
</div>

Event handlers in the back-end

Given:

<button {% on 'click 'inc' amount=2 %}>Increment</button>

You will need an event handler in that component in the back-end:

async def inc(self, amount: int):
    ...

It is good if you annotate the signature so the types are validated and converted if they have to be.

More complex components

I made a TODO list app using models that signals from the model to the respective channels to update the interface when something gets created, modified or deleted.

This example contains nested components and some more complex interactions than a simple counter, the app is in the /tests/ directory.

Development & Contributing

Clone the repo and create a virtualenv or any other contained environment, get inside the repo directory, build the development environment and the run tests.

git clone [email protected]:edelvalle/reactor.git
cd reactor
make install
make test

If you want to run the included Django project used for testing do:

make
cd tests
python manage.py runserver

Enjoy!

reactor's People

Contributors

adamghill avatar cyface avatar dependabot[bot] avatar edelvalle avatar fmalina avatar jandemter avatar kbni avatar mvaled avatar nerdoc avatar tanrax avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

reactor's Issues

ModuleNotFoundError: No module named 'reactor.urls'

I followed the README and encountered this error:

File "/home/me/project/app/asgi.py", line 34, in
from reactor.urls import websocket_urlpatterns
ModuleNotFoundError: No module named 'reactor.urls'

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from reactor.urls import websocket_urlpatterns

application = ProtocolTypeRouter({
    'http': get_asgi_application(),
    'websocket': AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
})

There is no urls file in the package. Where am I supposed to get websocket_urlpatterns from?

setup guide: "reactor.urls" should be renamed correctly

Setup like shown in the README does not work:

from reactor.urls import websocket_urlpatterns

is not correct, as there is no reactor.urls - could you please correct this in the README? Because it's hard to setup from a beginners view else...

I checked the code, there is no websocket_urlpatterns in reactor besides in asgi.py, where a var only is searched for in your project's urls.py. So How to deal with that anyway?

put websocket_urlpatterns in your project's urls.py?

daphne/channels update

Hi, just trying to setup reactor.
If I make a plain python project, and just do a pip install django-reactor as recommended, I get:

channels==2.4.0
daphne==2.5.0
Django==3.2.4
django-reactor==2.2.1b0
etc.

But, in your poetry.lock you have channels version = "3.0.3", and daphne = ">=3.0,<4".

How comes this?
(BTW, I didn't get the websockets part working yet... still struggling)

Loading state for component

Hi Eddy,
I just pulled your new version. Awesome !
I love the @ syntax for event binding.

Do you think it would be hard to add a "loading" state on components ?
Where should I start to implement this ?

Thanks

Cannot get subscriptions to work, _channel_name is None

When I call self._subscribe on my components, nothing happens. I have stepped through the code with a debugger and it seems as if the property channel_name is always empty, so the check if _channel_name in def send_to_channel is always false for me and thus nothing happens when it's called.

Is this a bug or am I missing something in my configuration? I was unable to figure out how the property was supposed to get its value.

This is with the Redis channel layer, on version 2.2.1.b with Django 3.1.7 and Python 3.7

Bug? '__signature__' attribute of 'BlockEditor' is class-only

When using the simplest possible reactor component, I always get this error...
ATM, current reactor is not installable via pip. It's only possible using the git version, so you have to use this one, not the Pypi package.

I've put together a small demo app, that basically creates a small component and includes it in a simple template.

It always raises this error:

'__signature__' attribute of 'BlockEditor' is class-only`.
Exception Location: | pydantic/utils.py, line 612, in pydantic.utils.ClassAttribute.__get__
Python Version: | 3.10.2

during rendering of {% component "TestComponent" %}.

I can't figure out what this means.

AttributeError: '__signature__' attribute of 'XCounter' is class-only

Hello,

I try to apply the reactor readme.
I written this example, I try to use a component.
Unfortunately, I get the error AttributeError: '__signature__' attribute of 'XCounter' is class-only.

Do can you inform myself of what I forget?

This is my full traceback logs:

  File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 472, in thread_handler
    raise exc_info[1]
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 42, in inner
    response = await get_response(request)
  File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
    response = await wrapped_callback(
  File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 435, in __call__
    ret = await asyncio.wait_for(future, timeout=None)
  File "/usr/local/lib/python3.10/asyncio/tasks.py", line 408, in wait_for
    return await fut
  File "/usr/local/lib/python3.10/concurrent/futures/thread.py", line 58, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.10/site-packages/asgiref/sync.py", line 476, in thread_handler
    return func(*args, **kwargs)
  File "/app/navi2/views.py", line 21, in counter
    return render(request, 'counter.html', context={"title": "index"})
  File "/usr/local/lib/python3.10/site-packages/django/shortcuts.py", line 24, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "/usr/local/lib/python3.10/site-packages/django/template/loader.py", line 62, in render_to_string
    return template.render(context, request)
  File "/usr/local/lib/python3.10/site-packages/django/template/backends/django.py", line 62, in render
    return self.template.render(context)
  File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 175, in render
    return self._render(context)
  File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 167, in _render
    return self.nodelist.render(context)
  File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 1005, in render
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 1005, in <listcomp>
    return SafeString("".join([node.render_annotated(context) for node in self]))
  File "/usr/local/lib/python3.10/site-packages/django/template/base.py", line 966, in render_annotated
    return self.render(context)
  File "/usr/local/lib/python3.10/site-packages/django/template/library.py", line 237, in render
    output = self.func(*resolved_args, **resolved_kwargs)
  File "/app/src/djangoreactor/reactor/templatetags/reactor.py", line 54, in component
    return component.render(repo) or ""
  File "/app/src/djangoreactor/reactor/component.py", line 338, in render
    return self.reactor.render(self, repo)
  File "/app/src/djangoreactor/reactor/component.py", line 140, in render
    context = self._get_context(component, repo)
  File "/app/src/djangoreactor/reactor/component.py", line 171, in _get_context
    {
  File "/app/src/djangoreactor/reactor/component.py", line 172, in <dictcomp>
    attr: getattr(component, attr)
  File "pydantic/utils.py", line 614, in pydantic.utils.ClassAttribute.__get__
AttributeError: '__signature__' attribute of 'XCounter' is class-only

Thank's by advance

Release notes/changelog?

This project has many releases. However, it is difficult to understand what is changing with each release. Please consider adding release notes or a changelog file to the repository, describing additions, improvements, and deprecations in each version.

Error when connecting to the WebSocket

When loading up the sample increment/decrement code, the WebSocket seemed to never connect. This was the error I was seeing in error logs:

File "/Users/.../Library/Caches/pypoetry/virtualenvs/django-reactor-py3.7/lib/python3.7/site-packages/reactor/channels.py", line 44, in connect
    self.scope['channel_name'] = self.channel_name
  'ReactorConsumer' object has no attribute 'channel_name'

understanding reactor

Let's say I use reactor to build a big table of 1,000 rows. I want to allow the user to edit 1 row. How would reactor do this? I understand morphdom can replace html content as below, but how would I change only 1 row? I see you are using the uuid4 library so I am guessing each element has an id? Does it mean every time you send an event from client to server you also send the element id?

Another question, is it true all application state in reactor is in the actual html? I am not used to this thinking, it makes a lot of sense to me, but I am used to virtual doms so I wonder if this method has any drawbacks? I am guessing in my 1,000 rows example above the entire state would be in the <table> component, right? Does it mean that if there is a connection issue the server relies on the client html to get the latest available state and re-mount the component?

I must say I would love to use reactor but I am a heavy flask user so I am also considering a port to flask of reactor.

Thank you for library, I have some questions

Hello Eddy, thank you for the library!

I am not a very experienced developer and I have several questions and problems with the library:

  1. Is it possible to render a component by link? I need this for the following case: there is a main form, and some of its fields are list of objects of another model(many-to-many relationship), I need to asynchronously add an object to this list through another form that is displayed in a new browser tab and then re-render the main form. I tried to do it in different ways, but I constantly ran into errors or nothing worked. If it is not possible to render component by link, how can I implement this functionality? If you give an example of the code, it will be generally great, but if you describe it in words, then it will be well too.
  2. send_redirect method takes url as django url name or it is string?
  3. I ran into the problem that when sending an event, the component is unmounted for some reason. What is the reason for this?

Thanks again for work you've done!

Waiting for your reply

Problem with inline script tag evaluation

Hi @edelvalle !
I just hit another pb with execution of inline script tags in reactor components.

It is related with this:

  • Morphdom by default use innerHtml to replace content of nodes and this prevents execution of script tags (for security reasons)
  • a workaround is proposed here patrick-steele-idem/morphdom#178 would you mind introducing it in reactor ? Should I propose a PR ?

It's quite useful to easily add some full height components for instance or add any JS libraries to components.

Websocket path & django served in subpath

Hi Eddy,
I just came across an issue when serving django on a subpath.
The websocket url __reactor__ is hardcoded as /__reactor__ in the frontend.
And I see that you hardcoded django.setup(set_prefix=False) so that the parameter FORCE_SCRIPT_NAME is not used...

So Django is configured to work on a subpath, let's say example.com/app but reactor frontend will try to connect at example.com/__reactor__ and not example.com/app/__reactor__ which should be handled correctly by Django.

Any idea on how to solve that ? A variable passed to js in the template ?

Cheers !

Missing 2.2.1b0 tag on GitHub

Hi guys !

I am trying to learn how django-reactor is working.

When I installed it with pipenv, version 2.2.1b0 was installed but this tag is not available on GitHub, only on pypi.

I would like to run the test project in tests repository of the last release.

What is the best version to learn ? Master branch, 2.2.1b0 ou 2.2.0b ?

_url_params: switch key/value in dict to have a more "natural" direction?

As a newcomer, I maybe don't understand fully the ideas behind your intentions, but sometimes may have a fresh view to the scene.

I wonder why in _url_params, the key is the local variable, and the value the URL GET parameter.

As it is called "_url_param", I would expect it to be the pother way round: the keys should be the url params that are the "source" of the value, and the values of the dict should be the name of the local vars.

I always have to think outside the box when I create _url_params, they don't feel naturally.

Yes, I am aware that this would break things in the first place, but I think reactor is still in a state wher this could be easily changed without hitting 1000s of installations.... yet 😆

What do you say?

Question about the end of the component life cycle

Hi @edelvalle, thanks for this very nice library!

I have a question: is there a way to receive some sort of signal when the component is disconnected?

I have tried to override the destroy method on the component but it seems to have no effect when WS connection is closed.

Thanks in advance!

Great job, I have a few questions

Hello Eddy,

I just wanted to say "Hi" and thank you for doing this! I've been dreaming about LiveView for Django since the day it first got announced. Definitely going to play with Reactor.

I have a few questions:

  1. What's the feature parity compared to the original Phoenix LiveView, does it implement everything or there's something missing?
  2. Have you experienced any performance drawbacks? I know that Elixir is fast, and WebSockets are super cheap on Erlang, but what about Python/Django?
  3. Do you consider this library more or less complete? Are you're planning to add more features?
  4. And sorry about a question, but why do you use coffeescript? Is there any benefit? I'm asking because I think it might be a contribution blocker to a lot of people (me including).

Other than that, great job and thank you again!

/__reactor__ URL not found when no ASGI (dev) server is installed. Needs doc++

Hi Eddy, I have not used reactor for a long time now (approx. v2 before), and much seems to have changed.

However; I can't setup a test project from your README.md - I use redis with proper RedisChannelLayer, redis is runing, but I always get the error message in the Django logs:

[28/Jun/2023 21:49:33] "GET /__reactor__ HTTP/1.1" 404 2222
Not Found: /__reactor__

And the browser dev console says:

Firefox can’t establish a connection to the server at ws://127.0.0.1:8000/__reactor__.

So this seems weird, as __reactor__ seems to be loaded as websocket pattern.

redis works, ASGI works.
websocket_urlpatterns of reactor.urls is properly added to the ProtocolTypeRouter in asgi.py.

Any hints? Do I have a misconfiguration? Or are there some hints missing in the README.md?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.