Reputation: 1297
I am making a form generator, which uses components in it for input fields, buttons etc. I want to be able to generate the form depending on what options I pass to it.
But I can't get it to render the components.
I tried to return pure HTML but that won't render the components.
I call the form generator from my Home.vue template where I want the form with an options object like this:
options: {
name: {
type: 'input',
label: 'Name'
},
submit: {
type: 'button',
label: 'Send'
}
}
In template:
<template>
<form-generator :options="options"></form-generator>
</template>
In the form generator component I have tried multiple things like:
<template>
{{ generateForm(this.options) }}
// ... or ...
<div v-html="generateForm(this.options)"></div>
</template>
I include all the components like:
import {
FormButton,
FormInput
} from './FormComponents'
Now the final part is how do I make FormInput
render?
This does not work since it outputs the HTML literally:
methods: {
generateForm(options) {
// .. do stuff with options ..
var form = '<form-input />'
return form
}
}
Upvotes: 18
Views: 35908
Reputation: 17940
Vue has a very simple way of generating dynamic components:
<component :is="dynamicComponentName"></component>
So I suggest you define the options as an array and set the type to be the component name:
options: [
{
type: 'FormInput',
propsData: {label: 'Name'}
},
{
type: 'FormButton',
propsData: {label: 'Send'}
}
]
Then use it in the form generator like this:
<component :is="option.type" v-for="option in options"></component>
You can also pass properties as you'd pass to any other component, but since it's dynamic and every component has a different set of properties i would pass it as an object and each component would access the data it needs:
<component :is="option.type" v-for="option in options" :data="option.propsData"></component>
UPDATE
Since you don't have control of the components it requires a bit more manipulation:
For each component that requires text, add a text attribute in the options:
options: [
{
type: 'FormInput',
propsData: {label: 'Name'}
},
{
type: 'FormButton',
text: 'Send',
propsData: {label: 'Send'}
}
]
And then just use it in the component:
<component :is="option.type" v-for="option in options">{{option.text}}</component>
For passing attributes, I think you can pass it using v-bind and then it will automatically destructure them, so if a button accepts 2 props: rounded, color
the options would look like:
{
type: 'FormButton',
text: 'Button',
propsData: {rounded: true, color: '#bada55'}
}
and then the component:
<component :is="option.type" v-for="option in options" v-bind="option.propsData">{{option.text}}</component>
Upvotes: 42
Reputation: 4668
@tomer's answer is really good. And I think it's correct.
Just in my case, we had a special Vite configuration that required me to pass the actual component instance rather than by a string of the name of the component:
import FormInput from '@components/form/FormInput'
import FormButton from '@components/form/FormButton'
options: [
{
type: FormInput,
propsData: {label: 'Name'}
},
{
type: FormButton,
propsData: {label: 'Send'}
}
]
<component :is="option.type" v-for="option in options"></component>
Upvotes: 1
Reputation: 654
you can create an Array like this:
components_data: [
{
name: 'checkbox',
data: false
},
{
name: 'text',
data: 'Hello world!'
}
]
and then loop through this array inside of the <component>
:
<component
v-for="(component,i) in components_data"
:key="i"
:is="component.name"
:data="component.data"
/>
this will create 2 component [<text>, <checkbox>]
dynamically and give them data via props.
when you push new data like this components_data.push({name:'image',data: {url:'cat.jpg'}})
it will render a new component as <image :data="{url:'cat.jpg'}"/>
Upvotes: 3