DX object to manage htmx attributes

Created on 10 May 2025, 3 months ago

Problem/Motivation

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>

Instead, we have declarative attributes much like the
href attribute on anchor tags and the action
attribute on form tags. The hx-get attribute tells htmx:
β€œWhen the user clicks this button, issue a GET request to
/contacts/1.” The hx-target attribute tells
htmx: β€œWhen the response returns, take the resulting HTML and place it
into the element with the id contact-ui.”

from Hypermedia Systems

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.

Proposed resolution

Rather than expecting Drupal developers to learn all the names and data requirements of all those attributes, create a class with methods to abstract the details of these attributes. These methods would both document and collect the necessary data.

Our current HTML Attribute object does not implement an interface. Create a shared interface that can identify Attribute and HtmxAttribute objects and move shared methods to a trait or traits.

Alter \Drupal\Core\Template\AttributeHelper::mergeCollections and Attribute::merge() to operate on the interface.

Support the HTMX architecture that some attribute values are either JSON or a concatenation of a string to JSON. These attributes should use the XSS::filter() method rather than Html::escape to support the double quotes needed for JSON. They also need to implement the single quoted syntax for HTML attributes.

  • Create AttributeJson
  • Create AttributeHtmxString to encapsulate the filter change.

Remaining tasks

User interface changes

None

Introduced terminology

None

API changes

HtmlAttributeInterface abstracts features of \Drupal\Core\Template\Attribute.

Data model changes

Two new attribute value objects

Release notes snippet

πŸ“Œ Task
Status

Active

Version

11.0 πŸ”₯

Component

render system

Created by

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

Live updates comments and jobs are added and updated live.
  • Needs release note

    The major change should have a special release note written to summarize the importance of the change. See Write a release note for an issue.

  • Needs change record

    A change record needs to be drafted before an issue is committed. Note: Change records used to be called change notifications.

Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @fathershawn
  • Merge request !12097Resolve #3523725 "Htmx attributes" β†’ (Open) created by fathershawn
  • Pipeline finished with Failed
    3 months ago
    Total: 194s
    #493893
  • Pipeline finished with Failed
    3 months ago
    Total: 162s
    #493909
  • Pipeline finished with Failed
    3 months ago
    Total: 612s
    #493914
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    All tests green

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

    Missed bringing over the Twig function

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Success
    3 months ago
    Total: 654s
    #494795
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    New test passes.

  • Pipeline finished with Failed
    3 months ago
    Total: 217s
    #495311
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • The Needs Review Queue Bot β†’ tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Failed
    3 months ago
    Total: 583s
    #495811
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Success
    3 months ago
    Total: 1475s
    #495861
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Failed
    3 months ago
    Total: 160s
    #499822
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Applied a suggestion and will refactoring some improvements inspired by @larowan review. A maintainer should add him to the credits.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Failed
    3 months ago
    Total: 193s
    #500129
  • Pipeline finished with Failed
    3 months ago
    Total: 247s
    #500139
  • Pipeline finished with Success
    3 months ago
    Total: 671s
    #500143
  • πŸ‡«πŸ‡·France nod_ Lille

    I don't understand why we need the single quote. If you add a JSON::encode(), to the process assets MR it just works.

        $build['replace'] = [
          '#type' => 'html_tag',
          '#tag' => 'button',
          '#attributes' => [
            'type' => 'button',
            'name' => 'replace',
            'data-hx-get' => $url->toString(),
            'data-hx-select' => 'div.ajax-content',
            'data-hx-target' => '[data-drupal-htmx-target]',
    // add this part
            'data-hx-vals' => Json::encode(['test' => 'ok']),
          ],
          '#value' => 'Click this',
          '#attached' => [
            'library' => [
              'core/drupal.htmx',
            ],
          ],
        ];
    

    It doesn't matter that the source html doesn't look pretty. Once it's in the JS it's the correct value.

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

    That's a great question! I didn't think it would but I didn't try. I'll repeat the experiment with an attribute value selector and remove the complexity. Simpler is better!!

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

    Confirmed! I can now remove a trait and a class - thank you @nod_ :)

    I altered the render arrays in the test module to

        $build['replace'] = [
          '#type' => 'html_tag',
          '#tag' => 'button',
          '#attributes' => [
            'type' => 'button',
            'name' => 'replace',
            'data-hx-get' => $url->toString(),
            'data-hx-select' => 'div.ajax-content',
            'data-hx-target' => 'div[data-drupal-htmx-target="insert-here"]',
          ],
          '#value' => 'Click this',
          '#attached' => [
            'library' => [
              'core/drupal.htmx',
            ],
          ],
        ];
    
        $build['content'] = [
          '#type' => 'container',
          '#attributes' => [
            'data-drupal-htmx-target' => 'insert-here',
            'class' => ['htmx-test-container'],
          ],
    

    Which produces data-hx-target="div[data-drupal-htmx-target=&quot;insert-here&quot;]" through the standard string attribute. HTMX worked as expected.

  • Pipeline finished with Failed
    3 months ago
    Total: 160s
    #500288
  • Pipeline finished with Success
    3 months ago
    Total: 587s
    #500289
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    All threads resolved - all tests passing :)

  • Pipeline finished with Success
    3 months ago
    Total: 509s
    #500892
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    We agreed in Slack to pause on this builder while we work on πŸ“Œ Refactor BigPipe to use HTMX Active using attributes that are "hand-rolled."

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

    Coming back to this now - we needed the htmx javascript API in πŸ“Œ Refactor BigPipe to use HTMX Active but not attributes.

  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Success
    about 1 month ago
    Total: 565s
    #540307
  • Issue was unassigned.
  • Status changed to Needs review about 1 month ago
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Tagged \Drupal\Core\Template\HtmlAttributeInterface as @internal as that seems consistent with the policy on Attribute class internals β†’ .

  • Pipeline finished with Success
    27 days ago
    Total: 682s
    #551934
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Discussed and agreed in Slack that a single change record and release note will cover:

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

    Based on #26 and the description of the tag (A change record needs to be drafted before an issue is committed.) I'm removing the CR tag from this issue as these are sequential dependencies and the tag belongs on final component.

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

    Reading through the governance doc I am marking this for framework manager review since it is blocking other work.

  • Pipeline finished with Failed
    22 days ago
    Total: 285s
    #556040
  • Pipeline finished with Success
    22 days ago
    Total: 850s
    #556042
  • Pipeline finished with Success
    21 days ago
    Total: 531s
    #556778
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    I brought the summary up to date with the code. We have 5 unresolved comment threads on the MR. Most open threads have a response.

    Following a comment from @catch I updated the code in \Drupal\Core\Template\HtmxAttribute for:

    • ::get
    • ::post
    • ::put
    • ::patch
    • ::delete
    • ::pushUrl
    • ::replaceUrl

    to pass in and instance of CacheableDependencyInterface and collect the cacheable metadata emitted by the Url object.

    There is an open question about the advisability of adding return types in \Drupal\Core\Template\Attribute. I understand this class to be internal by policy but maybe that's not right?

    The other 3 open threads all have answers.

    I have time to move this project forward to let's get this to RTBC so it can be committed and work can start on the next layer!!

  • Pipeline finished with Failed
    20 days ago
    Total: 157s
    #557623
  • Pipeline finished with Success
    20 days ago
    Total: 2821s
    #557628
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    @catch can you give guidance about adding return types to \Drupal\Core\Template\Attribute?

    I hit a name collision on the BC policy with the Attribute name (As you and @nicxvan noted that was for PHP attributes). But it also seems to be permitted with these:

    PHP and JavaScript classes β†’
    In general, only interfaces can be relied on as the public API. With the exception of base classes, developers should generally assume that implementations of classes will change, and that they should not inherit from classes in the system, or be prepared to update code for changes if they do. See also more specific notes below.

    Public methods not specified in an interface β†’
    Public methods on a class that aren't on a corresponding interface are considered @internal.

    We are adding an interface in this issue, but there no interface for this class now. @larowan seemed to think it was okay here: https://git.drupalcode.org/project/drupal/-/merge_requests/12097#note_51...

  • Pipeline finished with Failed
    16 days ago
    Total: 211s
    #561088
  • The Needs Review Queue Bot β†’ tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

    This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

    Consult the Drupal Contributor Guide β†’ to find step-by-step guides for working with issues.

  • Pipeline finished with Failed
    15 days ago
    #561572
  • Pipeline finished with Failed
    15 days ago
    #561574
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York
  • Pipeline finished with Success
    15 days ago
    #561586
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Thank you @phenaproxima for improving the code with your questions and suggestions. I've resolved all comments with either updates or answers except those related to return types (see #30). All tests are passing.

  • πŸ‡¬πŸ‡§United Kingdom catch

    Given @larowlan's:

    I did a search of contrib for classes that extend this where these new return types might be a BC break and could only find on in display suite, that was only adding a new method - and one in htmx which also didn't override any of the methods we're adding too here So I think this change is fine to do in a minor release, with a change record

    and https://www.drupal.org/about/core/policies/core-change-policies/bc-polic... β†’ I think we're OK to add the return type hints to Attribute here with a change record. It's allowed under the bc policy, and we can be fairly confident it won't break any existing code.

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

    Ok that was tedious, but I clicked on all documentation links and confirmed they align except the ones that I have suggestions for.

    I also reviewed all changes and have been following. I think this is ready assuming the three suggestions are accepted and tests pass.

    Only remaining substantial question I have is where does sanitization happen?
    I see some attributes do check the expected value is a boolean, but I don't see anything else, is that all handled upstream of this?

  • Pipeline finished with Canceled
    14 days ago
    Total: 127s
    #562298
  • Pipeline finished with Canceled
    14 days ago
    Total: 70s
    #562301
  • Pipeline finished with Failed
    14 days ago
    Total: 163s
    #562302
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    @nicxvan First, thank you for so carefully checking the tedious mundane details! Suggestions committed and test updated for the one method name fix.

    Sanitization happens in the attribute value object's ::__toString method. They all extend AttributeValueBase which sanitizes the name property. We add AttributeJson in this MR, and follow this pattern.

  • Pipeline finished with Failed
    14 days ago
    Total: 3042s
    #562309
  • Pipeline finished with Failed
    14 days ago
    Total: 140s
    #562372
  • Pipeline finished with Failed
    14 days ago
    Total: 2677s
    #562375
  • Pipeline finished with Success
    13 days ago
    Total: 1298s
    #563092
  • πŸ‡ΊπŸ‡ΈUnited States nicxvan

    I think this is ready, the extensive feedback has been addressed!

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

    @catch @larowlan @nod_ A CacheableMetada parameter was added to methods that take a URL based on feedback. This complicates calling these methods within Twig as the caching has already bubbled.

    Does this change the utility of a providing the attribute object within Twig? Should we drop that function from this MR?

  • πŸ‡«πŸ‡·France nod_ Lille

    I'd drop it. We can always add a followup to add it and we can figure out the problems without holding the working part of the MR

  • Pipeline finished with Success
    11 days ago
    #564724
  • πŸ‡ΊπŸ‡ΈUnited States fathershawn New York

    Thanks @nod_ !!

    Removed the twig function from this issue's MR and the change record. It is extra.

    CR is updated with the full picture of how all 4 issue changes work together.

Production build 0.71.5 2024