I'm currently building a website with django and htmx and i like the combination so far. Let's say I use an htmx attribute on a button to replace a div in the DOM with another div that is supposed to contain a wysiwyg editor. Now the wysiwyg editor has to be initialized with JavaScript. How do I do this? Can I just return the script tag under the editor div that is being requested with htmx? Wouldn't that be also a little ugly or bad practice because you'd have script tags in the middle of the html body? What's the best way of solving this?
Check out HTMX after request event.
It should look something like this
htmx.on('htmx:afterRequest', (evt) => {
// check which element triggered the htmx request. If it's the one you want call the function you need
//you have to add htmx: before the event ex: 'htmx:afterRequest'
})
Short Answer
To run specific code after an HTMX request, you need to be listening for the htmx:afterRequest
event.
Whether it's just a single htmx:afterRequest
event that you are trying to listen to (or if you have code that should run after all htmx requests), you can use the below event listener to run your code after any HTMX request is completed:
document.addEventListener('htmx:afterRequest', function(evt) {
// Put the JS code that you want to execute here
});
Long Answer
If you want it to run after a specific event only, you need access to the evt
object associated with the event, to which you should refer to the official docs here in full. However I have put some simple examples below to demonstrate what this can look like.
document.addEventListener('htmx:afterRequest', function(evt) {
if(evt.detail.xhr.status == 404){
/* Notify the user of a 404 Not Found response */
return alert("Error: Could Not Find Resource");
}
if (evt.detail.successful != true) {
/* Notify of an unexpected error, & print error to console */
alert("Unexpected Error");
return console.error(evt);
}
if (evt.detail.target.id == 'info-div') {
/* Execute code on the target of the HTMX request, which will
be either the hx-target attribute if set, or the triggering
element itself if not set. */
let infoDiv = document.getElementById('info-div');
infoDiv.style.backgroundColor = '#000000'; // black background
infoDiv.style.color = '#FFFFFF'; // white text
}
});
For a full list of HTMX events you can listen to (e.g. htmx:configRequest
to modify the HTMX AJAX request before it is sent) check out the official reference here.
As to the question of where to put this in your code, the only technical necessity is to be listening for the event before the event triggers: i.e. do not send this code in your response, already have it in your page beforehand. Then where to put stylistically is essentially a matter of preference. In a Django + HTMX stack, it would be perfectly valid to either include this as a script
tag within the template itself, or put it inside a static js file and link to it. Just make sure the HTMX library is loaded before this executes, and you have accounted for csrf
issues appropriately.
Edit: As has been well pointed out by @Leon_G84, you can achieve much of the above without listening to events in your .js
files, and instead listen to them via the hx-on::after-request
attribute inside your HTML elements. You still need to define any custom subroutines in your .js
files or <script>
tags, and ensure they're within scope appropriately.
Example:
<script>
function performAnimation(evt) {
// Insert animation based code
}
</script>
<div id="text-box"></div>
<button
hx-get="/text"
hx-target="#text-box"
hx-on::after-request="performAnimation()">
Press Me
</button>
Note 1: it is after-request
, not afterRequest
, when accessed through the HTML element's hx-on attribute. This is because DOM attributes are treated as case insensitive.
Note 2: One can apply the above logic to other HTMX DOM events, like beforeRequest
and afterSettle
etc. Check the docs to work out exactly what event(s) you should be responding to for your specific use case.
htmx:afterSettle
event. In the case of both htmx:afterRequest
and htmx:afterSwap
I found that the DOM wasn't completely replaced at the time the JS code ran. With htmx:afterSettle
the replacement seems to be there reliably :-) –
Zomba There's no need to write JavaScript for this (except the script to run), which is one of the main points of using HTMX.
hx-on
attribute (mentioned first by cyberfly), https://htmx.org/attributes/hx-on/
<button hx-get="/info" hx-on::after-request="alert('Done making a request!')">
Get Info!
</button>
you can also replace hx-on::after-request
with hx-on:htmx:after-request
(Take note of the double colon ::
without htmx)
You can also handle multiple events:
<button hx-get="/info"
hx-on::before-request="alert('Making a request!')"
hx-on::after-request="alert('Done making a request!')">
Get Info!
</button>
You can also use hx-on
with other event such as click. I.e. hx-on:click
Check out HTMX after request event.
It should look something like this
htmx.on('htmx:afterRequest', (evt) => {
// check which element triggered the htmx request. If it's the one you want call the function you need
//you have to add htmx: before the event ex: 'htmx:afterRequest'
})
The HTMX after request event is triggered after all the requests.
We then have to check the event.target and trigger a function or not depending on the current target. It's not really relevant for the optimization.
I haven't found a built-in solution to execute a script only after a specific request. If anyone has a solution with sample code I'm interested.
On the other hand, I don't see any problem with integrating a <script />
tag into the response sent back by the server. It works like a charm, it's simple and effective.
I'm surprised that no one mentioned triggering an event by using response headers.
General example with htmx:before-request
and htmx:after-request
// form.html
<button
hx-post="/api/v1/title"
hx-on:htmx:before-request="emptyDiv('#titles_container')"
hx-on:htmx:after-request="doSomething()"
type="button"
class="btn btn-primary"
>
Generate Title
</button>
// form.js
window.emptyDiv = function emptyDiv(id) {
$(id).empty();
};
window.doSomething = function doSomething() {
console.log('do something');
};
Consider using HX-Trigger Response Headers when you want to have the server decide which function should be called next on the clint when an htmx ajax call completes.
One can imagine an "initialize" function for the Editor like this
<script>
document.body.addEventListener("initialize_wysiwyg", function(evt){
// wysiwyg editor initialized in here ...
})
</script>
Adding this HEADER to the response from your server can be used to trigger client side actions.
HX-Trigger: initialize_wysiwyg
or only AFTER successful calls, with a param
HX-Trigger-After-Settle: {"initialize_wysiwyg": "editorName"}
if you had a function requiring input params - strings - like this one
<script>
document.body.addEventListener("initialize_wysiwyg", function(evt){
if(evt.detail.driver === "someJS"){
alert(evt.detail.port);
}
})
</script>
you could return a headder like this
HX-Trigger-After-Settle: {"initialize_wysiwyg": {"driver" : "someJS", "port" : 8080}}
and have the values passed from the server in the header
© 2022 - 2024 — McMap. All rights reserved.