Verified Commit 25e70b1b authored by Dave Long's avatar Dave Long
Browse files

fix: #3560659 HTMX Drupal behaviors are not applied when swapped element is body

By: mxh
By: nod_
By: fathershawn
By: godotislate
(cherry picked from commit 07d426b4)
parent 5226fc8a
Loading
Loading
Loading
Loading
Loading
+8 −1
Original line number Diff line number Diff line
@@ -137,8 +137,15 @@
  // @see https://htmx.org/events/#htmx:afterSettle
  htmx.on('htmx:afterSettle', ({ detail }) => {
    (requestAssetsLoaded.get(detail.xhr) || Promise.resolve()).then(() => {
      let processTarget = detail.elt.parentElement;
      // Some HTMX swaps put the incoming element before or after detail.elt.
      htmx.trigger(detail.elt.parentNode, 'htmx:drupal:load');
      // We normally target the parent element so that we process the added
      // elements. When there is no parent element or detail.elt is the body,
      // we target the element itself.
      if (detail.elt === document.body || processTarget === null) {
        processTarget = detail.elt;
      }
      htmx.trigger(processTarget, 'htmx:drupal:load');
      // This should be automatic but don't wait for the garbage collector.
      requestAssetsLoaded.delete(detail.xhr);
    });
+21 −13
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@
namespace Drupal\test_htmx\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Htmx\Htmx;
use Drupal\Core\Url;

@@ -31,7 +30,7 @@ public function page(): array {
   *   A render array.
   */
  public function before(): array {
    return self::generateHtmxButton('beforebegin');
    return self::generateHtmxButton(swap: 'beforebegin');
  }

  /**
@@ -41,7 +40,7 @@ public function before(): array {
   *   A render array.
   */
  public function after(): array {
    return self::generateHtmxButton('afterend');
    return self::generateHtmxButton(swap: 'afterend');
  }

  /**
@@ -51,7 +50,24 @@ public function after(): array {
   *   A render array.
   */
  public function withWrapperFormat(): array {
    return self::generateHtmxButton('', TRUE);
    return self::generateHtmxButton(swap: '', useWrapperFormat: TRUE);
  }

  /**
   * Tests body targeting and swapping.
   *
   * @return mixed[]
   *   A render array.
   */
  public function selectBody(): array {
    return [
      '#title' => $this->t('Boosted body'),
      '#type' => 'link',
      '#url' => Url::fromRoute('test_htmx.attachments.replace'),
      '#attributes' => [
        'class' => ['htmx-test-link'],
      ],
    ];
  }

  /**
@@ -92,15 +108,7 @@ public static function replaceWithAjax(): array {
   *   The render array.
   */
  public static function generateHtmxButton(string $swap = '', bool $useWrapperFormat = FALSE): array {
    $options = [];
    if ($useWrapperFormat) {
      $options = [
        'query' => [
          MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_htmx',
        ],
      ];
    }
    $url = Url::fromRoute('test_htmx.attachments.replace', [], $options);
    $url = Url::fromRoute('test_htmx.attachments.replace');
    $build['replace'] = [
      '#type' => 'html_tag',
      '#tag' => 'button',
+30 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\test_htmx\Hook;

use Drupal\Core\Hook\Attribute\Hook;
use Drupal\Core\Routing\RouteMatchInterface;

/**
 * Hooks for the test_htmx module.
 */
class TestHtmxHooks {

  public function __construct(
    protected RouteMatchInterface $routeMatch,
  ) {}

  /**
   * Implements hook_preprocess_HOOK() for html.
   */
  #[Hook('preprocess_html')]
  public function boost(array &$variables): void {
    if ($this->routeMatch->getRouteName() === 'test_htmx.attachments.body') {
      $variables['#attached']['library'][] = 'core/drupal.htmx';
      $variables['attributes']['data-hx-boost'] = 'true';
    }
  }

}
+8 −0
Original line number Diff line number Diff line
@@ -30,6 +30,14 @@ test_htmx.attachments.wrapper:
  requirements:
    _permission: 'access content'

test_htmx.attachments.body:
  path: '/htmx-test-attachments/body'
  defaults:
    _title: 'Body testing'
    _controller: '\Drupal\test_htmx\Controller\HtmxTestAttachmentsController::selectBody'
  requirements:
    _permission: 'access content'

test_htmx.attachments.replace:
  path: '/htmx-test-attachments/replace'
  defaults:
+23 −0
Original line number Diff line number Diff line
@@ -138,4 +138,27 @@ module.exports = {
      .assert.elementPresent(scriptSelector)
      .assert.elementPresent(cssSelector);
  },

  'Boosted Body': (browser) => {
    // Load the route htmx will use for the request on click and confirm the
    // markup we will be looking for is present in the source markup.
    browser
      .drupalRelativeURL('/htmx-test-attachments/replace')
      .waitForElementVisible('body', 1000)
      .assert.elementPresent(elementInitSelector);
    // Now load the page with the htmx enhanced link and verify the absence
    // of the markup to be inserted. Click the link
    // and check for inserted javascript and markup.
    browser
      .drupalRelativeURL('/htmx-test-attachments/body')
      .waitForElementVisible('body', 1000)
      .assert.not.elementPresent(scriptSelector)
      .assert.not.elementPresent(cssSelector)
      .waitForElementVisible('a.htmx-test-link', 1000)
      .click('a.htmx-test-link')
      .waitForElementVisible(elementSelector, 1100)
      .waitForElementVisible(elementInitSelector, 1100)
      .assert.elementPresent(scriptSelector)
      .assert.elementPresent(cssSelector);
  },
};