Search code examples
javascriptvue.jsvuelidate

Getting a dynamic v-model to compare to data - VueJS and Vuelidate


I'm working on a simple form that shows 3 elements

  1. A starting barcode field
  2. An ending barcode field
  3. A label field

A user enters 2 barcodes in each field. The first 2 digits of the barcodeEnd value gets compared against the allBarcodePrefixes data to see if there is a match, and prints the corresponding agency name or "Agency Not Found" if there is no match. If there is no match, the form stops there (user must fix the barcode entry) but if it finds a match it brings up a new line with a newly generated set of field and v-models

This was working fine until I made the v-models dynamic so I can manipulate each entry independently for validations and value

PROBLEM Two odd things are happening I need help with guys.

  1. The starting barcode field clears out when I blur out and into the next input field. Then when I enter something there, it comes back! I have a clear console too so I have no idea what is going on.
  2. The showAgencyName() method is supposed to get the v-model and compare it to the data to get the agency name. But my v-model is dynamic and in a string literal. I'm not sure how to pull this value

I have the code in a codepen for you to check it out and see what I mean. I put the onAddBarcodes function in the failing line so you can see it validate correctly (that part works). Eventually, this line will be removed since it's only supposed to validate for minLength and maxLength after it validated for the agency first.

    <div id="q-app">
        <div class="q-pa-md">
          <div>
            <div v-for="(barcode, index) in barcodes" :key="index">
              <div class="full-width row no-wrap justify-start items-center q-pt-lg">
                <div class="col-3">
                  <label>Starting Roll #:</label>
                  <q-input outlined square dense v-model="$v[`barcodeStart${index}`].$model"></q-input>

                  <div class="error-msg">
                    <div v-if="!$v[`barcodeStart${index}`].maxLength || !$v[`barcodeStart${index}`].minLength">
                      <span> Must be exactly 9 characters. </span>
                    </div>
                  </div>
                </div>

                <div class="col-3">
                  <label>Ending Roll #:</label>
                  <q-input outlined square dense v-model="$v[`barcodeEnd${index}`].$model" @change="showAgencyName(barcode)"></q-input>
                  <div class="error-msg">
                    <div v-if="!$v[`barcodeEnd${index}`].maxLength || !$v[`barcodeEnd${index}`].minLength">
                      <span> Must be exactly 9 characters. </span>
                    </div>
                  </div>
                </div>

                <div class="col-3">
                  <label>Agency:</label>
                  <div v-if="barcode.agencyName">
                    {{ barcode.agencyName }}
                  </div>
                  <div v-else></div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>


      Vue.use(window.vuelidate.default)
      const { required, minLength, maxLength } = window.validators

      new Vue({
        el: '#q-app',
        data () {
          return {
            barcodes: [
              {
                barcodeStart: "",
                barcodeEnd: "",
                agencyName: ""
              }
            ],
            newPackage: "",
            reset: true,
            allBarcodePrefixes: {
              "10": "Boston",
              "11": "New York",
              "13": "Houston",
              "14": "Connecticut",
              "16": "SIA",
              "17": "Colorado",
              "18": "Chicago"
            }
          }
        },
        validations() {
          const rules = {};
          this.barcodes.forEach((barcode, index) => {
            rules[`barcodeStart${index}`] = {
                minLength: minLength(9),
                maxLength: maxLength(9)
            };
          });

          this.barcodes.forEach((barcode, index) => {
            rules[`barcodeEnd${index}`] = {
                minLength: minLength(9),
                maxLength: maxLength(9)
            };
          });
          return rules;
        },
        methods: {
          onAddBarcodes() {
            // creating a new line when requested on blur of barcodeEnd
            const newBarcode = {
              barcodeStart: "",
              barcodeEnd: "",
              agencyName: ""
            };
            this.barcodes.push(newBarcode);
          },

          showAgencyName(barcode) {
            var str = barcode.barcodeEnd; // I need to pull the v-model value
            var res = str.substring(0, 2); //get first 2 char of v-model
            if (this.allBarcodePrefixes[res] == undefined) {
              //compare it to data
              barcode.agencyName = "Agency not found"; //pass this msg if not matched
              this.onAddBarcodes(); //adding it to the fail just for testing
            } else {
              barcode.agencyName = this.allBarcodePrefixes[res]; //pass this if matched
              this.onAddBarcodes(); //bring up a new line
            }
          },
        }
      })

Thanks in advance!


Solution

  • What you can do is set the v-model to the object in your v-for loop. So that it will look like this.

    <q-input outlined square dense v-model="barcode.barcodeStart"></q-input>
    

    and

    <q-input outlined square dense v-model="barcode.barcodeEnd"></q-input>
    

    This way they update the barcode object and due to it being an observable your showAgencyName function can stay as it is and the barcode object in it will be updated.

    EDIT

    When you call the onchange event you can then pass through the index

    @change="showAgencyName(barcode, index)"
    

    then in your showAgencyName function you can access the validation with this.$v.

    To make sure the rules have past you will need to update the $model. Like this

     this.$v[`barcodeStart${index}`].$model = barcode.barcodeStart
    this.$v[`barcodeEnd${index}`].$model = barcode.barcodeEnd
    
    // Check for errors
    if (this.$v[`barcodeEnd${index}`].$error) {
       return
    }