Creating Multiple sliders using rangeslider.js plugin

Viewed 303

I am working on a photo editing page. The goal is for users to be able to adjust parameters such as hue, saturation, brightness by uploading pictures and then editing them using range sliders. In order to ensure the program worked well on mobile browsers I decided to use the rangeslider.js library. After getting an initial slider up and working I decided to try more advanced designs. I found this design online and like it. My goal now is to have multiple slider each which displays its value like the linked slider. I am having trouble achieving this. To keep things simple I am currently formatting my slider using the standard css from rangeslider.js. I am able to create a single slider which behaves like the one in the linked design, but now I want to generalize this to multiple sliders, which is where I am having trouble. In the single design the code relevant to creating the rangeslider is

//custom slider javascript
var $element = $('input[type="range"]');
var $handle;


$element
  .rangeslider({
    polyfill: false,
    onInit: function() {
      $handle = $('.rangeslider__handle', this.$range);
      updateHandle($handle[0], this.value);
    }
  })
  .on('input', function() {
    updateHandle($handle[0], this.value);
  });

function updateHandle(el, val) {
  el.textContent = " " + "$" + val + " ";
}

$(document).ready(function(){
  
  //when slider changes, hide start message
$("input").on("change", function() {
  $("#helper").fadeOut("slow");
});

//promo-box
$("#js-promo-box").hide();
$("#promo-link").on("click", function(){
  $("#js-promo-box").slideToggle();
  return false;
});
  
});    

If I leave this function unchanged when declaring multiple range sliders in the html code then there are no errors but only the range input which was declared last will have a label displaying its value. But all of the sliders will be able to change this value. I narrowed this problem down to the fact that there is only a single $handle variable. To fix this I created the code below

<script>
        
        //custom slider javascript
      
        var $element = $('input[type="range"]'); // Gets all the elements of type range
        var $handle = new Array($element.length);
    
        console.log($element);
        for(i=0; i<$element.length;i++){
            console.log($element[i]);
            var $temp = $($element[i]);
            $temp.rangeslider({
                polyfill: false,
                onInit: function() {
                    $handle[i] =  $('.rangeslider__handle', this.$range);
                    updateHandle($handle[i][0], this.value);
                }
            })
            .on('input',function() {
                updateHandle($handle[i][0], this.value);
            });
        }

        function updateHandle(el, val) {
          console.log(el);
          el.textContent = " " + "$" + val + " ";
        }
        
        $(document).ready(function(){  
            //when slider changes, hide start message
            $("input").on("change", function() {
              $("#helper").fadeOut("slow");
            });

            //promo-box
            $("#js-promo-box").hide();
            $("#promo-link").on("click", function(){
              $("#js-promo-box").slideToggle();
              return false;
            });  
        });
    </script>

Now $handler is an array with as many entries as there are range inputs. When run everything initializes fine and the initial values of the sliders are displayed as expected. Despite this when I change the slider I get the error "Uncaught TypeError: can't access property 0, $handle[i] is undefined". This occurs in

.on('input',function() {
                    updateHandle($handle[i][0], this.value);
                });

I do not understand why this error is occurring, especially since the values are initially displayed. Considering the only difference between the code for a single slider and multiple sliders is the $handle is now an array I believe it must have something to do with this but I am unsure where exactly the issue is.

Thank You

1 Answers

It turns out that this is a variable scope problem more than anything. An incredibly useful resource to explain this problem and its solution is "JavaScript Callbacks Variable Scope Problem" by Itay Grudev.

Essentially because the callback variable is created in a for loop use of the variable i (the looping variable) is not a good idea. This is because on later calls the value of i is not known to the callback function because it is outside of its scope. There are multiple ways to fix this as described by Itay Grudev. The easiest is to change

for(var i=0; i<$element.length;i++){ ...

Into for(let i=0; i<$element.length;i++){ ...

But I believe this is not supported on all browsers. Instead what I did was create an inline closure. This allows me to create a variable within the closure which contains the value of i for a specific slider. In this way each callback can know which slider it corresponds to and react accordingly. My final code is below

for(var i=0; i<$element.length;i++){
            console.log($element[i]);
            var $temp = $($element[i]);
            $temp.rangeslider({
                polyfill: false,
                onInit: function() {
                    //On init this is fine I don't need a separate definition of i
                    $handle[i] =  $('.rangeslider__handle', this.$range);
                    updateHandle($handle[i][0], this.value);
                }
            })
            .on('input', (function() {
                var j = i;
                return function() {
                    updateHandle($handle[j][0], this.value);
                }
            })() );

Now instead of calling a function to adjust the slider values this script creates a function to create a new function and then calls that function. The created function has access to variable j, which is what was missing previously

Related