Problem/Motivation

Use HTMX to provide ajax pagination and exposed filters.

Steps to reproduce

Create an ajax view, use htmx for the implementation.

Proposed resolution

Make views work with htmx.

There are many out of scope changes because I tried a few things. There are things I'm doing in the wrong place but I want it to work and let people who know the subsystem tell me how to do it better.

A new feature is that we can get the URL for the HTMX request dynamically. if I have a link with a known href, instead of duplicating the URL, I can do

<a href="/some/url" data-hx-get="attr(href)">link</a>
// this will submit to 
"/some/url"

and that will tell htmx to submit to the url in href, works for action attribute or any other attribute value on the element where data-hx-get is.

We can also add some query parameters (especially to set the _wrapper_format), by adding the query string after a space

<a href="/some/url" data-hx-get="attr(href) ?_wrapper_format=drupal_htmx">link</a>
// In the request this URL would be used.
"/some/url?_wrapper_format=drupal_htmx"

// works with additional params:
<a href="/some/url?filter=data" data-hx-get="attr(href) ?_wrapper_format=drupal_htmx">link</a>
// resulting URL is
"/some/url?filter=data&_wrapper_format=drupal_htmx"

About the syntax I started with [href] but took inspiration from CSS attr(), that can't really be used from JS but the syntax should be familiar to some.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3548100

Command icon 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:

Comments

nod_ created an issue. See original summary.

nod_ changed the visibility of the branch 3548100-views-htmx to hidden.

nod_’s picture

Issue summary: View changes
Status: Active » Needs work

This MR builds on #3546105: Automatically manage _wrapper_format for HTMX requests and clean up ajax_page_state in urls, #3545179: Refactor ConfigSingleExportForm to use HTMX, #3538544: Ajaxify the user interface translation forms, which is why the diff is important.

To test, toggle the "use ajax" setting on the view you want (I only tested the /admin/content table and exposed filter at this point).

nod_’s picture

Issue summary: View changes
nod_’s picture

Issue summary: View changes
fathershawn’s picture

If it helps, I also did some views work in the contrib module. There is a mini-pager: https://git.drupalcode.org/project/htmx/-/blob/1.5.x/src/Plugin/views/pager/HtmxMini.php and https://git.drupalcode.org/project/htmx/-/blob/1.5.x/htmx.module?ref_type=heads#L63

There's a display which makes use of the route option to always return a simple response: https://git.drupalcode.org/project/htmx/-/blob/1.5.x/src/Plugin/views/display/Htmx.php

And the exposed form is configured to update the view via htmx: https://git.drupalcode.org/project/htmx/-/blob/1.5.x/htmx.module?ref_type=heads#L17

fathershawn’s picture

data-hx-get="attr(href)" is creative @nod_! Is it recreating the functionality of hx-boost?

nod_’s picture

Seems like it! the boost attribute does more than I need though,

quietone’s picture

Issue tags: +Needs title update

Let's not have a question in the title, which will be the commit message.

nod_’s picture

Title: Use HTMX for (some?) ajax views » [PoC] Use HTMX for (some?) ajax views
mortona2k’s picture

I think this ticket has a list of ajaxy things we want to do with views. Maybe not what we're looking for in a POC, but things to cover in the long run? https://www.drupal.org/project/drupal/issues/2022297

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

macsim’s picture

Please have a look at https://www.drupal.org/project/views_htmx
It handles views exposed filters and pager.

fathershawn’s picture

@macsim How does your views_htmx project differ from the pager and display in ?

macsim’s picture

I'm not sure what you mean by "the pager and display in" — there is currently nothing in core on this topic yet.

If you are referring to the current MR, I haven't dug deep enough into the code here to give a proper answer. From a quick look, one thing I noticed is that views_htmx doesn't require a single line of JavaScript — even for pager history management — though I've only tested it with a page display so far.
The second diff I see is that I didn't override "Use Ajax" behavior but added a separate "Use HTMX" option instead (which could potentially conflict with "Use Ajax" if both are enabled).

When this MR eventually lands in core, my module will most likely become obsolete. In the meantime, it might serve as a useful reference to help move this MR forward.

Could you share an update on what still needs to be done to get this merged?

fathershawn’s picture

I'm referring to the htmx contrib module that was the initial source of htmx in Drupal before we began moving functionality into core. It has some views tooling similar to this: https://project.pages.drupalcode.org/htmx/tools/views/. If you want to work on views integration and also maintain it for Drupal <= 12.0 I'm happy to move that functionality to https://www.drupal.org/project/views_htmx.

We are currently working to make a major version update for Drupal 12 to htmx v.4 which has API changes Views work may wait a few weeks.

macsim’s picture

Oops, I hadn't dug into the original module — I assumed everything it contained had been migrated into core. Thanks for the clarification!

I'd be happy to play with Views HTMX for Drupal >= 11.2 and < 12!

I've aligned my module to use the "Use Ajax" option to avoid the potential conflicts I mentioned earlier, and to make future version upgrades easier. In the meantime I also added support for table sorting.

I'll provide a release in few days when I'll be able to opt into security advisory coverage

mortona2k’s picture

It seems like one of the hangups here is how the new htmx paradigm is applied while preserving backwards compatibility with ajax.

This MR is doing a replacement, Views HTMX is a drop in when the module is enabled, and HTMX has a view display that the view would need to be built on.

I think this can be solved with a 3 way switch for enabling new htmx, legacy ajax, or none.

The MR approach could become the default, with htmx enabled for filters, pagers, and sorts, and disabling views ajax.

For preexisting views that need ajax, it could switch to legacy ajax.

None is an option to disable both, which is the prior default. However, I think it's better to make HTMX the new default because it provides a better UX in most cases. I think the only time you really want to disable HTMX/AJAX is when it breaks something.

mortona2k’s picture

I made some toys.

Here's the admin content view, with some extra htmx buttons in the header (Add node, Random page, Second page).

Drupal views htmx buttons

We have pagers, exposed filters, and table sorts, which are handled by the MR.

Second page
is the simplest, should be the same as the pager, page 2 link.

<a href="/admin/content?page=1" class="button views-htmx-button--second-page" data-hx-boost="true" data-hx-swap="none ignoreTitle:true" data-hx-drupal-only-main-content="">Second page</a>

Random page uses ajax commands to pick a random page and load via htmx.

Browser shows XHR request to:
/admin/content?_wrapper_format=drupal_htmx&ajax_page_state%5Btheme%5D=mantra_starter&ajax_page_state%5Btheme_token%5D=null&ajax_page_state%5Blibraries%5D=eJyVUtFuxCAM-yFaPgmlJeWyAUEk7bq_H-s6XXt30roXlNhGGMsDBVeooB32wYycFVedIVpf5wKxvyNdpPwuTVLRjpwKZ8wqfYKsFZwoVMXadUOF7CmHP4UFAl4SuYm5Lde0NwR_QVtRGiW0oEuY5__qnXIIcbe_JwWjNnpL6YR7gsihhzdYz3jlMsyqnE-wwhDx-IsjfrfxzAlGHPUFzlXNxDUpFbG_gzn_0B7cPDC4Fhb03USxrU8XQ-QB4iPaXhaTYaEASpxdq1BjlTmKpdYpI5-imCz4RNkshB9it7NP7OeW7La4m6bV_diy373i5LbavGBxauHcvgDu5Bza&page=60

But the url in the browser updates to /admin/content?page=60, great!

Add node mimics work on Views Add Button, and this issue:
Open in modal and reload view with ajax

It's meant to replace the [+ Add content] button above it, which is currently implemented as a local action, and not tied to the view.

This is were I'd love to see some work on a new htmx dialog method, as that would be useful in updating the views admin UI as well. For now this uses regular ajax form dialogs, and then triggers an htmx view refresh after the node is saved.

__

What has been eliminated is the views ajax library and refresh view command, replaced with the new htmx wrapper. I still have to do some janky things to make this all work with the current MR. But I think they could get included with it so that downstream code can remain lean.

Here's my sample code: https://github.com/mortona42/views-htmx-button
This was generated with claude, but prompted by my thinking.
Next step is to get a better handle on the extra customizations in the module that could be done in core.

fathershawn’s picture

I'm so grateful for your experimentation @mortona2k! As to modals, we have this issue for figuring out an htmx solution: Implement OpenDialogCommand in htmx Primary tabs I would be please to come up with a plan there!

mortona2k’s picture

When the view is refreshed, where should the window scroll?

Currently the MR triggers top of page. I don't think that's right.

If you were scrolling down and see a little view block and click a button, you wouldn't want your window to jump down to the view.

But if you click on a pager link, you would expect to go to the top of the list. Except if it's already in view.

We could have something to check if the top of the view (or results?) is already in the viewport, and scroll to top of it otherwise.

Here's what claude generated for that: https://github.com/mortona42/views-htmx-button/blob/main/js/views-htmx-b....

It works and feels like a good default behavior for views refresh. Not sure if it can be slimmed down or should be implemented differently.

mortona2k’s picture

Is this a reasonable way to scroll to the top of the view?

https://git.drupalcode.org/project/drupal/-/merge_requests/16268/diffs?c...

I'm not sure about the scroll modifier, or if there's a simpler way to get the id.

With this, it always scrolls to the top of the view. If we like the idea of only scrolling up when the top is not visible, I think more client side js is needed to handle the logic.

mortona2k’s picture

I created a view field plugin for the Action Link module to test how contrib will trigger an HTMX refresh of the view.

https://www.drupal.org/project/action_link/issues/3563129#comment-16703397

The HTMX action link adds a redirect back to the view, with _wrapper_format=drupal_htmx.

One issue is that the action links were rendered as placeholders, and that wasn't working in the htmx response. So there is an event subscriber needed to fix them. This should be addressed in this ticket so it's not an issue for contrib.

mortona2k’s picture

Views AJAX scrolling behavior is being worked on in Make Views AJAX scroll to top optional.