EDITOR'S NOTE: As I was writing out the problem, I actually managed to find a solution. However, I decided to still post it here in case it might be helpful to someone else (as my brief search on Stack Overflow didn't show anyone asking exactly the same question).
I have a form with a web component that is controlled by JavaScript, structured like this:
<form id="myform">
<input type="text" value="example" tabindex="1" />
<web-component tabindex="2"></web-component>
<input type="submit" tabindex="3" />
</form>
<script>
let form = document.getElementById('myform');
form.addEventListener('submit' (e) => {
e.preventDefault();
console.log('Form is posting');
// Handle form logic here
})
</script>
In the web component, I want the form to be 'submitted' when tabbing to the web component and pressing 'enter'. For this purpose, I added the following event handler within the web component:
class WebComponent extends HTMLElement {
constructor() {
...
let form = this.closest('form');
this.addEventListener('keydown', (e) => {
if(form && e.code === 'Enter') form.submit();
})
}
}
The issue arises because form.submit()
submits the form without triggering an event that can be monitored. This causes the form event handler not to execute, resulting in the form being posted without any JavaScript validation. One approach to address this would involve changing the form submit function to a named function as follows:
form.addEventListener('submit', handleFormSubmission);
myComponent.addEventListener('keydown', (e) => {
if(e.code === 'Enter') handleFormSubmission();
})
However, this solution limits the usability of the web component across multiple forms, making it less readable. Another option is to search for an input[type=submit]
or button[type=submit]
within the form and trigger a click on it, but that seems cumbersome.
After further investigation, I discovered the use of dispatchEvent
, which fires a custom event instead of just calling submit()
:
class WebComponent extends HTMLElement {
constructor() {
...
let form = this.closest('form');
this.addEventListener('keydown', (e) => {
if(form && e.code === 'Enter') form.dispatchEvent(new Event('submit'));
})
}
}
Although close to a solution, I encountered a challenge where e.preventDefault()
was not respected, meaning that JS validation couldn't halt the submission. Delving deeper into the specifications, I learned that the Event
constructor accepts additional settings, such as
{bubbles: true, cancelable: true}
. This final tweak made it work smoothly:
let form = document.getElementById('myform');
form.addEventListener('submit' (e) => {
e.preventDefault();
console.log('Form is posting');
// Handle form logic here
});
class WebComponent extends HTMLElement {
constructor() {
...
let form = this.closest('form');
this.addEventListener('keydown', (e) => {
if(form && e.code === 'Enter') form.dispatchEvent(new Event('submit', {cancelable: true}));
})
}
}