bigskysoftware / htmx

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

Proposal: hx-template to clone node from html template (not client-side-templates) #960

Open BoPeng opened 2 years ago

BoPeng commented 2 years ago

I am trying to implement inline editing for some elements following this click-to-edit example. My form is quite simple so instead of retrieving the form from the server, I am wondering if there can be some way to get the form from the frontend. Conceptually, what I need is

onclick-> get HTML code from somewhere -> render the code -> replace hx-target with the new element 

I can implement this in JS or hyperscript, but because onclick, render the code, replace are all existing mechanisms provided by hx-trigger, hx-get, and hx-swap, it would be nice to be able to create the node from the frontend.

I have already been using HTML template so something like the following would work perfectly for me:

<template id='my_form'>
  <form><input><button></button></form>
</template>

<span hx-template="#my_form" hx-swap='outerHTML' hx-trigger='click'>my name</span>

Here hx-template will simply get the element from the template (or some other elements), optionally replace values of matching elements from hx-include or form elements.

Note that I do know the existence of the client-side-template extension. However, that extension allows post-processing JSON data retrieved from hx-template and my scenario does not retrieve any data from the server at all. If many of the interactive parts can be pre-loaded as templates, this technique can potentially reduce a lot of unnecessary server requests.

jollytoad commented 1 year ago

I've just started using htmx, and found myself wanting to do something very similar, loading content from an inline template rather than a request. After looking through the source it looks like the 'swapping' functionality is deeply embedding within the request code. So I started experimenting with using a temporary URL by creating a Blob and then using URL.createObjectURL along these lines...

      htmx.findAll("[hx-template], [data-hx-template]").forEach(el => {
        const selector = el.getAttribute('hx-template') || el.getAttribute('data-hx-template');
        const template = htmx.find(selector);
        const blob = new Blob([template.innerHTML], { type: "text/html" });
        const url = URL.createObjectURL(blob);
        el.setAttribute('hx-get', url);
        htmx.process(el);
      });

This looks for any element with hx-template attribute as in the example above, creating a Blob & URL of the content of the template element, and adding a hx-get back onto the original element.

I've only tested this with a hx-trigger="load" though, but I don't see why it should work with any trigger in theory.

I hope this helps anyone else who comes across this thread.

Maybe this could even be implemented as a htmx extension? I've not reached that far into my htmx journey though... I'll post a followup if I manage to do that.

kgscialdone commented 10 months ago

I've published an extension that provides similar functionality in a way that can be handled "seamlessly" through HTMX's extension API - while extensions can't add new verbs without similar "silently convert to a different attribute"-based methods, it's relatively simple for an extension to implement a custom "URL protocol" that it detects and preempts the request.

That said, I do think a proper verb for this as originally proposed would be an excellent idea long-term. An extension like mine is a stopgap rather than an ideal solution, and the ability to bypass making a network request for simple changes is a vital one for many situations.

planeth44 commented 10 months ago

Have you considered using a service worker to intercept the request and serve the response from the client ?

jollytoad commented 10 months ago

I've done a few experiments using ServiceWorkers and htmx, it's not the most pleasant or practical of experiences, considering browser support is still inconsistent, ie. some still don't support ES modules for ServiceWorkers!

It's also pretty overkill for the situations where hx-template referencing a local template is ideal.

From this experience I would only consider ServiceWorkers when an optimization of an htmx request is absolutely necessary, and it can't just be done using a static template and the extension or suggestions above.

But as is always the case, your mileage may vary.

(If anyone is curious, I've got a very simple demo of a ServiceWorker with htmx here: https://jollytoad.deno.dev/calc)

jollytoad commented 10 months ago

I think this #255 is what we need to have an hx-template extension without jumping through hoops.

BoPeng commented 10 months ago

Have you considered using a service worker to intercept the request and serve the response from the client ?

That is essentially the "mock" server that the htmx documentation uses, right? We currently use customized JS code to handle templates in the frontend, I am not quite sure how much a service worker can help.

kgscialdone commented 10 months ago

I think this #255 is what we need to have an hx-template extension without jumping through hoops.

While this would be very helpful from a convenience perspective, it's not especially difficult to extract getSwapSpecification, makeSettleInfo, and selectAndSwap from the internal API passed to an extension's init function, and from there call selectAndSwap directly. In fact, that's exactly what some earlier prototypes of my extension did.

The real impediment to implementing hx-template via extension at the moment is that HTMX doesn't process elements which don't have a "verb attribute" (hx-get et al.), and it's not possible for extensions to modify that list. Trying to work around this limitation results in either

  1. Translating the hx-template attribute into a verb attribute with some sort of dummy URL, which can then be intercepted in the same way my extension intercepts template:templateName URLs.
  2. Bypassing HTMX's processing system entirely, which either breaks or requires manually reimplementing a significant portion of its bubbling and filtering logic.

It's also worth mentioning that it's quite tricky to get HTMX's support for CSS transitions to work properly when utilizing selectAndSwap directly. My extension gets around that by making sure that only the absolute minimum code - the actual call to xhr.send that launches the request, and a couple lines around it - is bypassed and needs to be reimplemented. But for a less hacky solution, that wouldn't be an option.

HTMX's process is currently very tightly coupled to the making and sending of an XMLHttpRequest. If it were to be decoupled, to where the request itself is handled via dependency injection, it would be significantly more reasonable to implement these types of features.

charring commented 8 months ago

I've been coming up to speed on HTMX this month as I contemplate a big web site refactor. As part of that learning, I made an echo web service so that I can really experiment with the client-side semantics without doing custom server-side handlers. Using my echo endpoint and client side template, I created this click to edit demo.

https://codepen.io/charring/pen/dyaqPgx