Skip to content

Commit

Permalink
customRequest added to onSubmit
Browse files Browse the repository at this point in the history
  • Loading branch information
ciscoheat committed Jul 9, 2024
1 parent db98de5 commit ead1b7e
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- New validation library: [Superstruct](https://docs.superstructjs.org/)!
- `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.

### Fixed

Expand Down
30 changes: 28 additions & 2 deletions src/lib/client/superForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
import { beforeNavigate, goto, invalidateAll } from '$app/navigation';
import { SuperFormError, flattenErrors, mapErrors, updateErrors } from '$lib/errors.js';
import { cancelFlash, shouldSyncFlash } from './flash.js';
import { applyAction, enhance as kitEnhance } from '$app/forms';
import { applyAction, deserialize, enhance as kitEnhance } from '$app/forms';
import { setCustomValidityForm, updateCustomValidity } from './customValidity.js';
import { inputInfo } from './elements.js';
import { Form as HtmlForm, scrollToFirstError } from './form.js';
Expand Down Expand Up @@ -105,6 +105,13 @@ export type FormOptions<
* Override client validation temporarily for this form submission.
*/
validators: (validators: Exclude<ValidatorsOption<T>, 'clear'>) => void;
/**
* Use a custom fetch or XMLHttpRequest implementation for this form submission. It must return an ActionResult in the response body.
* If the request is using a XMLHttprequest, the promise must be resolved when the request is complete.
*/
customRequest: (
validators: (input: Parameters<SubmitFunction>[0]) => Promise<Response | XMLHttpRequest>
) => void;
}
) => MaybePromise<unknown | void>;
onResult: (event: {
Expand Down Expand Up @@ -1588,10 +1595,14 @@ export function superForm<
);

let currentRequest: AbortController | null;
let customRequest:
| ((input: Parameters<SubmitFunction>[0]) => Promise<Response | XMLHttpRequest>)
| undefined = undefined;

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

const submit = {
...submitParams,
Expand All @@ -1603,8 +1614,11 @@ export function superForm<
},
validators(adapter: Exclude<ValidatorsOption<T>, 'clear'>) {
validationAdapter = adapter;
},
customRequest(request: typeof customRequest) {
customRequest = request;
}
};
} satisfies Parameters<NonNullable<FormOptions<T, M>['onSubmit']>>[0];

const _submitCancel = submit.cancel;
let cancelled = false;
Expand Down Expand Up @@ -1959,6 +1973,18 @@ export function superForm<
unsubCheckforNav();
}

if (customRequest) {
if (!cancelled) _submitCancel();
const response = await customRequest(submitParams);
const result: ActionResult =
response instanceof Response
? deserialize(await response.text())
: deserialize(response.responseText);

if (result.type === 'error') result.status = response.status;
validationResponse({ result });
}

return validationResponse;
});
}
Expand Down
23 changes: 23 additions & 0 deletions src/routes/(v2)/v2/customrequest/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { superValidate, message, withFiles } from '$lib/index.js';
import { zod } from '$lib/adapters/zod.js';
import { fail } from '@sveltejs/kit';
import { schema } from './schema.js';

export const load = async () => {
const form = await superValidate(zod(schema));
return { form };
};

export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
console.log(formData);

const form = await superValidate(formData, zod(schema), { allowFiles: true });
console.log(form);

if (!form.valid) return fail(400, withFiles({ form }));

return message(form, 'Form posted successfully!');
}
};
112 changes: 112 additions & 0 deletions src/routes/(v2)/v2/customrequest/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<script lang="ts">
import { page } from '$app/stores';
import { superForm } from '$lib/index.js';
import SuperDebug from '$lib/index.js';
import FileInput from './FileInput.svelte';
import type { PageData } from './$types.js';
import type { SubmitFunction } from '@sveltejs/kit';
export let data: PageData;
let progress = 0;
function fileUploadWithProgressbar(input: Parameters<SubmitFunction>[0]) {
return new Promise<XMLHttpRequest>((res) => {
let xhr = new XMLHttpRequest();
// listen for upload progress
xhr.upload.onprogress = function (event) {
progress = Math.round((100 * event.loaded) / event.total);
console.log(`File is ${progress}% uploaded.`);
};
// handle error
xhr.upload.onerror = function () {
console.log(`Error during the upload: ${xhr.status}.`);
};
// upload completed successfully
xhr.onload = function () {
if (xhr.readyState === xhr.DONE) {
console.log('Upload completed successfully.');
progress = 0;
res(xhr);
}
};
xhr.open('POST', input.action, true);
xhr.send(input.formData);
return xhr;
});
}
const { form, errors, message, enhance } = superForm(data.form, {
taintedMessage: null,
onSubmit({ customRequest }) {
customRequest(fileUploadWithProgressbar);
}
});
const acceptedExtensions = '.flac, .mp3';
</script>

<SuperDebug data={$form} />

<h3>Superforms testing ground - Zod</h3>

{#if $message}
<!-- eslint-disable-next-line svelte/valid-compile -->
<div class="status" class:error={$page.status >= 400} class:success={$page.status == 200}>
{$message}
</div>
{/if}

<form method="POST" enctype="multipart/form-data" use:enhance>
<FileInput
name="track"
label="Track"
accept={acceptedExtensions}
bind:value={$form.track}
errors={$errors.track}
/>
<br /><progress max="100" value={progress}>{progress}%</progress>

<div><button>Submit</button></div>
</form>

<hr />
<p><a target="_blank" href="https://superforms.rocks/api">API Reference</a></p>

<style>
.status {
color: white;
padding: 4px;
padding-left: 8px;
border-radius: 2px;
font-weight: 500;
}
.status.success {
background-color: seagreen;
}
.status.error {
background-color: #ff2a02;
}
a {
text-decoration: underline;
}
button {
margin-top: 1rem;
}
hr {
margin-top: 4rem;
}
form {
padding-top: 1rem;
padding-bottom: 1rem;
}
</style>
36 changes: 36 additions & 0 deletions src/routes/(v2)/v2/customrequest/FileInput.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script lang="ts">
import type { InputConstraint } from '$lib/index.js';
export let value: File | File[] | null;
export let name: string;
export let label: string | undefined = undefined;
export let errors: string[] | undefined = undefined;
export let constraints: InputConstraint | undefined = undefined;
const input = (e: Event) => {
const file = (e.currentTarget as HTMLInputElement).files?.item(0) ?? null;
value = file;
};
</script>

<label for={name}>{label}</label>
<input
{name}
id={name}
type="file"
on:change={input}
aria-invalid={errors ? 'true' : undefined}
{...constraints}
{...$$restProps}
/>
{#if errors}<span class="error">{errors}</span>{/if}

<style>
.error {
color: red;
}
input {
margin-bottom: 0;
}
</style>
5 changes: 5 additions & 0 deletions src/routes/(v2)/v2/customrequest/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { z } from 'zod';

export const schema = z.object({
track: z.instanceof(File, { message: 'Please upload a file.' })
});

0 comments on commit ead1b7e

Please sign in to comment.