18

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
  }
}

3 Answers 3

42

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>
Sign up to request clarification or add additional context in comments.

6 Comments

This could work if I controlled the component directly. I am using Quasar Framework atm. And for a button you need to put text between <q-btn>Text</q-btn> as seen here: quasar-framework.org/components/button.html#Basic-Usage ... Also it can accept some custom attributes like "round" etc. Using your example would mean I need to wrap each and every component of the framework depending on what they need as input. Is there an easier way in this use case?
Updated the answer
Thanks, this seems to work. One last question, how would you pass on things like "@click" and "v-bind"?
just add a click function to the options and set it on the component like: @click="options.onClick"
I tried that but it gives me: "Error in event handler for "click": "TypeError: fns.apply is not a function". I made a method named "submitForm" and passed that in options.onClick: "submitForm"
|
3

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'}"/>

Comments

1

@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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.