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

htmx.process not fully applying HTMX behaviors to elements in shadowDom #2454

Open
lachdoug opened this issue Apr 2, 2024 · 7 comments
Open
Labels

Comments

@lachdoug
Copy link

lachdoug commented Apr 2, 2024

I am testing HTMX with web components and have found two issues, which seem to be related to how htmx.process() works.

Issue 1:
The hx-on::before-request attribute is ignored when using htmx.process() as outlined in the docs.
Note that this could be related to #2406

Issue 2:
The hx-get attribute is ignored when htmx.process() is called on a web component that is a child of another web component.

The example code below shows five buttons:

  1. Not a component - Created using plain HTML. hx-get and hx-on::before-request both work.
  2. MyComponent - A web component created by following the docs (https://v2-0v2-0.htmx.org/examples/web-components/). hx-get works and hx-on::before-request does not work.
  3. MyComponentAlt - A web component similar to MyComponent, but with htmx.process(buttonEl) instead of htmx.process(root). hx-get and hx-on::before-request both work.
  4. MyButton - A simple button web component. hx-get and hx-on::before-request both work.
  5. MyButtonWrapper - A more complex web component, with one web component inside another. hx-get does not work (and can't tell if hx-on::before-request works).

All five button should behave the same: click to show an alert. Buttons 'Not a component', MyComponentAlt and MyButton all work as expected. MyComponent and MyButtonWrapper do not.

I have tried with both HTMX v1.9.11 and v2.0.0-alpha1, with similar results.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>HTMX WebComponents</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- <script src="https://unpkg.com/htmx.org@1.9.11/dist/htmx.min.js"></script> -->
    <script src="https://unpkg.com/htmx.org@2.0.0-alpha1/dist/htmx.min.js"></script>
  </head>

  <body>
    <button
      hx-on::before-request="beforeRequestHandler(event)"
      hx-get="#not-a-component"
    >
      Not a component
    </button>
    <hr />
    <my-component></my-component>
    <my-compnent-alt></my-compnent-alt>
    <hr />
    <my-button url="#my-button">MyButton</my-button>
    <my-button-wrapper></my-button-wrapper>

    <template id="my-button-template">
      <style>
        button {
          cursor: pointer;
        }
      </style>
      <button
        hx-get="#"
        hx-boost="true"
        hx-on::before-request="beforeRequestHandler(event)"
      >
        <slot></slot>
      </button>
    </template>

    <template id="my-button-wrapper-template">
      <my-button url="#my-button-wrapper">MyButtonWrapper</my-button>
    </template>

    <script>
      window.beforeRequestHandler = (evt) => {
        evt.preventDefault();
        alert(evt.detail.pathInfo.requestPath);
      };

      customElements.define(
        "my-component",
        class MyComponent extends HTMLElement {
          connectedCallback() {
            const root = this.attachShadow({ mode: "closed" });

            root.innerHTML = `
              <button 
              hx-on::before-request="beforeRequestHandler(event)"
              hx-get="#my-component" 
              >MyComponent</button>`;

            htmx.process(root);
          }
        }
      );

      customElements.define(
        "my-compnent-alt",
        class MyComponentAlt extends HTMLElement {
          connectedCallback() {
            const root = this.attachShadow({ mode: "closed" });

            root.innerHTML = `
              <button
              hx-on::before-request="beforeRequestHandler(event)"
              hx-get="#my-compnent-alt"
              >MyComponentAlt</button>`;

            const buttonEl = root.querySelector("button");

            htmx.process(buttonEl);
          }
        }
      );

      customElements.define(
        "my-button",
        class MyButton extends HTMLElement {
          connectedCallback() {
            const root = this.attachShadow({ mode: "closed" });

            const content = document
              .querySelector(`#my-button-template`)
              .content.cloneNode(true);

            root.replaceChildren(content);

            const url = this.getAttribute("url");

            const buttonEl = root.querySelector("button");

            buttonEl.setAttribute("hx-get", url);

            htmx.process(buttonEl);
          }
        }
      );

      customElements.define(
        "my-button-wrapper",
        class MyButtonWrapper extends HTMLElement {
          connectedCallback() {
            const root = this.attachShadow({ mode: "closed" });

            const content = document
              .querySelector(`#my-button-wrapper-template`)
              .content.cloneNode(true);

            root.replaceChildren(content);
          }
        }
      );
    </script>
  </body>
</html>

I have two questions:

  1. Are the docs wrong? Should it be htmx.process(someElement) instead of htmx.process(root)?
  2. Should htmx.process() work on nested web components?
@andrejota
Copy link
Contributor

Issue 2:
The hx-get attribute is ignored when htmx.process() is called on a web component that is a child of another web component.

I think this is because the current code in bodyContains() only checks a single level of nesting of shadow DOM.

I sent a pull request #2434 that I think solves the issue. You may want to try it and see if it works for you.

@lachdoug
Copy link
Author

lachdoug commented Apr 3, 2024

Thanks andrejota, but still not working with #2434.

@andrejota
Copy link
Contributor

Thanks andrejota, but still not working with #2434.

Any chance you could try with mode:"open" shadow DOM?

@kgscialdone
Copy link
Contributor

Issue 1:
The hx-on::before-request attribute is ignored when using htmx.process() as outlined in the docs.

This is indeed #2406.

@lachdoug
Copy link
Author

lachdoug commented Apr 5, 2024

Any chance you could try with mode:"open" shadow DOM?

Thanks for the suggestion. I gave that a go, but no difference.

@Telroshan Telroshan added the 2.0 label Apr 29, 2024
@1cg
Copy link
Contributor

1cg commented May 15, 2024

partially fixed w/ the fix for #2406 (hx-on)

I don't know about the nested web components thing, will defer to @kgscialdone who understands web components better than me.

@kgscialdone
Copy link
Contributor

I have two questions:

  1. Are the docs wrong? Should it be htmx.process(someElement) instead of htmx.process(root)

No, the docs are correct. Either will work, and it was actually quite a chore to ensure that calling htmx.process with a ShadowRoot as its parameter will work correctly.

  1. Should htmx.process() work on nested web components?

Yes, and at the moment I'm not entirely sure why it isn't. I'll try to look into it more closely when I have the chance.

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

No branches or pull requests

5 participants