Max13
Max13

Reputation: 921

v-model on composite data

I'm new to Vue.js and I would like to set some data using a two-way data binding via v-model (on two separated inputs)

Why I need it: I have two inputs (input[type=number] and a select containing "days", "months", "years"). And obviously, these two inputs will be concatenated to a data.

Another constraint is that the user is able to add as many "periods" as they want, so the data is a string in an array. Using v-for="(period, index) in periods", it's easy to target a given index in the data array, but it's becoming complicated if I have to use a computed with a get() exploding the value, and the set() concatenating them... But if there is no easier magical way, I'll have to stick to it...

Example HTML:

<agreement inline-template>
    <div v-for="(period, index) in ftc_trial_periods">    
        <input type="number" min="1" v-model="??" required>

        <select v-model="??" required>
            <option>days</option>
            <option>months</option>
            <option>years</option>
        </select>
    </div>
</agreement>

Example Component:

Vue.component('agreement', {
    data() {
        return {
            ftc_trial_periods: []
        }
    }
});

When a number is changed or when an option is selected, I'd like the value to be concatenated/set/appended in ftc_trial_periods. For instance, ftc_trial_periods could be: ['1 day', '3 months'].

Upvotes: 0

Views: 590

Answers (2)

Andria
Andria

Reputation: 5075

Generate <select> based on <input> with validation

A solution to your problem would be to use a computed value to create periods and iterate over this value to create multiple selects with v-for='index in period, from here we need to bind the value of each select dynamically in an array selectedArray: ['']. To do this I use the computed property periods to check for the change in number of periods in the string against the length of selectedArray. If there are more periods, I .push another string onto selectedArray, if there are less, I .pop the last one off. To bind each select element I use v-bind='selectedArray[index]. Lastly, we validate the string and check that every select is filled out, if so put all the data together in an array of strings.

Here is the code (below and in JSFiddle):

Vue.js Code:

Vue.component('agreement', {
  template: `
    <div>    
      <input
        type="text"
        v-model="numbers"
      />
      <select
        v-for='index in periods'
        v-model='selectedArray[index - 1]'
        required
      >
        <option>days</option>
        <option>months</option>
        <option>years</option>
      </select>
      <p>
        {{ output }}
      </p>
    </div>
  `,
  data: () => ({
    numbers: '',
    selectedArray: ['']
  }),
  computed: {
    inputsAreValid: function() {
      return this.numbers.match(/^\d+((\.\d+)?)+$/g) && this.selectedArray.every(selection => selection !== '')
    }, 
    output: function() {
      if (!this.inputsAreValid)
        return []
      return this.numbers.split('.').map((number, index) => `${number} ${this.selectedArray[index]}`)
    },
    periods: function() {
      const numberOfPeriods = this.numbers.split('.').length
      if (numberOfPeriods > this.selectedArray.length)
        this.selectedArray.push('')
      else if (numberOfPeriods < this.selectedArray.length)
        this.selectedArray.pop()
      return numberOfPeriods
    }
  }
})

new Vue({
  el: "#app"
})

HTML Template Code:

<div id="app">
  <agreement></agreement>
</div>

Upvotes: 0

Jacob Goh
Jacob Goh

Reputation: 20855

In this scenario, I would store form values in data and make ftc_trial_periods a computed value of these data. So no exploding/concatenating is required.

https://jsfiddle.net/jacobgoh101/kgodqmbx/6/

<div id="app">
    <button @click="addPeriod">add period</button><br><br>
    <div v-for="(form) in formInputs">    
        <input type="number" min="1" v-model="form.value" required>

        <select v-model="form.type" required>
            <option>days</option>
            <option>months</option>
            <option>years</option>
        </select>
    </div>
    ftc_trial_periods: {{ftc_trial_periods}}
</div>
new Vue({
  el: "#app",
  data: {
    formInputs: [
        {
        value: null,
        type: null
      }
    ]
  },
  computed: {
    ftc_trial_periods: function() {
        return this.formInputs.map(obj => {
        return `${obj.value} ${obj.type}`;
      });
    }
  },
  methods: {
    addPeriod: function() {
        this.formInputs.push({
        value: null,
        type: null
      });
    }
  }
})

Upvotes: 1

Related Questions