1

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!

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

4 Comments

Good catch! In fact I was working in the context of a Photoshop HTML Panel (Chromium Embedded Framework) and while prototyping/testing the interaction, I've rebuilt a simpler version of the scenario in jsbin: my mistake, I turned the input of type number I had in CEF, into a type text. Still, I had the same issue I've commented in Matt Kelliher answer – that I've eventually solved with smaller range's step and no Math.round in the setter. Thank you!
Yes, Math.round() was a bug in your original formula. If you set actualValue to 101, sliderValue becomes 100.08 and immediately gets rounded to 100. That pulls actualValue back to 100. But the real problem was integer and string addition. Javascript or any programming language should not allow such things in the first place. Nevertheless, for now, you can get that Vue devtools, which helped me spot the error.
@DavideBarranca I have modified my answer above, as there is a lot more info and I couldn't fit it in a comment. I have cloned your jsBin so that I could include two console.log statements inside the map2 function and see what is going on there. Clearly there is a circular dependency issue between radius and sliderRadius (through the non linear mapping function - map2) and therefore it is unable to take certain values. This map2 function needs to be modified in such a way that it stabilizes for values between 51 and 58.
Thank you Mani, I'll be able to look at that in the weekend and will report back here
1

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));
      }
    }
  }

1 Comment

It works great, see the jsbin here. My only concern is that, while the number input can actually display all 500 values, the actualValue jumps (500, 488, 472) basically because the slider has only 32 slots to cover 400 values. I've changed the code, see a new jsbin so that the range has 0.1 as the step, and I've removed Math.round from the set function

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.