15

I have a 3rd party input component (a vuetify v-text-field).

For reasons of validation i prefer to wrap this component in my own.

my TextField.vue

<template>
    <v-text-field
            :label="label"
            v-model="text"
            @input="onInput"
            @blur="onBlur"
            :error-messages="this.getErrors(this.validation, this.errors)"
    ></v-text-field>
</template>

<script>
    import VTextField from "vuetify/es5/components/VTextField";
    import {vuelidateErrorsMixin} from '~/plugins/common.js';
    export default {
        name: "TextField",
        props: ['label', 'value', 'validation', 'errors'],
        mixins: [vuelidateErrorsMixin], //add vuelidate
        data: function() {
            return {
                'text': this.value
            }
        },
        components: {
            VTextField
        },
        methods : {
            onInput: function(value) {
                this.$emit('input', value);
                this.validation.$touch();
            },
            onBlur: function() {
                this.validation.$touch();
            }
        },
        watch: {
            value: {
                immediate: true,
                handler: function (newValue) {
                    this.text = newValue
                }
            }
        }
    }
</script>

which is used in another component

<template> 
 ...   
  <TextField v-model="personal.email" label="Email" 
        :validation="$v.personal.email" :errors="[]"/> 
   ... 
</template> 
<script> 
      ...imports etc. 
      export default {   ...     
          data: function() {
              return {
                  personal: {
                      email: '',
                      name: ''
                  }
            }      
      },      
      components: [ TextField ] 
    } 
</script>

This works fine but i wonder if there is a much more cleaner approach than to replicate the whole v-model approach again. As now my data is duplicated in 2 places + all the extra (non needed) event handling...

I just want to pass the reactive data directly through to the v-text-field from the original temlate. My TextField doesn't actually need access to that data at all - ONLY notified that the text has changed (done via the @input, @blur handlers). I do not wish to use VUEX as this has it's own problems dealing with input / forms...

Something more close to this...

<template>
    <v-text-field
            :label="label"
            v-model="value" //?? SAME AS 'Mine'
            @input="onNotify"
            @blur="onNotify"
            :error-messages="this.getErrors(this.validation, this.errors)"
    ></v-text-field>
</template>

<script>
    import VTextField from "vuetify/es5/components/VTextField";
    import {vuelidateErrorsMixin} from '~/plugins/common.js';
    export default {
        name: "TextField",
        props: ['label', 'validation', 'errors'], //NO VALUE HERE as cannot use props...
        mixins: [vuelidateErrorsMixin], //add vuelidate
        components: {
            VTextField
        },
        methods : {
            onNotify: function() {
                this.validation.$touch();
            }
        },
    }
</script>

I cannot find anything that would do this.

Using props + v-model wrapping is what i do.

hobbit_be
  • 196
  • 1
  • 5

2 Answers2

21

You need to forward the value prop down to the wrapped component, and forward the update event back up (see https://v2.vuejs.org/v2/guide/components.html#Using-v-model-on-Components for more details):

<template>
  <wrapped-component
    :value='value'
    @input="update"
  />
</template>

<script>
  import wrappedComponent from 'wrapped-component'
  
  export default {
    components: { 'wrapped-component': wrappedComponent },
    props: ['value'],
    methods: {
      update(newValue) { this.$emit('input', newValue); }
    }
  }
</script>

Somewhere else:

<my-wrapping-component v-model='whatever'/>
tony19
  • 125,647
  • 18
  • 229
  • 307
Meekohi
  • 10,390
  • 6
  • 49
  • 58
  • Watch out, [these names have been changed in Vue 3](https://v3-migration.vuejs.org/breaking-changes/v-model.html): prop: `value` -> `modelValue` and event: `input` -> `update:modelValue` – Markus Weninger Mar 21 '23 at 20:38
1

I've create a mixin to simplify wrapping of a component.

You can see a sample here.

The mixin reuse the same pattern as you with "data" to pass the value and "watch" to update the value during a external change.

export default {
  data: function() {
    return {
      dataValue: this.value
    }
  },
  props: {
    value: String
  },
  watch: {
    value: {
      immediate: true,
      handler: function(newValue) {
        this.dataValue = newValue
      }
    }
  }
}

But on the wraping component, you can use "attrs" and "listeners" to passthrough all attributes and listener to your child component and override what you want.

<template>
  <div>
    <v-text-field
        v-bind="$attrs"
        solo
        @blur="onBlur"
        v-model="dataValue"
        v-on="$listeners" />
  </div>
</template>

<script>
import mixin from '../mixins/ComponentWrapper.js'

export default {
  name: 'my-v-text-field',
  mixins: [mixin],
  methods: {
    onBlur() {
      console.log('onBlur')
    }
  }
}
</script>