mapPropsModel Helper

Bring together "Props Down, events up" pattern and v-model

by abernov

HTML

<script src="https://unpkg.com/vue"></script>

<div id="app">
  <h4>Passing one Object as prop, and one listener to update all properties</h4>
  <my-form ref="form" v-bind:data="{name, age, size}" @update="handleChange"></my-form>
  
  <hr>
  <h4>Passing properties as individual props and listening to individual updates.</h4>
  <my-other-form 
  :name="name" :age="age" :size="size"
  @name="name = $event" @age="age = $event" @size="size = $event"
    
  ></my-other-form>
  <hr>
  <h4>3rd Vriation: individual props, but one listener</h4>
  <my-other-form2 
  :name="name" :age="age" :size="size"
  @update="handleChange"
    
  ></my-other-form2>
  <hr>
  <h4>4th Variation: one Data props, multiple event listeners</h4>
  <my-other-form3
  :data="{ name, age, size }"
  @name="name = $event" @age="age = $event" @size="size = $event"
    
  ></my-other-form3>
  <pre>
    {{$data}}
  </pre>
</div>

<template id="my-form">
  <div>
    <label>Name
      <input type="text" v-model="nameModel">
    </label><br>
    <label>Age
    <input type="number" v-model.number="ageModel">
    </label><br>
    <label>Size
      <select v-model="sizeModel">
        <option v-for="option in ['S', 'M', 'L', 'XL']">{{option}}</option>
      </select>
    </label><br>
  </div>
</template>

Babel + JSX

function mapPropsModels (props = [], {object, event} = {}) {
  return props.reduce((obj, prop) => {
  	const propModel = prop + 'Model'
    const computedProp = {
    	get() {
      	return object ? this[object][prop] : this[prop]
      },
      set(value) {
      	if (event) {
        	this.$emit(event, { prop, value })
        } else {
        	this.$emit(prop, value)
        }
      }
    }
    obj[propModel] = computedProp
    return obj
  }, {})
}

Vue.component('my-form', {
	name: 'myForm',
  template: '#my-form',
  props: ['data'],
  computed: {
  	...mapPropsModels(['name', 'age', 'size'], { object: 'data', event: 'update'})
  }
})

Vue.component('my-other-form', {
	name: 'myOtherForm',
  template: '#my-form',
  props: ['name', 'age', 'size'],
  computed: {
  	...mapPropsModels(['name', 'age', 'size'], /*{ event: 'update'}*/)
  }
})

Vue.component('my-other-form2', {
	name: 'myOtherForm',
  template: '#my-form',
  props: ['name', 'age', 'size'],
  computed: {
  	...mapPropsModels(['name', 'age', 'size'], {event: 'update'})
  }
})

Vue.component('my-other-form3', {
	name: 'myOtherForm',
  template: '#my-form',
  props: ['data'],
  computed: {
  	...mapPropsModels(['name', 'age', 'size'], { object: 'data'})
  }
})

var app = new Vue({
  el: '#app',
  data: {
    name: 'Tom',
    age: 25,
    size: 'XL'
  },
  methods: {
    handleChange({prop, value}) {
    	console.log('handling...', prop, value)
      this[prop] = value
    }
  }
})

console.log(app)