Open console to view form payloads.
.
├── package.json
├── public
│ ├── favicon.ico
│ └── index.html
├── README.md
└── src
├── App.vue // root component
├── components
│ ├── base
│ │ ├── BaseInput.vue
│ │ └── BaseSelect.vue
│ ├── BasicForm.vue
│ ├── DynamicFormField.vue
│ └── UpdateUserForm.vue
├── containers
│ └── FormContainer.vue
├── data
│ ├── mainFormSchema.json
│ ├── secondFormSchema.json
│ └── users.js
├── main.js
├── mixins
│ └── validation.js // used to provide validation functionality
└── utils
├── schemaParser.js
└── validators.js // validators used by validation mixin
Communication between components sending props from parent to child and emitting events and subsequent data changes using $emit
.
In the props-down and events-up pattern, the parent component’s data object is the single source of truth for the whole application. Child components should receive data from the parent’s data object via props — “props-down”. And parent component should update its state by handling events emitted from child components — “events-up”.
FormContainer in this case acting as the container holding the state and source of truth.
The the form value state is held in form
and the state of validations against the form in formValidations
.
data() {
return {
form: {},
formValidations: {},
};
},
These values are updated on input
events via v-model=form[name]
and update
events handled in onValueUpdate
. You can read more about how v-model works here.
Note the use of v-on="$listeners"
in each usage of FormContainer
. This technique allows for the listeners from DynamicForm
and UpdateUserForm
(the parent) to be applied to FormContainer
(the child). In this case the only listener is the submit
event, which is transparently passed from FormContainer
to the parent component.
Any event can be passed from the child to the parent for example:
<DynamicForm @error="onError" @submit="submitDynamicForm" />
Here, onError
would be triggered if FormContainer
emitted an "error" event like so $emit('error', value)
, any custom or DOM event from a child can be listened to by the parent using v-on="$listeners"
without explicitly passing that event from child to parent.
However, as we are only listening to the submit
event, the same result can be achieved by explicitly emitting the event up the component tree like so (as seen in StaticForm
):
<FormContainer @submit="$emit('submit', $event)">
....
</FormContainer>
v-on="$listeners" is similar in concept to $attrs. So much so they are combined them in Vue 3. They are a powerful part of Vue's API, but like slots and dynamic components can be a little abstract initially.
The form layout can be cutomised using named slots. Default values are provided in the field
by using the DynamicFormField
component for each element in the schema and action
slots which must be a button of type submit
in order to trigger the submit handler, there is an optional header
layout used in UpdateUserForm
.
Dynamic fields are generated using vues dynamic component functionality. The component name is setup in schemaParser.js and dynamically imported and returned in the a computed property of DynamicFormField
Validations are dynamically assigned to fields via createValidations
found here, those validations are then applied via a mixin. The application and interface to apply validations is largely inspired by vuelidate.
When the "action" button is clicked it will trigger form validation which checks each value in the formValidations
state and ensures that all have passed. The initial formValidations are setup in the created
lifecycle hook of the DynamicFormField
.
The submit
event is emitted on form submition using @submit.prevent,note the use of the .prevent
modifier which prevents the default action of the form allowing the event to be handled in the onSubmit
method.
Using the header
slot to add in a BaseSelect
component to allow for selection of a user from a list which is generated by faker. Once a user is selected the values are used to provide initialFormValues
which are applied to the form
data. The initialFormValues
prop is watched in FormContainer
and applied on to form
state on value change.