Verified Commit 62362fa4 authored by Dave Long's avatar Dave Long
Browse files

task: #3546105 Automatically manage _wrapper_format for HTMX requests and...

task: #3546105 Automatically manage _wrapper_format for HTMX requests and clean up ajax_page_state in urls

By: nod_
By: fathershawn
By: nicxvan
By: godotislate
By: papagrande
By: mxh
By: longwave
By: idiaz.roncero
(cherry picked from commit ca4031b9)
parent 774f29f6
Loading
Loading
Loading
Loading
Loading
+35 −12
Original line number Diff line number Diff line
@@ -199,19 +199,37 @@ protected function createJsonAttribute(string $id, array $value): void {
   * @param string $method
   *   The request method.
   * @param \Drupal\Core\Url|null $url
   *   The URL for the request.
   *   The URL for the request. If NULL, is passed it will use the current URL
   *   without any query parameter.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   */
  protected function buildRequestAttribute(string $method, ?Url $url = NULL): static {
    if (is_null($url)) {
      $value = '';
      $request_url = Url::fromRoute('<none>');
    }
    else {
      $value = $this->urlValue($url);
      // The Htmx helper should not modify the original URL object.
      $request_url = clone $url;
    }
    $this->createStringAttribute($method, $value);
    $this->createStringAttribute($method, $this->urlValue($request_url));
    return $this;
  }

  /**
   * Decides when to use the `drupal_htmx` wrapper format for Htmx requests.
   *
   * @param bool $toggle
   *   Toggle to use the full HTML response or just the main content.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   *
   * @see core/misc/htmx/htmx-assets.js
   */
  public function onlyMainContent(bool $toggle = TRUE) {
    $this->createBooleanAttribute('hx-drupal-only-main-content', $toggle);
    return $this;
  }

@@ -518,7 +536,8 @@ public function triggerAfterSwapHeader(string|array $data): static {
   * request to the current url. If parameters are used, both are required.
   *
   * @param \Drupal\Core\Url|null $url
   *   The URL for the GET request. If NULL the current page is used.
   *   The URL for the GET request. If NULL, the current page is used without
   *   the query parameters.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
@@ -538,7 +557,8 @@ public function get(?Url $url = NULL): static {
   *  request to the current url. If parameters are used, both are required.
   *
   * @param \Drupal\Core\Url|null $url
   *   The URL for the POST request. If NULL the current page is used.
   *   The URL for the GET request. If NULL, the current page is used without
   *   the query parameters.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
@@ -558,7 +578,8 @@ public function post(?Url $url = NULL): static {
   *  request to the current url. If parameters are used, both are required.
   *
   * @param \Drupal\Core\Url|null $url
   *   The URL for the PUT request. If NULL the current page is used.
   *   The URL for the GET request. If NULL, the current page is used without
   *   the query parameters.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
@@ -572,13 +593,14 @@ public function put(?Url $url = NULL): static {
  /**
   * Creates a `data-hx-patch` attribute.
   *
   * This attribute instructs HTMX to issue a PATCH request
   * This attribute instructs HTMX to issue a PATCH request.
   *
   *  This request method also accepts no parameters, which issues a patch
   *  request to the current url. If parameters are used, both are required.
   *
   * @param \Drupal\Core\Url|null $url
   *   Collects the cacheable metadata from the URL generation.
   *   The URL for the GET request. If NULL, the current page is used without
   *   the query parameters.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
@@ -599,7 +621,8 @@ public function patch(?Url $url = NULL): static {
   *  request to the current url. If parameters are used, both are required.
   *
   * @param \Drupal\Core\Url|null $url
   *   The URL for the DELETE request. If NULL the current page is used.
   *   The URL for the GET request. If NULL, the current page is used without
   *   the query parameters.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
@@ -855,7 +878,7 @@ public function vals(array $values): static {
   *
   * @see https://htmx.org/attributes/hx-boost/
   */
  public function boost(bool $value): static {
  public function boost(bool $value = TRUE): static {
    $this->createStringAttribute('hx-boost', $value ? 'true' : 'false');
    return $this;
  }
@@ -999,7 +1022,7 @@ public function headers(array $headerValues): static {
   * data from being saved to the localStorage cache when htmx takes a snapshot
   * of the page state. This attribute is effective when set on any element in
   * the current document, or any html fragment loaded into the current document
   * by htmx
   * by htmx.
   *
   * @return static
   *   Returns this object to allow chaining methods.
+26 −2
Original line number Diff line number Diff line
@@ -37,8 +37,15 @@
   * @see https://htmx.org/events/#htmx:configRequest
   */
  htmx.on('htmx:configRequest', ({ detail }) => {
    const url = new URL(detail.path, document.location.href);
    if (Drupal.url.isLocal(url.toString())) {
    if (Drupal.url.isLocal(detail.path)) {
      if (detail.elt.hasAttribute('data-hx-drupal-only-main-content')) {
        // Add _wrapper_format query parameter for all non full page requests.
        // Drupal expects this parameter to be in the query string, not the post
        // values.
        const url = new URL(detail.path, window.location);
        url.searchParams.set('_wrapper_format', 'drupal_htmx');
        detail.path = url.toString();
      }
      // Allow Drupal to return new JavaScript and CSS files to load without
      // returning the ones already loaded.
      // @see \Drupal\Core\StackMiddleWare\AjaxPageState
@@ -56,6 +63,23 @@
    }
  });

  // When saving to the browser history always remove wrapper format and ajax
  // page state from the query string.
  htmx.on('htmx:beforeHistoryUpdate', ({ detail }) => {
    const url = new URL(detail.history.path, window.location);
    [
      '_wrapper_format',
      'ajax_page_state[theme]',
      'ajax_page_state[theme_token]',
      'ajax_page_state[libraries]',
      '_triggering_element_name',
      '_triggering_element_value',
    ].forEach((key) => {
      url.searchParams.delete(key);
    });
    detail.history.path = url.toString();
  });

  // @see https://htmx.org/events/#htmx:beforeSwap
  htmx.on('htmx:beforeSwap', ({ detail }) => {
    // Custom event to detach behaviors.
+2 −0
Original line number Diff line number Diff line
@@ -100,6 +100,7 @@ public function buildForm(array $form, FormStateInterface $form_state, string $c
    $form_url = Url::fromRoute('config.export_single', ['config_type' => $config_type, 'config_name' => $config_name]);
    (new Htmx())
      ->post($form_url)
      ->onlyMainContent()
      ->select('*:has(>select[name="config_name"])')
      ->target('*:has(>select[name="config_name"])')
      ->swap('outerHTML')
@@ -117,6 +118,7 @@ public function buildForm(array $form, FormStateInterface $form_state, string $c
    // Select and replace the wrapper element of the export textarea.
    (new Htmx())
      ->post($form_url)
      ->onlyMainContent()
      ->select('[data-export-wrapper]')
      ->target('[data-export-wrapper]')
      ->swap('outerHTML')
+1 −0
Original line number Diff line number Diff line
@@ -112,6 +112,7 @@ public static function generateHtmxButton(string $swap = '', bool $useWrapperFor
    ];
    $replace_htmx = (new Htmx())
      ->get($url)
      ->onlyMainContent($useWrapperFormat)
      ->select('div.ajax-content')
      ->target('[data-drupal-htmx-target]');
    if ($swap !== '') {
+2 −2
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ public function testFrontAndRecipesPagesAuthenticated(): void {

    $expected = [
      'ScriptCount' => 6,
      'ScriptBytes' => 203600,
      'ScriptBytes' => 204618,
      'StylesheetCount' => 5,
      'StylesheetBytes' => 81750,
    ];
@@ -71,7 +71,7 @@ public function testFrontAndRecipesPagesEditor(): void {
    }, 'umamiFrontAndRecipePagesEditor');
    $expected = [
      'ScriptCount' => 8,
      'ScriptBytes' => 396300,
      'ScriptBytes' => 397256,
      'StylesheetCount' => 5,
      'StylesheetBytes' => 204350,
    ];
Loading