Replies: 6 comments 16 replies
-
@nerdoc I like the idea. In your example, would values like In thinking about this, it seems like there are two separate things we are bundling here:
If the data manipulation is more model-focused, then maybe the custom class-based view (similar to As for creating the views, the library neapolitan has functionality where it populates CRUD views, which sounds similar to what you describe. We could have it add any provided functions, instead of just CRUD functions. The point being, Not that we would use neapolitan, but it serves as an example of this sort of thing. # urls.py
from neapolitan.views import CRUDView
class BookmarkView(CRUDView):
model = Bookmark
fields = ["url", "title", "note"]
filterset_fields = [
"favourite",
]
urlpatterns = [ ... ] + BookmarkView.get_urls()``` |
Beta Was this translation helpful? Give feedback.
-
Sorry for not responding sooner, totally missed this thread. I really like this idea too, and it’s something I’ve been thinking about for a while. Some thoughts:
@rtr1 About state handling, I think we can leave it to the component to decide what to do: let the user add a inc-method to the component, and then have the users call that to increment and persist that, so that the next reload has the updated value. |
Beta Was this translation helpful? Give feedback.
-
@EmilStenstrom there’s an example of using SSE with Django here. |
Beta Was this translation helpful? Give feedback.
-
A few thoughts to your answers, thanks by the way for taking this up. IMHO a model centric behavior should be considered, but not enforced. There could be a use case where a component facades a model, and maybe even uses model methods. But there could be some other use cases too. Look here at django-unicorn, or tetraframework. Unicorn takes that really cool, as it enables just "variables" in a component - which in fact are variables in the Using django models in all cases is not desireable. I don't want to reinvent the wheel, because all the other solutions exist. And if you want to use components with all you said in your comments, just use Unicorn. I would definitively use HTMX, because using it helps you
I understand that you don't want to lock yourself in with HTMX, but like I said, I've tried them all, each of them has their advantages and shortcomings, and I always thought about - why does everyone go and make a "yet another component framework". Why don't they combine the best things together?
(I hope I got everything right, please correct me if not.) In reply to @EmilStenstrom's points:
That is easily (and very eficciently) done using HTMX, using morphdom. works brilliantly, fast, and reliable, using target
All frameworks do that automatically after calling some of the compoents' methods. I think Tetraframework does that best. It has a
Maybe a framework agnostic approach would be really cool, and HTMX could be one "plugin" that adds reactability. BUT, that would be even more work. IF you want to use React, Vue, Svelte etc. with django-components, you don't eveen need persistent state and all I talked about, because all you need is an (e.g. REST) API. Going into Server state without using a monolithic frontend Js framework is the exact opposite of them. Integrating Django plugins frameworks (like yapsy, or my own GDAPS) need to have components server-side rendered and integratable - this is only possible with things like django-components. And adding reactivity to that would be a killer feature IMHO. There is nothing out there (that I know of) that matches the stability, speed and ease of use of HTMX - and I would definitely not recreate all of their work and reinvent the wheel with a custom framework when everything is already there... Just my 2 cents. |
Beta Was this translation helpful? Give feedback.
-
I've been playing around a bit with this, and even though, I couldn't wrap my head around the idea of the websockets/SSE channel to update components, I got a simple HTTP-only version of these HTMX enhanced components: Without backend state: components/urls.py from django.urls import path
from components.stateless_counter import StatelessCounterComponent
urlpatterns = [
path(
"stateless_counter/<str:method>/<int:id>",
StatelessCounterComponent.as_view(),
name="stateless_counter",
),
] components/stateless_counter.py from django_components import component
@component.register("stateless_counter")
class StatelessCounterComponent(component.Component):
def get_context_data(self, id, **kwargs):
return {"count": 0, "id": id}
def post(self, request, method, id, *args, **kwargs):
if method == "inc":
count = int(request.POST.get("count", 0)) + 1
elif method == "dec":
count = int(request.POST.get("count", 0)) - 1
context = {
"count": count,
"id": id,
}
slots = {}
return self.render_to_response(context, slots)
template = """
<div id="stateless-counter-component-{{ id }}"
hx-target="#stateless-counter-component-{{ id }}"
hx-vals='{ "count": {{ count}} }'>
<div>Count: {{ count }}</div>
<button type="button" hx-post="{% url 'stateless_counter' method='dec' id=id %}"> -1 </button>
<button type="button" hx-post="{% url 'stateless_counter' method='inc' id=id %}"> +1 </button>
</div>
""" With backend state: components/urls.py from django.urls import path
from components.counter import CounterComponent
urlpatterns = [
path("counter/<str:method>/<int:id>", CounterComponent.as_view(), name="counter"),
] counter.py from django_components import component
from app.models import Counter
@component.register("counter")
class CounterComponent(component.Component):
model = Counter
def get_context_data(self, id, **kwargs):
counter, _ = self.model.objects.get_or_create(id=id) # probably you don't want a create there, but just for testing
return {"count": counter.count, "id": id}
def post(self, request, method, id, *args, **kwargs):
counter, _ = self.model.objects.get_or_create(id=id) # same applies here
if method == "inc":
counter.count += 1
counter.save()
elif method == "dec":
counter.count -= 1
counter.save()
context = {
"count": counter.count,
"id": id,
}
slots = {}
return self.render_to_response(context, slots)
template = """
<div id="counter-component-{{ id }}" hx-target="#counter-component-{{ id }}">
<div>Count: {{ count }}</div>
<button type="button" hx-post="{% url 'counter' method='dec' id=id %}"> -1 </button>
<button type="button" hx-post="{% url 'counter' method='inc' id=id %}"> +1 </button>
</div>
""" Counter.mov |
Beta Was this translation helpful? Give feedback.
-
I also built a small POC with SSE using redis: Notifications.movcomponents/notifications.py import json
from typing import AsyncGenerator
import redis.asyncio as redis
from django.http import StreamingHttpResponse
from django_components import component
@component.register("notification")
class NotificationComponent(component.Component):
template = """
<div style="text-color: {{color}};" role="alert">
<span style="font-weight: bold;">{{ title }}</span> {{ message }}
</div>
"""
notification_component = NotificationComponent(
registered_name="notification",
)
def sse_message(event_id: int, event: str, data: str) -> str:
data = data.replace("\n", "")
return f"id: {event_id}\n" f"event: {event}\n" f"data: {data.strip()}\n\n"
r = redis.from_url("redis://localhost")
async def get_component_updates(*args, **kwargs) -> AsyncGenerator[str, None]:
async with r.pubsub() as pubsub:
await pubsub.subscribe("notifications_channel")
try:
while True:
message = await pubsub.get_message(ignore_subscribe_messages=True)
if message is not None:
notification_data = json.loads(message["data"].decode())
sse_message_rendered = sse_message(
notification_data["id"],
"notification",
notification_component.render(
{
"title": notification_data["title"],
"message": notification_data["message"],
"color": notification_data["color"],
}
),
)
yield sse_message_rendered
finally:
await r.aclose()
async def stream_component(request):
return StreamingHttpResponse(
streaming_content=get_component_updates(),
content_type="text/event-stream",
) components/urls.py from django.urls import path
from components.notifications import stream_component
urlpatterns = [
path(
"notification/",
stream_component,
name="stream_notification",
),
] index.html {% include "_base.html" %}
{% block content %}
<div hx-ext="sse"
sse-connect="{% url 'stream_notification' %}"
sse-swap="notification"></div>
<br>
{% endblock %} Not sure if I'm on the right track with this, but was fun haha. If this makes any sense, I think we could probably integrate some of this stuff without a lot of effort. |
Beta Was this translation helpful? Give feedback.
-
Hello. I was just playing around with the idea of extending django-components with some HTMX functionality, to create "interactive" components.
Call me crazy.
There are already some libs that fill that gap in the net like Unicorn, Tetra(dead?), Reactor(instable/dead?) and others (TurboDjango(dead?), Sockpuppet(dead?) etc). And djhtmx(dead?).
What would it take to make an "enhanced" component that
Some ideas:
__components__/<component_name>/<method>/
that points tohx-swap
The first 2 are simple to implement (already done here as a test), for the 3rd I have no idea, and some things seem to burst my head:
Thanks for your feedback...
Beta Was this translation helpful? Give feedback.
All reactions