Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically adding different components in Vue

I want to create a simple form builder with Vue where users click on buttons from a menu to add different form fields to a form. I know that if there was just one type of form field to add, I could do it with something like this (https://jsfiddle.net/u6j1uc3u/32/):

<div id="app">
  <form-input v-for="field in fields"></form-input>

  <button type="button" v-on:click="addFormElement()">Add Form Element</button>
</div>

<script type="x-template" id="form-input">
  <div>
    <label>Text</label>
    <input type="text" />
  </div>
</script>

And:

Vue.component('form-input', {
  template: '#form-input'
});

new Vue({
  el: '#app',
  data: {
    fields: [],
    count: 0
  },

  methods: {
    addFormElement: function() {
      this.fields.push({type: 'text', placeholder: 'Textbox ' + (++this.count)});
    }
  }
})

But what if there's more than one type of form field (input, file, select, etc...)? I was thinking maybe build a different component for each type, but then how would I show multiple types of components in a single list of form elements?

Could I maybe create a component with children components of different types based on the data in the fields array?

Or is there a better way to go about this situation that I'm missing? I've just started learning Vue, so any help is appreciated!

like image 449
Chris Avatar asked May 18 '18 20:05

Chris


People also ask

How do I dynamically load components in Vue?

To make the component dynamic, we can bind it to a set property with the v-bind directive. Your component is now bound with the component property in the data. If you switch the component to Test2 , it will automatically mount the Test 2 component. Test it out on your browser.

How do I create a dynamic component in Vue 3?

To create a dynamic component, we use the component element and bind the is prop to it to specify which component we want to render. note The component and slot elements aren't true components, they are component-like features of Vue's template syntax. We do not have to import them like we do with regular components.

How do I add a class dynamically on Vue?

Adding a dynamic class name is as simple as adding the prop :class="classname" to your component. Whatever classname evaluates to will be the class name that is added to your component.

How do I include dynamic components in a Vue app?

It's bare-bones, just the mentioned functionality. Let's create a default Vue app using vue create your-app and add the router plugin with vue add router. Then, create a folder within components where you're going to put you components you want to include dynamically, let's say, dynComps

How do I mount a component in Vue?

You don’t need to mount anything, or create new instances of vue components. You only should define your data/props and template properly, vue will do everything for you. Instead using ‘displayWidget’ method, just create separated component, define it in parent ‘components’ attribute and put it in template.

How to achieve dynamic components with conditional structures in Vue?

The feature saves you from a lot of code since you can easily achieve dynamic components with Vue conditional structures such as v-if and v-else. You can use conditional structures to achieve dynamic components by using a placeholder approach to easily bind logic to the component.

How do I reference HTML elements in my Vue components?

This post serves as an introduction to the ways you can reference HTML elements in your components in Vue.js. You can toggle between views or component templates by using the Vue router or by creating dynamic components. The Vue router is used to navigate between views or component templates in the DOM.


Video Answer


3 Answers

Ok, so I looked into dynamic elements and managed to pull this together:

Vue.component('form-input', {
  template: '#form-input'
});

Vue.component('form-select', {
  template: '#form-select'
});

Vue.component('form-textarea', {
  template: '#form-textarea'
});

new Vue({
  el: '#app',
  data: {
    fields: [],
    count: 0
  },

  methods: {
    addFormElement: function(type) {
      this.fields.push({
        'type': type,
        id: this.count++
      });
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<div id="app">
  <component v-for="field in fields" v-bind:is="field.type" :key="field.id"></component>

  <button type="button" v-on:click="addFormElement('form-input')">Add Textbox</button>
  <button type="button" v-on:click="addFormElement('form-select')">Add Select</button>
  <button type="button" v-on:click="addFormElement('form-textarea')">Add Textarea</button>
</div>

<script type="x-template" id="form-input">
  <div>
    <label>Text</label>
    <input type="text" />
  </div>
</script>

<script type="x-template" id="form-select">
  <div>
    <label>Select</label>
    <select>
      <option>Option 1</option>
      <option>Option 2</option>
    </select>
  </div>
</script>

<script type="x-template" id="form-textarea">
  <div>
    <label>Textarea</label>
    <textarea></textarea>
  </div>
</script>

So instead of creating a new form-input component for each item in the fields array, I'm creating a new component that is associated with the correct component via the type property of the fields

like image 71
Chris Avatar answered Oct 24 '22 11:10

Chris


You can pass the field object as props of your form-input component and make the type dynamic:

Vue.component('form-input', {
  template: '#form-input',
  props: ['field']
})

new Vue({
  el: '#app',
  data: {
    fields: [],
    inputType: '',
    count: 0
  },
  methods: {
    addFormElement(val) {
      this.fields.push({type: val, placeholder: 'Textbox ' + (++this.count)});
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>
<div id="app">
  <h3>Add form element</h3>
  <select size="3" v-model='inputType' @click="addFormElement(inputType)">
    <option value="text">Text</option>
    <option value="checkbox">Checkbox</option>
    <option value="radio">Radio</option>
  </select>
  <p>
    <form-input v-for="field in fields" :field="field"></form-input>
  </p>
</div>

<template id="form-input">
  <div>
    <label>{{ field.type }}</label>
    <input :type="field.type" />
  </div>
</template>
like image 1
Sovalina Avatar answered Oct 24 '22 11:10

Sovalina


Based on the code from the answer, one could add dynamic content for each one of those form controls as well ( the full concept could be seen from the following site):

   Vue.component('form-input', {
  template: '#form-input'
  , props: ['label','cnt']
   });

Vue.component('form-select', {
 template: '#form-select'
 , props: ['label','cnt']
});

Vue.component('form-textarea', {
   template: '#form-textarea'
   , props: ['label','cnt']
   });
new Vue({
  el: '#app',
  data: {
    fields: [],
    count: 0
  }
  , mounted() {
    // fetch those from back-end
    this.addFormElement('form-input','lbl', "form-input-content")
    this.addFormElement('form-textarea','lbl', "form-textarea-content")
    var select_cnt = [
      {'value': 1, 'text': 'item-01'},
      {'value': 2, 'text': 'item-02'}
    ]
    this.addFormElement('form-select','some-label',select_cnt)
  }
  , methods: {
    addFormElement: function(type,label,cnt) {
     this.fields.push({
       'type': type
       , id: this.count++
       , 'label':label
       , 'cnt':cnt
     });
   }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js"></script>
<div id="app">
 <component v-for="field in fields" v-bind:is="field.type" :key="field.id" :cnt="field.cnt" :label="field.label"></component>
</div>

<script type="x-template" id="form-input">
  <div v-on:keyup.tab="this.document.execCommand('selectAll',false,null);">
<label>{{label}}</label>
<input type="text" :value="cnt"/>
  </div>
</script>

<script type="x-template" id="form-textarea">
  <div v-on:keyup.tab="this.document.execCommand('selectAll',false,null);">
<label>{{label}}</label>
<textarea :value="cnt"></textarea>
  </div>
</script>

<script type="x-template" id="form-select">
  <div>
<label>Select</label>
<select>
  <option v-for="oitem in cnt" :value="oitem.value">{{oitem.text}}</option>
</select>
  </div>
  <div v-html="cnt"></div>
</script>
like image 1
Yordan Georgiev Avatar answered Oct 24 '22 11:10

Yordan Georgiev