Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typebox plugin docs #138

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .examples/typebox/errors.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script setup>
import { createTypeboxPlugin } from '@formkit/typebox'
import { Type } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'

// some setup code
const typeboxSchema = Type.Object({
personalInfo: Type.Object({
firstName: Type.String({ minLength: 3, maxLength: 25 }),
lastName: Type.String({ minLength: 3, maxLength: 25 }),
}),
missingField: Type.Number()
})
const [typeboxPlugin, submitHandler] = createTypeboxPlugin(typeboxSchema, async () => { await new Promise((r) => setTimeout(r, 2000))})

// In a real app, you'd likely get the errors from
// your server, but for this example we'll do the
// parsing and retrieve the errors here.
function setupFormNode(node) {
const invalidValues = {personalInfo: { firstName: 'A', lastName: 'K' },
missingField: 'not a number'
}
const checker = TypeCompiler.Compile(typeboxSchema)
const errors = checker.Errors(invalidValues)
// pass the ValueErrorIterator from Typebox to the setTypeboxErrors method
// which is added by the Typebox plugin.
node.setTypeboxErrors(errors)
}
</script>

<template>
<h1 class="text-2xl font-bold mb-2">Errors set with node.setTypeboxErrors()</h1>
<p class="text-base mb-4">
This form cannot be successfully submitted because
the form fields do not match the provided schema.<br>
This is done to illustrate hydration of form-level errors.<br>
Do not actually do this. :)
</p>
<FormKit
type="form"
@node="setupFormNode"
:plugins="[typeboxPlugin]"
@submit="submitHandler"
>
<FormKit type="group" name="personalInfo">
<FormKit type="text" name="firstName" label="First Name" />
<FormKit type="text" name="lastName" label="Last Name" />
</FormKit>
</FormKit>
</template>
3 changes: 3 additions & 0 deletions .examples/typebox/importMap.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"typebox": "https://cdn.jsdelivr.net/npm/@sinclair/[email protected]/build/cjs/index.min.js"
}
51 changes: 51 additions & 0 deletions .examples/typebox/validation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<script setup>
import { createTypeboxPlugin } from '@formkit/typebox'
import { Type } from '@sinclair/typebox'
import { FormatRegistry } from '@sinclair/typebox'

// http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'wilful violation')
const emailPattern = /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i
FormatRegistry.Set('email', (value) => emailPattern.test(value))

const typeboxSchema = Type.Object({
personalInfo: Type.Object({
firstName: Type.String({minLength: 3, maxLength: 25}),
lastName: Type.String({minLength: 3, maxLength: 25}),
}),
email: Type.String({format: 'email'}),
arrayMin: Type.Array(Type.String(), {min: 2}),
})

const [typeboxPlugin, submitHandler] = createTypeboxPlugin(
typeboxSchema,
async (formData) => {
// fake submit handler, but this is where you
// do something with your valid data.
await new Promise((r) => setTimeout(r, 2000))
alert('Form was submitted!')
console.log(formData)
}
)
</script>

<template>
<h1>Validation from Typebox schema</h1>
<FormKit type="form" :plugins="[typeboxPlugin]" @submit="submitHandler">
<FormKit type="group" name="personalInfo">
<FormKit
validation-visibility="live"
type="text"
name="firstName"
label="First Name"
/>
<FormKit type="text" name="lastName" label="Last Name" />
</FormKit>
<FormKit type="text" name="email" label="Your email" />
<FormKit
type="checkbox"
name="arrayMin"
label="Typebox features"
:options="['Validation', 'Type-Safety', 'Reusability']"
/>
</FormKit>
</template>
63 changes: 63 additions & 0 deletions .examples/typebox/with-formkit-validation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<script setup>
import { createTypeboxPlugin } from '@formkit/typebox'
import { Type } from '@sinclair/typebox'
import { FormatRegistry } from '@sinclair/typebox'

// http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'wilful violation')
const emailPattern = /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i
FormatRegistry.Set('email', (value) => emailPattern.test(value))

const typeboxSchema = Type.Object({
personalInfo: Type.Object({
firstName: Type.String({minLength: 3, maxLength: 25}),
lastName: Type.String({minLength: 3, maxLength: 25}),
}),
email: Type.string({format: 'email'}),
arrayMin: Type.Array(Type.String(), {min: 2}),
})

const [typeboxPlugin, submitHandler] = createTypeboxPlugin(
typeboxSchema,
async (formData) => {
// fake submit handler, but this is where you
// do something with your valid data.
await new Promise((r) => setTimeout(r, 2000))
alert('Form was submitted!')
console.log(formData)
}
)
</script>

<template>
<h1 class="text-2xl font-bold mb-4">Typebox with FormKit Validation</h1>
<FormKit type="form" :plugins="[typeboxPlugin]" @submit="submitHandler">
<FormKit type="group" name="personalInfo">
<FormKit
validation="required|length:3,25"
validation-visibility="live"
type="text"
name="firstName"
label="First name"
/>
<FormKit
validation="required|length:3,25"
type="text"
name="lastName"
label="Last name"
/>
</FormKit>
<FormKit
validation="required|email"
type="text"
name="email"
label="Your email"
/>
<FormKit
validation="required|min:2"
type="checkbox"
name="arrayMin"
label="Typebox features"
:options="['Validation', 'Type-Safety', 'Reusability']"
/>
</FormKit>
</template>
36 changes: 36 additions & 0 deletions api-reference/formkit-typebox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: formkit/typebox
---

# @formkit/typebox

<page-toc></page-toc>

## Introduction

The first-party typebox package/plugin for FormKit. Read the [documentation](https://formkit.com/essentials/typebox) for usage instructions.

## Functions

### createTypeboxPlugin()

Creates a new Typebox schema plugin for form validation.

#### Signature

<client-only>

```typescript
createTypeboxPlugin<T extends TSchema>(typeboxSchema: T, submitCallback: (payload: Static<typeof typeboxSchema>, node: FormKitNode | undefined) => void | Promise<void>): [FormKitPlugin, (payload: any, node: FormKitNode | undefined) => void];
```

</client-only>

#### Parameters

- `typeboxSchema` — A Typebox schema to validate the form against.
- `submitCallback` — A callback to run when the form is submitted and it passes validation.

#### Returns

A tuple of a [FormKitPlugin](/api-reference/formkit-core#formkitplugin) and a submit handler.
88 changes: 88 additions & 0 deletions plugins/typebox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
title: Typebox Plugin
description: Use your Typebox schema to validate your FormKit forms.
---

# Typebox Plugin

:PageToc

With the `@formkit/typebox` package you can easily enable validation of your FormKit forms with your Typebox schema. This provides a convenient way to have isomorphic types and ensure that your front-end and back-end are using the same validation rules.

When validating against a Typebox schema all validation errors will be mapped to their corresponding inputs, show or hide based on your form / input's `validation-visibility` prop, and prevent submission when form data does not pass validation with Typebox.

## Installation

To use this plugin with FormKit, install `@formkit/typebox`:

```bash
yarn add @formkit/typebox
```

Once you've installed the `@formkit/typebox` package, you'll need to register the plugin on a per-form basis and each form that requires validation with a Typebox schema will create a new instance of the plugin using the `createTypeboxPlugin` function.

## Usage

To use the Typebox plugin we need to import the `createTypeboxPlugin` function from `@formkit/typebox`, call the `createTypeboxPlugin` function to create receive our `typeboxPlugin` and `submitHandler`, and then add them both to our FormKit form.

The `createTypeboxPlugin` function takes two arguments:

- `typeboxSchema`: The Typebox schema that you would like to use to validate against the form.
- `submitCallback`: a function you would like to run once validation has succeeded on your form — this is where you would handle posting your data to your backend or other submit-related tasks. You form data will be provided with full TypeScript support based on your Typebox schema.

The `createTypeboxPlugin` will return a tuple of:

- `typeboxPlugin`: The plugin that should be applied to your target form's `plugins` prop.
- `submitHandler`: The submit handler that should be attached to your form's `@submit` action. When the form data passes validation of your provided Typebox schema your `submitCallback` will fire.

### For form validation

Here is an example of using a Typebox schema to validate a FormKit form. It's important that your FormKit input `name`s match the expected values for your Typebox schema.

::Example
---
name: 'Typebox Validation'
file: [
'/\_content/_examples/typebox/validation.vue',
]
import-map: '/\_content/_examples/typebox/importMap.json'
---
::

Now your FormKit form will use your Typebox Schema for validation — and all messages will adjacent to each matching FormKit just live native FormKit validation!

### In addition to FormKit validation

Using Typebox to validate your form doesn't mean you have to forgo using FormKit's built-in validation messages. If you add FormKit validation to your FormKit inputs then Typebox validation errors will only show if all FormKit validations have been satisfied and there are remaining unsatisfied Typebox validations.

This has a few benefits:

- You can use FormKit's built-in rules such as `confirm` which don't have easy-to-use equivalents within Typebox.
- Your messages can be translated to one of the many existing languges in `@formkit/i18n` without any additional effort on your part.
- The built-in FormKit validation messages are written to be contextually aware of your input names and knowing that they will be attached directly to their corresponding inputs — so they are more precise and easier to understand than their generic Typebox counterparts.

Here's the same form as before, but now using FormKit validation messages in addition to Typebox schema validation.

::Example
---
name: 'Typebox Validation with FormKit Validation'
file: [
'/\_content/_examples/typebox/with-formkit-validation.vue',
]
import-map: '/\_content/_examples/typebox/importMap.json'
---
::

### For setting form errors

If you need to set errors on your form you can do so with the `node.setTypeboxErrors` function that is made available by the `typeboxPlugin`. The `node.setTypeboxErrors` function accepts a Typebox `ValueErrorIterator` and will map the errors to each input. Any non-matching errors will be shown as form-level errors.

::Example
---
name: 'Typebox Errors'
file: [
'/\_content/_examples/typebox/errors.vue',
]
import-map: '/\_content/_examples/typebox/importMap.json'
---
::