Skip to content
Merged
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
1 change: 1 addition & 0 deletions apps/docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default defineConfig({
{ text: 'FieldColor', link: '/guide/fields/FieldColor' },
{ text: 'FieldMask', link: '/guide/fields/FieldMask' },
{ text: 'FieldNumber', link: '/guide/fields/FieldNumber' },
{ text: 'FieldObject', link: '/guide/fields/FieldObject' },
{ text: 'FieldPassword', link: '/guide/fields/FieldPassword' },
{ text: 'FieldRadio', link: '/guide/fields/FieldRadio' },
{ text: 'FieldReset', link: '/guide/fields/FieldReset' },
Expand Down
63 changes: 63 additions & 0 deletions apps/docs/components/examples/fields/FieldObjectExample.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<template>
Person: <code>{{ form.model.person }}</code>
<vue-form-generator :schema="form.schema" :model="form.model" />
</template>

<script setup>
import { onBeforeMount, ref } from 'vue'

const props = defineProps({
addValidators: Boolean
})

const form = ref({
model: {
person: {
name: '',
surname: '',
age: null
}
},
schema: {
fields: [
{
type: 'object',
model: 'person',
schema: {
fields: [
{
type: 'input',
inputType: 'text',
model: 'name',
label: 'Name'
},
{
type: 'input',
inputType: 'text',
model: 'surname',
label: 'Surname'
},
{
type: 'input',
inputType: 'number',
model: 'age',
label: 'Age'
}
]
}
}
]
}
})

onBeforeMount(() => {
if (props.addValidators) {
const fields = form.value.schema.fields[0].schema.fields

const minLengthThree = (value) => value && value.length >= 3
fields[0].validator = minLengthThree
fields[1].validator = minLengthThree
fields[2].validator = (value) => value && value >= 18
}
})
</script>
112 changes: 112 additions & 0 deletions apps/docs/guide/fields/FieldObject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# FieldObject
`FieldObject` is a field that has its own `schema`, meaning the field itself
renders other fields. These fields will return their values to the object inside
the model that is assigned to the `FieldObject` component.

### type `object`

<script setup>
import FieldObjectExample from '/components/examples/fields/FieldObjectExample.vue'
</script>

## Basic example
::: details Code
```js
const form = ref({
model: {
person: {
name: '',
surname: '',
age: null
}
},
schema: {
fields: [
{
type: 'object',
model: 'person',
schema: {
fields: [
{
type: 'input',
inputType: 'text',
model: 'name',
label: 'Name'
},
{
type: 'input',
inputType: 'text',
model: 'surname',
label: 'Surname'
},
{
type: 'input',
inputType: 'number',
model: 'age',
label: 'Age'
}
]
}
}
]
}
})
```
:::
<FieldObjectExample/>

## With validators
::: details Code
```js
function minLengthThree (value) {
return value && value.length >= 3
}

function overEighteen (value) {
return value && value >= 18
}

// ......
fields: [
{
type: 'object',
model: 'person',
schema: {
fields: [
{
type: 'input',
inputType: 'text',
model: 'name',
label: 'Name',
validator: minLengthThree
},
{
type: 'input',
inputType: 'text',
model: 'surname',
label: 'Surname',
validator: minLengthThree
},
{
type: 'input',
inputType: 'number',
model: 'age',
label: 'Age',
validator: overEighteen
}
]
}
}
]
```
:::
::: info
In this example, `name` and `surname` must have a length of three letters or more, `age` must be at least 18.
:::
<FieldObjectExample add-validators/>


## Properties
| Property | Default | Type | Description |
|----------|---------|----------|---------------------------------------------|
| schema | `{}` | `Object` | A form schema, as seen in `FormGenerator.vue` |
25 changes: 24 additions & 1 deletion apps/docs/guide/form-generator/events.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
These are events emitted by the `vue-form-generator` component.

## `field-validated`
Emitted when a field inside the form has been validated.

### Event arguments
- `validations` - an object with the field's errors and the field schema
- `fieldErrors` - an array of error messages that have been thrown during validations;
- `field` - the field as defined in the schema

An example from the [`FieldObject`](/guide/fields/FieldObject) component:
```vue [FieldObject.vue]
<script setup>
const onFieldValidated = (validation) => {
const key = `${field.value.model}.${validation.field.model}`
emits(
'validated',
validation.fieldErrors.length === 0,
validation.fieldErrors,
{ ...field.value, model: key }
)
}
</script>
```

## `submit`
Emitted when all fields have been validated and no errors occurred during said validations.
Emitted when all fields have been validated and no errors occurred during said validations.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"main": "./dist/vue3-form-generator.js",
"scripts": {
"dev": "vite",
"dev:sass": "sass --watch src/scss/themes:playground/css/",
"dev:sass": "sass --watch src/scss/themes:apps/playground/css/",
"test": "vitest",
"build": "vite build && sass src/scss/themes/:dist/themes/",
"preview": "vite preview",
Expand Down Expand Up @@ -79,4 +79,4 @@
"@vueuse/core": "^13.1.0",
"maska": "^3.1.1"
}
}
}
69 changes: 26 additions & 43 deletions src/FormGenerator.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,31 @@
<script setup>
<script setup lang="ts">
import type { FieldValidation, FormGeneratorProps, FormOptions } from '@/resources/types/generic'
import type { Field } from '@/resources/types/field/fields'
import type { ComponentPublicInstance, ComputedRef, Ref } from 'vue'
import { computed, ref } from 'vue'
import { resetObjectProperties, toUniqueArray } from '@/helpers'
import FormGroup from './FormGroup.vue'

const emits = defineEmits([ 'submit' ])
const emits = defineEmits([ 'submit', 'field-validated' ])

const props = defineProps({
id: {
type: String,
required: false,
default: ''
},
idPrefix: {
type: String,
required: false,
default: ''
},
options: {
type: Object,
default: () => ({})
},
schema: {
type: Object,
required: true
},
model: {
type: Object,
required: true
},
enctype: {
type: String,
default: 'application/x-www-form-urlencoded'
}
const props = withDefaults(defineProps<FormGeneratorProps>(), {
enctype: 'application/x-www-form-urlencoded',
id: '',
idPrefix: ''
})

type FormGroupInstance = ComponentPublicInstance<InstanceType<typeof FormGroup>>
/** Data / Refs */
const fieldElements = ref([])
const formErrors = ref({})
const formOptions = computed(() => ({ ...props.options, idPrefix: props.idPrefix }))
const fieldElements: Ref<FormGroupInstance[]> = ref([])
const formErrors: Ref<Record<string, any>> = ref({})
const formOptions: ComputedRef<FormOptions> = computed(() => ({ ...props.options, idPrefix: props.idPrefix }))

/**
* Update form model key with its new value.
* @param {String} model model key to update.
* @param {any} value value to set.
*/
const updateGeneratorModel = ({ model, value }) => {
const updateGeneratorModel = ({ model, value }: { model: string, value: any }) => {
// eslint-disable-next-line vue/no-mutating-props
props.model[model] = value
}
Expand All @@ -54,7 +35,8 @@ const updateGeneratorModel = ({ model, value }) => {
* @param fieldErrors errors discovered during validation of the field.
* @param field field schema object that has been validated.
*/
const onFieldValidated = ({ fieldErrors, field }) => {
const onFieldValidated = ({ fieldErrors, field }: FieldValidation) => {
emits('field-validated', { fieldErrors, field })
if (!fieldErrors.length) {
if (!(field.model in formErrors.value)) return
else {
Expand All @@ -67,22 +49,23 @@ const onFieldValidated = ({ fieldErrors, field }) => {

/** Compute if the form has errors */
const hasErrors = computed(() => {
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e === true).length)
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e).length)
})

/**
* Handle the submit event from the form element.
*/
const onSubmit = () => {
if (hasErrors.value === false) emits('submit')
if (!hasErrors.value) emits('submit')
}

const onReset = () => {
// eslint-disable-next-line vue/no-mutating-props
props.model = resetObjectProperties(props.model)
// Hideous hack to update the model, without TypeScript crying about mutation of props. And yes, I know it isn't
// recommended to update the props.
(props as any).model = resetObjectProperties(props.model)
}

defineExpose({ hasErrors })
defineExpose({ hasErrors, formErrors })
</script>

<template>
Expand All @@ -95,8 +78,8 @@ defineExpose({ hasErrors })
>
<fieldset v-if="props.schema.fields">
<template v-for="field in props.schema.fields" :key="field">
<form-group
:ref="el => fieldElements.push(el)"
<FormGroup
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
:form-options="formOptions"
:field="field"
:model="props.model"
Expand All @@ -110,8 +93,8 @@ defineExpose({ hasErrors })
{{ group.legend }}
</legend>
<template v-for="field in group.fields" :key="field">
<form-group
:ref="el => fieldElements.push(el)"
<FormGroup
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
:form-options="formOptions"
:field="field"
:model="props.model"
Expand Down
5 changes: 3 additions & 2 deletions src/composables/useFieldAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { computed, ComputedRef } from 'vue'
import { type Field } from '@/resources/types/fields'
import type { Field } from '@/resources/types/field/fields'
import { TDynamicAttributeBooleanFunction, TDynamicAttributeStringFunction } from '@/resources/types/functions'
import type { FormModel } from '@/resources/types/fieldAttributes'

export function useFieldAttributes (
model: Record<string, any>,
model: FormModel,
field: Field
) {

Expand Down
5 changes: 3 additions & 2 deletions src/composables/useFieldValidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { getMessage } from '@/validators/messages'
import { isFunction, isString, toUniqueArray } from '@/helpers'
import { TValidatorFunction } from '@/resources/types/functions'
import { ValidatorMap } from '@/resources/types/generic'
import { Field } from '@/resources/types/fields'
import type { FormModel } from '@/resources/types/fieldAttributes'
import type { Field } from '@/resources/types/field/fields'
import validators from '@/validators'

/**
Expand All @@ -27,7 +28,7 @@ function getValidator (validator: string | TValidatorFunction | undefined): TVal
}

export function useFieldValidate (
model: Record<string, any>,
model: FormModel,
field: Field,
isDisabled: boolean = false,
isRequired: boolean = false,
Expand Down
Loading