hotwire-django / turbo-django

Unmaintained // An early stage integration of Hotwire Turbo with Django
https://discuss.hotwire.dev/t/django-backend-support-for-hotwire/1570/
Other
385 stars 20 forks source link

Components #53

Closed scuml closed 2 years ago

scuml commented 2 years ago

Resolves #51

Initial framework for components, which simplifies streaming code, and is a great starting point for new users to the framework.

Components are declared by inheriting from a parent component class. Components are subclasses of Stream. This checkin has two components to get us started, BroadcastComponent and UserBroadcastComponent.

BroadcastComponent

We'll start with the simplest of implementations:

# streams.py
from turbo.components import BroadcastComponent

class AlertBroadcastComponent(BroadcastComponent):
    template_name = "components/sample_broadcast_component.html"
<!-- `templates/components/sample_broadcast_component.html` -->
{% if alert_content %}
<div class="alert alert-{{alert_class}}" role="alert">
  {{alert_content}}
</div>
{% endif %}
<!-- in the template to display the component --> 
{% load turbo_streams %}
{% turbo_component alert_broadcast_component %} <!-- if instantiated in the parent view --> 
OR
{% turbo_component "app_name:AlertBroadcastComponent" %}   <!-- to access globally --> 

(Multiple instances of the same turbo component will not accept updates due to hotwire limitations.)

To stream out html to this component, use the following command in a python shell:

>>> from streams import AlertBroadcastComponent
>>> a = AlertBroadcastComponent()
>>> a.render(alert_class='warning', alert_content="Server will shut down in 10 minutes.")

UserBroadcastComponent

UserBroadcastComponents broadcast information to a specific user. These component streams have a permissions check and will not be streamed to other users.

class CartCountComponent(UserBroadcastComponent):
    template_name = "components/cart_count_component.html"

    def get_context(self):
        # For the initial render, or if not passed into render() for an update, pull the context from this method. 
        return {
            "count": user.cart.items_in_cart
        }
<!-- components/cart_count_component.html -->
<button type="button" class="btn btn-primary position-relative">
  Cart

  {% if count %}
  <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
    {{count}}
    <span class="visually-hidden">Items in cart</span>
  </span>
  {% endif %}

</button>
<!-- in the template to display the component --> 
{% load turbo_streams %}
{% turbo_component "app_name:CartCountComponent" request.user %}
OR
{% turbo_component cart_count_component %}

To update this component, use:

cart_component = CartCountComponent(user)
cart_component.render()  # or cart_component.render(count=99) to override get_context()