15

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!

2
  • 2
    it seems you need dynamic components, check Vue Official Guide: dynamic component Commented May 18, 2018 at 20:44
  • Thanks, that led me on the right track Commented May 19, 2018 at 6:35

3 Answers 3

23

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

Sign up to request clarification or add additional context in comments.

Comments

1

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>

1 Comment

Thanks! This is a start, but it only works for input types like this. I'm not sure how I'd have to implement it in order to also use things like selects and text areas
1

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>

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.