Loading core/lib/Drupal/Core/Htmx/Htmx.php +35 −12 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading Loading @@ -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; } Loading Loading @@ -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. Loading core/misc/htmx/htmx-assets.js +26 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading core/modules/config/src/Form/ConfigSingleExportForm.php +2 −0 Original line number Diff line number Diff line Loading @@ -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') Loading @@ -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') Loading core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php +1 −0 Original line number Diff line number Diff line Loading @@ -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 !== '') { Loading core/profiles/demo_umami/tests/src/FunctionalJavascript/AssetAggregationAcrossPagesTest.php +2 −2 Original line number Diff line number Diff line Loading @@ -50,7 +50,7 @@ public function testFrontAndRecipesPagesAuthenticated(): void { $expected = [ 'ScriptCount' => 6, 'ScriptBytes' => 203600, 'ScriptBytes' => 204618, 'StylesheetCount' => 5, 'StylesheetBytes' => 81750, ]; Loading @@ -71,7 +71,7 @@ public function testFrontAndRecipesPagesEditor(): void { }, 'umamiFrontAndRecipePagesEditor'); $expected = [ 'ScriptCount' => 8, 'ScriptBytes' => 396300, 'ScriptBytes' => 397256, 'StylesheetCount' => 5, 'StylesheetBytes' => 204350, ]; Loading Loading
core/lib/Drupal/Core/Htmx/Htmx.php +35 −12 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading @@ -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. Loading Loading @@ -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; } Loading Loading @@ -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. Loading
core/misc/htmx/htmx-assets.js +26 −2 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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. Loading
core/modules/config/src/Form/ConfigSingleExportForm.php +2 −0 Original line number Diff line number Diff line Loading @@ -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') Loading @@ -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') Loading
core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php +1 −0 Original line number Diff line number Diff line Loading @@ -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 !== '') { Loading
core/profiles/demo_umami/tests/src/FunctionalJavascript/AssetAggregationAcrossPagesTest.php +2 −2 Original line number Diff line number Diff line Loading @@ -50,7 +50,7 @@ public function testFrontAndRecipesPagesAuthenticated(): void { $expected = [ 'ScriptCount' => 6, 'ScriptBytes' => 203600, 'ScriptBytes' => 204618, 'StylesheetCount' => 5, 'StylesheetBytes' => 81750, ]; Loading @@ -71,7 +71,7 @@ public function testFrontAndRecipesPagesEditor(): void { }, 'umamiFrontAndRecipePagesEditor'); $expected = [ 'ScriptCount' => 8, 'ScriptBytes' => 396300, 'ScriptBytes' => 397256, 'StylesheetCount' => 5, 'StylesheetBytes' => 204350, ]; Loading