UI component library #547
Replies: 5 comments 14 replies
-
Very interesting summary! Looks viable to build a set of AlpineJS-components that's compatible with django-components. To be clear: The total setup you are pushing for is Django + Django-components + AlpineJS? Would I be correct in saying that this would be mainly used for a SPA-like app? And not make use of django-components feature of rendering template fragments? And the alternative would be Django + Django-components + HTMX, and then rendering things on the serverside and updating the components that way instead? Are there other options? |
Beta Was this translation helpful? Give feedback.
-
Btw, huge update! This week, I was exploring this, and got the stack for Django + Django-component + AlpineJS + Vuetify working just today! As a proof of concept, I implemented Vuetify's VDivider component, as that's one of the simplest ones. from typing import Any, Dict
from django_components import component, types
class AlpinuiDemo(component.Component):
def get(self, request, *args, **kwargs):
return self.render_to_response()
def get_context_data(self, *args, **kwargs) -> Dict[str, Any]:
return {}
template: types.django_html = """
{% component "Alpinui" %}
{% component "ADivider" opacity=0.3 attrs:style="margin:20px" %}
My content hello!
{% endcomponent %}
{% endcomponent %}
""" Which then rendered something like this (straight line): I gave the library a working title "Alpinui", and parked the code over at https://github.com/JuroOravec/alpinui/tree/main/packages/alpinui-django. I published the component library under Also made a video overview https://www.youtube.com/watch?v=V0tMYsy3RgQ. What's great is that I didn't have to make any changes on the CSS, and I will need to update only about 5-10% of JS logic. Only the HTML needs to be rewritten from JSX to django template. Basically what I did was to re-implement Vue's composition API in AlpineJS, which is possible because AlpineJS is using Vue's reactivity system. So in the best cases, I need to update only import statements. Installation
NOTE: I'll close this one, as further discussion can continue on https://github.com/JuroOravec/alpinui |
Beta Was this translation helpful? Give feedback.
-
I have been adding some features to an existing Django application as components with Bootstrap + AlpineJS + HTMX + Django components. I have now 26 components, some of which are quite complex. Only few of them are generic enough to be used from several places in my application. The main benefit of using components is that I can reload parts of the page when data has been updated, so the application has more modern feeling. Often the interrelationships between components is such that I cannot split them naturally into small components without passing so much state around that it becomes impractical. I have two components that I need on almost all pages in the application:
|
Beta Was this translation helpful? Give feedback.
-
@JuroOravec wow! This looks amazing! 🙇 Can you please give us an update of the current state of the UI component library and where you’d be happy about help. Btw, how does this compare to the component library created by Michael? |
Beta Was this translation helpful? Give feedback.
-
Regarding: #547 (reply in thread) Can you please outline what alpinui will have more compared to just copying HTML from Tailwind examples and wrapping that in components? That would be very helpful to fully understand. Thanks! 🙇 |
Beta Was this translation helpful? Give feedback.
-
Intro
This thread is meant to be the main place for discussing building component library / design system on top of Django + django-components (DC).
When I refer to (UI) component library or a design system, I mean a package/library of pre-made UI components (like material design).
As an example, most of the time I'll refer to Vuetify, because:
Moreover, though, AlpineJS uses reactivity system from Vue (fundamentals, in depth). Since Vuetify is also based on Vue, this means that inspecting Vuetify's internal implementation of components can be a good approximation of how it could be implemented with AlpineJS.
Interface
Components would be used as django-components (DC) Components, so like from this example:
Since they would be DC Components, they could be used also in Python like so:
They would be limited in the same way as django-components is. Meaning that the components would have to be used with Django templating language. And if django-components ever supports Jinja2, then the components could be used with Jinja2 too.
Implementation notes
AlpineJS vs HTMX vs plain JS vs other
As mentioned above, since I work with AlpineJS, I'm mainly focusing on that.
Moreover, though, UI components based on AlpineJS and HTMX would fundamentally have different behaviour and implementation. Based on how I've seen that the two have been used:
AlpineJS is like a mini-Vue - it has the concept of components and isolation (to some extend), event passing, reactivity. AlpineJS makes sense for rich interactions - where a page is loaded, and user interactions trigger changes on the same page, captured and processed by the JS layer.
HTMX I've seen mostly around the concept of HTML fragments - where when user interacts with the page, it triggers a request, which returns and updated section of HTML (fragment), and the new fragment is then inserted. I don't know if HTMX has any support for "components" or isolation.
Because if this, I think that if we were to make a component library for HTMX, it would not consider itself with reactivity. In other words, the library components would make it easier to make static HTML, that's it. Because if we then wanted to change the state of the UI, we'd make a request to generate the updated HTML, which would again generate static HTML.
On the other hand, since AlpineJS supports reactivity, this approach would be closer to server-side rendering with Vue/React - First the HTML is generated, but once it gets to user's browser, it "boots up" the JS state (hydration), and handles changes through JS, until user navigates to a new page.
In theory, you could have a singular interface defined as Django components, and through some settings you could switch whether, behind the scene, the components would use AlpineJS or HTMX or neither. But the AlpineJS mode would probably be more featureful, because it can handle reactivity. And that raises question on whether the two implementations should then have the same interface, if they don't have the same capabilities.
So that's why I'm drawn to implement the components in AlpineJS.
Passing data from Python (Django) to AlpineJS components
Since the interface would be in Python (Django template), but the component would eventually exist as AlpineJS component, we'd need a way to pass data from Python to JS.
This is possible because AlpineJS has an
init
hook, which can be used to set the initial state.So what I do is that, when the Django template is being rendered, I set
data-init
(data attribute) to a JSON with initial state, e.g.:Where
tabs_data
looks like this:So effectively the HTML looks like this:
Then, on the AlpineJS side, the data is read as JSON from the
data-init
attribute, and the component state is populated from it:So this is the same as the concept of hydration in other frameworks.
Passing data down within AlpineJS
For some more complex components, it may be necessary to "bind" data from parent component to child component. "Binding" meaning to pass it down, but keeping it reactive. So when the data in parent AlpineJS component changes, and that data is passed to the child, then the child AlpineJS component also receives the update.
ChatGPT suggested to use the
$parent
magic property from the alpine-parent package:However, in this example the data flow is inverted - the child AlpineJS component needs to know the data on the parent. Or to put it differently, the parent needs to implement child's interface for the child component to work.
So, instead, we may be better of if there was a way to do it other way around, so the child wouldn't have to access the parent component. One approach could be to define a custom directive, e.g.
v-props
, so we'd have a control over what data is passed from parent AlpineJS component to the child:Emitting events up within AlpineJS
Props are used to pass data down, events are used to send them up.
AlpineJS does support emitting ($dispatch) and listening (x-on) to events, so we'd just use that.
Running JS once vs per each component
AlpineJS already handles this for us. When we define an instance of AlpineJS component with
x-data
:Then the component will be generated with its own state, and it will run
init()
method. So we don't have to take care of much else.And when it comes to running one-off JS scripts, aka scripts that should run only once, no matter how many instances of that component is there, well for that we have the
Media.js
property on django-components Component class:To ensure that the
runMeOnce.js
really only executes once per page is out of scope for the component library, and is responsibility of django-components (see #478).Configuring JS and CSS with variables passed into Django components
This is out of scope of component library, and should be handled in django-components.
Codebase
A component library like this should live in it's own project.
Non-goals
Web optimizations
Vuetify does more than just components - e.g. supports use of icon fonts, lazy-loading images, exposing CSS as SASS variables, etc.
With the component library, I want to focus on the functionality - that a component renders the HTML/CSS/JS and fulfills basic interactions. But features that feel like optimizations (lazy loading, etc) are out of scope.
Server interactions
If we compare django-components to e.g. django-unicorn or tetra then the latter two also manage the server-client communication to some extend. On the other hand, django-components is purely frontend, meaning concerned only with templating (generating HTML/JS/CSS), and leaves the server-client communication up to the user.
Hence, the components should respect this too. As an example, something like autocomplete component would require user to specify how to fetch data, so user has the freedom to whichever method they prefer.
Components
There is A LOT of components by Vuetify. To get a sense of what to focus on, I reviewed and ranked them:
High priority components
Mid priority components
Mid-Low priority components
Low priority components
<table>
HTML element?Beta Was this translation helpful? Give feedback.
All reactions