Hugo Mixer BS
Hugo Mixer BS

Reputation: 13

How to build dynamic HTML controls in Vue from a JSON?

Good day Community.

I need your help to build HTML controls dynamically in VUE from a JSON.

Currently I have an exercise done, I have an html template that renders a list of input controls from the parameters set in a JSON. However I want to build more controls, not just inputs controls.

export default {
  name: 'app',
  data () {
    return {
      msg: 'Hola Mundo',
      fields: [
        {
          "name": "fechaRegistro",
          "label": "Fecha de Registro",
          "type": "date",
          "placeholder": 'Ingresa Fecha'
        },
        {
          "name": "nombreDeUsuario",
          "label": "Nombre de Usuario",
          "type": "text",
          "placeholder": "Ingresa Usuario"
        },
        {
          "name": "passwordUsuario",
          "label": "Password",
          "type": "password",
          "placeholder": "Contraseña"
        },
        {
          "name": "adjuntarArchivo",
          "label": "Adjuntar",
          "type": "file"
        },
        {
          "name": "activo",
          "label": "Activo",
          "type": "checkbox"
        },
        {
          "name": "roles",
          "label": "Roles",
          "type": "multiSelect",
          "sortedByKey": false,
          "options": [{
              "name": "admin",
              "label": "Admin"
            },
            {
              "name": "user",
              "label": "User"
            },
            {
              "name": "guest",
              "label": "Invitado"
            }
          ]
        }
      ]
    }
  }
}
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

h1, h2 {
  font-weight: normal;
}

ul {
  list-style-type: none;
  padding: 0;
}

li {
  display: inline-block;
  margin: 0 10px;
}

a {
  color: #42b983;
}
<template>
  <div id="app">
    <h1>{{msg}}</h1>
    <form>
      <ul id="controles">
        <li v-for="field in fields" :key="field">
          <label :for="field.name">{{field.label}}</label>
          <input :id="field.name" :type="field.type" :placeholder="field.placeholder">
        </li>
      </ul>
    </form>
  </div>
</template>

My objective is to receive in the Json the type of control that I should build along with its parameters. For example, if a select type control comes in the json, I must build a select, if a textarea type control comes, I must build a textarea and so on.

Leave your solution ideas please, thank you.

Upvotes: 1

Views: 617

Answers (1)

Hannah
Hannah

Reputation: 1143

You would need to know in advance which field types to expect. You could create a method hasInputTag like this:

methods: {
  hasInputTag(type) {
    return ['text', 'password', 'file', 'date'].includes(type);
  }
}

Then check the field.type inside your v-for loop and render the appropriate element, e.g.:

<li v-for="field in fields" :key="field">
  <label :for="field.name">
    {{ field.label }}
  </label>
  <input
    v-if="hasInputTag(field.type)"
    :name="field.name"
    :type="field.type"
  >
  <select
    v-else-if="field.type === 'select'"
    :name="field.name"
  >
    <option v-for="opt in field.options">
      // etc.
    </option>
  </select>
  <textarea
    v-else-if="field.type === 'textarea'"
    :name="field.name"
  >
</li>

UPDATE 1:

If you wanted to use separate components, you could do something like this in your template:

<component
  :is="getComponentName(field.type)"
  v-bind="field"
/>

And your getComponentName method could look like this (where each component has a name such as Custom_input, Custom_checkbox, etc. and you have imported them in the parent):

getComponentName(type) {
  if (['text', 'password', 'file', 'date'].includes(type)) return 'Custom_input';
  return `Custom_${type}`;
}

UPDATE 2:

To store the values of each component, I would create a fieldValues object in the parent and initialise it like this:

fieldValues: this.fields.reduce((res, field) => {
  res[field.name] = null;
  return res;
}, {})

Then use v-model="fieldValues[field.name]" in your loop.

Upvotes: 2

Related Questions