htmx-extensions icon indicating copy to clipboard operation
htmx-extensions copied to clipboard

Multiple extensions that modify the parameters cannot be used together

Open namzug16 opened this issue 10 months ago • 4 comments

I've implemented my own extension that merges the hx-vals from other elements to the current one

The problem is that when using it together with other extensions (in this case json-enc-custom) that modify too the parameters, it seems that only one of the extensions works

This is how I've implemented my extension

    encodeParameters: function(xhr, parameters, elt) {
      xhr.overrideMimeType("text/json");

      let mergedVals = api.mergeObjects({}, parameters);

      if (elt.hasAttribute("hx-merge-vals")) {
        let targetSelector = elt.getAttribute("hx-merge-vals");
        let targetElt = api.querySelectorExt(elt, targetSelector);
        if (targetElt) {
          let vals = api.getExpressionVars(targetElt);
          mergedVals = api.mergeObjects(mergedVals, vals);

          let children = targetElt.querySelectorAll("[hx-vals]");
          children.forEach(child => {
            let childVals = api.getExpressionVars(child);
            mergedVals = api.mergeObjects(mergedVals, childVals);
          });
        }
      }

      return JSON.stringify(mergedVals);
    }

Then I thought of some sort of workaround and I added this to the extension

      api.withExtensions(elt, function(extension) {
        if (extension != this) {
          mergedVals = extension.encodeParameters(xhr, mergedVals, elt)
        }
      })

The problem is that withExtensions calls getExtensions and it gets all the extension from the element (included my extension), which causes an infinite loop and it is not possible right now to ignore extensions from withExtensions

Is there another way to do this? (Pipe down the result of encodeParameters to multiple extensions) or am I doing something wrong?

PS:

The result of the encoded parameters depends on the order in which the extensions are called e.g. when using hx-ext="json-enc-custom,merge-vals" only json-enc-custom works

namzug16 avatar Feb 16 '25 19:02 namzug16

Hey, what you described is correct ; htmx will only care about the first extension that can process parameters See https://github.com/bigskysoftware/htmx/blob/90a91a60e0fcd2d7c42e04c615177cba89c33151/src/htmx.js#L3735-L3741

With the current implementation, encodedParameters wouldn't be null anymore after json-enc-custom (or any other) processes the params, ignoring the other available extensions

Seems it's been working like that since the beginning, not sure if that was intended though Yet, I'm afraid that changing this right now could be a breaking change... Will have to discuss with @1cg about that

You could work around it using a api.withExtensions call just like you did, but your this check is likely invalid here, as this in your example refers to the function (extension) callback itself, not your extension object as a whole.

~~I think you could define your custom extension as a const object (const myExtension = { init: function(apiRef) .... }), pass it to htmx.defineExtension("whatever", myExtension), then check if extension !== myExtension in your snippet above~~ Edit: nope, htmx actually merges your extension into the extension base so your extension object isn't the one used in the end and an equality check wouldn't work. Though, you could define any arbitrary custom property on it (whatever: true) which would let you compare that in your withExtensions callback (if (!extension.whatever))

Hope this helps!

Telroshan avatar Feb 17 '25 13:02 Telroshan

Hey @Telroshan!

I've been able to implement a workaround as you suggested, and right now it seems to work fine. (I simply added a name to the object and then checked for it)

In order to fix these kinds of issues (filter the extensions within an extension) and to avoid introducing breaking changes, something that we could do is allow withExtensions to accept extensionsToIgnore and then pass it to getExtensions instead of modifying the encoding params logic directly

Even though I'm not very sure this adds any value to htmx, since I'm the only one experiencing the "issue", do you think it is something worth pursuing? or should I close the issue right here?

Thank you for the help!

namzug16 avatar Feb 17 '25 16:02 namzug16

I would personally be in favor of making the internal API more flexible to let people achieve what they want with it.

~~From what I see, this can likely be done with a 2-lines change to htmx ; withExtensions calls the internal function getExtensions, that accepts a parameter extensionsToIgnore (array of strings) but that withExtensions doesn't define~~

~~So, unless I'm missing something (as I didn't test this at all), I suggest that we add a third, optional parameter to withExtensions, identical to the one of getExtensions, and pass it to the latter This would allow you to call withExtensions and ignore any extension of your choice, with a very minimalistic change to the core library~~

Bruh that's literally what you said, seems I have reading issues this morning! If you're interested, please feel free to PR this 😁

Telroshan avatar Feb 18 '25 08:02 Telroshan

I've just opened the PR!

namzug16 avatar Feb 18 '25 17:02 namzug16