Vue BindRoute Mixin

Use v-model on route query params as easily as on data properties

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.0.1/vue-router.min.js"></script>
<div id="vue">
  <input v-model="search" placeholder="search">
  <div>
    <input type="checkbox" v-model="caseSensitive" id="caseSensitive">
    <label for="caseSensitive">Case sensitive</label>
  </div>
  <div>
    <label for="maxPrice">Max price:</label>
    <input type="range" v-model="maxPrice" min="0" max="40">{{maxPrice}}
    <button v-if="maxPrice !== null" @click="maxPrice = null">No max price</button>
  </div>
  <h3>Route path:</h3>
  {{$route.fullPath}}
  <button @click="add()">
  CLick
  </button>
  <h3>Items matching query:</h3>
  <ul v-if="results.length">
    <li v-for="item in results"><b>{{item.name}}</b> {{item.price}}:-</li>
  </ul>
  <div v-else>No matches</div>
  <h3>$route:</h3>
  <pre>{{$route}}</pre>
</div>

JavaScript

//This is a mixin factory! It lets us pass arguments to the mixin.
function bindRoute(params) {
  const mixin = {
    computed: {}
  }
  for (let key in params) {
    let def = params[key]
    mixin.computed[key] = {
      get() {
        if (this.$route.query.hasOwnProperty(key)) {
          return this.$route.query[key]
        } else {
          return def
        }
      },
      set(val) {
        if (val === def) { //if value is same as the default, remove it from the query to keep the URL neat
          const query = { ...this.$route.query}
          delete query[key]
          this.$router.replace({query})
        } else {
          this.$router.replace({query: {...this.$route.query, ...{[key]: val}}})
        }
      }
    }
  }
  return mixin
}

//Setup up VueRouter and the Vue app

const router = new VueRouter()

Vue.use(VueRouter)

new Vue({
  el: '#vue',
  router,
  mixins: [ //Here's how you use the mixin
    bindRoute({
      search: '', //these values are the default values
      caseSensitive: false,
      maxPrice:null
    })
  ],
  data() {
    return {
      items: [
      	{name:'Cupcake', price:35},
        {name:'Biscuit', price:15},
        {name:'Cinnabun', price:25},
        {name:'Coffee', price:29},
        {name:'Tea', price:29},
        {name:'Frappuccino', price:39}
      ]
    }
  },
  computed: {
    results() {
    	const matcher = new RegExp(this.search, this.caseSensitive ? '' : 'i')
      let items = this.items.filter(item => matcher.test(item.name))
      if(this.maxPrice !== null){
      	items = items.filter(item => item.price <= this.maxPrice)
      }
      return items
    }
  },
  methods:{
  	add(){
    this.items.push(Name)
    }
  }
})