Closed (fixed)
Project:
Drupal core
Version:
11.x-dev
Component:
ajax system
Priority:
Normal
Category:
Task
Assigned:
Unassigned
Issue tags:
Reporter:
Created:
11 May 2024 at 11:33 UTC
Updated:
3 Jul 2025 at 16:45 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #2
fathershawnComment #3
fathershawnComment #4
fathershawnThree initial thoughts:
#ajaxrender array key with an#htmxkey? If we did that, then our BC strategy is writing a converter that rewrites the render array to equivalent valuesComment #5
catchContent replacement with css/js would be a good proof of concept I think especially if that's already partially implement in the contrib module. If the new markup comes with CSS and JavaScript that's a fairly small but also complex use-case to cover.
Comment #6
nod_Agreed with #5 about scope. For the poc a new #htmx key sounds fine but we'll need to make existing code work as-is as much as possible down the line
Comment #7
fgmI would think it saner (safer ?) to introduce such a
#htmxkey instead of overloading the meaning of#ajax.Then new code could opt in to the new mechanism, and the two mechanisms could coexist with the older one being deprecated and removed over time without having to maintain a compatibility layer. That seems more sustainable and easier to implement over time across multiple core releases.
We would probably just need to error when both keys are present on an element.
Comment #8
quietone commentedFix formatting in IS
Comment #9
cosmicdreams commentedFrom reading through this issue it sounds to me that the Next steps here are:
Next Steps
Do I have that right?
Comment #10
fathershawnI think those steps are close to what I'm thinking about. I would adjust it like so:
#htmxrender array property.#ajaxrender array key-value pairs into#htmxrender array key-value pairs. This is our BC layer.For use cases I think we may have agreed on
Content replacement using
#wrapperI would suggest one of the commands that are similar, such as append, for the second.
Comment #11
fgmMakes sense, especially worrying about the BC layer only if the first steps succeed. Because it is a "nice to have", IMHO, not a "must".
Comment #12
jwilson3fixed broken link in IS due to errant newlines
Comment #13
fathershawnI've been simmering ideas on my "back burner" and want to get some feedback before making something.
HTMX expresses itself in HTML attributes. What if we extended
\Drupal\Core\Template\Attributeto create anHtmxAttributethat has class methods for the various attributes used by HTMX? That would allow us to support something like this:Each class method would call
\Drupal\Core\Template\Attribute::createAttributeValueand create the correct attribute. Our process function callback for the render process then just needs to merge theHtmxAttributeobject with any existing Attribute object and set the result as the element's Attribute.Comment #14
jwilson3I love the idea of interoperability with Attributes, your approach sounds like a reasonable architecture. But this makes me curious about the Twig template side, we have the
create_attribute()function to build out attributes from scratch, or to completely override the {{ attributes }} variable passed in from the backend when Drupal provides too much extra stuff for a given template's needs (contextual links, data attributes, etc). It's not clear to me yet how this could be useful in Twig so maybe something to just note for future as needs arise, but thinking along the lines of helpers likeattributes.addClass(),.removeClass(), etc.Would the Attributes integration be syntactic sugar for the PHP side only, or exposed to manipulation via attributes variable in Twig as well?
If there's no straightforward approach here, it is perfectly fine for now to decide that when working in Twig expect to write HTML attributes manually — just like the examples from the HTMX site would have one do. I do worry though, due to Drupal's extensibility that someone somewhere will want to hook-in from a template override to modify a default swap behavior or the default ID of the element being swapped or whathaveyou.
Comment #15
fathershawnGood questions! I'm working backwards from where we need to end up. In the end, we need markup like this
So the yes, the
Attributesobject which arrives in Twig asattributesseems like the natural way to get that. And yes, if we mergeHtmxAttributeor if we send it directly, it has all the methods of Attribute either through inheritance or because we've merged into one.I did a quick search into how we process and render the
#idproperty and I didn't land on how that happens, but it arrives in Twig in the attribute variable so we end up in the same place right?Comment #16
fathershawnI've had an inquiry about the appropriate markup to use and have moved that to a related issue: #3456070: [policy, no patch] Choose a markup strategy for HTMX POC Please add your guidance there!
Comment #17
fgmIf we go the
Attributeroute, I suggest we use composition instead of inheritance for future protection. With PHP not supporting multiple inheritance, should theAttributeclass get child classes, we would be in a diamond problem situation if we inherit that class. Also, in recent years, Drupal core appears to have started to embrace the trend offinalclasses/methods/fields. ComposingAttributeinstead of inheriting from it offers us two advantages:I'd even suggest creating an
AttributeInterfacecurrently made of thepublicmethods (no public fields) of theAttributeclass, and composing that instead of the class itself, for even further protection.Comment #18
fathershawnThank you @fgm for reminding me that we are working on core! I was surprised there wasn’t an interface but I’m so used to working in contrib, I thought inheritance was what we had. We totally need an interface here.
Comment #19
fathershawnI decided to implement the inheritance version of this strategy in the contrib module so that people can easily try it out. The MR is pending with the extended class and a documentation page with some usage examples for that implementation.
This issue's implementation would use composition, and have a dedicated key, taking care of merging the attributes in processing. That said, feedback welcome over in contrib to refine any of this before we stand it up here in Core.
Comment #20
fathershawnIt looks to me like the Attribute magic happens in
template_preprocessfound incore/includes/theme.inc. If I add a#htmxkey in a render array, and then addright after the existing #attributes merge, then a breakpoint at the assignment here trips.
Comment #21
fathershawnAs I look more deeply at implementing the idea from @fgm in #17, it seems to me that the interface should be much smaller than the public methods, otherwise we're just moving everything in
Attributeto various traits or something as there is only one protected method and it has the logic for creating values before storage. Many of the other existing public methods implement other Interfaces forAttribute.I looked through all the places we currently test for
instanceof Attributeand I propose these methods on the interface:::hasAttribute::toArray::mergeBecause in templates we want the full suite of
Attributemethods, I'd leave theseinstanceofconditionals as they are inAttributeHelperComment #22
fgmMakes sense. Smallest interfaces are the most useful ones, since you can always extend interfaces but not reduce them.
Comment #23
fathershawnHere's an initial draft of the attribute changes: https://git.drupalcode.org/issue/drupal-3446642/-/compare/11.x...3446642-poc-implementing-htmx
Comment #24
fathershawnWell, #20 is not the right solution for where
to merge in '#htmx' because of the nature of
template_preprocess_HOOKsuch as this inform.inc. Probably why existing ajax is doing it's work at the element pre-process level.Comment #25
fathershawnSome additional adjustment to
FormBuilderis needed, and I could use guidance as to where. The POC is onadmin/config/development/configuration/single/exportHere's the problem sequence:
At this point the form class is called to build the form, but this but doesn't run:
FormBuilder.php:1294
so the name is not reset.
Then the code above is executed but the form is not rebuilt.
I feel like we therefore need to set a rebuild if our request is htmx but I'm not sure the correct place to do that in form builder.
Here is a diff with 11.x of the current state of the work.
Comment #26
longwaveA blog post noting some rough edges in HTMX: https://chrisdone.com/posts/htmx-critique/ - none of these may be that problematic for us but something to bear in mind.
Comment #27
fathershawnCritical thinking is a valuable means of understanding something new. I recognize that the author of the critique works regularly with HTMX and I'll watch for some of the edge cases called out, but I wonder if there are some a priori assumptions from working in single-page-applications when I read
Comment #28
andyf commentedJust for additional context, I think the author of htmx responded at https://news.ycombinator.com/item?id=41782080.
Comment #29
ambient.impactNot to dunk on the author of that too much, but I'm in agreement with @fathershawn, and would also highlight:
...which baffles me because it sounds like this person doesn't understand the core philosophy behind htmx and similar approaches. Sending a JSON blob for React to convert into a virtual DOM has nothing to do with htmx, and it's in fact entirely antithetical to htmx as I understand it.
Comment #30
fathershawnI have a working POC on the Single Config Export form: https://git.drupalcode.org/issue/drupal-3446642/-/compare/11.x...3446642...
Feedback invited!
@nicxvan asked in Slack for steps to review functionally. Also posting them here:
admin/config/development/configuration/single/exportFor convenience:
The dynamic features in the form are using htmx
Comment #31
nicxvan commentedThis looks really interesting!
I'm looking at this from the perspective of someone unfamiliar with htmx, this will not be a technical review.
I was mostly interested in seeing how it looked on the front end.
It seems like it's more attributes on html. I don't know if it's clearer or not, but what I'm reading is that it's well documented and adopted outside of Drupal.
It worked for a bit even though I see console errors for Uncaught ReferenceError: oobSwapEvent is not defined.
Eventually the htmx events stopped firing, but this is just a POC so that is likely not super helpful feedback.
Another thing I noticed, is it is verbose. I see an hx-target attribute in the docs, but it's data-hx-target.
It's not a huge thing, but an already verbose element is even more verbose: is data required to be on all of them.
Comment #32
fathershawnThanks for the extra eyes @nicxvan! I missed a variable change moving some JS over from the HTMX module. Updated the branch.
The markup format is discussed and decided in #3456070: [policy, no patch] Choose a markup strategy for HTMX POC
Comment #33
fathershawnAdded support for HTMX Response Headers which allowed me to implement a missing feature in
ConfigSingleExportForm. The route and the form are built to take parameters for configuration type and name. However the implementation via core ajax does not update the url.This htmx implementation now does so that the result can be captured via a link.
Comment #34
fathershawnOur second part of the POC is to replicate a more complex command. I first thought of an
Htmxobject composed of the attributes and headers as a organization tool, but I realize it has more power than that. More complex behaviors will use both toolsets. In particular, we can achieve any behavior not available natively in HTMX using one of the trigger headers to invoke our own javascript.Proposed architecture
HtmxInterfaceto make it easy for contrib to create additional behaviorsHtmxInterfaceobject that returns anHtmxInterfaceobject.Htmxclass that implements each of the current ajax behaviors by creating a method that configures the appropriate attributes and/or headers.Comment #35
fathershawnImplemented architecture
HtmxInterfaceobject that returns anHtmxInterfaceobject.HtmxOperationInterfaceobjects that implements each of the current ajax behaviors by configuring the appropriate attributes and/or headers.HtmxOperationInterfaceobjects into theHtmxInterfaceobject and process them at render so that they can be altered.The example implementation in
ConfigSingleExportFormis updated with this approachTwo
HtmxOperationInterfaceimplemented:Passing tests:
Comment #36
fathershawnComment #37
fathershawnComment #38
ambient.impactFinally had a chance to try this out and it seems to work well. Awesome work! Is it just
ConfigSingleExportFormso far, or are there other forms or interactions that have been switched over?Comment #39
fathershawnComment #41
catchComment #42
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. 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.
Comment #44
jurgenhaasI've rebased the MR as there have been many conflicts mainly due to the usage of
static::classinstead of$classin info arrays.That's also resolving the tasks from the review bot in #42.
Comment #45
fathershawnRebased on this week's changes. Looking at test failures next
Comment #46
needs-review-queue-bot commentedThe 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.
Comment #47
fathershawnLinting complete.
Most tests fixed.
Only 1 failing test:
ActiveLinkResponseFilterTest. Suggestions welcome from anyone else who has worked in this area. I'm going to fire up xdebug to step through and puzzle out what is neededComment #48
fathershawnI feel like this issue did it's job. We are now taking the learning from this work into new child issues on #3404409: [Plan] Gradually replace Drupal's AJAX system with HTMX
Comment #49
fathershawnResetting status and moving to "Fixed" at @jurgenhaas suggestion so someone can comment and assign issue credits. Thought we needed to commit something to do that :)
Comment #50
catchI had a go at assigning credit, new to doing that on closed-not-fixed issues, moving back to 'closed (outdated)' because the credit system can handle this now.
Comment #54
fathershawn@jurgenhaas pointed out that we should call this "fixed" since it accomplished it's purpose!
Comment #55
fathershawn