bigskysoftware / htmx

</> htmx - high power tools for HTML
https://htmx.org
Other
35.22k stars 1.17k forks source link

An observation on Alpine JS templates and HTMX #655

Closed peterswords closed 2 years ago

peterswords commented 2 years ago

Just a further observation rather than an issue -- this is probably too Alpine-specific to go in HTMX documentation. In #652 I was trying to figure out how to activate HTMX functionality embedded in a template element loaded by Alpine JS. This was the user interface I was trying to achieve:

The search results are rendered from templates using both x-if and x-for after the search completes:

<template x-if="output.isLoaded">
  <table ... headers etc.><tbody>
     <template x-for="result in output.data">
        <tr> ... rows </tr>
      </template>
    </tbody></table>
</template>

The View links on each row were going to use HTMX hx-get to load a details page. However, since the table isn't loaded with the initial search page, it is necessary to use Alpine's $watch(output.isLoaded ...) on an outer div and call htmx.process() on the newly loaded table content as per #652. I then ran into further problems trying to send the client id as a query parameter along with the hx-get in the View link. (It wasn't clear how hx-params could pick up the id from a table row, though I'm sure this is solvable).

It occurred to me that the HTMX javascript API is always available, even to content that hasn't been processed with htmx.process(). So it was far easier to use Alpine JS to call HTMX, no $watch or htmx.process() required, and no need to figure out parameters:

<template x-if="output.isLoaded">
  <table ... headers etc.><tbody>
     <template x-for="result in output.data">
        <tr>
          <td x-text="result.id"></th>
          <td x-text="result.firstname"></td>
          <td x-text="result.surname"></td>
          <td x-text="result.address"></td>
          <td><a @click="htmx.ajax('GET','/admin/client?id='+result.id,'#shell')" href="#">View</a></td>
        </tr>
      </template>
    </tbody></table>
</template>

There's definitely a place for $watch and htmx.process() when you need HTMX functionality in post-loaded content. But where the API suffices, this is definitely simpler.

1cg commented 2 years ago

is there an event triggered by alpine when new content is added to the DOM?

hooking into that would be the cleanest integration, IMO

peterswords commented 2 years ago

I'm pretty new to all this. As I understand it, Alpine only fires a load event at the entire page level (https://github.com/alpinejs/alpine/issues/228). Alpine uses a DOM mutation observer for its own purposes (i.e. to initialise its own content) but I'm not aware of an event that it fires. Interestingly, someone mentions an issue (https://github.com/alpinejs/alpine/issues/412) with Alpine functionality not working in content swapped in by HTMX, i.e. the opposite of the HTMX-inside-Alpine that I'm discussing here, but I'm already doing Alpine-inside-HTMX and find that it's working perfectly.

I guess one could use one's own mutation observer to look for content that needs htmx.process'ing but it sounds like too much of a scattergun blast for my liking. Right now I have everything I want working well. I'm prototyping patterns for an enterprise application using HTMX + Alpine at the front, and Python, FastAPI, Pydantic, SQLAlchemy, Postgresql at the back end. (I came across HTMX in your 'Talk Python To Me' interview). Really enjoying turning my back on SPA frameworks.

I'm also trying to make the entire user interface "static" -- HTMX mainly pulls down pre-built templates which are populated by further AJAX calls done by Alpine. I can build the templates on the fly with Jinja2 at the backend during development, but in production they'll be entirely cached by a Service Worker. Hoping to get some SPA-like advantages but with progressively downloaded UI fragments, and more browser-friendliness (multiple tabs, "real" browser history etc.).

1cg commented 2 years ago

I am trying to coordinate with the creator of Alpine to see if we can offer better integration. Going to close this for now.

hudon commented 1 year ago

old post but I think this is the way:

<template x-for="foo in bar">
                        <a
                                :hx-get="foo"
                                x-init="$nextTick(() => htmx.process($el))"

x-init runs when Alpine initializes the element, $nextTick allows you to run script after the initialization, and htmx.process hooks it up to htmx. You can make a new directive call it x-after-init that implicitly does the $nextTick if you like. And note that this is using :hx-get which is shorthand for x-bind:hx-get, which gives you a dynamically set hx-get attribute

ssured commented 1 year ago

@hudon Thanks, your solution works well

Ladet02 commented 2 months ago

@hudon this works! thank you!