HTMX behavior attachment fails with some swap strategies

Created on 24 June 2025, about 1 month ago

Problem/Motivation

HTMX swap strategies beforebegin and afterend insert the new content outside the target. When combined with the default target property, this results in failed behaviors attachment.

In htmx-assets.js we trigger Drupal.behaviors with this code block:

  // Trigger the Drupal processing once all assets have been loaded.
  // @see https://htmx.org/events/#htmx:afterSettle
  htmx.on('htmx:afterSettle', ({ detail }) => {
    (Drupal.htmx.assetsPromises.get(detail.xhr) || Promise.resolve()).then(
      () => {
        htmx.trigger(detail.elt, 'htmx:drupal:load');
        // This should be automatic but don't wait for the garbage collector.
        Drupal.htmx.assetsPromises.delete(detail.xhr);
      },
    );
  });

When this markup is used:

<button class="button" name="replace" data-hx-get="/htmx-test-attachments/replace" data-hx-select="div.dropbutton-wrapper" data-hx-swap="afterend  ignoreTitle:true">Click this</button>

The default target is the element issuing the request, which is the button, and the dropdown markup is correctly inserted after the button. But the button is passed in the call to htmx.trigger which becomes the context for Drupal.behaviors.dropButton which finds no dropbutton in the button tag.

Proposed resolution

Change detail.elt to detail.elt.parentNode to account for these swap strategies.

Remaining tasks

Add test cases for these swap strategies.

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

πŸ› Bug report
Status

Active

Version

11.0 πŸ”₯

Component

javascript

Created by

πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @fathershawn
  • Pipeline finished with Failed
    about 1 month ago
    Total: 158s
    #530444
  • Pipeline finished with Failed
    about 1 month ago
    Total: 674s
    #530459
  • Pipeline finished with Failed
    about 1 month ago
    Total: 1011s
    #530473
  • Pipeline finished with Success
    about 1 month ago
    Total: 732s
    #530480
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    New tests now pass after adjusting big_pipe.js.

  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    One question on the wait time haven't finished the review, but test only passes.

  • Pipeline finished with Success
    29 days ago
    Total: 386s
    #531278
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Fixed @nicxvan concern about wait time

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Failed
    29 days ago
    Total: 591s
    #531573
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Nightwatch tests were not visible in the "test only" job. I created a draft MR without the fix. Tests in that MR fail as expected.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Been a fly on the wall for all this HTMX stuff and very neat stuff.

    Came here from the request in the maintainer slack channel.

    And yes unfortunately test-only with nightwatch does not work, probably because nightwatch decides when it wants to work.

    Posting the failure from the draft MR

      ️TEST FAILURE (2m 5s):  
       - 2 assertions failed; 1406 passed
       βœ– 1) htmx/htmxTest
       – Swap Before (2.382s)
       β†’ βœ– NightwatchAssertError
       Timed out while waiting for element <.ajax-content[data-once="htmx-init"]> to be present for 1100 milliseconds. - expected "visible" but got: "not found" (1526ms)
        Error location:
        /builds/issue/drupal-3532159/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js:68
        ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
         66 |       .click('[name="replace"]')
         67 |       .waitForElementVisible(elementSelector, 1100)
         68 |       .waitForElementVisible(elementInitSelector, 1100) 
         69 |       .assert.elementPresent(scriptSelector)
         70 |       .assert.elementPresent(cssSelector);
        ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
       – Swap After (2.382s)
       β†’ βœ– NightwatchAssertError
       Timed out while waiting for element <.ajax-content[data-once="htmx-init"]> to be present for 1100 milliseconds. - expected "visible" but got: "not found" (1524ms)
        Error location:
        /builds/issue/drupal-3532159/core/tests/Drupal/Nightwatch/Tests/htmx/htmxTest.js:91
        ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
         89 |       .click('[name="replace"]')
         90 |       .waitForElementVisible(elementSelector, 1100)
         91 |       .waitForElementVisible(elementInitSelector, 1100) 
         92 |       .assert.elementPresent(scriptSelector)
         93 |       .assert.elementPresent(cssSelector);
        ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
    

    Summary is complete
    Will go on a limb since the change is small and seems to work.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
    • nod_ β†’ committed ac36d01e on 11.2.x
      Issue #3532159 by fathershawn, nicxvan, smustgrave: HTMX behavior...
    • nod_ β†’ committed 7cf60930 on 11.x
      Issue #3532159 by fathershawn, nicxvan, smustgrave: HTMX behavior...
  • πŸ‡«πŸ‡·France nod_ Lille

    Committed and pushed 7cf609309d5 to 11.x and ac36d01e3f1 to 11.2.x. Thanks!

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024