5

Continuing from https://stackoverflow.com/a/62859896/4534, I simply want to manipulate a range input element and have the URL update with that value.

// https://github.com/vuejs/vue-router/blob/dev/examples/route-props/Hello.vue
var Hello = Vue.component('Hello', {
  template: `
  <div>
  <h2 class="hello">Hello {{agility}} {{ $attrs }}</h2>
  <input min=0 max=100 v-model="agility" type="range">
  </div>
  `,
  props: {
    agility: {
      type: Number,
      default: 25
    }
  }
});

const router = new VueRouter({
  // mode: 'history',
  routes: [
    { path: '*', component: Hello, props: true },
    { path: '/:agility', component: Hello, props: true },
  ]
})

new Vue({
  router,
  template: `
    <div id="app">
      <router-view></router-view>
    </div>
  `
}).$mount('#app')
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app"></div>

When no value is supplied, I would like the default 25 to be appended to the URL, e.g. https://dabase.com/tips/web/2021/Minimal-VueJS-route-bind/#25

If there is a simpler way to do this without vue-router, I'm all ears!

0

4 Answers 4

1

I solved it without vue-router! 👼

Here we set the data with what we get from the URL hash part:

  created: function() {
    var hash = window.location.hash.substr(1)
    if (hash) {
        this.agility = hash
        }
  },

When the data changes, update the URL hash part

   watch: {
    agility: function (newValue) {
      window.location.hash = newValue
    }
  },
Sign up to request clarification or add additional context in comments.

4 Comments

watch is very costly as far as I knnow. Is there any better way?
@KickButtowski Do you have any evidence that watch is costly in terms of performance? I think it is preferred to use computed properties over watchers in terms of design, not performance.
@hendry What if user changes the hash manually in URL? For this reason, I would also listen to hashchange event somehow.
look at my answer, it is more efficient and clean way to do the same thing. stackoverflow.com/a/69144716/9640177
1

While the solution works, there are two problems:

  • The Hello component mutates its own agility property. This shouldn't happen (Vue generates a warning in the console), instead an event should be emitted so that the property is changed upstream. That way, the value is always stored in only one place, modifying the value in that one place will modify it everywhere else.
  • If the url's hash changes, due to the user manually entering the new url or clicking a link, the value in the input will not be updated

To solve the first problem, we will not use v-model on the agility property, as we don't want to mutate it. We need to listen to the input's event directly:

<input min=0 max=100 :value="agility" type="range" @input="updateAgility">
{
   ...,
   methods: {
     updateAgility(event /*: InputEvent */) {
       this.$router.push({...this.$route, hash: "#" + event.target.value});
     }
   }
}

This will directly change the url when the input changes value. The second issue is how to update the prop agility when the url changes.

Doing it in created is not enough, as the component is not recreated if the page stays the same but the hash changes.

So we go back to the original solution, directly setting the prop from the router:

{ 
  path: "*", 
  component: Hello, 
  props: route => route.hash.length > 1 ? 
     { agility: Number(route.hash.slice(1))} :
     {}
}

Now whether you update the value from the URL or the input, the URL & input value are always in sync. And the warning about mutating a prop is gone.

// https://github.com/vuejs/vue-router/blob/dev/examples/route-props/Hello.vue
var Hello = Vue.component('Hello', {
  template: `
  <div>
  <h2 class="hello">Hello {{agility}}, Current route's hash: {{ $route.hash}}</h2>
  <input min=0 max=100 :value="agility" @input=updateAgility type="range">
  <br>
  <router-link to="#1">Hash 1 link</router-link>   <router-link to="#15">Hash 15 link</router-link>
  </div>
  `,
  props: {
    agility: {
      type: Number,
      default: 25
    }
  },
  methods: {
    updateAgility(event/*: InputEvent */) {
      this.$router.push({...this.$route, hash: "#" + event.target.value});
    }
  }

});

const router = new VueRouter({
  // mode: 'history',
  routes: [
    { path: '*', component: Hello, props: route => route.hash.length > 1 ? { agility: Number(route.hash.slice(1))} : {}},
  ]
})

new Vue({
  router,
  template: `
    <div id="app">
      <router-view></router-view>
    </div>
  `
}).$mount('#app')
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app"></div>

Just for info: it's also possible to listen to url changes in the component itself, by creating a watcher on '$route.hash', but it's not the recommended way.

Comments

1

You can do it very elegantly and efficiently without using router. Use change or input event to update the hash.

new Vue({
  el: "#app",
  data: {
    agility: 25,
    hash: 0
  },
  methods: {
    addHash() {
      window.location.hash = this.agility;
      this.hash = this.agility;
    }
  },
  created: () => {
    const hash = window.location.hash.substr(1)
    if (hash) {
      this.agility = hash
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>

<div id="app">
  <h2 class="hello">Hello {{agility}}</h2>
  <input min=0 max=100 v-model="agility" @change="addHash" type="range">
  <h4>testing hash = {{hash}}</h4>
</div>

Change event is triggered when user stops scrolling or leaves the input range thumb. If you want to see the change while the thumb is being moved, use input event.

Comments

0

Create a writable cmoputed property for that. I think it's kinda nice, since when you need to update your route, you just assign this.agility = newAgility and voilà, everything just works. To set the default value of 25, you can use the created hook.

But keep in mind altering your route so much like that can lead to performance issues. Consider throttling the set function on the computed property (you can use lodash.throttle or implement your own) or replace the route parameter with a hash parameter.

// https://github.com/vuejs/vue-router/blob/dev/examples/route-props/Hello.vue
var Hello = Vue.component('Hello', {
  template: `
  <div>
  <h2 class="hello">Hello {{agility}}</h2>
  <input min=0 max=100 v-model="agility" type="range">
  </div>
  `,
  created() {
    if (typeof this.agility === 'undefined') {
      this.agility = 25;
    }
  },
  computed: {
    agility: {
      get() {
        return this.$route.params.agility;
        // Or with hash parameter
        // return this.$route.hash;
      },
      set(agility) {
        this.$router.replace(`/${agility}`);
        // Or with hash parameter
        // this.$router.replace({ hash: String(agility) });
      },
    },
  },
});

const router = new VueRouter({
  // mode: 'history',
  routes: [
    { path: '*', component: Hello },
    { path: '/:agility', component: Hello },
  ]
})

new Vue({
  router,
  template: `
    <div id="app">
      <router-view></router-view>
    </div>
  `
}).$mount('#app')
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app"></div>

Comments

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.