class Htmx

Presents the HTMX controls for developers to use with render arrays.

HTMX is designed as an extension of HTML. It is therefore a declarative markup system that uses attributes.


<button hx-get="/contacts/1" hx-target="#contact-ui"> <1>
  Fetch Contact
</button>

HTMX is just as happy with `data-hx-get` and so we will maintain standard markup in our implementation.

HTMX uses over 30 such attributes. The other control surface for HTMX is a set of response headers. HTMX supports 11 custom response headers.

For example, to make a select element interactive so that it will:

  • Send a POST request to the form URL.
  • Select the wrapper element of the new <select> element from the response.
  • Target the wrapper element of the current <select> in the rendered form for replacement.
  • Use the outerHTML strategy, which is to replace the whole tag.

use Drupal\Core\Htmx\Htmx;
use Drupal\Core\Url;

$form['config_type'] = [
  '#title' => $this->t('Configuration type'),
  '#type' => 'select',
  '#options' => $config_types,
  '#default_value' => $config_type,
];

$htmx = new Htmx();

$htmx->post()
  ->select('*:has(>select[name="config_name"])')
  ->target('*:has(>select[name="config_name"])')
  ->swap('outerHTML');
$htmx->applyTo($form['config_type']);
}

To dynamically update the url in the browser using a response header when the config_name selector is returned:

if (!empty($default_type) && !empty($default_name)) {
  $push = Url::fromRoute('config.export_single', [
    'config_type' => $default_type,
    'config_name' => $default_name,
  ]);
  $htmx = new Htmx();
  $htmx->pushUrlHeader($push);
  $htmx->applyTo($form['config_name']);
}

Whenever a method calls for a Url object, the cacheable metadata emitted by rendering the object to string is also collected and merged to the render array by the `::applyTo` method.

A static method `Htmx::createFromRenderArray` is provided which takes a render array as input and builds an new instance of Htmx with all the HTMX specific attributes and headers loaded from the array.

Hierarchy

  • class \Drupal\Core\Htmx\Htmx

Expanded class hierarchy of Htmx

See also

https://htmx.org/reference/

https://hypermedia.systems/book/contents/

6 files declare their use of Htmx
FormBuilder.php in core/lib/Drupal/Core/Form/FormBuilder.php
HtmxAttributesTest.php in core/tests/Drupal/Tests/Core/Htmx/HtmxAttributesTest.php
HtmxHeadersTest.php in core/tests/Drupal/Tests/Core/Htmx/HtmxHeadersTest.php
HtmxTestAttachmentsController.php in core/modules/system/tests/modules/test_htmx/src/Controller/HtmxTestAttachmentsController.php
HtmxTestForm.php in core/modules/system/tests/modules/test_htmx/src/Form/HtmxTestForm.php

... See full list

File

core/lib/Drupal/Core/Htmx/Htmx.php, line 89

Namespace

Drupal\Core\Htmx
View source
class Htmx {
  
  /**
   * All HTMX attributes begin with this string.
   */
  protected const ATTRIBUTE_PREFIX = 'data-';
  
  /**
   * Initialize empty storage.
   *
   * Allows for passing a populated HeaderBag to support merging.
   */
  public function __construct(protected Attribute $attributes = new Attribute(), protected HeaderBag $headers = new HeaderBag(), protected CacheableMetadata $cacheableMetadata = new CacheableMetadata()) {
  }
  
  /**
   * Utility method to transform camelCase strings to kebab-case strings.
   *
   * Passes kebab-case strings through without any transformation.
   *
   * @param string $identifier
   *   The string to verify or transform.
   *
   * @return string
   *   The original or transformed string.
   */
  protected function ensureKebabCase(string $identifier) : string {
    // Check for existing kebab case.
    $kebabParts = explode('-', $identifier);
    // If the number of lower case parts matches the number of parts, then
    // all the parts are lower case.
    $isKebab = count($kebabParts) === count(array_filter($kebabParts, function ($part) {
      return ctype_lower($part);
    }));
    if ($isKebab) {
      return $identifier;
    }
    return (string) u($identifier)->snake()
      ->replaceMatches('#[_:]#', '-');
  }
  
  /**
   * Helper method to get the url string and store cache metadata.
   *
   * @param \Drupal\Core\Url $url
   *   The URL to generate.
   *
   * @return string
   *   The url string.
   */
  protected function urlValue(Url $url) : string {
    $generatedUrl = $url->toString(TRUE);
    $this->cacheableMetadata
      ->addCacheableDependency($generatedUrl);
    return $generatedUrl->getGeneratedUrl();
  }
  
  /**
   * Utility method to create and store a string value as an attribute.
   *
   * @param string $id
   *   The HTMX attribute id.
   * @param string $value
   *   The attribute value.
   */
  protected function createStringAttribute(string $id, string $value) : void {
    $key = self::ATTRIBUTE_PREFIX . $id;
    $this->attributes[$key] = new AttributeString($key, $value);
  }
  
  /**
   * Utility method to create and store a boolean value as an attribute.
   *
   * @param string $id
   *   The HTMX attribute id.
   * @param bool $value
   *   The attribute value.
   */
  protected function createBooleanAttribute(string $id, bool $value) : void {
    $key = self::ATTRIBUTE_PREFIX . $id;
    $this->attributes[$key] = new AttributeBoolean($key, $value);
  }
  
  /**
   * Utility method to create and store an array as an attribute.
   *
   * @param string $id
   *   The HTMX attribute id.
   * @param array{string, string} $value
   *   The attribute values.
   */
  protected function createJsonAttribute(string $id, array $value) : void {
    $key = self::ATTRIBUTE_PREFIX . $id;
    // Ensure the object format HTMX shows in documentation.
    // Ensure numeric strings are encoded as numbers.
    $json = json_encode($value, JSON_FORCE_OBJECT | JSON_NUMERIC_CHECK);
    $this->attributes[$key] = new AttributeString($key, $json);
  }
  
  /**
   * Utility function for the request attributes.
   *
   * Provides the logic for the request attribute methods.  Separate public
   * methods are maintained for clear correspondence with the attributes of
   * HTMX.
   *
   * @param string $method
   *   The request method.
   * @param \Drupal\Core\Url|null $url
   *   The URL for the request.
   *
   * @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 = '';
    }
    else {
      $value = $this->urlValue($url);
    }
    $this->createStringAttribute($method, $value);
    return $this;
  }
  
  /**
   * Apply the header values to the render array.
   */
  protected function applyHeaders() : array {
    $drupalHeaders = [];
    foreach ($this->headers as $name => $values) {
      foreach ($values as $value) {
        // Set replace to true.
        $drupalHeaders[] = [
          $name,
          $value,
          TRUE,
        ];
      }
    }
    return $drupalHeaders;
  }
  
  /**
   * Checks if a header is set.
   *
   * @param string $name
   *   The name of the header.
   *
   * @return bool
   *   True if header is stored.
   */
  public function hasHeader(string $name) : bool {
    return $this->headers
      ->has($name);
  }
  
  /**
   * Checks if an attribute is set.
   *
   * @param string $name
   *   The name of the attribute.
   *
   * @return bool
   *   True if attribute is stored.
   */
  public function hasAttribute(string $name) : bool {
    return $this->attributes
      ->hasAttribute($name);
  }
  
  /**
   * Removes a header from the header store.
   *
   * @param string $name
   *   The header name to remove.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   */
  public function removeHeader(string $name) : static {
    $this->headers
      ->remove($name);
    return $this;
  }
  
  /**
   * Removes an attribute from the attribute store.
   *
   * @param string $name
   *   The attribute name to remove.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   */
  public function removeAttribute(string $name) : static {
    $this->attributes
      ->removeAttribute($name);
    return $this;
  }
  
  /**
   * Get the attribute storage.
   *
   * @return \Drupal\Core\Template\Attribute
   *   The attribute storage.
   */
  public function getAttributes() : Attribute {
    return $this->attributes;
  }
  
  /**
   * Get the header storage.
   *
   * @return \Symfony\Component\HttpFoundation\HeaderBag
   *   The header storage.
   */
  public function getHeaders() : HeaderBag {
    return $this->headers;
  }
  
  /**
   * Set HX-Location header.
   *
   * @param \Drupal\Core\Url|\Drupal\Core\Htmx\HtmxLocationResponseData $data
   *   Use Url if only a path is needed.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-location/
   */
  public function locationHeader(Url|HtmxLocationResponseData $data) : static {
    if ($data instanceof HtmxLocationResponseData) {
      $value = (string) $data;
      $this->cacheableMetadata
        ->addCacheableDependency($data->getCacheableMetadata());
    }
    else {
      $value = $this->urlValue($data);
    }
    $this->headers
      ->set('HX-Location', $value);
    return $this;
  }
  
  /**
   * Set HX-Push-Url header.
   *
   * @param \Drupal\Core\Url|false $value
   *   URL to push to the location bar or false to prevent a history update.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-push-url/
   */
  public function pushUrlHeader(Url|false $value) : static {
    $url = 'false';
    if ($value instanceof Url) {
      $url = $this->urlValue($value);
    }
    $this->headers
      ->set('HX-Push-Url', $url);
    return $this;
  }
  
  /**
   * Set HX-Replace-Url header.
   *
   * @param \Drupal\Core\Url|false $data
   *   URL for history replacement, false  prevents updates to the current URL.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-replace-url/
   */
  public function replaceUrlHeader(Url|false $data) : static {
    $value = 'false';
    if ($data instanceof Url) {
      $value = $this->urlValue($data);
    }
    $this->headers
      ->set('HX-Replace-Url', $value);
    return $this;
  }
  
  /**
   * Set HX-Redirect header.
   *
   * @param \Drupal\Core\Url $url
   *   Destination for a client side redirection.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-redirect/
   */
  public function redirectHeader(Url $url) : static {
    $this->headers
      ->set('HX-Redirect', $this->urlValue($url));
    return $this;
  }
  
  /**
   * Set HX-Refresh header.
   *
   * @param bool $refresh
   *   If set to “true” the client-side will do a full refresh of the page.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/reference/#response_headers
   */
  public function refreshHeader(bool $refresh) : static {
    $this->headers
      ->set('HX-Refresh', $refresh ? 'true' : 'false');
    return $this;
  }
  
  /**
   * Set HX-Reswap header.
   *
   * @param string $strategy
   *   Specify how the response will be swapped (see hx-swap).
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/reference/#response_headers
   */
  public function reswapHeader(string $strategy) : static {
    $this->headers
      ->set('HX-Reswap', $strategy);
    return $this;
  }
  
  /**
   * Set HX-Retarget header.
   *
   * @param string $strategy
   *   CSS selector that replaces the target to a different element on the page.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/reference/#response_headers
   */
  public function retargetHeader(string $strategy) : static {
    $this->headers
      ->set('HX-Retarget', $strategy);
    return $this;
  }
  
  /**
   * Set HX-Reselect header.
   *
   * @param string $strategy
   *   CSS selector that changes the selection taken from the response.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/reference/#response_headers
   */
  public function reselectHeader(string $strategy) : static {
    $this->headers
      ->set('HX-Reselect', $strategy);
    return $this;
  }
  
  /**
   * Set HX-Trigger header.
   *
   * See the documentation for the structure of the array.
   *
   * @param string|array $data
   *   An event name or an array which will be JSON encoded.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-trigger/
   */
  public function triggerHeader(string|array $data) : static {
    if (is_array($data)) {
      $data = json_encode($data);
    }
    $this->headers
      ->set('HX-Trigger', $data);
    return $this;
  }
  
  /**
   * Set HX-Trigger-After-Settle header.
   *
   * See the documentation for the structure of the array.
   *
   * @param string|array $data
   *   An event name or an array which will be JSON encoded.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-trigger/
   */
  public function triggerAfterSettleHeader(string|array $data) : static {
    if (is_array($data)) {
      $data = json_encode($data);
    }
    $this->headers
      ->set('HX-Trigger-After-Settle', $data);
    return $this;
  }
  
  /**
   * Set HX-Trigger-After-Swap header.
   *
   * See the documentation for the structure of the array.
   *
   * @param string|array $data
   *   An event name or an array which will be JSON encoded.
   *
   * @return static
   *   Self for chaining.
   *
   * @see https://htmx.org/headers/hx-trigger/
   */
  public function triggerAfterSwapHeader(string|array $data) : static {
    if (is_array($data)) {
      $data = json_encode($data);
    }
    $this->headers
      ->set('HX-Trigger-After-Swap', $data);
    return $this;
  }
  
  /**
   * Creates a `data-hx-get` attribute.
   *
   * This attribute instructs HTMX to issue a GET request to the specified URL.
   *
   * This request method also accepts no parameters, which issues a get
   * 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.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   *
   * @see https://htmx.org/attributes/hx-get/
   */
  public function get(?Url $url = NULL) : static {
    return $this->buildRequestAttribute('hx-get', $url);
  }
  
  /**
   * Creates a `data-hx-post` attribute.
   *
   * This attribute instructs HTMX to issue a POST request to the specified URL.
   *
   *  This request method also accepts no parameters, which issues a post
   *  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.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   *
   * @see https://htmx.org/attributes/hx-post/
   */
  public function post(?Url $url = NULL) : static {
    return $this->buildRequestAttribute('hx-post', $url);
  }
  
  /**
   * Creates a `data-hx-put` attribute.
   *
   * This attribute instructs HTMX to issue a PUT request to the specified URL.
   *
   *  This request method also accepts no parameters, which issues a put
   *  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.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   *
   * @see https://htmx.org/attributes/hx-put/
   */
  public function put(?Url $url = NULL) : static {
    return $this->buildRequestAttribute('hx-put', $url);
  }
  
  /**
   * Creates a `data-hx-patch` attribute.
   *
   * 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.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   *
   * @see https://htmx.org/attributes/hx-patch/
   */
  public function patch(?Url $url = NULL) : static {
    return $this->buildRequestAttribute('hx-patch', $url);
  }
  
  /**
   * Creates a `data-hx-delete` attribute.
   *
   * This attribute instructs HTMX to issue a DELETE request
   * to the specified URL.
   *
   *  This request method also accepts no parameters, which issues a delete
   *  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.
   *
   * @return static
   *   returns self so that attribute methods may be chained.
   *
   * @see https://htmx.org/attributes/hx-delete/
   */
  public function delete(?Url $url = NULL) : static {
    return $this->buildRequestAttribute('hx-delete', $url);
  }
  
  /**
   * Creates a `data-hx-on` attribute.
   *
   * This attribute instructs HTMX to react to events with inline scripts
   * on elements.
   * - $event is the name of the JavaScript event.
   * - $action is the JavaScript statement to execute when the event occurs.
   *   This can be a short instruction or call a function in a script that
   *   has been loaded by the page.
   *
   * @param string $event
   *   An event in either camelCase or kebab-case.
   * @param string $action
   *   The JavaScript statement.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-on/
   */
  public function on(string $event, string $action) : static {
    // Special case: the `::EventName` shorthand for `htmx:EventName`.
    // Prepare a leading '-' so that our final attribute is
    // `data-hx--event-name` rather than `data-hx-event-name`.
    $extra = str_starts_with($event, '::') ? '-' : '';
    $formattedEvent = 'hx-on-' . $extra . $this->ensureKebabCase($event);
    $this->createStringAttribute($formattedEvent, $action);
    return $this;
  }
  
  /**
   * Creates a `data-hx-push-url` attribute.
   *
   * This attribute instructs HTMX to control URLs in the browser history.
   *
   * Use a boolean when this attribute is added along with ::get
   * - true: pushes the fetched URL into history.
   * - false: disables pushing the fetched URL if it would otherwise be pushed
   *   due to inheritance or hx-boost.
   *
   * Use a URL to cause a push into the location bar. This may be relative or
   * absolute, as per history.pushState()
   *
   * @param bool|\Drupal\Core\Url $value
   *   Use a Url object or a boolean, depending on the use case.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-push-url/
   */
  public function pushUrl(bool|Url $value) : static {
    $url = $value === FALSE ? 'false' : 'true';
    if ($value instanceof Url) {
      $url = $this->urlValue($value);
    }
    $this->createStringAttribute('hx-push-url', $url);
    return $this;
  }
  
  /**
   * Creates a `data-hx-select` attribute.
   *
   * This attribute instructs HTMX which content to swap in from a response.
   * HTMX uses the given selector to select elements from the response.
   * For example, passing 'data-drupal-selector="edit-theme-settings"' will
   * instruct HTMX to select the element with this data attribute and value.
   *
   * @param string $selector
   *   A CSS selector string.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-select/
   */
  public function select(string $selector) : static {
    $this->createStringAttribute('hx-select', $selector);
    return $this;
  }
  
  /**
   * Creates a `data-hx-select-oob` attribute.
   *
   * This attribute instructs HTMX to select content for an out-of-band swap
   * from a response. Each value can specify any valid hx-swap strategy by
   * separating the selector and the swap strategy with a colon,
   * such as #alert:afterbegin.
   *
   * @param string|string[] $selectors
   *   A value or array of values.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-select-oob/
   */
  public function selectOob(string|array $selectors) : static {
    if (is_array($selectors)) {
      $selectors = implode(',', $selectors);
    }
    $this->createStringAttribute('hx-select-oob', $selectors);
    return $this;
  }
  
  /**
   * Creates a `data-hx-swap` attribute.
   *
   * This attribute allows you to specify how the response will be
   * swapped into the DOM relative to the target of an AJAX request.
   *
   * @param string $strategy
   *   The swap strategy.
   * @param string $modifiers
   *   Optional modifiers for changing the behavior of the swap.
   * @param bool $ignoreTitle
   *   Instruct HTMX not to swap in the page title from the request.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-swap/
   */
  public function swap(string $strategy, string $modifiers = '', bool $ignoreTitle = TRUE) : static {
    if ($modifiers !== '') {
      $strategy .= ' ' . $modifiers;
    }
    // HTMX defaults this behavior to FALSE, that is it replaces the page title.
    // We believe our most common use case is to not change the title.
    if ($ignoreTitle) {
      $strategy .= ' ignoreTitle:true';
    }
    $this->createStringAttribute('hx-swap', $strategy);
    return $this;
  }
  
  /**
   * Creates a `data-hx-swap-oob` attribute.
   *
   * This attribute is used in the markup of the returned response. It
   * specifies that some content in a response should be swapped into the DOM
   * somewhere other than the target, that is “Out of Band”. This allows you to
   * piggyback updates to other elements on a response.
   *
   * @param true|string $value
   *   Either true, a swap strategy, or strategy:CSS-selector.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-swap-oob/
   */
  public function swapOob(true|string $value) : static {
    if ($value === TRUE) {
      $value = 'true';
    }
    $this->createStringAttribute('hx-swap-oob', $value);
    return $this;
  }
  
  /**
   * Creates a `data-hx-target` attribute.
   *
   * This attribute allows you to target a different element for
   * swapping than the one issuing the AJAX request. There are a variety
   * of target string syntaxes.  See the URL below for details.
   *
   * @param string $target
   *   The target descriptor.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-target/
   */
  public function target(string $target) : static {
    $this->createStringAttribute('hx-target', $target);
    return $this;
  }
  
  /**
   * Creates a `data-hx-trigger` attribute.
   *
   * This attribute instructs HTMX when to trigger a request.
   *
   * Used with an HTMX request attribute. Allows:
   * - An event name (e.g. “click” or “myCustomEvent”) followed by an event
   *   filter and a set of event modifiers
   * - A polling definition of the form every <timing declaration>
   * - A comma-separated list of such events.
   *
   * @param string|string[] $triggerDefinition
   *   The trigger definition.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-trigger/
   */
  public function trigger(string|array $triggerDefinition) : static {
    if (is_array($triggerDefinition)) {
      $triggerDefinition = implode(',', $triggerDefinition);
    }
    $this->createStringAttribute('hx-trigger', $triggerDefinition);
    return $this;
  }
  
  /**
   * Creates a `data-hx-vals` attribute.
   *
   * This attribute instructs HTMX to add values to the parameters that will be
   * submitted with an HTMX request.
   *
   * The value of this attribute is a list of name-expression values
   * which will be converted to JSON (JavaScript Object Notation) format.
   *
   * @param array<string, string> $values
   *   The values in an array of 'name' => 'value' pairs.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-vals/
   */
  public function vals(array $values) : static {
    $this->createJsonAttribute('hx-vals', $values);
    return $this;
  }
  
  /**
   * Creates a `data-hx-boost` attribute.
   *
   * This attribute instructs HTMX to add progressive enhancement
   * to links or forms. The attribute allows you to “boost” normal anchors and
   * form tags to use AJAX instead. This has the nice fallback that, if the
   * user does not have javascript enabled, the site will continue to work.
   *
   * @param bool $value
   *   Should the element and its descendants be "boosted"?
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-boost/
   */
  public function boost(bool $value) : static {
    $this->createStringAttribute('hx-boost', $value ? 'true' : 'false');
    return $this;
  }
  
  /**
   * Creates a `data-hx-confirm` attribute.
   *
   * This attribute instructs HTMX to shows a confirm() dialog before issuing
   * a request.
   *
   * @param string $message
   *   The user facing message.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-confirm/
   */
  public function confirm(string $message) : static {
    $this->createStringAttribute('hx-confirm', $message);
    return $this;
  }
  
  /**
   * Creates a `data-hx-disable` attribute.
   *
   * This attribute instructs HTMX to disable HTMX processing for the given
   * node and any descendants.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-disable/
   */
  public function disable() : static {
    $this->createBooleanAttribute('hx-disable', TRUE);
    return $this;
  }
  
  /**
   * Creates a `data-hx-disabled-elt` attribute.
   *
   * This attribute instructs HTMX to add the disabled attribute to the
   * specified elements during a request.
   *
   * The descriptor syntax is the same as hx-target. See the documentation
   * link below for more details.
   *
   * @param string $descriptor
   *   The attribute value.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-disabled-elt/
   */
  public function disabledElt(string $descriptor) : static {
    $this->createStringAttribute('hx-disabled-elt', $descriptor);
    return $this;
  }
  
  /**
   * Creates a `data-hx-disinherit` attribute.
   *
   * This attribute instructs HTMX to control and disable automatic HTMX
   * attribute inheritance for child nodes.
   *
   * @param string $names
   *   The attribute names to disinherit or * for all.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-disinherit/
   */
  public function disinherit(string $names) : static {
    $this->createStringAttribute('hx-disinherit', $names);
    return $this;
  }
  
  /**
   * Creates a `data-hx-encoding` attribute.
   *
   * This attribute instructs HTMX to change the request encoding type.
   *
   * @param string $method
   *   The encoding method.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-encoding/
   */
  public function encoding(string $method = 'multipart/form-data') : static {
    $this->createStringAttribute('hx-encoding', $method);
    return $this;
  }
  
  /**
   * Creates a `data-hx-ext` attribute.
   *
   * This attribute instructs HTMX to enable HTMX extensions for an element
   * and descendants.
   *
   * @param string $names
   *   An extension name, or a comma separated list of names.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-ext/
   */
  public function ext(string $names) : static {
    $this->createStringAttribute('hx-ext', $names);
    return $this;
  }
  
  /**
   * Creates a `data-hx-headers` attribute.
   *
   * This attribute instructs HTMX to add to the headers that will be submitted
   * with an HTMX request.
   *
   * @param array<string, string> $headerValues
   *   The header values as name => value.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-headers/
   */
  public function headers(array $headerValues) : static {
    $this->createJsonAttribute('hx-headers', $headerValues);
    return $this;
  }
  
  /**
   * Creates a `data-hx-history` attribute.
   *
   * The attribute value is set to false. This attribute prevents sensitive
   * 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
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-history/
   */
  public function history() : static {
    $this->createStringAttribute('hx-history', 'false');
    return $this;
  }
  
  /**
   * Creates a `data-hx-history-elt` attribute.
   *
   * This attribute instructs HTMX which element to snapshot and restore
   * during history navigation.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-history-elt/
   */
  public function historyElt() : static {
    $this->createBooleanAttribute('hx-history-elt', TRUE);
    return $this;
  }
  
  /**
   * Creates a `data-hx-include` attribute.
   *
   * This attribute instructs HTMX to include additional element values
   * in HTMX requests.
   *
   * The descriptor syntax is the same as hx-target. See the documentation
   * link below for more details.
   *
   * @param string $descriptors
   *   The element descriptors.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-include/
   */
  public function include(string $descriptors) : static {
    $this->createStringAttribute('hx-include', $descriptors);
    return $this;
  }
  
  /**
   * Creates a `data-hx-indicator` attribute.
   *
   * This attribute instructs HTMX which element should receive the
   * htmx-request class on during the request.
   *
   * @param string $selector
   *   The element CSS selector value. Selector may be prefixed with `closest`.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-indicator/
   */
  public function indicator(string $selector) : static {
    $this->createStringAttribute('hx-indicator', $selector);
    return $this;
  }
  
  /**
   * Creates a `data-hx-inherit` attribute.
   *
   * This attribute instructs HTMX how to control automatic attribute
   * inheritance for child nodes.
   *
   * HTMX evaluates attribute inheritance with hx-inherit in two ways when
   * hx-inherit is set on a parent node:
   *  - data-hx-inherit="*"
   *    All attribute inheritance for this element will be enabled.
   * - data-hx-hx-inherit="hx-select hx-get hx-target"
   *   Enable inheritance for only one or multiple specified attributes.
   *
   * @param string $attributes
   *   The attributes to inherit.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-inherit/
   */
  public function inherit(string $attributes) : static {
    $this->createStringAttribute('hx-inherit', $attributes);
    return $this;
  }
  
  /**
   * Creates a `data-hx-params` attribute.
   *
   * This attribute instructs HTMX to filter the parameters that will
   * be submitted with a request.
   *
   * Verify the current filter syntax at the link below.
   * - To include all parameters, use the string '*'.
   * - To pass no parameters use 'none'.
   * - To exclude some parameters use an array of parameters names, prefixing
   *   the first name with `not`, as in ['not param1', 'param2', 'param3'].
   * - To only submit some parameters, use an array of parameter names as in
   *   ['param1', 'param2', 'param3'].
   * parameters use an array, pre
   *
   * @param string|string[] $filter
   *   The filter string or strings.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-params/
   */
  public function params(string|array $filter) : static {
    if (is_array($filter)) {
      $filter = implode(',', $filter);
    }
    $this->createStringAttribute('hx-params', $filter);
    return $this;
  }
  
  /**
   * Creates a `data-hx-preserve` attribute.
   *
   * This attribute instructs HTMX that matching elements should be kept
   * unchanged between requests. Depends on an unchanging id property on the
   * element.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-preserve/
   */
  public function preserve() : static {
    $this->createBooleanAttribute('hx-preserve', TRUE);
    return $this;
  }
  
  /**
   * Creates a `data-hx-prompt` attribute.
   *
   * This attribute instructs HTMX to show a prompt() before
   * submitting a request.
   *
   * @param string $message
   *   The message to display in the prompt.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-prompt/
   */
  public function prompt(string $message) : static {
    $this->createStringAttribute('hx-prompt', $message);
    return $this;
  }
  
  /**
   * Creates a `data-hx-replace-url` attribute.
   *
   * This attribute instructs HTMX to control URLs in the browser location bar.
   *
   * Use a boolean when this attribute is added along with a request:
   * - true: replaces the fetched URL in the browser navigation bar.
   * - false: disables replacing the fetched URL if it would otherwise be
   *   replaced due to inheritance.
   *
   * Use a URL to replace the value in the location bar. This may be relative or
   * absolute, as per history.replaceState().
   *
   * @param bool|\Drupal\Core\Url $value
   *   A Url object, or a boolean, depending on the use case. See details above.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-replace-url/
   */
  public function replaceUrl(bool|Url $value) : static {
    $url = $value ? 'true' : 'false';
    if ($value instanceof Url) {
      $url = $this->urlValue($value);
    }
    $this->createStringAttribute('hx-replace-url', $url);
    return $this;
  }
  
  /**
   * Creates a `data-hx-request` attribute.
   *
   *  This attribute instructs HTMX to configure various aspects of the request.
   *
   * The hx-request attribute supports the following configuration values:
   * - timeout: (integer) the timeout for the request, in milliseconds.
   * - credentials: (boolean) if the request will send credentials.
   * - noHeaders: (boolean) strips all headers from the request.
   *
   * Dynamic javascript values are not supported for security and for
   * simplicity.  If you need calculated values you should do determine them
   * here on the server-side
   *
   * @param array<string, int|bool> $configValues
   *   The configuration values as name => value.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-request/
   */
  public function request(array $configValues) : static {
    $this->createJsonAttribute('hx-request', $configValues);
    return $this;
  }
  
  /**
   * Creates a `data-hx-sync` attribute.
   *
   * This attribute instructs HTMX to synchronize AJAX requests
   * between multiple elements.
   *
   * @param string $selector
   *   A CSS selector followed by a strategy.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-sync/
   */
  public function sync(string $selector) : static {
    $this->createStringAttribute('hx-sync', $selector);
    return $this;
  }
  
  /**
   * Creates a `data-hx-validate` attribute.
   *
   * This attribute instructs HTMX to cause an element to validate itself
   * before it submits a request.
   *
   * @param bool $value
   *   Should the element validate before the request.
   *
   * @return static
   *   Returns this object to allow chaining methods.
   *
   * @see https://htmx.org/attributes/hx-validate/
   */
  public function validate(bool $value = TRUE) : static {
    $this->createStringAttribute('hx-validate', $value ? 'true' : 'false');
    return $this;
  }
  
  /**
   * Exports data from internal storage to a render array.
   *
   * @param mixed[] $element
   *   The render array for the element.
   * @param string $attributeKey
   *   Optional target key for attribute output: defaults to '#attributes'.
   */
  public function applyTo(array &$element, string $attributeKey = '#attributes') : void {
    // Attach HTMX and Drupal integration javascript.
    if (!in_array('core/drupal.htmx', $element['#attached']['library'] ?? [])) {
      $element['#attached']['library'][] = 'core/drupal.htmx';
    }
    // Consolidate headers.
    if ($this->headers
      ->count() !== 0) {
      $element['#attached']['http_header'] = $element['#attached']['http_header'] ?? [];
      $element['#attached']['http_header'] = NestedArray::mergeDeep($element['#attached']['http_header'], $this->applyHeaders());
    }
    if (count($this->attributes
      ->storage()) !== 0) {
      // Consolidate attributes.
      $element[$attributeKey] = $element[$attributeKey] ?? [];
      $element[$attributeKey] = AttributeHelper::mergeCollections($element[$attributeKey], $this->attributes);
    }
    $this->cacheableMetadata
      ->applyTo($element);
  }
  
  /**
   * Creates an Htmx object with values taken from a render array.
   *
   * @param array $element
   *   A render array.
   * @param string $attributeKey
   *   Optional target key for attribute output: defaults to '#attributes'.
   *
   * @return static
   *   A new instance of this class.
   */
  public static function createFromRenderArray(array $element, string $attributeKey = '#attributes') : static {
    $incomingAttributes = $element[$attributeKey] ?? [];
    $incomingHeaders = $element['#attached']['http_header'] ?? [];
    // Filter for HTMX values.
    $incomingAttributes = array_filter($incomingAttributes, function (string $key) {
      return str_starts_with($key, 'data-hx-');
    }, ARRAY_FILTER_USE_KEY);
    $preparedHeaders = [];
    foreach ($incomingHeaders as $value) {
      if (is_array($value) && str_starts_with($value[0], 'hx-')) {
        // Header value array may have 3 values, we want the first two.
        $preparedHeaders[$value[0]] = $value[1];
      }
    }
    $attributes = new Attribute($incomingAttributes);
    $headers = new HeaderBag($preparedHeaders);
    $cacheableMetadata = CacheableMetadata::createFromRenderArray($element);
    return new static($attributes, $headers, $cacheableMetadata);
  }

}

Members

Title Sort descending Modifiers Object type Summary
Htmx::applyHeaders protected function Apply the header values to the render array.
Htmx::applyTo public function Exports data from internal storage to a render array.
Htmx::ATTRIBUTE_PREFIX protected constant All HTMX attributes begin with this string.
Htmx::boost public function Creates a `data-hx-boost` attribute.
Htmx::buildRequestAttribute protected function Utility function for the request attributes.
Htmx::confirm public function Creates a `data-hx-confirm` attribute.
Htmx::createBooleanAttribute protected function Utility method to create and store a boolean value as an attribute.
Htmx::createFromRenderArray public static function Creates an Htmx object with values taken from a render array.
Htmx::createJsonAttribute protected function Utility method to create and store an array as an attribute.
Htmx::createStringAttribute protected function Utility method to create and store a string value as an attribute.
Htmx::delete public function Creates a `data-hx-delete` attribute.
Htmx::disable public function Creates a `data-hx-disable` attribute.
Htmx::disabledElt public function Creates a `data-hx-disabled-elt` attribute.
Htmx::disinherit public function Creates a `data-hx-disinherit` attribute.
Htmx::encoding public function Creates a `data-hx-encoding` attribute.
Htmx::ensureKebabCase protected function Utility method to transform camelCase strings to kebab-case strings.
Htmx::ext public function Creates a `data-hx-ext` attribute.
Htmx::get public function Creates a `data-hx-get` attribute.
Htmx::getAttributes public function Get the attribute storage.
Htmx::getHeaders public function Get the header storage.
Htmx::hasAttribute public function Checks if an attribute is set.
Htmx::hasHeader public function Checks if a header is set.
Htmx::headers public function Creates a `data-hx-headers` attribute.
Htmx::history public function Creates a `data-hx-history` attribute.
Htmx::historyElt public function Creates a `data-hx-history-elt` attribute.
Htmx::include public function Creates a `data-hx-include` attribute.
Htmx::indicator public function Creates a `data-hx-indicator` attribute.
Htmx::inherit public function Creates a `data-hx-inherit` attribute.
Htmx::locationHeader public function Set HX-Location header.
Htmx::on public function Creates a `data-hx-on` attribute.
Htmx::params public function Creates a `data-hx-params` attribute.
Htmx::patch public function Creates a `data-hx-patch` attribute.
Htmx::post public function Creates a `data-hx-post` attribute.
Htmx::preserve public function Creates a `data-hx-preserve` attribute.
Htmx::prompt public function Creates a `data-hx-prompt` attribute.
Htmx::pushUrl public function Creates a `data-hx-push-url` attribute.
Htmx::pushUrlHeader public function Set HX-Push-Url header.
Htmx::put public function Creates a `data-hx-put` attribute.
Htmx::redirectHeader public function Set HX-Redirect header.
Htmx::refreshHeader public function Set HX-Refresh header.
Htmx::removeAttribute public function Removes an attribute from the attribute store.
Htmx::removeHeader public function Removes a header from the header store.
Htmx::replaceUrl public function Creates a `data-hx-replace-url` attribute.
Htmx::replaceUrlHeader public function Set HX-Replace-Url header.
Htmx::request public function Creates a `data-hx-request` attribute.
Htmx::reselectHeader public function Set HX-Reselect header.
Htmx::reswapHeader public function Set HX-Reswap header.
Htmx::retargetHeader public function Set HX-Retarget header.
Htmx::select public function Creates a `data-hx-select` attribute.
Htmx::selectOob public function Creates a `data-hx-select-oob` attribute.
Htmx::swap public function Creates a `data-hx-swap` attribute.
Htmx::swapOob public function Creates a `data-hx-swap-oob` attribute.
Htmx::sync public function Creates a `data-hx-sync` attribute.
Htmx::target public function Creates a `data-hx-target` attribute.
Htmx::trigger public function Creates a `data-hx-trigger` attribute.
Htmx::triggerAfterSettleHeader public function Set HX-Trigger-After-Settle header.
Htmx::triggerAfterSwapHeader public function Set HX-Trigger-After-Swap header.
Htmx::triggerHeader public function Set HX-Trigger header.
Htmx::urlValue protected function Helper method to get the url string and store cache metadata.
Htmx::validate public function Creates a `data-hx-validate` attribute.
Htmx::vals public function Creates a `data-hx-vals` attribute.
Htmx::__construct public function Initialize empty storage.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.