fix(form): dispatch submit ev on native form node and add docs
This commit is contained in:
parent
37f975ea48
commit
1a5e353f7f
5 changed files with 116 additions and 16 deletions
5
.changeset/strange-cougars-shout.md
Normal file
5
.changeset/strange-cougars-shout.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@lion/form': patch
|
||||
---
|
||||
|
||||
Dispatch submit event on native form node instead of calling submit() directly, which circumvents lion-form submit logic and will always do a page reload and cannot be stopped by the user.
|
||||
|
|
@ -7,6 +7,7 @@ For usage and installation please see the appropriate packages.
|
|||
|
||||
```js script
|
||||
import { html } from '@lion/core';
|
||||
import '@lion/button/lion-button.js';
|
||||
import '@lion/checkbox-group/lion-checkbox-group.js';
|
||||
import '@lion/checkbox-group/lion-checkbox.js';
|
||||
import '@lion/combobox/lion-combobox.js';
|
||||
|
|
@ -31,6 +32,8 @@ import '@lion/textarea/lion-textarea.js';
|
|||
import { MinLength, Required } from '@lion/form-core';
|
||||
import { loadDefaultFeedbackMessages } from '@lion/validate-messages';
|
||||
|
||||
loadDefaultFeedbackMessages();
|
||||
|
||||
export default {
|
||||
title: 'Forms/Features Overview',
|
||||
};
|
||||
|
|
@ -40,7 +43,6 @@ export default {
|
|||
|
||||
```js story
|
||||
export const main = () => {
|
||||
loadDefaultFeedbackMessages();
|
||||
Required.getMessage = () => 'Please enter a value';
|
||||
return html`
|
||||
<lion-form>
|
||||
|
|
@ -161,3 +163,67 @@ export const main = () => {
|
|||
`;
|
||||
};
|
||||
```
|
||||
|
||||
## Submitting a form
|
||||
|
||||
To submit a form, use a regular button (or `LionButton`) with `type="submit"` (which is default) somewhere inside the native `<form>`.
|
||||
|
||||
Then, add a `submit` handler on the `lion-form`.
|
||||
|
||||
You can use this event to do your own (pre-)submit logic, like getting the serialized form data and sending it to a backend API.
|
||||
|
||||
Another example is checking if the form has errors, and focusing the first field with an error.
|
||||
|
||||
To fire a submit from JavaScript, select the `lion-form` element and call `.submit()`.
|
||||
|
||||
```js preview-story
|
||||
export const formSubmit = () => {
|
||||
const submitHandler = ev => {
|
||||
if (ev.target.hasFeedbackFor.includes('error')) {
|
||||
const firstFormElWithError = ev.target.formElements.find(el =>
|
||||
el.hasFeedbackFor.includes('error'),
|
||||
);
|
||||
firstFormElWithError.focus();
|
||||
return;
|
||||
}
|
||||
const formData = ev.target.serializedValue;
|
||||
fetch('/api/foo/', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
};
|
||||
|
||||
const submitViaJS = ev => {
|
||||
// Call submit on the lion-form element, in your own code you should use
|
||||
// a selector that's not dependent on DOM structure like this one.
|
||||
ev.target.previousElementSibling.submit();
|
||||
};
|
||||
|
||||
return html`
|
||||
<lion-form @submit=${submitHandler}>
|
||||
<form @submit=${ev => ev.preventDefault()}>
|
||||
<lion-input
|
||||
name="first_name"
|
||||
label="First Name"
|
||||
.validators="${[new Required()]}"
|
||||
></lion-input>
|
||||
<lion-input
|
||||
name="last_name"
|
||||
label="Last Name"
|
||||
.validators="${[new Required()]}"
|
||||
></lion-input>
|
||||
<div style="display:flex">
|
||||
<lion-button raised>Submit</lion-button>
|
||||
<lion-button
|
||||
type="button"
|
||||
raised
|
||||
@click=${ev => ev.currentTarget.parentElement.parentElement.parentElement.resetGroup()}
|
||||
>Reset</lion-button
|
||||
>
|
||||
</div>
|
||||
</form>
|
||||
</lion-form>
|
||||
<button @click=${submitViaJS}>Explicit submit via JavaScript</button>
|
||||
`;
|
||||
};
|
||||
```
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
`lion-form` is a webcomponent that enhances the functionality of the native `form` component.
|
||||
It is designed to interact with (instances of) the [form controls](?path=/docs/forms-system-overview--page).
|
||||
|
||||
> Note: Make sure to explicitly put `<form>` native element as a first child of `<lion-form>`, in order to function properly.
|
||||
|
||||
```js script
|
||||
export default {
|
||||
title: 'Forms/Form/Overview',
|
||||
|
|
|
|||
|
|
@ -15,20 +15,8 @@ const throwFormNodeError = () => {
|
|||
export class LionForm extends LionFieldset {
|
||||
constructor() {
|
||||
super();
|
||||
/** @param {Event} ev */
|
||||
this._submit = ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.submitGroup();
|
||||
this.dispatchEvent(new Event('submit', { bubbles: true }));
|
||||
};
|
||||
/** @param {Event} ev */
|
||||
this._reset = ev => {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.resetGroup();
|
||||
this.dispatchEvent(new Event('reset', { bubbles: true }));
|
||||
};
|
||||
this._submit = this._submit.bind(this);
|
||||
this._reset = this._reset.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
|
|
@ -50,12 +38,24 @@ export class LionForm extends LionFieldset {
|
|||
|
||||
submit() {
|
||||
if (this._formNode) {
|
||||
this._formNode.submit();
|
||||
// Firefox requires cancelable flag, otherwise we cannot preventDefault
|
||||
// Firefox still runs default handlers for untrusted events :\
|
||||
this._formNode.dispatchEvent(new Event('submit', { cancelable: true }));
|
||||
} else {
|
||||
throwFormNodeError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_submit(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.submitGroup();
|
||||
this.dispatchEvent(new Event('submit', { bubbles: true }));
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this._formNode) {
|
||||
this._formNode.reset();
|
||||
|
|
@ -64,6 +64,16 @@ export class LionForm extends LionFieldset {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_reset(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
this.resetGroup();
|
||||
this.dispatchEvent(new Event('reset', { bubbles: true }));
|
||||
}
|
||||
|
||||
__registerEventsForLionForm() {
|
||||
this._formNode.addEventListener('submit', this._submit);
|
||||
this._formNode.addEventListener('reset', this._reset);
|
||||
|
|
|
|||
|
|
@ -138,6 +138,23 @@ describe('<lion-form>', () => {
|
|||
expect(submitEv.composed).to.be.false;
|
||||
});
|
||||
|
||||
it('redispatches a submit event on the native form node when calling submit() imperatively', async () => {
|
||||
const nativeFormSubmitEventSpy = spy();
|
||||
const el = await fixture(html`
|
||||
<lion-form>
|
||||
<form @submit=${nativeFormSubmitEventSpy}>
|
||||
<button type="submit">submit</button>
|
||||
</form>
|
||||
</lion-form>
|
||||
`);
|
||||
const submitSpy = spy(el, 'submit');
|
||||
const submitGroupSpy = spy(el, 'submitGroup');
|
||||
el.submit();
|
||||
expect(submitSpy.calledOnce).to.be.true;
|
||||
expect(nativeFormSubmitEventSpy.calledOnce).to.be.true;
|
||||
expect(submitGroupSpy.calledOnce).to.be.true;
|
||||
});
|
||||
|
||||
it('handles internal submit handler before dispatch', async () => {
|
||||
const el = await fixture(html`
|
||||
<lion-form>
|
||||
|
|
|
|||
Loading…
Reference in a new issue