Skip to content

Commit 0f182da

Browse files
authored
Merge pull request #33 from kevinkosterr/master
Pending: New Release
2 parents cc63658 + ec8bb81 commit 0f182da

38 files changed

+824
-269
lines changed

apps/docs/.vitepress/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export default defineConfig({
5858
{ text: 'FieldColor', link: '/guide/fields/FieldColor' },
5959
{ text: 'FieldMask', link: '/guide/fields/FieldMask' },
6060
{ text: 'FieldNumber', link: '/guide/fields/FieldNumber' },
61+
{ text: 'FieldObject', link: '/guide/fields/FieldObject' },
6162
{ text: 'FieldPassword', link: '/guide/fields/FieldPassword' },
6263
{ text: 'FieldRadio', link: '/guide/fields/FieldRadio' },
6364
{ text: 'FieldReset', link: '/guide/fields/FieldReset' },
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
Person: <code>{{ form.model.person }}</code>
3+
<vue-form-generator :schema="form.schema" :model="form.model" />
4+
</template>
5+
6+
<script setup>
7+
import { onBeforeMount, ref } from 'vue'
8+
9+
const props = defineProps({
10+
addValidators: Boolean
11+
})
12+
13+
const form = ref({
14+
model: {
15+
person: {
16+
name: '',
17+
surname: '',
18+
age: null
19+
}
20+
},
21+
schema: {
22+
fields: [
23+
{
24+
type: 'object',
25+
model: 'person',
26+
schema: {
27+
fields: [
28+
{
29+
type: 'input',
30+
inputType: 'text',
31+
model: 'name',
32+
label: 'Name'
33+
},
34+
{
35+
type: 'input',
36+
inputType: 'text',
37+
model: 'surname',
38+
label: 'Surname'
39+
},
40+
{
41+
type: 'input',
42+
inputType: 'number',
43+
model: 'age',
44+
label: 'Age'
45+
}
46+
]
47+
}
48+
}
49+
]
50+
}
51+
})
52+
53+
onBeforeMount(() => {
54+
if (props.addValidators) {
55+
const fields = form.value.schema.fields[0].schema.fields
56+
57+
const minLengthThree = (value) => value && value.length >= 3
58+
fields[0].validator = minLengthThree
59+
fields[1].validator = minLengthThree
60+
fields[2].validator = (value) => value && value >= 18
61+
}
62+
})
63+
</script>

apps/docs/guide/fields/FieldObject.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# FieldObject
2+
`FieldObject` is a field that has its own `schema`, meaning the field itself
3+
renders other fields. These fields will return their values to the object inside
4+
the model that is assigned to the `FieldObject` component.
5+
6+
### type `object`
7+
8+
<script setup>
9+
import FieldObjectExample from '/components/examples/fields/FieldObjectExample.vue'
10+
</script>
11+
12+
## Basic example
13+
::: details Code
14+
```js
15+
const form = ref({
16+
model: {
17+
person: {
18+
name: '',
19+
surname: '',
20+
age: null
21+
}
22+
},
23+
schema: {
24+
fields: [
25+
{
26+
type: 'object',
27+
model: 'person',
28+
schema: {
29+
fields: [
30+
{
31+
type: 'input',
32+
inputType: 'text',
33+
model: 'name',
34+
label: 'Name'
35+
},
36+
{
37+
type: 'input',
38+
inputType: 'text',
39+
model: 'surname',
40+
label: 'Surname'
41+
},
42+
{
43+
type: 'input',
44+
inputType: 'number',
45+
model: 'age',
46+
label: 'Age'
47+
}
48+
]
49+
}
50+
}
51+
]
52+
}
53+
})
54+
```
55+
:::
56+
<FieldObjectExample/>
57+
58+
## With validators
59+
::: details Code
60+
```js
61+
function minLengthThree (value) {
62+
return value && value.length >= 3
63+
}
64+
65+
function overEighteen (value) {
66+
return value && value >= 18
67+
}
68+
69+
// ......
70+
fields: [
71+
{
72+
type: 'object',
73+
model: 'person',
74+
schema: {
75+
fields: [
76+
{
77+
type: 'input',
78+
inputType: 'text',
79+
model: 'name',
80+
label: 'Name',
81+
validator: minLengthThree
82+
},
83+
{
84+
type: 'input',
85+
inputType: 'text',
86+
model: 'surname',
87+
label: 'Surname',
88+
validator: minLengthThree
89+
},
90+
{
91+
type: 'input',
92+
inputType: 'number',
93+
model: 'age',
94+
label: 'Age',
95+
validator: overEighteen
96+
}
97+
]
98+
}
99+
}
100+
]
101+
```
102+
:::
103+
::: info
104+
In this example, `name` and `surname` must have a length of three letters or more, `age` must be at least 18.
105+
:::
106+
<FieldObjectExample add-validators/>
107+
108+
109+
## Properties
110+
| Property | Default | Type | Description |
111+
|----------|---------|----------|---------------------------------------------|
112+
| schema | `{}` | `Object` | A form schema, as seen in `FormGenerator.vue` |
Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
11
These are events emitted by the `vue-form-generator` component.
22

3+
## `field-validated`
4+
Emitted when a field inside the form has been validated.
5+
6+
### Event arguments
7+
- `validations` - an object with the field's errors and the field schema
8+
- `fieldErrors` - an array of error messages that have been thrown during validations;
9+
- `field` - the field as defined in the schema
10+
11+
An example from the [`FieldObject`](/guide/fields/FieldObject) component:
12+
```vue [FieldObject.vue]
13+
<script setup>
14+
const onFieldValidated = (validation) => {
15+
const key = `${field.value.model}.${validation.field.model}`
16+
emits(
17+
'validated',
18+
validation.fieldErrors.length === 0,
19+
validation.fieldErrors,
20+
{ ...field.value, model: key }
21+
)
22+
}
23+
</script>
24+
```
25+
326
## `submit`
4-
Emitted when all fields have been validated and no errors occurred during said validations.
27+
Emitted when all fields have been validated and no errors occurred during said validations.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"main": "./dist/vue3-form-generator.js",
1010
"scripts": {
1111
"dev": "vite",
12-
"dev:sass": "sass --watch src/scss/themes:playground/css/",
12+
"dev:sass": "sass --watch src/scss/themes:apps/playground/css/",
1313
"test": "vitest",
1414
"build": "vite build && sass src/scss/themes/:dist/themes/",
1515
"preview": "vite preview",
@@ -79,4 +79,4 @@
7979
"@vueuse/core": "^13.1.0",
8080
"maska": "^3.1.1"
8181
}
82-
}
82+
}

src/FormGenerator.vue

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,31 @@
1-
<script setup>
1+
<script setup lang="ts">
2+
import type { FieldValidation, FormGeneratorProps, FormOptions } from '@/resources/types/generic'
3+
import type { Field } from '@/resources/types/field/fields'
4+
import type { ComponentPublicInstance, ComputedRef, Ref } from 'vue'
25
import { computed, ref } from 'vue'
36
import { resetObjectProperties, toUniqueArray } from '@/helpers'
47
import FormGroup from './FormGroup.vue'
58
6-
const emits = defineEmits([ 'submit' ])
9+
const emits = defineEmits([ 'submit', 'field-validated' ])
710
8-
const props = defineProps({
9-
id: {
10-
type: String,
11-
required: false,
12-
default: ''
13-
},
14-
idPrefix: {
15-
type: String,
16-
required: false,
17-
default: ''
18-
},
19-
options: {
20-
type: Object,
21-
default: () => ({})
22-
},
23-
schema: {
24-
type: Object,
25-
required: true
26-
},
27-
model: {
28-
type: Object,
29-
required: true
30-
},
31-
enctype: {
32-
type: String,
33-
default: 'application/x-www-form-urlencoded'
34-
}
11+
const props = withDefaults(defineProps<FormGeneratorProps>(), {
12+
enctype: 'application/x-www-form-urlencoded',
13+
id: '',
14+
idPrefix: ''
3515
})
3616
17+
type FormGroupInstance = ComponentPublicInstance<InstanceType<typeof FormGroup>>
3718
/** Data / Refs */
38-
const fieldElements = ref([])
39-
const formErrors = ref({})
40-
const formOptions = computed(() => ({ ...props.options, idPrefix: props.idPrefix }))
19+
const fieldElements: Ref<FormGroupInstance[]> = ref([])
20+
const formErrors: Ref<Record<string, any>> = ref({})
21+
const formOptions: ComputedRef<FormOptions> = computed(() => ({ ...props.options, idPrefix: props.idPrefix }))
4122
4223
/**
4324
* Update form model key with its new value.
4425
* @param {String} model model key to update.
4526
* @param {any} value value to set.
4627
*/
47-
const updateGeneratorModel = ({ model, value }) => {
28+
const updateGeneratorModel = ({ model, value }: { model: string, value: any }) => {
4829
// eslint-disable-next-line vue/no-mutating-props
4930
props.model[model] = value
5031
}
@@ -54,7 +35,8 @@ const updateGeneratorModel = ({ model, value }) => {
5435
* @param fieldErrors errors discovered during validation of the field.
5536
* @param field field schema object that has been validated.
5637
*/
57-
const onFieldValidated = ({ fieldErrors, field }) => {
38+
const onFieldValidated = ({ fieldErrors, field }: FieldValidation) => {
39+
emits('field-validated', { fieldErrors, field })
5840
if (!fieldErrors.length) {
5941
if (!(field.model in formErrors.value)) return
6042
else {
@@ -67,22 +49,23 @@ const onFieldValidated = ({ fieldErrors, field }) => {
6749
6850
/** Compute if the form has errors */
6951
const hasErrors = computed(() => {
70-
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e === true).length)
52+
return Boolean(Object.values(formErrors.value).map(e => Boolean(e.length)).filter(e => e).length)
7153
})
7254
7355
/**
7456
* Handle the submit event from the form element.
7557
*/
7658
const onSubmit = () => {
77-
if (hasErrors.value === false) emits('submit')
59+
if (!hasErrors.value) emits('submit')
7860
}
7961
8062
const onReset = () => {
81-
// eslint-disable-next-line vue/no-mutating-props
82-
props.model = resetObjectProperties(props.model)
63+
// Hideous hack to update the model, without TypeScript crying about mutation of props. And yes, I know it isn't
64+
// recommended to update the props.
65+
(props as any).model = resetObjectProperties(props.model)
8366
}
8467
85-
defineExpose({ hasErrors })
68+
defineExpose({ hasErrors, formErrors })
8669
</script>
8770

8871
<template>
@@ -95,8 +78,8 @@ defineExpose({ hasErrors })
9578
>
9679
<fieldset v-if="props.schema.fields">
9780
<template v-for="field in props.schema.fields" :key="field">
98-
<form-group
99-
:ref="el => fieldElements.push(el)"
81+
<FormGroup
82+
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
10083
:form-options="formOptions"
10184
:field="field"
10285
:model="props.model"
@@ -110,8 +93,8 @@ defineExpose({ hasErrors })
11093
{{ group.legend }}
11194
</legend>
11295
<template v-for="field in group.fields" :key="field">
113-
<form-group
114-
:ref="el => fieldElements.push(el)"
96+
<FormGroup
97+
:ref="el => (el && '$el' in el) ? fieldElements.push((el as FormGroupInstance)) : null"
11598
:form-options="formOptions"
11699
:field="field"
117100
:model="props.model"

src/composables/useFieldAttributes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { computed, ComputedRef } from 'vue'
2-
import { type Field } from '@/resources/types/fields'
2+
import type { Field } from '@/resources/types/field/fields'
33
import { TDynamicAttributeBooleanFunction, TDynamicAttributeStringFunction } from '@/resources/types/functions'
4+
import type { FormModel } from '@/resources/types/fieldAttributes'
45

56
export function useFieldAttributes (
6-
model: Record<string, any>,
7+
model: FormModel,
78
field: Field
89
) {
910

src/composables/useFieldValidate.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import { getMessage } from '@/validators/messages'
33
import { isFunction, isString, toUniqueArray } from '@/helpers'
44
import { TValidatorFunction } from '@/resources/types/functions'
55
import { ValidatorMap } from '@/resources/types/generic'
6-
import { Field } from '@/resources/types/fields'
6+
import type { FormModel } from '@/resources/types/fieldAttributes'
7+
import type { Field } from '@/resources/types/field/fields'
78
import validators from '@/validators'
89

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

2930
export function useFieldValidate (
30-
model: Record<string, any>,
31+
model: FormModel,
3132
field: Field,
3233
isDisabled: boolean = false,
3334
isRequired: boolean = false,

0 commit comments

Comments
 (0)