2

I have the following component:

Vue.component('email-input', {
  template: '#tmpl-email-input',
  name: 'email-input',
  delimiters: ['((', '))'],

  props: ['name', 'required', 'value'],

  data: () => ({
    suggestedEmail: '',
    email: '',
  }),

  methods: {
    onInput() {
      this.checkEmail();
      this.$emit('input', this.email);
    },

    checkEmail() {
      Mailcheck.run({
        email: this.email,

        suggested: suggestion => {
          this.suggestedEmail = suggestion.full;
        },

        empty: () => {
          this.suggestedEmail = '';
        },
      });
    },

    confirmSuggestion(confirm) {
      if (confirm) this.email = this.suggestedEmail;
      this.suggestedEmail = '';
    },
  },

  mounted() {
    this.checkEmail = _.debounce(this.checkEmail.bind(this), 1000);
  },
});

using this template

<template id="tmpl-email-input">
  <div>
    <input
      type="email"
      class="form-control"
      :name="name || 'email'"
      :required="required"
      v-on:input="onInput"
      v-model="email"
    />
    <small class="email-correction-suggestion" v-if="suggestedEmail">
      Did you mean ((suggestedEmail))?
      <a href="#" class="btn-sm yes" @click.prevent="confirmSuggestion(true)">Yes</a>
      <a href="#" class="btn-sm no" @click.prevent="confirmSuggestion(false)">No</a>
    </small>
  </div>
</template>

<!-- Lodash from GitHub, using rawgit.com -->
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>

<!-- Mailcheck: https://github.com/mailcheck/mailcheck -->
<script src="/js/lib/mailcheck.js"></script>

<script src="/js/views/partials/email_input.js"></script>

And I'm calling it using

<email-input name="email" required></email-input>

I'd like to set an initial value for this email input, something like

<email-input name="email" required value="[email protected]"></email-input>

and have that show in the input.

I assumed I could do this by simply setting email to this.value in data but that doesn't help. How can I do this?

3
  • add : before value. :value="[email protected]" Commented Aug 31, 2018 at 8:18
  • thank you for the comment. If I add : before value the template no longer compiles. So <email-input name="email" required="" :value="[email protected]"></email-input> causes the template to throw invalid expression: :value="[email protected]" Commented Aug 31, 2018 at 8:26
  • : is used to pass data to props. Not sure why error happens. Btw, you can not specify name="email" required becouse email-input is not an input but vue anchor. Commented Aug 31, 2018 at 8:39

1 Answer 1

4

There's a value prop but you are not using it at all! So it doesn't really matter which value you pass down as value prop: it won't be used.

I think what you are trying to achieve is expose an API similar to the one exposed by input component. That can be done and it's detailed in the docs.

What Vue does to handle the v-model bindings is assuming the component will emit an input event passing the new value as $event. It will also pass down to the component a value to the value prop. So this 2-way binding is automatically handled by Vue as long as you define a value prop and emit an input event.

The problem is that your component acts as a middleware for the underlying input component but it is passing down a different binding instead of forwarding it.

Translating this into your component, you should not use v-model to pass down email to the input component but a combination of :value and @input bindings: you pass down the value prop of email-input component to the value prop of the input component and as handler of input event of the input component you should just emit another input event with the same $event payload.

Template:

<template id="tmpl-email-input">
  <div>
    <input
      type="email"
      class="form-control"
      :name="name || 'email'"
      :required="required"
      :value="value"
      @input="onInput($event.target.value)"
    />
    <small class="email-correction-suggestion" v-if="suggestedEmail">
      Did you mean ((suggestedEmail))?
      <a href="#" class="btn-sm yes" @click.prevent="confirmSuggestion(true)">Yes</a>
      <a href="#" class="btn-sm no" @click.prevent="confirmSuggestion(false)">No</a>
    </small>
  </div>
</template>

<!-- Lodash from GitHub, using rawgit.com -->
<script src="https://cdn.rawgit.com/lodash/lodash/4.17.4/dist/lodash.min.js"></script>

<!-- Mailcheck: https://github.com/mailcheck/mailcheck -->
<script src="/js/lib/mailcheck.js"></script>

<script src="/js/views/partials/email_input.js"></script>

Note the change from @input="onInput" to @input="onInput($event.target.value)" so we have access to the new value in onInput method.

Component:

Vue.component('email-input', {
  template: '#tmpl-email-input',
  name: 'email-input',
  delimiters: ['((', '))'],

  props: ['name', 'required', 'value'],

  data: () => ({
    suggestedEmail: ''
  }),

  methods: {
    onInput(newValue) {
      this.$emit('input', newValue);
      this.checkEmail();
    },

    checkEmail() {
      Mailcheck.run({
        email: this.value,

        suggested: suggestion => {
          this.suggestedEmail = suggestion.full;
        },

        empty: () => {
          this.suggestedEmail = '';
        },
      });
    },

    confirmSuggestion(confirm) {
      if (confirm) this.$emit('input', this.suggestedEmail);
      this.suggestedEmail = '';
    },
  },

  mounted() {
    this.checkEmail = _.debounce(this.checkEmail.bind(this), 1000);
  },
});

Note the change in onInput method: now it takes a parameter with the new value and emits an input event with that value before checking the email address. It's emitted in that order to ensure we have synced the value of the value binding before checking the address.

Also note the change in confirmSuggestion method: instead of updating email data attribute it just emits an input event.

That's the key to solve this issue: the old implementation forced us to have 2 different variables: one where parent component could pass down a value and another one email-input could modify to store the chosen suggestion.

If we just emit the chosen suggestion as a regular change then we can get rid of the email variable and work with just one binding.


Suggestion totally not related with the issue: you can use debounce directly in methods instead of replacing the method on mounted hook:

Vue.component('email-input', {
      template: '#tmpl-email-input',
      name: 'email-input',
      delimiters: ['((', '))'],

      props: ['name', 'required', 'value'],

      data: () => ({
        suggestedEmail: ''
      }),

      methods: {
        onInput(newValue) {
          this.$emit('input', newValue);
          this.checkEmail();
        },

        checkEmail: _.debounce(function () {
          Mailcheck.run({
            email: this.value,

            suggested: suggestion => {
              this.suggestedEmail = suggestion.full;
            },

            empty: () => {
              this.suggestedEmail = '';
            },
          });
        }, 1000),

        confirmSuggestion(confirm) {
          if (confirm) this.$emit('input', this.suggestedEmail);
          this.suggestedEmail = '';
        },
      }
    });

Lodash will take care of binding this of the underlying function to the same this that called the debounced function.

Sign up to request clarification or add additional context in comments.

1 Comment

Although Lodash documentation does not explicitly says anything about this context of the underlying function, that can be checked in the source code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.