Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple extensions that modify the parameters cannot be used together #145

Open
namzug16 opened this issue Feb 16, 2025 · 4 comments
Open

Comments

@namzug16
Copy link

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

@Telroshan
Copy link
Collaborator

Telroshan commented Feb 17, 2025

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!

@namzug16
Copy link
Author

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!

@Telroshan
Copy link
Collaborator

Telroshan commented Feb 18, 2025

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 😁

@namzug16
Copy link
Author

I've just opened the PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants