Search code examples
djangohtmx

HX-Trigger to issue a request using arguments


I want to create a request after a trigger using htmx after a button was clicked with django:

def SomeView(request, var1, var2):
    trigger_data = json.dumps({"dlstart": {"pid": pid, "var1": var1}})
    return HttpResponse("", headers={"HX-Trigger": trigger_data})

I can see in the Network Tab the GET request is working. But now I want to add the two arguments from trigger_data to my hx-get dynamically. I did not find a way in htmx alone to achieve this, so I did a JS workaround (which I would gladly drop in favor of a htmx only solution):

document.body.addEventListener("dlstart", function() {
    const url = `/someURL/${event.detail.pid}/${event.detail.var1}`;
    const filename = "test.png";

    fetch(url)
        .then(response => {
            if (!response.ok) {
                throw new Error('was not ok');
            }
            return response.blob(); 
        })
        .then(blob => {
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);
            link.download = filename; 
            document.body.appendChild(link);
            link.click(); // feels so ugly
            document.body.removeChild(link);
        })
});

I imagine some combination of hx-get and dynamically add the arguments must work too?

Edit: SomeView is just being called by a click on a button.

def DLView(request, pid, var1): 
    ## stuff        
    prod = Product.objects.get(id=pid)
    pdf = pdf_create(prod, var1)
    with open(pdf, "rb") as pdf_file:
        response = HttpResponse(pdf_file.read(), content_type="application/pdf")
        response["Content-Disposition"] = f"""attachment; filename="{pdf}" """ # filename not used, see js part
        return response

urls:

    path("someurl/<int:pid>/<str:var1>", DLView, name="some-url"),

In the solution suggested I added a modal save button like this:

<button type="submit"
        id="start-dl-button"
        hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
        hx-post="{% url 'start-dl' pid  var1 %}" 
        hx-on::before-request="document.getElementById('modal-container-{{ pid }}').innerHTML=''">
    Start DL
</button>

Solution

  • It is pretty sneaky to download a server-generated file with htmx-powered requests. But there is an easy workaround that avoids any custom JavaScript at all.

    The general idea is to have a view that only offers the download option to the client, like you're doing. The trick then is to simply redirect the request to that view.

    In your case, first refactor SomeView:

    from django.urls import reverse_lazy
    
    def SomeView(request, var1, var2):
        dl_url = reverse_lazy("some-url", kwargs=dict(pid=pid, var1=var1))
        return HttpResponse(headers={"Hx-Redirect" : dl_url})
    
    

    This will issue a client redirect to the supplied url. Then in the DLView, return a FileResponse that makes is easy to offer a download option to the client:

    from django.http import FileResponse
    
    def DLView(request, pid, var1): 
        # do stuff
        pdf = pdf_create(prod, var1)
        return FileResponse(open(pdf, "rb"), as_attachment=True, filename="test.pdf")
    

    This view should do what your JavaScript is doing by opening a download dialog in the browser. I hope that's what you wish to accomplish.

    Note:

    It's a convention that function names in Python should be in snake_case, while class names should be in PascalCase. Thus, instead of def DLView..., it should be def dl_view....

    EDIT

    To close the modal before the download starts you can listen to afterRequest event on the button, and add any modal-closing logic there:

    <button hx-on::after-request="document.getElementById('modal-id').classList.remove('is-active')" ...>Print </button>
    

    I am not familiar with Bulma so include whatever logic you're using to close the modal.