Created
November 4, 2021 09:28
-
-
Save bojanv55/4861a0d0543fdd4c0b3b48f20677bf9f to your computer and use it in GitHub Desktop.
Svelte custom form validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"svelte": "latest", | |
"zod": "^3.11.6" | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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