0

I'm trying to generate some input text fields (v-text-field) and render them in which works fine until this point.

Reference JSON

{
  "documentAttributes" : {
    "documentNumber" : "textField",
    "issueDate" : "dateField",
    "issuingAuthority" : "textField",
    "dynamicProperties" : {
      "recordedCityOfIssue" : "textField",
      "recordedStateOrProvinceOfIssue" : "textField",
      "recordedPaperNumber" : "textField",
      "recordedFamilyNumber" : "textField",
      "recordedOrderNumber" : "textField",
      "issueRecordNumber" : "textField"
    }
  },
  "personalAttributes" : {
    "socialNumber" : "textField",
    "surname" : "textField",
    "name" : "textField",
    "paternalName" : "textField",
    "maternalName" : "textField",
    "placeOfBirth" : "textField",
    "dateOfBirth" : "dateField",
    "maritalStatus" : "textField",
    "gender" : "textField"
  }
}

When I try to render text inputs on my template as follow;

<v-expansion-panel>
  <v-expansion-panel-header>Document Attributes</v-expansion-panel-header>
  <v-expansion-panel-content
    v-for="(value,name,index) in refJSON"
    :key="name"
  >
    <v-text-field
      :label="name"
      :placeholder="name"
      :id="index"
      :value="name"
      v-model="docAttributesExtractionResult[index].name"
    />   
  </v-expansion-panel-content>
</v-expansion-panel>

I create an array to save typed values from dynamically generated text fields in template rendering but I couldn't achieve it so far as all text fields are having the same value as soon as I type anything on the field.

data: () => ({
  docAttributesExtractionResult: [{}, {}, {}, {}],
}),

I'm basically trying to achieve the following result on my docAttributesExtractionResult array

docAttributesExtractionResult: [{"documentNumber":"typedValue"},  {"issueDate":"selectedDateValue",.... etc}

Does anyone have any clue on achieving this with correct v-model binding on the array?

An example fiddle which I'm trying to do the same but couldn't succeed.

https://jsfiddle.net/6zhLdrpm/

sebasaenz
  • 1,917
  • 2
  • 20
  • 25

1 Answers1

0

I'd argue your question is not related to Vuetify.
As in, you'd have the same exact problem using BootstrapVue or Vue with plain HTML inputs. In fact, it barely has anything to do with Vue, as you'd have the same problem in any other library using two way bindings on input elements.


First and foremost, :value and v-model are incompatible. You either use one or the other. :value sets the value when component is mounted, while v-model provides 2-way data binding. You clearly want v-model.

Then, you want to create a dynamic form from an arbitrary structure containing either collections of items or items.
This can be solved using a recursive component (which either renders an input if current node is not a collection or a v-for of recursive components if node is a collection). While that model does work, it reaches a high level of complexity which makes debugging a pain. For less than 3 levels of recursion it's not worth the trouble, IHMO.

Here's a practical way to solve your case:

Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
  el: '#app',
  data: () => ({
    refJSON: jsonData(),
    extraction: nullify(jsonData())
  }),
  methods: {
    getInputType(type) {
      return type.replace('Field', '')
    }
  }
})

function nullify(obj) {
  for (const prop in obj) {
    obj[prop] = typeof obj[prop] === 'object' ?
      nullify(obj[prop]) :
      null
  }
  return obj;
}

function jsonData() {
  return {
    "documentAttributes": {
      "documentNumber": "textField",
      "issueDate": "dateField",
      "issuingAuthority": "textField",
      "dynamicProperties": {
        "recordedCityOfIssue": "textField",
        "recordedStateOrProvinceOfIssue": "textField",
        "recordedPaperNumber": "textField",
        "recordedFamilyNumber": "textField",
        "recordedOrderNumber": "textField",
        "issueRecordNumber": "textField"
      }
    },
    "personalAttributes": {
      "socialNumber": "textField",
      "surname": "textField",
      "name": "textField",
      "paternalName": "textField",
      "maternalName": "textField",
      "placeOfBirth": "textField",
      "dateOfBirth": "dateField",
      "maritalStatus": "textField",
      "gender": "textField"
    }
  }
}
label {
  display: flex;
  align-items: center;
  justify-content: flex-end;
}
input {
  margin-left: .5rem;
}
  #app {
    display: flex;
    flex-direction: row-reverse;
    justify-content: space-between;
    flex-wrap: wrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.js"></script>
<div id="app">
  <div v-for="(a, b) in refJSON" :key="b">
    <div v-for="(c, d) in a">
      <label v-if="typeof c === 'string'">
        <pre>{{`${b}.${d}`}}</pre>
        <input  v-model="extraction[b][d]" :type="getInputType(c)" />
      </label>
      <div v-else>
        <label v-for="(e, f) in c">
          <pre>{{`${b}.${d}.${f}`}}</pre>
          <input v-model="extraction[b][d][f]" :type="getInputType(e)" />
        </label>
      </div>
    </div>
  </div>
  <pre v-html="extraction" />
</div>

The model structure is provided by the nullify function, from the JSON source). Obviously, you can tweak/improve it to provide default values based on type.

tao
  • 82,996
  • 16
  • 114
  • 150