Generically populate each input field as a custom step duiring the form process flow

This commit is contained in:
Samuel Andert 2023-08-08 09:30:39 +02:00
parent c98253ceb7
commit 20563628e3

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { createMachine, assign } from 'xstate';
import { useMachine } from '@xstate/svelte';
import { superForm } from 'sveltekit-superforms/client';
import { afterUpdate } from 'svelte';
import { writable } from 'svelte/store';
import TextInput from './inputFields/TextInput.svelte';
import ToggleInput from './inputFields/ToggleInput.svelte';
import SliderInput from './inputFields/SliderInput.svelte';
@ -27,100 +27,155 @@
clearOnSubmit: 'errors-and-message'
});
const successMessage = writable<string | null>(null);
// Update the isValidated property of the store whenever the errors object changes
$: {
let isValid = fields.every((field) => !$errors[field.name]);
if (isValid) {
$me.do.state.send('VALIDATE');
} else {
$me.do.state.send('INVALIDATE');
const formMachine = createMachine({
id: 'form',
initial: 'start',
context: {
currentField: 0,
formData: initialFormData
},
states: {
start: {
on: {
NEXT: 'input'
}
},
input: {
on: {
NEXT: {
target: 'input',
actions: assign({
currentField: (context) => context.currentField + 1
}),
cond: (context) => context.currentField < fields.length - 1
},
PREV: {
target: 'input',
actions: assign({
currentField: (context) => context.currentField - 1
}),
cond: (context) => context.currentField > 0
},
SUBMIT: 'submitting'
}
},
submitting: {
// Add your submission logic here
}
}
}
});
const { state, send } = useMachine(formMachine);
async function handleSubmit() {
// Manually validate the form
const validationResult = await validate();
// Prevent submission if there are errors
if (!validationResult.valid) {
return;
}
// Here, we'll just log the form data
console.log(form);
// Set the success message after successful submission
successMessage.set('Form submitted successfully!');
send('SUBMIT');
}
function handleReset() {
form.set({});
successMessage.set(null);
}
// After update, scroll to the message container for better user experience
afterUpdate(() => {
const messageContainer = document.getElementById('message-container');
if (messageContainer) {
messageContainer.scrollIntoView({ behavior: 'smooth' });
}
});
</script>
<div class="p-6">
My ID is: {$me.id} <br />
My state is: {$me.state}
{#if $me.state === 'validated'}
<aside id="message-container" class="p-4 mt-4 bg-green-500 rounded-md">
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-semibold text-white">Success!</h2>
<p class="mt-1 text-white">{$me.message}</p>
</div>
<button
class="p-1 ml-2 text-white bg-red-500 rounded-full hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500"
on:click={handleReset}
<form on:submit|preventDefault={handleSubmit} class="w-full max-w-md">
<div class="mb-4">
{#if $errors[fields[$state.context.currentField].name]}
<span class="block mb-2 font-semibold text-red-500">
{$errors[fields[$state.context.currentField].name]}
</span>
{:else}
<label
for={fields[$state.context.currentField].name}
class="block mb-2 font-semibold text-white"
>
Reset
</button>
</div>
</aside>
{:else}
<form on:submit|preventDefault={handleSubmit} class="w-full max-w-md">
{#each fields as field (field.name)}
<div class="mb-4">
{#if $errors[field.name]}
<span class="block mb-2 font-semibold text-red-500">{$errors[field.name]}</span>
{:else}
<label for={field.name} class="block mb-2 font-semibold text-white"
>{field.name.charAt(0).toUpperCase() + field.name.slice(1)}
</label>
{/if}
{#if field.type === 'text'}
<TextInput {form} {errors} {validate} {field} {constraints} />
{:else if field.type === 'email'}
<TextInput {form} {errors} {validate} {field} {constraints} />
{:else if field.type === 'textarea'}
<TextAreaInput {form} {errors} {validate} {field} {constraints} />
{:else if field.type === 'select'}
<SelectInput {form} {errors} {validate} {field} {constraints} />
{:else if field.type === 'slider'}
<SliderInput {form} {errors} {validate} {field} {constraints} />
{:else if field.type === 'toggle'}
<ToggleInput {form} {errors} {validate} {field} {constraints} />
{:else if field.type === 'number'}
<NumberInput {form} {errors} {validate} {field} {constraints} />
{/if}
</div>
{/each}
{fields[$state.context.currentField].name.charAt(0).toUpperCase() +
fields[$state.context.currentField].name.slice(1)}
</label>
{/if}
{#if fields[$state.context.currentField].type === 'text'}
<TextInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{:else if fields[$state.context.currentField].type === 'email'}
<TextInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{:else if fields[$state.context.currentField].type === 'textarea'}
<TextAreaInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{:else if fields[$state.context.currentField].type === 'select'}
<SelectInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{:else if fields[$state.context.currentField].type === 'slider'}
<SliderInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{:else if fields[$state.context.currentField].type === 'toggle'}
<ToggleInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{:else if fields[$state.context.currentField].type === 'number'}
<NumberInput
{form}
{errors}
{validate}
field={fields[$state.context.currentField]}
{constraints}
/>
{/if}
</div>
<div class="flex justify-between mt-4">
<button
type="button"
on:click={() => send('PREV')}
class="px-4 py-2 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={$state.context.currentField === 0}
>
Previous
</button>
<button
type="button"
on:click={() => send('NEXT')}
class="px-4 py-2 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={$errors[fields[$state.context.currentField].name]}
>
Next
</button>
<button
type="submit"
class="w-full px-4 py-2 mt-4 text-white bg-blue-500 rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={$me.state === 'notValidated'}
class="px-4 py-2 text-white bg-green-500 rounded-md hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500"
>
Submit
</button>
</form>
{/if}
</div>
</form>
</div>