Davide Barranca
Davide Barranca

Reputation: 359

Vue.js: error setting a computed property

in the following code (jsbin available here) I have two input elements, a range and a text, bound together via a computed property.

var vm = new Vue({
  el: '#main-container',
  data: {
    sliderValue: 100,
  },
  computed: {
    actualValue: {
      get: function() {
        if (this.sliderValue <= 100) {
          return this.sliderValue;
        } else {
          return Math.round(this.sliderValue * 12.5 - 1150);
        }
      },
      /* set won't work for val > 100*/
      set: function(val) {
        if (val <= 100) {
          this.sliderValue = val;
        } else {
          this.sliderValue = Math.round((val + 1150)/12.5);
        }
      }
    }
  },
  methods: {
    
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
    <div id="main-container">
      <input type="range" v-model="sliderValue" min=1 max=132>
      <input type="text" v-model="actualValue">
      <p>Slider: {{sliderValue}}</p>
      <p>Actual: {{actualValue}}</p>
    </div>

The range goes from 1 to 132, and its range is mapped [1..500] in the text input, with a simple transformation (basically it's a linear mapping with two different slopes for [1..100] and [101..132]) using the actualValue computed property.

Getting actualValue works as expected: dragging the slider correctly updates the input text with appropriate values in the range [1..500].

I'm not able to find a way to set actualValue, though. I'd like to be able to type a value in the text input, and make the slider's thumb update accordingly to the inverse transformation (val + 1150)/12.5.

It works as long as the typed number is in the range [1..100], but it "explodes" for numbers >100, e.g. 101 makes the sliderValue jump at 80892 and actualValue is then re-calculated as 1010000. As far as I understand, it's a looping-feedback scenario.

I've tried also alternative approaches (watch; v-on:change in the text input; using a third variable) to no avail.

Thanks in advance for any suggestion!

Upvotes: 1

Views: 1346

Answers (2)

Mani Jagadeesan
Mani Jagadeesan

Reputation: 24275

It's an amazing puzzle, and challenged me for a long time!

Look at the screenshot below. Your sliderValue and actualValue are strings, not integers. When you set actualValue as 101, you are actually setting it as a string value of "101"

enter image description here

Now, your sliderValue = ((actualValue + 1150)/12.5)

"101" + 1150 = "1011150" (another string!, try it in the developer console)

That messes up your entire calculation. Now you know how to fix it :-)

And you need to get that Vue devtools from here: https://github.com/vuejs/vue-devtools

EDIT: Response to comment #3

Here is the modified jsBin: http://jsbin.com/mahejozeye/1/edit?html,js,output

The only difference is introduction of two console.log statements in your map2 function. This helps you identify if your non-linear mapping function is working correctly or not. If you keep your developer console open, you will see what is happening in this function.

  • Case 1: When you set the value radius = 25 using the text box, your sliderRadius gets set to 111.55518394648828 and your radius again gets re-calculated as 25. So it comes around in a full circle and everything is stable here.

  • Case 2: When you set the value radius = 55, your sliderRadius gets set to 173.03607214428857 through your non-linear map2() function, which resets radius to 51.29869180420927

Clearly there is a circular dependency issue. Your sliderRadius and radius are very much dependent on each other and therefore radius is unable to take the value between 51 and 58.

You need to evaluate why it happens, as it has a lot to do with the non-linear mapping function that you have defined. The moment radius can take stable values at 55 (through the map2 function), then your current problem will be resolved.

enter image description here

Upvotes: 2

Matt Kelliher
Matt Kelliher

Reputation: 136

The simplest fix is to set your input type to number:

<input type="number" v-model="actualValue">

or you can convert your value to an integer with something like:

  set: function(val) {
    var intVal = parseInt(val, 10);

    if (!isNaN(intVal)) {
      if (intVal <= 100) {
        this.sliderValue = Math.max(1, intVal);
      } else {
        this.sliderValue = Math.min(132, Math.round((intVal + 1150) / 12.5));
      }
    }
  }

Upvotes: 1

Related Questions