Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue: v-model and input event in custom component derived of a custom component

I have a custom input where I recieve a value prop and emit a input event on the input event. I can use this custom input without problems with a model, now I'm creating a custom password input that I initialize as a custom input but I can't bind the model using value and input event handlers (passing them to the custom input). How can I approach this?

Custom Input:

  • My program model > custom input (value and input event handler) : works
  • My program model > custom password (value and input event handler) > custom input: doesn't work.

Code:

Input.vue:

<template>
    <div class="form-group">

      <label for="" v-if="typeof label !== 'undefined'">{{ label }}</label>

      <!-- GROUP  -->
      <template v-if="isGroup">
        <div class="input-group">
          <!-- PREPEND  -->
          <div v-if="hasPrepend" class="input-group-prepend"
              :class="{'inside bg-transparent' : prependInside, 'pointer': prependPointer}"
              @click="clickPrepend">

            <span class="input-group-text"
                  :class="{'bg-transparent' : prependInside}">

              <i  aria-hidden="true"
                  v-if="prependType === 'icon'"
                  :class="'fa fa-' + prependContent"></i>

              <template v-if="prependType === 'text'">{{ prependContent }}</template>
            </span>

          </div>

          <!-- INPUT  -->
          <input  class="form-control"
              :type="type"
              :class="generatedInputClass"
              :readonly="readonly"
              :disabled="disabled"
              :value="value"
              @input="inputEvent"
              @change="onChange">

          <!-- APPEND  -->
          <div v-if="hasAppend" class="input-group-append"
              :class="{'inside bg-transparent' : appendInside, 'pointer': appendPointer}"
              @click="clickAppend">

            <span class="input-group-text"
                  :class="{'bg-transparent' : appendInside}">

              <i  aria-hidden="true"
                  v-if="appendType === 'icon'"
                  :class="'fa fa-' + appendContent"></i>

              <template v-if="appendType === 'text'">{{ appendContent }}</template>

            </span>

          </div>
      </div>
      </template>

      <!-- INPUT  -->
      <template v-else>
        <input  class="form-control"
              :type="type"
              :class="generatedInputClass"
              :readonly="readonly"
              :disabled="disabled"
              :value="value"

              @input="inputEvent"
              @change="onChange"
              >
      </template>

      <small  class="form-text"
              v-if="typeof helpText !== 'undefined'"
              :class="generatedHelperClass">
        {{ helpText }}
      </small>

    </div>
</template>

<script>
export default {
  name: 'InputGroup',
  props: {
    value: String,
    label: String,
    helpText: String,
    size: String,
    prependContent: String,
    appendContent: String,
    prependType: {
      type: String,
      default: 'icon',
    },
    appendType: {
      type: String,
      default: 'icon',
    },
    prependInside: {
      type: Boolean,
      default: false,
    },
    appendInside: {
      type: Boolean,
      default: false,
    },
    prependPointer: {
      type: Boolean,
      default: false,
    },
    appendPointer: {
      type: Boolean,
      default: false,
    },
    readonly: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    type: {
      type: String,
      default: 'text',
    },
    valid: {
      type: Boolean,
      default: null,
    },
  },
  watch: {
    valid() {

    },
  },
  computed: {
    isGroup() {
      return this.hasPrepend || this.hasAppend;
    },
    hasPrepend() {
      return typeof this.prependContent !== 'undefined';
    },
    hasAppend() {
      return typeof this.appendContent !== 'undefined';
    },
    generatedInputClass() {
      const size = typeof this.size !== 'undefined' ? `form-control-${this.size}` : '';
      let valid = '';
      if (this.valid !== null) {
        valid = this.valid ? 'is-valid' : 'is-invalid';
      }
      return `${size} ${valid}`;
    },
    generatedHelperClass() {
      let valid = 'text-muted';
      if (this.valid !== null) {
        valid = this.valid ? 'valid-feedback' : 'invalid-feedback';
      }
      return `${valid}`;
    },
  },
  methods: {
    inputEvent(e) {
      this.$emit('input', e.target.value);
    },
    clickPrepend(e) {
      this.$emit('click-prepend', e);
    },
    clickAppend(e) {
      this.$emit('click-append', e);
    },
    onChange(e) {
      this.$emit('change', this.value, e);
    },
  },
};
</script>

Password.vue:

<template>
<div>

  <app-input
              :label="label"
              :type="type"
              prepend-content="lock"
              :append-content="passwordIcon"
              :append-inside="true"
              :append-pointer="true"
              @click-append="tooglePassword"
              :value="value"
              @input="inputEvent">
  </app-input>
</div>
</template>

<script>
import Input from './Input';

export default {
  name: 'Password',
  components: {
    appInput: Input,
  },
  props: {
    value: String,
    label: {
      type: String,
      default: 'Contraseña',
    },
    readonly: {
      type: Boolean,
      default: false,
    },
    disabled: {
      type: Boolean,
      default: false,
    },
    valid: {
      type: Boolean,
      default: null,
    },
  },
  data() {
    return {
      pass: '',
      type: 'password',
    };
  },
  computed: {
    passwordIcon() {
      return this.type === 'password' ? 'eye' : 'eye-slash';
    },
  },
  methods: {
    tooglePassword() {
      this.type = this.type === 'password' ? 'text' : 'password';
    },
    inputEvent(e) {
      this.$emit('input', e.target.value);
    },
  },
};
</script>
like image 722
lntg Avatar asked Dec 19 '25 15:12

lntg


1 Answers

The thing is the input event your Password listens form app-input component, has as value the actual string value already, not the element (that you'd have to call e.target.value to get the string value)

In other words, in Password.vue, instead of:

inputEvent(e) {
  this.$emit('input', e.target.value);
},

Do:

inputEvent(e) {
  this.$emit('input', e);
},

CodeSandbox demo here.

like image 61
acdcjunior Avatar answered Dec 24 '25 06:12

acdcjunior



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!