w3resource
Vue Tutorial

Custom Events

Event Names

Event names does not provide automatic case transformation unlike components and props.

Rather, the name of an emitted event must match the name used to listen to that event exactly. For instance, if we emit a camelCase event name:

this.$emit('myEvent')

listening to its kebab-case version will have no effect.

Also, unlike props and components, events names can never be used as property names or variable names in JavaScript. So there is no reason to use PascalCase or camelCase. In addition, v-on event listeners inside DOM templates will automatically be transformed to lowercase (due to HTML's case-insensitivity)., thus v-on:myEvent will become v-on:myevent.

Customizing Component v-model

On a component v-model use value as the prop and input as the event by default, but some input types such as radio buttons and checkboxes may want to use the value attribute for a totally different purpose. Using the model can help us avoid a conflict in such cases:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: 
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
   >
  
})

When we use the v-model on this component we have:

<base-checkbox v-model="lovingVueJs"></base-checkbox>

the value of lovingVueJs would be passed to the checked prop. When emits a change event with a new value the lovingVueJs property will be updated.

Binding Native Events to Components

There could be times when we wish to listen directly to a native event on the root element of a component. In such cases, we can use the native modifier for v-on:

<base-input v-on:focus.native="onFocus"></base-input>

Though this can be useful sometimes, it is however not a good idea when trying to listen on a very specific element, like an <input>. For instance, the <base-input> component shown above must refactor so that the root element is actually a <label> element:

<label>
  {{ label }}
  <input
    v-bind="$attrs"
    v-bind:value="value"
    v-on:input="$emit('input', $event.target.value)"
  >
</label>

The native listener in the parent would silently break in this case. Though there will be no error however, the onFocus handler wouldn't be called when we expected it to.

Vue however provides a solution to this problem, this solution is achieved using the $listener property containing an object of listeners being used on the component:

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

When we use the $listeners property, we can forward all event listeners on the component to a specific child element with v-on="$listeners". We naturally will want to use v-model for elements like <input>, in such cases it is useful to create a new computed property for listeners, as in the case of inputListeners below:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` merges objects together forming a new object
      return Object.assign({},
        // All the listeners are added from the parents
        this.$listeners,
        // We can then add custom listeners or override the
        // behavior of some listeners.
        {
          // This ensures that the component will work with v-model
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: 
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
})

.sync Modifier

There are certain cases when we may need "two-way binding" for a prop. However, that can create a maintenance issues. Instead it is recommended that we emit events in the pattern of update:myPropName. For instance, given a hypothetical component with a title prop, we could communicate the intent of assigning a new value with:

this.$emit('update:title', newTitle)

And the parent can then listen to that event and update a local data property, when it wants to. For instance:

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>