How to execute javascript code after htmx makes an ajax request?
Asked Answered
M

7

15

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?

Midtown answered 17/8, 2022 at 13:18 Comment(0)
S
6

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'
})
Susie answered 17/8, 2022 at 14:42 Comment(0)
H
18

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.

Huelva answered 1/11, 2022 at 9:53 Comment(4)
This should have been accepted answer. Works like a charm.Farley
The event listener takes an event as its argument. So right there in the event handler function you can examine the triggering event and take appropriate action.Zomba
@VinaySajip yes you're absolutely right, I will edit the code now to reflect that, many thanksHuelva
Also, it might be better to use the 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
S
9

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

Samekh answered 13/1 at 5:27 Comment(0)
S
6

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'
})
Susie answered 17/8, 2022 at 14:42 Comment(0)
B
1

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.

Bibliopegy answered 26/7, 2023 at 9:34 Comment(0)
B
1

I'm surprised that no one mentioned triggering an event by using response headers.

Buttress answered 8/4 at 3:14 Comment(0)
S
0

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');
};
Sloop answered 20/12, 2023 at 2:11 Comment(0)
N
0

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

Niedersachsen answered 10/7 at 10:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.