Browsers give events different execution priority. Here’s our take at using Vue’s custom events on input components.

tldr: Here’s a fiddle.

That sounds trivial, but trying to catch the native event in an app execution can be a pain, especially when dealing with user interaction on input fields.

Here’s how we used Vue.js reactivity handle the change on a child component input and sync the data with its parent using a custom watcher.

//app structure: outerData is synced via v-model
<div id="app">
 <checkyb v-model="outerData" label="inner data"></checkyb>
 
 <div>
  Outer data: {{outerData}} 
 </div>
</div>
// the component instance
Vue.component('checkyb', {
 props:['label'],
 data: function(){
  return {
   innerData: []
  }
 },
 watch: {
  innerData: function(){ //when this data changes emit the value to the parent
   this.$emit('input', this.innerData)
  }
 },
 template: '
 <div>
  <label>
    <input type="checkbox" id="check1" v-model="innerData" :value="1">
    <input type="checkbox" id="check2" v-model="innerData" :value="2">
   {{ label }}</label>
   <span>{{innerData}}</span></div>',
})

Finally create the Vue instance:

new Vue({
 el: '#app',
 data: {
 outerData: [],
 },
 watch: {
 outerData: function(){
 //an user has interacted with our data
 console.log('data has changed a query will start')
 }
 }
})

Background story

When a client commissioned us a single page application that will have to handle multiple input affecting the page behavior real time, we were faced with a tough choice: tackle the problem with JS+ JQuery  or try a more modern front-end framework.

Vue is a promising rising stars into the vast javascript front end frameworks echo system and offers a solid MVVM control of the page, so the decision came naturally.

We decided to set up a parent component that holds all the different information, and then have some child components that handles the user interaction.
This way we delegate the parent to handle the communication between child-user interaction and the back-end server.

// a visualized idea of how the app is composed.
-Parent: dataA  dataB  dataC
--ChildA: handles dataA
--ChildB: handles dataB
--ChildC: handles dataC

Turns out that Vue lets you use v-model on custom input.
Our first Approach looked something like this fiddle.

//html
<div id="app">
  <checkyb v-model="outerData" :outerData="outerData" label="inner data"> </checkyb>
 
 <div>
  Outer data: {{outerData}}
 </div>
</div>
//Vue js
Vue.component('checkyb', {
 props: {
 label: String,
 value: null,
 outerData: Array
 },
 data: function(){
 return {
 innerData: []
 }
 },
 methods: {
 emitValue(){
 // this won't get triggered either in Safari/Firefox
 this.$emit('input', this.innerData);
 }
 },
 template: '
   <div>
     <label>
      <input v-model="innerData" value=1 type="checkbox" @click="emitValue()">
      <input v-model="innerData" value=2 type="checkbox" @change="emitValue()">
      {{ label }}
     </label>
     <span>{{innerData}}</span>
   </div>',
})

new Vue({
 data: {
 outerData: [],
 },
 watch: {
 // when outerData changes a function will fire
 outerData: function(){
 console.log('data has changed a query will start')
 }
 }
}).$mount('#app')

 

What’s this?

If you are not familiar with Vue don’t worry: playing around with the fiddle will makes everything clearer.
The input has its own set of value innerData that thanks to v-model="innerData" will stay in sync.
Then on a DOM event on the input call the method emitValue() to emit the new set of values to the parent that, thanks to v-model="outerData" will “pick up” the event and change its data accordingly.

This way, in theory, we can delegate the input to performs different actions if needed, and when ready emit the data to the parent.

The problem.

Unfortunately this arise a problem: different browsers prioritize events differently.
That’s why one input listen for click and the other for change; so we can clearly see the difference.

If you try it into different browsers you’ll see that the behavior change.

So how can we expect consistency among different browser when events are picked up in different orders?

Our solution.

We decided to let Vue’s reactivity handle the functionality, avoiding entirely the usage of click or change.

Vue  allows the usage of computed properties, as well as watchers, so we can set one on the property that the v-model is watching and let it emit that an input has changed.

Vue.component('checkyb', {
 props:['label'],
 data: function(){
  return {
   innerData: []
  }
 },
 watch: {
  innerData: function(){
   this.$emit('input', this.innerData)
  }
 },
 template: '
 <div>
  <label>
    <input type="checkbox" id="check1" v-model="innerData" :value="1">
    <input type="checkbox" id="check2" v-model="innerData" :value="2">
   {{ label }}</label>
   <span>{{innerData}}</span></div>',
})

You can see and play around with our solution on this fiddle.

If you have any questions, comments or have a more elegant solution please let us know in the comment.
Happy coding.