Can I get a button in Shadow DOM to submit a form not in Shadow DOM?
Asked Answered
E

3

11

I just ran into an interesting situation where I have a submit <button> inside the Shadow DOM of a native custom element that is placed inside a <form>.

  <form id="one" action="" method="get">
    <s-button>Select</s-button>
      #shadow-root
        <button>...</button>
    <button>Outside</button>
  </form>

I also have a <button> as a direct child of the <form>.

The child <button> causes the form to submit.

But the <button> in the shadow-root does not.

In a way I guess this makes sense. But has anyone figured out a way to tell the shadow-root <button> to work correctly with the <form> or is this something I will have to handle through JS?

I know click events are blocked at the Shadow DOM layer, but I am surprised that there is no way to allow the button to still be a part of the form, something that can be set up through an attribute or a property.

Sure I can capture the click event and then send a new one from this but that does not do the same thing since my event will no longer be user generated and there are a huge set of rules associated with that.

Exposure answered 25/1, 2019 at 17:48 Comment(3)
There are more issues with shadowDOM and Forms they are tackling in V2. Do read/follow: github.com/w3c/webcomponents/issues/187Pardoes
And (from some years ago) Supersharps answer on copying shadowDOM inputs to the main DOM: #38623676Pardoes
on click/submit: From the submit Event documentation : "The submit event only fires when the user clicks a submit button [..] in a form. The event is not raised when calling the form.submit() method directly."Pardoes
M
2

You'll have to handle it through Javascript anyway.

A simple solution is to add a (masked) <button> in the light DOM, and transfer the click event to it.

customElements.define( 's-button', class extends HTMLElement {
    connectedCallback() {
        this.attachShadow( {mode: 'open'})
            .innerHTML = `<button>In Shadow</button>`
        var submit = this.appendChild( document.createElement( 'button' ) )
        this.onclick = () => submit.click()
    }
} )
<form onsubmit="console.log('submitted');return false">
    <s-button>Select</s-button>
    <button>Outside</button>
</form>
Mispickel answered 25/1, 2019 at 19:1 Comment(0)
B
5

A button triggers a submit Event (on the FORM element)

Since Events can not pass the shadow DOM boundary (do not bubble up into the parent DOM)

I presume that is why a shadowDOM button (dispatching a submit event) is not received by the FORM element.

Requires Supersharps workaround with a hidden button in the light DOM (which then dispatches a submit event in the parent DOM)

Or (starting from light DOM) you find the (parent) FORM tag and dispatch a submit event yourself:

this.closest('FORM').dispatchEvent(new Event('submit'))


Follow the experts on shadowDOM and FORMs at: https://github.com/w3c/webcomponents/issues/187


customElements.define( 'my-button', class extends HTMLElement {
  connectedCallback() {
    this.attachShadow({mode:'open'}).innerHTML=`<button>Button In Shadow DOM</button>`
    this.onclick = _ => this.closest('FORM').dispatchEvent(new Event('submit'))
  }
})
<form onsubmit="return console.log('submit Event occured')">
    <my-button></my-button>
    <button>button in Document DOM</button>
</form>

Nested shadowDOMs

If the FORM is not a direct ancestor, you can find it with something like: How to reference to a method in parent component from child component with vanilla JS Web Components? (Not any framework or Library)

Belgrade answered 27/1, 2019 at 9:41 Comment(0)
M
2

You'll have to handle it through Javascript anyway.

A simple solution is to add a (masked) <button> in the light DOM, and transfer the click event to it.

customElements.define( 's-button', class extends HTMLElement {
    connectedCallback() {
        this.attachShadow( {mode: 'open'})
            .innerHTML = `<button>In Shadow</button>`
        var submit = this.appendChild( document.createElement( 'button' ) )
        this.onclick = () => submit.click()
    }
} )
<form onsubmit="console.log('submitted');return false">
    <s-button>Select</s-button>
    <button>Outside</button>
</form>
Mispickel answered 25/1, 2019 at 19:1 Comment(0)
O
0

Something else you can do that is not exactly a button in the ShadowDOM, is if your button[type=submit] has been slotted into the ShadowDOM. So if you have something like:

<some-component>
<button type="submit" slot="buttonSlot"></button>
</some-component>

That button can be used to trigger your form. That button is in the light DOM but is easily handled from within your component via the slot. It will also retain all the appropriate keyboard, click, focus, etc. events without any trouble.

To minimize the light DOM html you don't even need it to be a type=submit you can set that from within your component and it will still be treated as the submit button for any parent form in the light DOM.

Bonus (or maybe trouble depending on how you look at it), it will retain the styling of other buttons on the page (unless you change that in your component).

Oxford answered 14/11, 2021 at 19:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.