bigskysoftware / htmx-extensions

102 stars 30 forks source link

response-targets: hx-target-error fails when hx-target is defined #7

Closed dmanpearl closed 4 months ago

dmanpearl commented 4 months ago

htmx extension: response-targets

The Problem

"hx-target-error" will not work when "hx-target" is also defined, but works when hx-target-error is the only target.

I need to have both "hx-target-error" and "hx-target".

Environment

htmx 1.9.10, response-targets 1.9.10, python 3.12, django 5.0.1

Example html:

<div hx-ext="response-targets">
    <button
        hx-get={% ur "appname:endpoint" %}
        hx-target-error="#id-fail"
        hx-target="#id-success"
        type="submit"
     >
         Press Me!
     </button>
</div>

hx-target

I have tried with various other types of targets such as "next td", "next span", etc. All of them produce a valid target value "request.htmx.target" in the endpoint defined in views.py.

hx-target-error

I have also tried alternatives targets such as "hx-target-*", "hx-target-404", etc with no change in results.

I verified that "response-targets" is installed correctly and being used correctly because when "hx-target" is removed from the "button", then "hx-target-error" works.

Error generation in views.ph

@login_required
def endpoint(request)
    return HttpResponseNotFound("Not found 404")

Logs

Bad Request: /testbed/endpoint/
127.0.0.1 - [27/Feb/2024] "GET /testbed/endpoint/ HTTP/1.1" 400

Work-Around

The current work-around is to omit "hx-target". In this case, 200 success response replace the current DOM object and all errors replace to DOM object pointed to by "hx-target-error". This work-around is NOT acceptable because we need targeted success snippet placement.

dmanpearl commented 4 months ago

I have a successful fix implemented with a small code-change in response-targets.js. It processes custom targets on "beforeOnLoad" because "beforeSwap" is being suppressed. I think there is a deeper problem causing the suppression, but I am open to sharing and discussion if anyone is interested and will eventually make a PR.

Here is the temporary fix:

    onEvent: function (name, evt) {

        // Original v1.9.10: Bug in which 'hx-target-*' fails to swap if 'hx-target' is also set.
        // if (name === "htmx:beforeSwap"    &&

        // DGM 2-27-2024: Add 'hx-target-*' error processing while 'hx-target' is simultaneously defined.
        if ((name === "htmx:beforeSwap" || name === "htmx:beforeOnLoad") &&
dmanpearl commented 4 months ago

Resolved: The solution is to move the "hx-ext" attribute

to a higher encompassing level than any DOM elements referenced by either "hx-target" or "hx-target-error".

In my case, "hx-target-error" pointed to a div 'id' outside of the div containing "hx-ext".

I came across this undocumented solution here

Example incorrect usage:

<tr>
    <td>
        <div hx-ext="response-targets">
            <button
                hx-get={% url "testbed:rebound" %}
                hx-headers='{"custom": "{{ test.id }}"}'
                hx-target-error="#GET-{{ test.id }}-fail"
                hx-target="#GET-{{ test.id }}-ok"
                class="btn btn-sm btn-primary testbed-btn"
            >
                GET {{ test.msg }}
            </button>
        </div>
    </td>
    <td>
        <span id="GET-{{ test.id }}-ok" />
            <span class="text-danger" id="GET-{{ test.id }}-fail" />
        </td>
</tr>

Example correct usage:

<tr hx-ext="response-targets">
    <td>
        <button
            hx-get={% url "testbed:rebound" %}
            hx-headers='{"custom": "{{ test.id }}"}'
            hx-target-error="#GET-{{ test.id }}-fail"
            hx-target="#GET-{{ test.id }}-ok"
            class="btn btn-sm btn-primary testbed-btn"
        >
            GET {{ test.msg }}
        </button>
    </td>
    <td>
        <span id="GET-{{ test.id }}-ok" />
        <span class="text-danger" id="GET-{{ test.id }}-fail" />
    </td>
</tr>
magiconair commented 2 months ago

We have a sidebar which loads a content pane in two different parts of the DOM and were stumbling over this. Moving the hx-ext="response-targets" to the <body> element fixes this but it feels too broad of a change since we only have to do this for the response-targets extension. Is there any downside in doing that?

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="https://unpkg.com/htmx.org@1.9.12/dist/htmx.js"></script>
    <script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/debug.js"></script>
    <script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/response-targets.js"></script>
  </head>
  <body>
    <div id="error"></div>
    <div id="nav">
      <a
        href="#"
        hx-ext="response-targets"
        hx-get="/fail"
        hx-target="#content"
        hx-target-error="#error"
        >fails</a
      >
      <a
        href="#"
        hx-ext="response-targets"
        hx-get="/ok"
        hx-target="#content"
        hx-target-error="#error"
        >works</a
      >
    </div>
    <div id="content"></div>
  </body>
</html>