preamble
Recently, I have been using DjangoStarter to develop a variety of small projects, before I preferred the separation of front-end and back-end, the back-end with the Ninja API, the front-end nextjs, the development is also quite comfortable, the interaction experience is also better.
However, while surfing the web, I also learned that there are lightweight front-end development libraries like htmx and Django that work well with Django, so I came to try them out on my new toy project.(Let's try it out in this article, using the LiveChat chat program we've been working on lately as an example)
Django template this back-end rendering development is very convenient, but sometimes need to do some interaction on the page, the older projects will choose jQuery, but now they have stopped maintenance, I naturally can not choose this; I use more is to introduce in the page to do simple interaction, can be replaced to a certain extent jQuery, the back even I even introduced react to write jsx directly, but I don't have webpack, and I introduced babel to render directly, which is a bit bad.
Reference: Introducing React and JSX in HTML
Traditional front-end frameworks such as React, and Angular are powerful, but for small to medium-sized projects or simple interactions, they can be unwieldy and have a steep learning curve. This has led developers to look for a lightweight yet flexible solution that can enable dynamic interactions without introducing a lot of complexity.
It is in this context that it was born. It is a lightweight JavaScript framework designed to provide the declarative and composable features of and React, but implemented in a minimalist way. It allows developers to embed interaction logic directly in HTML templates, without the need for complex build tools and configurations to achieve rich dynamic effects.
About the LiveChat Program
In fact, the techniques used in this project are the same as in the last articleUsing Django-Channels to realize websocket communication + big model conversation Ri is pretty much the same, but this one is a multiplayer chat, not a conversation with a big model.
The project is still in development, put a simple screenshot, now realize the text and voice chat.
with respect to
is a lightweight JavaScript framework designed to provide developers with a simple, efficient way to add interactivity to web pages. It is inspired by, but lighter and more concise than, JavaScript. By embedding clean directives directly into HTML templates, it allows developers to implement dynamic interactions with a minimal amount of code, without the need to introduce large frameworks or complex build tools.
Official website./
Development context
Launched in 2020 by developer Caleb Porzio, it aims to fill the gap between jQuery and modern front-end frameworks. It combines the immediacy of jQuery with the declarative benefits of jQuery to provide a clean and powerful tool for building interactive web pages.
Core features
- light-weight class (in athletics): The core library is only a few KB in size and does not significantly increase page load times, making it ideal for projects with stringent performance requirements.
-
Intuitive Grammar: Use commands like
x-data
、x-bind
、x-on
(etc.), allowing developers to write interaction logic directly in HTML, making the code more intuitive and easier to read. - Easy to integrateThe script files can be used in any existing project without the need to configure a complex build tool or project structure, simply by bringing in the script files.
- declarative rendering: A declarative programming style that focuses on describing "what" the interface should be rather than "how" it should be updated, greatly simplifying the development process.
- Highly flexible: Suitable for web applications ranging from simple interactive effects to medium complexity, it meets most front-end development needs.
Comparison with other frameworks
Compared to and React
- Smaller size: It's a fraction of the size of React, making it even lighter.
- Flat learning curve: You don't need to learn complex framework concepts or JSX syntax, but rather HTML and basic JavaScript to get started.
- No build tool required: Eliminates the need to configure build tools such as Webpack, Babel, etc. and reduces the complexity of the project.
Compared to jQuery
- Modernized Programming Approach: Declarative rendering and data binding to avoid tedious DOM manipulation.
- Better maintainability: The code is well-structured and the logic is tightly integrated with the view, facilitating later maintenance and iteration.
Applicable Scenarios
- Small projects or components: Ideal when the project does not require a large framework.
- Enhancements to existing projects: In a traditional backend rendering project, if you want to add some dynamic interactions, you can introduce them directly without refactoring the project structure.
- Rapid Prototyping: Ideal for scenarios where you need to quickly validate an idea or build a prototype.
mounting
Compared to vue and react projects, which require webpack, it's very easy to use, and all you need to do is introduce a js file into the HTML.
Using a CDN
The easiest thing to do is to add a js reference to the bottom of the page, directly using the jsdelivr CDN
<html>
<head>
...
<script defer src="/npm/alpinejs@/dist/"></script>
</head>
...
</html>
Use of local resources
DjangoStarter uses npm to manage front-end resources, so integrating it in DjangoStarter is a bit more involved, but it's pretty simple.
First use npm to install the dependencies
pnpm i alpinejs
The npm installation is done in thenode_modules
Inside, DjangoStarter uses gulp to collect front-end resources, so next you need to add alpinejs to the gulp task.
compiler
// Front-end component packages downloaded using npm
const libs = [
{name: 'alpinejs', dist: '. /node_modules/alpinejs/dist/**/*. *'}, [ {name: 'alpinejs', dist: '.
]
Run the gulp task to collect static resources
gulp move
This way alpinejs is in the static directory of the project and can be referenced in the templates
Edit the template, introduce static resources, and remember to load the static template tag.
{% load static %}
<html>
<body>
<script defer src="{% static 'lib/alpinejs/dist/' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
This is done, it seems very complicated, in fact, these are done in the initialization of the project, I will also integrate alpinejs and htmx into the new version of DjangoStarter, so the actual development of these steps do not need to repeat to do.
initialization
Adds the following to the elements that need to be taken over by alpinejsx-data
causality
Like this.
<div class="grid grid-cols-1 lg:grid-cols-4 gap-2 mb-4" x-data="chatApp()">
</div>
after thatchatApp()
Define in js
function chatApp() {
return {
messages: [], init()
init() {
// Initialization code
}, }
}
}
The code has been simplified and is easier to read
message box
It's more intuitive to see how we develop with alpinejs by going straight to the code.
Back-end rendering can also be componentized, and I wrote amessages_container.html
subassemblies
<!-- Messages -->
<div x-ref="messages"
class="flex-1 space-y-4 overflow-y-auto rounded-xl bg-slate-200 px-4 py-2 text-sm leading-6 text-slate-900 shadow-sm dark:bg-slate-900 dark:text-slate-300 sm:text-base sm:leading-7">
<template x-for="item in messages">
<div>
<template x-if="==='system'">
<div class="text-center text-sm text-gray-500" x-text=""></div>
</template>
<template x-if="==='user'">
<div class="flex flex-row-reverse items-start gap-2">
<div class="p-2 h-12 w-12 rounded-full bg-blue-500 flex justify-center items-center">
<i class="fa-solid fa-user text-white"></i>
</div>
<div class="flex min-h-[85px] rounded-b-xl rounded-tl-xl bg-slate-50 p-4 dark:bg-slate-800 sm:min-h-0 sm:max-w-md md:max-w-2xl">
<template x-if="==='text'">
<p x-text=""></p>
</template>
<template x-if="==='audio'">
<div class="flex flex-col gap-2">
<div>Audio <span x-text=""></span>s</div>
<button type="button" @click="playAudio(item)"
class="px-3 py-2 text-xs font-medium text-center inline-flex gap-2 items-center text-white bg-blue-700 rounded-lg hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 ">
<i class="fa-solid fa-play text-white"> </i>
play
</button>
</div>
</template>
</div>
</div>
</template>
<template x-if="==='ai'">
<div class="flex items-start gap-2">
<div class="p-2 h-12 w-12 rounded-full bg-blue-500 flex justify-center items-center">
<i class="fa-solid fa-robot text-white"> </i>
</div>
<div class="flex rounded-b-xl rounded-tr-xl bg-slate-50 p-4 dark:bg-slate-800 sm:max-w-md md:max-w-2xl">
<p x-text=""> </p>
</div>
</div>
</template>
</div>
</template>
</div>
You can see that this uses several attributes and elements that are not part of standard HTML
- x-ref
- x-for
- x-if
- template
I'll introduce them one by one.
x-ref
It's kind of like vue and react.
It is equivalent to using the directgetElementById
orquerySelector
to access the element
The main thing here is to realize the automatic scrolling of messages to the bottom (more on this later)
x-for and x-if
As the name suggests, it's a loop and a judgment, similar to the use of vue.
take note of
- These two statements can only be used with the template element
- template There can be only one element in a template element
- x-if only works on elements with x-data
x-text
When displaying messages, unlike vue's{}
to render it, but instead uses thex-text
directives
<p x-text=""></p>
This directive sets the text within the element, ditto for thex-html
command, you can directly set theinnerHtml
Automatic scrolling of messages to the bottom
Recall how this was previously implemented in react.
(() => {
// Automatically scroll to the bottom of the message container
({behavior: 'smooth'})
}, [messages]); {
Use the useEffect hook to listen for changes in messages and scroll to the bottom when they occur.
Listening is also possible in thechatApp
innerinit()
method, using the$watch
Magic Attributes
init() {
this.$watch('messages', () => {
('$watch messages changed', )
// Scroll to the latest news
this.$nextTick(() => {
// this.$({behavior: 'smooth'}); {
// Another way to scroll to the bottom
this.$ = this.$ ;
});
})
}
Change it here toscrollTop = scrollHeight
to achieve scrolling to the bottom, previously usedscrollIntoView
Doesn't seem to be working.
Magic Properties
$
The first one is called magic property, and a lot of functionality is achieved through this type of property.
Common Magic Attributes:
-
$el
: A reference to the current element. -
$refs
: references to other sites withx-ref
element of the command. -
$event
: The current event object. -
$nextTick
: Execute the function after the next DOM update.
Input Boxes and Data Binding
Let's move on to the core concepts of the code
Let's see the code.
<form class="mt-2">
<label for="chat-input" class="sr-only">Enter your prompt</label>
<div class="relative">
<button
type="button" @click="audioMode=!audioMode"
class="absolute inset-y-0 left-0 flex items-center pl-6 text-slate-500 hover:text-blue-600 dark:text-slate-400 dark:hover:text-blue-600"
>
<i class="fa-solid fa-microphone text-2xl"></i>
<span class="sr-only">use voice input</span>
</button>
<textarea
class="block w-full resize-none rounded-xl border-none bg-slate-200 p-4 pl-14 pr-20 text-sm text-slate-900 focus:outline-none focus:ring-1 focus:ring-blue-400 dark:bg-slate-900 dark:text-slate-200 dark:placeholder-slate-400 dark:focus:ring-blue-600 sm:text-base"
placeholder="Please enter your message"
rows="1"
required
x-model="message"
:disabled="!receiver"
@="sendMessage()"
></textarea>
<button
type="button" @click="sendMessage()" :disabled="!receiver"
class="absolute bottom-2 right-2.5 rounded-lg bg-blue-700 px-4 py-2 text-sm font-medium text-slate-200 hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 sm:text-base"
>
Send <span class="sr-only">Send</span>
</button>
</div>
</form>
Bi-directional data binding
The first is data binding, which is very similar to Vue and Blazor.
The input box for this project uses the<textarea>
element, using thex-model
directive implements a two-way binding between form input and data state.
modifier (computing)
-
.lazy
: inchange
event rather thaninput
Update data on event. -
.debounce
: Add stabilization to input events, e.g..500ms="query"
property binding
utilizationx-bind
command (which can be abbreviated as:
), data can be bound to an element's attributes to enable dynamic updating of attributes.
In the code above, this is reflected as<textare>
cap (a poem)<button>
elemental:disabled="!receiver"
event processing
pass (a bill or inspection etc)x-on
command (which can also be abbreviated as@
) to listen to DOM events and trigger the corresponding handler functions.
for example
<button @click="sendMessage()">
Send Message
</button>
modifier (computing)
Event modifiers are supported for controlling the behavior of events:
-
.prevent
: Call()
。 -
.stop
: Call()
。 -
.window
: Listen to the globalwindow
object's events.
for example
<button @="sendMessage()">
Send Message
</button>
The old cliché of anti-shake and throttling is also there
<input @.500ms="fetchResults">
weir valve
<div @.750ms="handleScroll">...</div>
extensions
This project uses not many functions, I looked at the official website of this library is quite rich in functionality, here are a few.
Global state management
Again, this is achieved using the magic attribute
Take the example of switching to a darker color mode (carried over an example from the official website)
<button x-data @click="$()">Toggle Dark Mode</button>
...
<div x-data :class="$ && 'bg-black'">
...
</div>
<script>
('alpine:init', () => {
('darkMode', {
on: false,
toggle() {
= !
}
})
})
</script>
anime
utilizationx-transition
directives can be animated, but there are not many animations that can be used at the moment, so if you really want to animate, you should use tailwindcss or write CSS directly.
<div x-data="{ open: false }">
<button @click="open = ! open">Toggle</button>
<div x-show="open" x-transition>
Hello 👋
</div>
</div>
more
As I write this, I read through the documentation, and there are more other features that I won't reread, all related to interaction, for example:
- for implementing modal
x-teleport
directives - Batch creation of elements with generated id's
x-id
directives - Cross-tab state plugin (based on localStorage)
- There are also a lot of plugins that are used to implement interactions (dropdown, collapse, sort, that sort of thing)
These extensions allow us to implement the components we need, but for many of the features I prefer to work with component libraries (such as tailwindcss-based libraries like flowbite) to avoid creating wheels.
How do I send a voice?
To expand on this, the LiveChat program can send not only text, but also voice.
Speech is still a bit tricky compared to text, and I've only got a demo version here, so it's not perfect yet.
sound recordings
primary useMediaRecorder
To realize the recording, a permission request will pop up on the browser, and the user will be allowed to start recording.
Refer to the MDN documentation./en-US/docs/Web/API/MediaRecorder
Start recording
// Start recording
startRecording() {
({audio: true})
.then(stream => {
= stream;
= new MediaRecorder(stream);
= event => {
();
};
= () => {
const audioBlob = new Blob(, {type: 'audio/wav'});
const reader = new FileReader();
= () => {
const arrayBuffer = ;
// establish AudioContext an actual example
const audioContext = new ( || )();
// Decode audio data
(arrayBuffer)
.then((audioBuffer) => {
const duration = ;
(`audio duration:${duration} unit of angle or arc equivalent one sixtieth of a degree`);
(arrayBuffer);
(new Message(
'user', audioBuffer, 'audio', duration
))
();
})
.catch((error) => {
('Decode audio data出错', error);
})
}
(audioBlob);
= [];
};
();
("Recording has begun.");
})
.catch(err => {
("Failed to get microphone privileges: ", err);
});
}
Stop recording
// Stop the recording
stopRecording() {
if () {
(); // Stop the microphone stream.
// Stop the microphone stream
().forEach(track => ()); ("Recording has stopped"); (); // Stop the microphone stream.
("Recording has been stopped");
}
}
Play audio
playAudio(message) {
('playAudio', message)
if ( === 'audio' && ) {
const audioContext = new ( || )();
const source = ();
= ;
();
(0);
}
}
wrap-up
Front-end frameworks are proliferating, projects are getting bigger and bigger, and libraries like htmx are the antithesis of the simplest way to develop modern web applications.
I'll be adding these two things to the new DjangoStarter, which is the most suitable technology for full-site developers~.