2
votes

I have an input wrapper component that has a Select and shows a Freeform text box where the user can enter an exact amount.

I can't figure out how to bind the Freeform text box to the parent field as well as the option the user selected. I'm not sure how to emit the change/input.

Trying to follow this from the Custom Events docs

Simple example here:

Edit vuejs-select-freeform

InputWrapper :

<template v-if="inputType === 'text'">
  <input type="text" v-bind:value="value" v-bind="$attrs" v-on="inputListeners">
</template>
<template v-else-if="inputType === 'select'">
  <select v-bind="$attrs" v-bind:value="value" v-on="inputListeners">
    <option value>Select</option>
    <option
      v-for="option in options"
      v-bind:value="option.Id"
      v-bind:key="option.Id"
    >{{option.Description}}</option>
  </select>
  <!--Only show the input if it's a "FreeformOption"-->
  <!--How do I make this update the parent??-->
  <input
    type="text"
    v-if="selectedOption.IsFreeformOption"
    v-bind:value.sync="freeformValue"
    v-bind="$attrs"
    v-on:update="$emit('update:person.ExactIncome', '111')"
    v-on:input="$emit('input:person.ExactIncome', '222')"
  >

  <!--Would ideally recursively call the InputWrapper component
   <InputWrapper
    inputType="text"
    v-if="selectedOption.IsFreeformOption"
    v-bind:value= "freeformValue"
    v-on:input="$emit('input', $event)"
    ></InputWrapper>
  -->
</template>

Demo:

<InputWrapper
  id="incomeLevels"
  inputType="select"
  :options="incomeLevels"
  :freeformValue.sync="person.ExactIncome"
  v-model="person.IncomeLevelID"
></InputWrapper>

Working Demo:

Essential wrap the freeformValue in a computed and emit the change there.

wrappedFreeform: {
      get() {
        return this.freeformValue;
      },
      set(v) {
        this.$emit("update:freeformValue", v);
      }
    }

Edit vuejs-select-freeform

2
Great question ! However, it's not clear how you structured your code. What's currently the code for the InputWrapper.vue component ? Can you clearify your examples above a bit ? - Pascal Lamers
Sry I overlooked your CodeSandbox Example ... all good ! - Pascal Lamers

2 Answers

3
votes

You are already trying to use .sync and this is one possible solution. Passing a primitive values like in following code does not work and will produce Vue warnings since we are mutating the prop directly:

// InputWrapper
<input
  v-model="freeformValue" 
  >
// Demo
<InputWrapper
  :freeformValue.sync="person.ExactIncome"
></InputWrapper>

We can pass an entire object as prop like in following example. This works and does not produce any warnings, but passing an object is not always a optimal solution and the source of the mutation stay not clear.

// InputWrapper
<input
  v-model="freeformValue.ExactIncome" 
  >
// Demo
<InputWrapper
  :freeformValue.sync="person" //pass an object here
></InputWrapper>

The solution proposed from @MisterIsaak is more convenient:

// InputWrapper
<input
  v-model="wrappedFreeform" 
  >

computed: {
  wrappedFreeform: {
      get() {
        return this.freeformValue; // here we just get the value
      },
      set(v) {
        this.$emit("update:freeformValue", v); // emmit an event instead of mutating the prop directly
      }
    }
}


// Demo
<InputWrapper
  :freeformValue.sync="person.ExactIncome" //the value will be updated properly
></InputWrapper>

2
votes

Emit is a reference to one function in the parent component

in your case the input will be

<input
        type="text"
        v-if="selectedOption.IsFreeformOption"
        :value="freeformValue"
        @input="$emit('updateFreeFormValue', $event.target.value)"
      >

and in your parent

<InputWrapper
      id="incomeLevels"
      inputType="select"
      :options="incomeLevels"
      :freeformValue.sync="person.ExactIncome"
      v-model="person.IncomeLevelID"
      @updateFreeFormValue="updateFreeFormValue"
    ></InputWrapper>

and your methods

methods: {
    updateFreeFormValue(value){
      this.person.ExactIncome = value;
    }
  },