Skip to content

Commit ead1b7e

Browse files
committed
customRequest added to onSubmit
1 parent db98de5 commit ead1b7e

File tree

6 files changed

+205
-2
lines changed

6 files changed

+205
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- New validation library: [Superstruct](https://docs.superstructjs.org/)!
13+
- `customRequest` added to the [onSubmit](https://superforms.rocks/concepts/events#onsubmit) options, that lets you use a custom [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) or [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) when submitting the form. Very useful for progress bars when uploading large files.
1314

1415
### Fixed
1516

src/lib/client/superForm.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import { beforeNavigate, goto, invalidateAll } from '$app/navigation';
2626
import { SuperFormError, flattenErrors, mapErrors, updateErrors } from '$lib/errors.js';
2727
import { cancelFlash, shouldSyncFlash } from './flash.js';
28-
import { applyAction, enhance as kitEnhance } from '$app/forms';
28+
import { applyAction, deserialize, enhance as kitEnhance } from '$app/forms';
2929
import { setCustomValidityForm, updateCustomValidity } from './customValidity.js';
3030
import { inputInfo } from './elements.js';
3131
import { Form as HtmlForm, scrollToFirstError } from './form.js';
@@ -105,6 +105,13 @@ export type FormOptions<
105105
* Override client validation temporarily for this form submission.
106106
*/
107107
validators: (validators: Exclude<ValidatorsOption<T>, 'clear'>) => void;
108+
/**
109+
* Use a custom fetch or XMLHttpRequest implementation for this form submission. It must return an ActionResult in the response body.
110+
* If the request is using a XMLHttprequest, the promise must be resolved when the request is complete.
111+
*/
112+
customRequest: (
113+
validators: (input: Parameters<SubmitFunction>[0]) => Promise<Response | XMLHttpRequest>
114+
) => void;
108115
}
109116
) => MaybePromise<unknown | void>;
110117
onResult: (event: {
@@ -1588,10 +1595,14 @@ export function superForm<
15881595
);
15891596

15901597
let currentRequest: AbortController | null;
1598+
let customRequest:
1599+
| ((input: Parameters<SubmitFunction>[0]) => Promise<Response | XMLHttpRequest>)
1600+
| undefined = undefined;
15911601

15921602
return kitEnhance(FormElement, async (submitParams) => {
15931603
let jsonData: Record<string, unknown> | undefined = undefined;
15941604
let validationAdapter = options.validators;
1605+
undefined;
15951606

15961607
const submit = {
15971608
...submitParams,
@@ -1603,8 +1614,11 @@ export function superForm<
16031614
},
16041615
validators(adapter: Exclude<ValidatorsOption<T>, 'clear'>) {
16051616
validationAdapter = adapter;
1617+
},
1618+
customRequest(request: typeof customRequest) {
1619+
customRequest = request;
16061620
}
1607-
};
1621+
} satisfies Parameters<NonNullable<FormOptions<T, M>['onSubmit']>>[0];
16081622

16091623
const _submitCancel = submit.cancel;
16101624
let cancelled = false;
@@ -1959,6 +1973,18 @@ export function superForm<
19591973
unsubCheckforNav();
19601974
}
19611975

1976+
if (customRequest) {
1977+
if (!cancelled) _submitCancel();
1978+
const response = await customRequest(submitParams);
1979+
const result: ActionResult =
1980+
response instanceof Response
1981+
? deserialize(await response.text())
1982+
: deserialize(response.responseText);
1983+
1984+
if (result.type === 'error') result.status = response.status;
1985+
validationResponse({ result });
1986+
}
1987+
19621988
return validationResponse;
19631989
});
19641990
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { superValidate, message, withFiles } from '$lib/index.js';
2+
import { zod } from '$lib/adapters/zod.js';
3+
import { fail } from '@sveltejs/kit';
4+
import { schema } from './schema.js';
5+
6+
export const load = async () => {
7+
const form = await superValidate(zod(schema));
8+
return { form };
9+
};
10+
11+
export const actions = {
12+
default: async ({ request }) => {
13+
const formData = await request.formData();
14+
console.log(formData);
15+
16+
const form = await superValidate(formData, zod(schema), { allowFiles: true });
17+
console.log(form);
18+
19+
if (!form.valid) return fail(400, withFiles({ form }));
20+
21+
return message(form, 'Form posted successfully!');
22+
}
23+
};
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<script lang="ts">
2+
import { page } from '$app/stores';
3+
import { superForm } from '$lib/index.js';
4+
import SuperDebug from '$lib/index.js';
5+
import FileInput from './FileInput.svelte';
6+
import type { PageData } from './$types.js';
7+
import type { SubmitFunction } from '@sveltejs/kit';
8+
9+
export let data: PageData;
10+
let progress = 0;
11+
12+
function fileUploadWithProgressbar(input: Parameters<SubmitFunction>[0]) {
13+
return new Promise<XMLHttpRequest>((res) => {
14+
let xhr = new XMLHttpRequest();
15+
16+
// listen for upload progress
17+
xhr.upload.onprogress = function (event) {
18+
progress = Math.round((100 * event.loaded) / event.total);
19+
console.log(`File is ${progress}% uploaded.`);
20+
};
21+
22+
// handle error
23+
xhr.upload.onerror = function () {
24+
console.log(`Error during the upload: ${xhr.status}.`);
25+
};
26+
27+
// upload completed successfully
28+
xhr.onload = function () {
29+
if (xhr.readyState === xhr.DONE) {
30+
console.log('Upload completed successfully.');
31+
progress = 0;
32+
res(xhr);
33+
}
34+
};
35+
36+
xhr.open('POST', input.action, true);
37+
xhr.send(input.formData);
38+
39+
return xhr;
40+
});
41+
}
42+
43+
const { form, errors, message, enhance } = superForm(data.form, {
44+
taintedMessage: null,
45+
onSubmit({ customRequest }) {
46+
customRequest(fileUploadWithProgressbar);
47+
}
48+
});
49+
const acceptedExtensions = '.flac, .mp3';
50+
</script>
51+
52+
<SuperDebug data={$form} />
53+
54+
<h3>Superforms testing ground - Zod</h3>
55+
56+
{#if $message}
57+
<!-- eslint-disable-next-line svelte/valid-compile -->
58+
<div class="status" class:error={$page.status >= 400} class:success={$page.status == 200}>
59+
{$message}
60+
</div>
61+
{/if}
62+
63+
<form method="POST" enctype="multipart/form-data" use:enhance>
64+
<FileInput
65+
name="track"
66+
label="Track"
67+
accept={acceptedExtensions}
68+
bind:value={$form.track}
69+
errors={$errors.track}
70+
/>
71+
<br /><progress max="100" value={progress}>{progress}%</progress>
72+
73+
<div><button>Submit</button></div>
74+
</form>
75+
76+
<hr />
77+
<p><a target="_blank" href="https://superforms.rocks/api">API Reference</a></p>
78+
79+
<style>
80+
.status {
81+
color: white;
82+
padding: 4px;
83+
padding-left: 8px;
84+
border-radius: 2px;
85+
font-weight: 500;
86+
}
87+
88+
.status.success {
89+
background-color: seagreen;
90+
}
91+
92+
.status.error {
93+
background-color: #ff2a02;
94+
}
95+
96+
a {
97+
text-decoration: underline;
98+
}
99+
100+
button {
101+
margin-top: 1rem;
102+
}
103+
104+
hr {
105+
margin-top: 4rem;
106+
}
107+
108+
form {
109+
padding-top: 1rem;
110+
padding-bottom: 1rem;
111+
}
112+
</style>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<script lang="ts">
2+
import type { InputConstraint } from '$lib/index.js';
3+
4+
export let value: File | File[] | null;
5+
export let name: string;
6+
export let label: string | undefined = undefined;
7+
export let errors: string[] | undefined = undefined;
8+
export let constraints: InputConstraint | undefined = undefined;
9+
10+
const input = (e: Event) => {
11+
const file = (e.currentTarget as HTMLInputElement).files?.item(0) ?? null;
12+
value = file;
13+
};
14+
</script>
15+
16+
<label for={name}>{label}</label>
17+
<input
18+
{name}
19+
id={name}
20+
type="file"
21+
on:change={input}
22+
aria-invalid={errors ? 'true' : undefined}
23+
{...constraints}
24+
{...$$restProps}
25+
/>
26+
{#if errors}<span class="error">{errors}</span>{/if}
27+
28+
<style>
29+
.error {
30+
color: red;
31+
}
32+
33+
input {
34+
margin-bottom: 0;
35+
}
36+
</style>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { z } from 'zod';
2+
3+
export const schema = z.object({
4+
track: z.instanceof(File, { message: 'Please upload a file.' })
5+
});

0 commit comments

Comments
 (0)