Skip to content

Commit 80194eb

Browse files
authored
Merge pull request #32 from kevinkosterr/#28
#28 FieldObject
2 parents 4bc1341 + 2a4c5d7 commit 80194eb

File tree

11 files changed

+425
-7
lines changed

11 files changed

+425
-7
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: 1 addition & 1 deletion
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",

src/FormGenerator.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { computed, ref } from 'vue'
33
import { resetObjectProperties, toUniqueArray } from '@/helpers'
44
import FormGroup from './FormGroup.vue'
55
6-
const emits = defineEmits([ 'submit' ])
6+
const emits = defineEmits([ 'submit', 'field-validated' ])
77
88
const props = defineProps({
99
id: {
@@ -55,6 +55,7 @@ const updateGeneratorModel = ({ model, value }) => {
5555
* @param field field schema object that has been validated.
5656
*/
5757
const onFieldValidated = ({ fieldErrors, field }) => {
58+
emits('field-validated', { fieldErrors, field })
5859
if (!fieldErrors.length) {
5960
if (!(field.model in formErrors.value)) return
6061
else {
@@ -82,7 +83,7 @@ const onReset = () => {
8283
props.model = resetObjectProperties(props.model)
8384
}
8485
85-
defineExpose({ hasErrors })
86+
defineExpose({ hasErrors, formErrors })
8687
</script>
8788

8889
<template>

src/fields/core/FieldObject.vue

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<template>
2+
<div class="field-object">
3+
<FormGenerator
4+
ref="formGenerator"
5+
:schema="field.schema"
6+
:model="currentModelValue"
7+
:options="props.formOptions"
8+
@field-validated="onFieldValidated"
9+
/>
10+
</div>
11+
</template>
12+
13+
<script setup>
14+
import FormGenerator from '@/FormGenerator.vue'
15+
import { useFieldProps, useFieldEmits, useFormModel } from '@/composables'
16+
import { toRefs, useTemplateRef, computed } from 'vue'
17+
18+
const emits = defineEmits(useFieldEmits())
19+
const props = defineProps(useFieldProps())
20+
21+
const formGenerator = useTemplateRef('formGenerator')
22+
const hasErrors = computed(() => formGenerator.value?.hasErrors ?? false)
23+
24+
const { field, model } = toRefs(props)
25+
26+
const { currentModelValue } = useFormModel(model.value, field.value)
27+
28+
/**
29+
* Emits the validated event
30+
* @param validation
31+
*/
32+
const onFieldValidated = (validation) => {
33+
const key = `${field.value.model}.${validation.field.model}`
34+
emits(
35+
'validated',
36+
validation.fieldErrors.length === 0,
37+
validation.fieldErrors,
38+
{ ...field.value, model: key }
39+
)
40+
}
41+
42+
defineExpose({ hasErrors })
43+
</script>

src/fields/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import FieldTextarea from '@/fields/core/FieldTextarea.vue'
1313
import FieldMask from '@/fields/core/FieldMask.vue'
1414
import FieldChecklist from '@/fields/core/FieldChecklist.vue'
1515
import FieldCheckbox from '@/fields/core/FieldCheckbox.vue'
16+
import FieldObject from '@/fields/core/FieldObject.vue'
1617

1718
import FieldSubmit from '@/fields/core/FieldSubmit.vue'
1819
import FieldReset from '@/fields/core/FieldReset.vue'
@@ -22,7 +23,7 @@ import FieldButton from '@/fields/core/FieldButton.vue'
2223
const fieldComponents = {
2324
FieldColor, FieldText, FieldCheckBox, FieldPassword, FieldSelect, FieldSelectNative, FieldRadio,
2425
FieldNumber, FieldSubmit, FieldReset, FieldButton, FieldSwitch, FieldTextarea, FieldMask, FieldChecklist,
25-
FieldCheckbox
26+
FieldCheckbox, FieldObject
2627
} as const
2728

2829
type FieldComponentNames = keyof typeof fieldComponents

tests/_resources/utils.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ export function generateSchemaSingleField (
4949
/**
5050
* Generate props for a single field component
5151
* @param {Object} formSchema - entire form schema object
52-
* @returns {{field: *, model, id: string, formGenerator: {}}}
52+
* @returns {{field: *, model, id: string, formOptions: {}}}
5353
*/
5454
export function generatePropsSingleField (formSchema) {
5555
return {
5656
id: formSchema.name + '_test_id',
57-
formGenerator: {},
57+
formOptions: {},
5858
field: { ...formSchema.schema.fields[0] },
5959
model: { ...formSchema.model }
6060
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { expect, it, describe, beforeAll } from 'vitest'
2+
import { config, mount } from '@vue/test-utils'
3+
4+
import FormGenerator from '@/FormGenerator.vue'
5+
import FieldText from '@/fields/core/FieldText.vue'
6+
import FieldTextarea from '@/fields/core/FieldTextarea.vue'
7+
import FieldSubmit from '@/fields/core/FieldSubmit.vue'
8+
import { generateSchemaSingleField } from '@test/_resources/utils.js'
9+
10+
beforeAll(() => {
11+
config.global.components = { FieldText, FieldTextarea, FieldSubmit }
12+
})
13+
14+
const textSchema = generateSchemaSingleField(
15+
'text',
16+
'textModel',
17+
'input',
18+
'text',
19+
'Text input label',
20+
'',
21+
{
22+
required: true
23+
}
24+
)
25+
26+
const textAreaSchema = generateSchemaSingleField(
27+
'textArea',
28+
'textAreaModel',
29+
'textarea',
30+
null,
31+
'Text area label',
32+
'',
33+
{}
34+
)
35+
36+
const schema = {
37+
schema: { fields: [ ...textSchema.schema.fields, ...textAreaSchema.schema.fields ] },
38+
model: { ...textSchema.model, ...textAreaSchema.model }
39+
}
40+
41+
describe('FormGenerator', () => {
42+
43+
it('Shouldn\'t render without a schema', async () => {
44+
const wrapper = mount(FormGenerator)
45+
expect(wrapper.find('form').exists()).toBeFalsy()
46+
})
47+
48+
it('Should render with a schema', async () => {
49+
const wrapper = mount(FormGenerator, { props: { model: schema.model, schema: schema.schema } })
50+
expect(wrapper.find('form').exists()).toBeTruthy()
51+
expect(wrapper.findComponent(FieldText).exists()).toBeTruthy()
52+
expect(wrapper.findComponent(FieldTextarea).exists()).toBeTruthy()
53+
})
54+
55+
})

0 commit comments

Comments
 (0)