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
hrefattribute on anchor tags and theaction
attribute on form tags. Thehx-getattribute tells htmx:
“When the user clicks this button, issue aGETrequest to
/contacts/1.” Thehx-targetattribute tells
htmx: “When the response returns, take the resulting HTML and place it
into the element with the idcontact-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. The other control surface for HTMX is a set of response headers. HTMX supports 11 custom response headers. Expecting Drupal developers to learn all the names and data requirements of those attributes and headers will make the Drupal learning curve even more challenging.
Proposed resolution
Create an \Drupal\Core\Htmx\Htmx class that provides methods to abstract the details of these attributes and headers. These methods would both document and collect the necessary data. Another way of understanding this class is to regard the attributes of HTMX and their expected data as an interface.
Inspired by \Drupal\Core\Cache\CacheableMetadata include an ::applyTo method that applies the necessary changes to a render array. Also include a ::createFromRenderArray method and ::removeAttribute and removeHeader methods which can be used to construct and modify the controls previously exported to a render array.
Remaining tasks
Implement and improve the proposed class.
User interface changes
Introduced terminology
API changes
Adds Htmx class.
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|
Issue fork drupal-3524030
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 3524030-htmx-facade
changes, plain diff MR !13050
- 3524030-facade-simplified
compare
Comments
Comment #2
fathershawnComment #3
fathershawnComment #4
fathershawnI had a conversation in Slack with @cilefen about how much we should present an abstract interface to the rest of the system and keep the HTMX implementation interface encapsulated. This alternate architecture would argue that we should name the facade interface
Ajaxas that is the concept we are implementing. We would not provide direct access to theHtmxAttributeandHtmxHeadersubsystems. The means of altering these subsystems would be to use an operation.Such an architecture only exposes HTMX concepts to developers that need to create a combination of attributes and headers that is not available as an operation. In that case, the developer would create a new operation to encapsulate that combination. Of course such a developer could forsake the facade altogether and deal with the attribute and header subsystem directly.
This idea could be take further in #3524394: [PP-3] Define and process an #htmx render array key and rather than define a new render array key, only add a new callback. The existing ajax callback would make an early return if the
#ajaxkey did not point to an array and similarly the new callback if it did not point to an instance of the facade.What is the right architecture?
Comment #5
catchiirc the original reason to have separate #ajax and #htmx render array keys was so that it would be easy to track converting from one to the other and eventually deprecating #ajax.
If we did something like this:
Then presumably we could also issue deprecations for the old syntax that way. So as long as there's a way to clearly identify when which system we're using, then it comes down to what we want the eventual API to be like. I don't currently have a strong opinion between adding new things to #ajax before removing old things from #ajax, vs. adding #htmx in parallel and then eventually removing #ajax.
If there are ways to consolidate/reduce the list of AJAX commands that's great, the main thing is we need clear instructions for how to convert specific things from one to the other, even if it's no longer a 1-1 conversion.
Comment #6
fathershawnThank you @catch. I wanted to layout the concerns expressed to me, and I'm comfortable with our original course. I think it is cleaner to create and transition to a parallel system. The
HtmxInterfacethat I propose in the issue summary offers two paths for a developer.A developer does not need to know anything about how HTMX works. Choosing from a defined set of operations, such as Replace or Insert, the developer adds the required data to the operation and inserts it into the Htmx object.
Alternatively, a developer can dive completely into the world of HTMX using the attribute and header subsystem objects that are part of the Htmx object. I guess there is also a third path which is to create their own implementation of
HtmxOperationInterfaceto package these into something reusable.This offers both simplicity and power in something that I think is straightforward to manage. Creating all the operations will be our final task in the initiative. I'm confident that we can offer a migration guide that maps ajax commands to htmx operations.
Comment #7
fathershawnComment #8
fathershawnComment #9
fathershawnComment #10
fathershawnComment #11
fathershawnComment #12
fathershawnDiscussed and agreed in Slack that a single change record and release note will cover:
Comment #13
fathershawnBased on #12 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.
Comment #14
fathershawnComment #15
fathershawnComment #16
fathershawnComment #17
fathershawnComment #18
fathershawnComment #19
fathershawnComment #20
fathershawnComment #21
fathershawnComment #23
fathershawnAll tests passing. Most of this MR is code moved from the the two prior RTBC issues. Comments on the MR indicate the overlap.
Comment #28
nod_major since it's an important api addition (for DX)
porting credits from the other issues too
Comment #29
fathershawnComment #30
fathershawnUpdated the CR to match the revised approach.
Comment #31
fathershawn@nod_ Thanks for the ideas represented in your branch. I read through the changes and have some notes.
Attributeis written to accept values ofAttributeValueBaseI think explicitly creating attribute values of the correct type in an attribute factory clearly communicates what kind of attribute we are creating to the reader. That's valuable for immediate comprehension. If we need to go with your approach to get this committed, I'll support the pivot however I prefer the original code for these reasons.Your change to the hx-name syntax from the data-hx-syntax reverses the decision in #3456070: [policy, no patch] Choose a markup strategy for HTMX POC. I don't have a strong opinion either way but it surprised me. Should we re-open and document the change?Comment #32
fathershawnI was surprised at the performance similarity as the original approach will have less branching since we know the type to use. I then realized that the issue is in this code block in
AttributeRecreating the object takes time. I don't find any instances of renaming created AttributeValue objects in core and the whole topic is out of scope for this issue, but moving the type check up like so:
improved performance of the original code by 40%.
Comment #33
fathershawnComment #35
fathershawnComment #36
fathershawnDiscussed, resolved questions and merged in remaining from #31.
All tests passing.
Comment #37
nicxvan commentedI updated the IS since it's not really an interface in drupal it's explicitly not an interface.
I confirmed everything lines up again.
I reviewed the bits that were called out as different and they seem sensible.
I think this is ready except the one test that is final for some reason, I don't think that is worth holding this up over and expect it will be applied without controversy.
Comment #38
nod_Just a sidenote about the attribute class
A little problem with this is that
$namecould be different than what's$valuehas been created with. Not sure why it'd happen but it's very easy to run into.In any case I'm happy with the MR so RTBC +1
Comment #39
catchI didn't do a full review yet but have a couple of minor questions on the MR.
Comment #40
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 #41
fathershawnResponded to feedback from @catch. I left his MR threads open so that it is easy for someone else to re-check with a goal of returning to RTBC?
Comment #42
nod_Checked that the feedback has been addressed
Comment #43
fathershawnComment #44
larowlanCouple of comments on the MR, great work!
Comment #45
fathershawnUpdated based on latest review
Comment #46
nicxvan commentedOk I went through all of the changes based on @larowlan's feedback.
All but one comment was addressed.
I kind of agree with both @larowlan and @fathershawn on whether to use an enum to validate headers / attributes.
I think at this point the priority should be to get this in so we can start truly testing the facade before 11.3 by implementing some changes.
The enum validation looks like something that can be pretty easily added in later and having some extra flexibility up front might be valuable.
I left the thread open for discussion but I'm going to mark this as ready in the meantime.
Comment #48
larowlanCommitted to 11.x
Left the change record as a draft until #3535173: Support dynamic forms using HTMX is in