Skip to content

Instantly share code, notes, and snippets.

@bojanv55
Created November 4, 2021 09:28
Show Gist options
  • Save bojanv55/4861a0d0543fdd4c0b3b48f20677bf9f to your computer and use it in GitHub Desktop.
Save bojanv55/4861a0d0543fdd4c0b3b48f20677bf9f to your computer and use it in GitHub Desktop.
Svelte custom form validation
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dumber Gist</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
</head>
<!--
Dumber Gist uses dumber bundler, the default bundle file
is /dist/entry-bundle.js.
The starting module is pointed to "index" (data-main attribute on script)
which is your src/index.ts.
-->
<body>
<div id="root"></div>
<script src="/dist/entry-bundle.js" data-main="index"></script>
</body>
</html>
{
"dependencies": {
"svelte": "latest",
"zod": "^3.11.6"
}
}
<script lang="ts">
import {z} from "zod";
const modelSchema = z.object({
requiredFiveChars: z.string().min(5, {message: "has to be at least 5 characters"}),
array: z.array(z.object({
name: z.string().min(7, {message: "has to be at least 7 characters"})
})),
subObject: z.object({
subSubObject: z.object({
subSubSubProp: z.string().nonempty({message: "cannot be empty"})
})
})
});
type modelType = z.infer<typeof modelSchema>;
const model : modelType = {
array: [],
subObject: {
subSubObject: {}
}
};
let problem = [];
const addNew = () => {
model.array = [...model.array, {name:""}]
}
const send = () => {
return fetch("some/location", {
method: "POST",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(model)
})
.then(response => {
if(!response.ok){
return response.json().then((data) => {
//here we get validation response from the server side
//handle server side response and update "problem" array
});
}
//here we redirect since we succeeded
});
};
//--------------- BEGIN ------ THIS GOES TO LIBRARY ------------------------
$: {
const result = modelSchema.safeParse(model);
if(!result.success) {
problem = result.error.issues;
}
else{
problem = []
}
}
const error = (reference, property) => {
let paths = visit(model, reference);
if(paths != null){
paths.push(property);
}
const samePath = (a,b) => {
if(!a || !b){
return false;
}
if(a.length!=b.length){
return false;
}
for(let i=0; i<a.length; i++){
if(a[i]!=b[i]){
return false;
}
}
return true;
}
if(paths!=null) {
for(let el of problem){
if (samePath(paths, el.path)) {
console.log(el.message);
return el.message;
}
}
}
return null;
}
const isReference = (model) => model !== null && typeof model === 'object';
const visit = (model, match) => {
if(model === match){
return [];
}
for(const k in model){
if(isReference(model[k])){
let rz = visit(model[k], match);
if(rz!==null && rz!==undefined){
rz.unshift(k);
return rz;
}
}
}
return null;
}
//--------------- END ------ THIS GOES TO LIBRARY ------------------------
</script>
<main>
<h1>Form</h1>
<form on:submit|preventDefault="{send}">
<label class="validation-message">Some prop:
<input type="text" bind:value="{model.requiredFiveChars}" required>
{#if error(model, "requiredFiveChars")}
<div class="error">{error(model, "requiredFiveChars")}</div>
{/if}
</label>
<label class="validation-message">Sub sub sub prop:
<input type="text" bind:value="{model.subObject.subSubObject.subSubSubProp}">
{#if error(model.subObject.subSubObject, "subSubSubProp")}
<div class="error">{error(model.subObject.subSubObject, "subSubSubProp")}</div>
{/if}
</label>
<div class="array">
<h3>Array handling</h3>
{#each model.array as a}
<label class="validation-message">Name:
<input type="text" bind:value="{a.name}" required>
<span class="error" data-error-for="{a.name}"></span>
{#if error(a, "name")}
<div class="error">{error(a, "name")}</div>
{/if}
</label>
{/each}
<button type="button" on:click|preventDefault={addNew}>Add New</button>
</div>
<button type="submit">Send</button>
</form>
</main>
<style>
label{
display: block;
}
.error{
color: red;
}
.array{
margin: 40px;
}
</style>
import App from './App.svelte';
const app = new App({
target: document.getElementById('root'),
props: {
name: 'Svelte'
}
});
export default app;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment