bigskysoftware / htmx

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

Allow async in hx-trigger conditional calls. #912

Open gnat opened 2 years ago

gnat commented 2 years ago

Use case

Hotwire recently added a similar feature. Would make available some intuitive low-code patterns in htmx.

If hx-trigger="submit[action()]" supported async calls, we could "pause" the event, do something, then "resume" the event. Real life uses:

Suggestion to achieve this: htmx can simply detect if hx-trigger is a promise or vanilla function, and handle it accordingly.

Benefits

It would totally dunk on our friends at Hotwire / Turbo to allow this. :sweat_smile: And go very well in the Migration Guide

The Hotwire way: https://turbo.hotwired.dev/handbook/drive#pausing-requests

Would simplify and cut down the amount of event listener code drastically in a very htmx-way.

Issues

Currently, you can pass an async function in, but maybeGenerateConditional() doesn't understand it: https://github.com/bigskysoftware/htmx/blob/master/src/htmx.js#L1001

@1cg said:

i've had a few comments asking about async support, but that would involve promises which are not IE compatible

gnat commented 2 years ago

As a side note, the Hotwire implementation of event pausing / interception: https://github.com/hotwired/turbo/pull/306

gnat commented 1 year ago

Someone else stumbling upon on this:

image

1cg commented 1 year ago

Is the (relatively recent) htmx:confrm event a satisfying solution to this: https://htmx.org/events/#htmx:confirm

Looking at the code, making all the places where filter expressions are evaluated handle async well would be a little ugly (e.g. polling and load)

eduardvercaemer commented 1 year ago

I was working on something that also required async event handlers (i.e. i was using htmx:configRequest to modify the request but it was async).

I came up with my own hack in htmx to support this logic:

function triggerEvent(elt, eventName, detail, continuation /* this is new */) {
      elt = resolveTarget(elt);
      if (detail == null) {
        detail = {};
      }
      detail["elt"] = elt;
      if (continuation) { /* this is new */
        var continuationPromises = [];
        detail["continuation"] = function (eventCb) {
          var resolveContinuation = null;
          continuationPromises.push(
            new Promise(function (resolve) {
              resolveContinuation = resolve;
            })
          );
          eventCb(resolveContinuation);
        };
      }
      var event = makeEvent(eventName, detail);
      if (htmx.logger && !ignoreEventForLogging(eventName)) {
        htmx.logger(elt, eventName, detail);
      }
      if (detail.error) {
        logError(detail.error);
        triggerEvent(elt, "htmx:error", { errorInfo: detail });
      }
      var eventResult = elt.dispatchEvent(event);
      var kebabName = kebabEventName(eventName);
      if (eventResult && kebabName !== eventName) {
        var kebabedEvent = makeEvent(kebabName, event.detail);
        eventResult = eventResult && elt.dispatchEvent(kebabedEvent);
      }
      withExtensions(elt, function (extension) {
        eventResult =
          eventResult && extension.onEvent(eventName, event) !== false;
      });
      if (continuation) { /* this is new */
        return Promise.all(continuationPromises).then(function () {
          return continuation(eventResult);
        });
      }
      return eventResult;
    }

    // ...

      return triggerEvent(
        elt,
        "htmx:configRequest",
        requestConfig,
        afterConfiguration
      );

      function afterConfiguration(eventResult) {
        if (!eventResult) {
          maybeCall(resolve);
          endRequestLock();
          return promise;
        }
        // ...

and you set up a handler like this


htmx.on("htmx:configRequest", (event) => {
  event.detail.continuation((resolve) => {
    // do something async
    resolve();
  });
});

now triggerEvent will allow listeners to register a promise and wont call the continuation untill all listeners are resolved.

yawaramin commented 9 months ago

Looks like this example shows how to accomplish all these use cases? https://htmx.org/examples/async-auth/