After you clone or download the starter repository, you will find yourself with a Vue CLI 3 project. The first thing to do is to take a look at what we are going to be working with! The repository contains a very simple form with some input fields and a select box. You can find the structure for the form in App.vue. As you can see, we are using two different custom components, BaseInput and BaseSelect. Both of these can be found inside the src/components folder. They both wrap an input and select tag, respectively, and expose some properties that we can use to inject the necessary data into each of them, such as labels and options.
I have taken the liberty of already adding Axios to the project dependencies; you can check out package.json to corroborate. Bootstrap's CSS file for some base classes has been imported inside main.js.
Now that we have a good overview of the project structure, let's go ahead and install the dependencies and run them on our browser. Follow these steps:
Now that you have an understanding of the starting point of our application, we are going to prepare the demo schema in the next section.
Currently, our form (as previously stated) is hardcoded. The first step that is required to start making it a dynamic form is to remove the need to add BaseInput or BaseSelect directly to our App.vue file every time we need to add a new field. This implies that we are going to need to have some sort of organized structure, or schema, to represent what we are trying to accomplish for our form. Since we are using JavaScript, the most logical way to do this is with a JSON object format. This will make it easier later on when we want to take it a step further and have our mock API feed the information directly to our form.
For now, we will use a static schema. Let's create a data folder inside src, and inside of it, make a new schema.json file. We are going to populate our JSON file with some dummy data. I have chosen, for the sake of an example, to make the top element an object, and each property inside of it will represent one of the fields in our form. Each element will consist of at least a component property and a label property. In the case of drop-down menus, however, we will also include options to populate it.
To create the demo schema, add the following data to schema.json:
{ "firstName": {
"component": "BaseInput",
"label": "First name"
},
"lastName": {
"component": "BaseInput",
"label": "Last name"
},
"favoriteAnimal": {
"component": "BaseSelect",
"label": "What's your favorite animal?",
"options": [
{ "label": "Cat", "value": "cat" },
{ "label": "Dog", "value": "dog" },
{ "label": "Sea Otter", "value": "onlyvalidanswer" }
]
}
} Now that we have a structured schema as a demo of what we want our dynamic form to understand, we can proceed to the next section—where we will load this schema into our application with the help of a Renderer component.
Now that we have a basic schema set up to work with, let's go ahead and load into the application so that we can use it. Later on in this chapter, we are going to create a dummy API that will feed us the data in a slightly different way, and we will transform it on our end to fit our app's requirements.
For now, let's go to App.vue and import the JSON. We will start by adding the following import statement to the top near the other import statements:
import schema from '@/data/schema.json';
Now that we have our data available to our application, we need some components to be able to parse this information into the BaseInput and BaseSelect components. Let's go ahead and create a new file inside the components folder, and name it Renderer.vue. This component will have a single purpose: to understand our schema and render the correct component to the screen. It will currently have a single property, element, which represents each of the elements in our schema. To do so, add the following code to Renderer.vue:
<template>
<component
:is="component"
v-bind="props"
/>
</template>
<script>
export default {
props: {
element: {
type: Object,
required: true
}
},
computed: {
component() {
const componentName = this.element.component;
return () => import(`./${componentName}`);
},
props() {
return this.element;
}
}
}
</script>
There's a couple of important things to note in this component. They are as follows:
- The element prop is an object and will be required. This component will not work at all without it. We have two computed properties. The first component takes care of dynamically loading whichever element we need. First, we create a componentName constant and assign it to the value of element.component, which is where the string name of our component is stored in the schema.
- It's important to mention that we are not just adding this const for clarity purposes. The way that computed properties work regarding caching requires that this const exists here since we are returning a function, which will not be inspected for dependencies.
- When this computed property is called by the <component> tag for the : is an attribute—it will load the component and pass it over. Note that this will only work if the component is globally registered; in any other case, a computed property that requires the correct component would be needed. For further information on dynamic components, check out the official documentation: https://vuejs.org/v2/guide/components-dynamic-async.html.
The second computed property, props, will simply pass down the whole element with its properties as props to whatever component we are loading using the v-on binding. For example, on the BaseSelect component, it will pass down the options property in our schema to the component so that it can render the correct options. If you are wondering why we are using a computed property instead of just passing the element directly to the v-on directive, you are on the right track. Right now, it is definitely not needed, but having it set up in this way to begin with allows us to, later on, add another level of logic or parsing that could be needed for a particular component.
Let's head back to App.vue.
We need to import our Renderer component and add it to the template. We also need to clean ...