Search code examples
javascriptjqueryjquery-uiuislider

JQuery UI Slider - Combined Max for multiple sliders that also have individual max values


- Solved -
I Know they exist, but I haven't found a slider that can easily meet some very basic needs and I'm sure I'm not the only one frustrated with this problem. This is what I have so far. Am I heading in the right direction?

What I'm Trying to Do:

Using the JQuery UI slider, I need multiple sliders on the page. Each slider will have a min of 0, a dynamic max, and a dynamic default value. I set these values in an input field with the html elements min, max, value and retrieve them later through javascript / jquery.

In addition to their individual max values, there also needs to be a total max value that the sum of all sliders cannot exceed. Finally, I also need availability to update the slider from the input box (as well as the slider itself).

What I have so Far:

After researching a number of sliders, I settled on using the JQuery UI slider and eventually came across this post that got me started with reaching my goals: Combined total for multiple jQuery-UI Sliders

I altered the code to (1) Use a hidden input field to retrieve total max value; (2) populate an input text field instead of a span with the current value; (3) set the individual slider's initial max value from the max= attribute; (4) set the slider to disabled: true if the initial max is 0; and (5) listen for changes to the text box and move the slider according to number entered if it will not exceed the max value.

Current Code (JSFiddle): (see the jsfiddle in answer)

(Slider #1 Initial max is 0 - disabled // Other Sliders have max of 500 // total table max is 1000)

What the Slider is Doing Right:

  1. It adjusts nicely and stays under the total max limit correctly
  2. Sets the initial individual max and total max correctly
  3. Sets and stays disabled when the max is 0
  4. Slides correctly when you change the text box

What I Need Help / Ideas on Fixing:

  1. Changing the sliders tends to change the individual slider max = the total max
  2. Changing the text box slides the current bar correctly, but doesn't update the other sliders to keep them from going over total max
  3. I am currently using the .focusout() event on the text boxes because .change() causes it to go crazy b/c the slider is also changing the text box.

Let me know what you think.

UPDATE:

I was sad not to hear any responses, but I think I cracked it. Needed to limit reliability on that original code and mostly rewrite. I will submit my solution as an answer below. Please try it out and let me know what you think. This was quite a process, so hopefully someone finds this useful :)


Solution

  • JSFiddle: http://jsfiddle.net/mikecruz/bAntY/

    Javascript / Jquery:

    var sliders = $("#sliders .slider");
    
    sliders.each(function() {
        var max = document.getElementById("sliderMax").value;
        var value = Number($(this).text(), 10),
            availableTotal = max;
    
        $(this).empty().slider({
            value: 0,
            min: 0,
            max: $(this).siblings().attr("max"),
            range: "max",
            step: 1,
            animate: 100,
            disabled: (function(curMax) {
                if (curMax < 1) {
                    return 1;
                }
                return 0;
            })($(this).siblings().attr("max")),
            stop: function(event, ui) {
                // Update text box to current value and call .change()
                $(this).siblings().attr("value", ui.value);
                $(this).siblings().trigger('change');
            }
        });
    });
    
    $(".value").change(function() {
        var thisAmount = Number($(this).prop("value"));
        var totalMax = Number(document.getElementById("sliderMax").value);
        var indMin = Number($(this).attr("min"));
        var indMax = Number($(this).attr("max"));
        var total = 0;
    
        //Get the values of all other text boxes
        $('.value').not(this).each(function() {
            total += Number($(this).prop("value"));
        });
    
        //Find the remaining from our total sliders max
        var remaining = totalMax - total;
    
        if(remaining < 0) {
          remaining = 0;   
        }
        //if we are under our minimums, go for it! Otherwise, reduce the number.
        if (thisAmount >= indMin && thisAmount < indMax && thisAmount < totalMax && thisAmount < remaining) {
            $(this).siblings(".slider").slider("option", "value", thisAmount);
            //total += thisAmount;
        }
        else {
            //var setMax = ((indMax + totalMax) - Math.abs(indMax - totalMax)) / 2;
            var setMax = Math.min(indMax, totalMax, remaining);
            $(this).siblings(".slider").slider("option", "value", setMax);
            $(this).prop("value", setMax);
            //total += (thisAmount - setMax);
        }
    
        //above was getting buggy, so lets just reset total and get it again
        total = 0;
        //Get the values of all text boxes
        $('.value').each(function() {
            total += Number($(this).prop("value"));
        });
    
        //Find our new remaining number after updating total for this value
        remaining = totalMax - total;
        if(remaining < 0) {
          remaining = 0;  
        }
        //Set each slider to the current point and update their max values.
        $('.value').each(function() {
        var sliderVal = Number($(this).prop("value"));
        var sliderMin = Number($(this).attr("min"));
        var sliderMax = Number($(this).attr("max"));
        var setNewMax = (((sliderMax + totalMax) - Math.abs(sliderMax - totalMax)) / 2);
        var newMax = sliderVal + remaining;
    
        $(this).prop("max", newMax);        
        if(newMax < setNewMax) {
            $(this).siblings('.slider').slider("option", "max", newMax);
            $(this).siblings('span').text('/' + (newMax));
        }
        else {
            $(this).siblings('.slider').slider("option", "max", setNewMax);
            $(this).siblings('span').text('/' + (setNewMax));
        }
        $(this).siblings(".slider").slider("option", "value", sliderVal);
        });
    
        $('#sliderTotal').attr("value", total);
    });
    

    HTML:
    You need an li for each slider
    You can use CSS to additionally style different parts. E.g. give slideBG a background image.
    You might want to make the totaling boxes hidden and, of course, you might script the max value.

    <form>
    <ul id="sliders">
        <li>
            <span class="slideBG"><div class="slider"></div>
            <input type="text" class="value" name="slider1" value="0" min="0" max="0"/>
            <span>0</span></span>
        </li>
        <li>
            <span class="slideBG"><div class="slider"></div>
            <input type="text" class="value" name="slider2" value="0" min="0" max="500" />
            <span>0</span></span>
        </li>
        <li>
            <span class="slideBG"><div class="slider"></div>
            <input type="text" class="value" name="slider3" value="0" min="0" max="500" />
            <span>0</span></span>
        </li>
        <li>
            <span class="slideBG"><div class="slider"></div>
            <input type="text" class="value" name="slider4" value="0" min="0" max="500"/>
               <span>0</span></span>
        </li>
        <li>
            <span class="slideBG"><div class="slider"></div>
            <input type="text" class="value" name="slider5" value="0" min="0" max="500"/>
               <span>0</span></span>
        </li>
        <li>
            <span class="slideBG"><div class="slider"></div>
            <input type="text" class="value" name="slider6" value="0" min="0" max="500"/>
               <span>0</span></span>
        </li>
    </ul>
        <br><label for="sliderMax">Total Max:</label>
        <input type="text" id="sliderMax" name="sliderMax" value="1000"/>
        <br><br><label for="sliderTotal">Slider Totals:</label>
        <input type="text" id="sliderTotal" name="sliderTotal" value="0" />
    </form>
    

    EDIT:

    You might want to add this in to keep folks from using the text box if the slider is disabled:

    $(".slider").each(function() {
        var disabled = Number($(this).slider("option", "disabled"));
        if(disabled == 1) {
            $(this).siblings('.value').attr('disabled', 'disabled');    
        }
    });