-5

I'm working on a calculator and it requires that there are four inputs where the max for each is 100 and the sum of all four is always equal to 100. The best way to accomplish this for good UX is when any slider is moved down the slider just before it moves up. If a slider is moved up the slider just after it moves down if is't not already at zero.

I have tried matching the input names and adding formulas. I really don't know the best way to approach this.

enter image description here

Joe Neuman
  • 115
  • 1
  • 8
  • What have you done so far and what's the sequence ? – westdabestdb Jan 13 '19 at 02:28
  • Please provide your current state, regarding the code, snapshots, etc. – mrmowji Jan 13 '19 at 02:29
  • I have no idea what I am doing. I have been working on this for the last 3 weeks. – Joe Neuman Jan 13 '19 at 03:35
  • @JoeNeuman please see here: https://stackoverflow.com/help/how-to-ask especially the section "Help others reproduce the problem" and within that (https://stackoverflow.com/help/mcve) – vol7ron Jan 14 '19 at 01:41
  • Okay, I read it. This makes me realize I should not have put in my code as it does not help explain the problem. – Joe Neuman Jan 14 '19 at 02:03
  • What does off-topic mean. How is this post any different than mine? https://stackoverflow.com/questions/469357/html-text-input-allows-only-numeric-input – Joe Neuman Jan 23 '19 at 20:45

5 Answers5

2

<!-- Author: devninja67 -->
<!-- ***** -->
<!DOCTYPE html>
<html>
<head>
 <script type="text/javascript">
  // Returns all selector elements
  const getRanges = () => Array.from(document.querySelectorAll('.hrange'));

  // return value to change
  const minValue = (v1, v2) => {
   if(v1 < 0)  return Math.abs(v1) < v2 ? v1 : -v2;
   else return v1 < 100 - v2 ? v1 : 100 - v2;
  }

  // Gets us an ability to sum selectors (can exclude a selector if required)
  const sumSelectors = (exclude) => getRanges()
      .filter((r) => (exclude) ? r !== exclude : true)
      .map(r => parseInt(r.value))
      .reduce((a, s) => a + s);
  
  // changing before or after slider
  const checkRanges = (idx) => {
  // after element for increasing and before element for decreasing
     let stride = (sumSelectors() > 100) ? 1 : -1;
     let selectorId = idx;
  // changing slider's value until sliders's sum equal 100
     while(true) {
       selectorId = (selectorId + stride + 4) % 4;
       let selector = document.querySelector("#range" + selectorId);
      let selectorValue = parseInt(selector.value);
      selector.value = selectorValue + minValue(100 - sumSelectors(), selectorValue);
      if(sumSelectors() !== 100) continue;
      else break;
   }
    document.querySelector('#amount').value = sumSelectors();
  };

  // Listening inputing slider
  document.addEventListener('DOMContentLoaded', function() {
    getRanges().forEach((r, idx) => {
      r.addEventListener('input', (event) => {
        checkRanges(idx);
      });
    });
  });
 </script>

 <style type="text/css">
  .hrange {
    display: block;
  }
 </style>
</head>
<body>
 <div class="hThree">
   <input type="range" id="range0" orient="vertical" value="25" class="hrange" min="0" max="100" />
   <input type="range" id="range1" orient="vertical" value="25" class="hrange" min="0" max="100" />
   <input type="range" id="range2" orient="vertical" value="25" class="hrange" min="0" max="100" />
   <input type="range" id="range3" orient="vertical" value="25" class="hrange" min="0" max="100" />
   <input id="amount" type="number" value="100" min="0" max="100" />
 </div>
</body>
</html>
ninja dev
  • 36
  • 2
  • 1
    Hello ninja. Thanks for your answer, but can you add some text to explain the 2 blocs of code. Putting only code without some explanation is not well received. – schlebe Jan 14 '19 at 12:29
1

I think this will work for you. Let me know.

Some notes:

  • Removed your ID attributes as duplicates is not allowed in HTML
  • Removed BR tag and went with CSS (much better).
  • If you exceed 100, the loop simply reduces the next highest in a round robin model by 1 until you are 100 or less.
  • Tweaked, now it will always be 100.
  • Did a major tidy of the code.

// Returns all selector elements
const getRanges = () => Array.from(document.querySelectorAll('.hrange'));

// Gets us an ability to sum selectors (can exclude a selector if required)
const sumSelectors = (exclude) => getRanges()
    .filter((r) => (exclude) ? r !== exclude : true)
    .map(r => parseInt(r.value))
    .reduce((a, s) => a + s);

// Get the selector with highest current value.
const maxSelector = (exclude) => getRanges()
    .filter(r => (exclude) ? exclude !== r : true)
    .reduce((a, r) => (parseInt(r.value) > parseInt(a.value)) ? r : a);;

const minSelector = (exclude) => getRanges()
    .filter(r => (exclude) ? exclude !== r : true)
    .reduce((a, r) => (parseInt(r.value) < parseInt(a.value)) ? r : a);
;

const checkRanges = (r) => {
  while (sumSelectors() !== 100) {
    let stride = (sumSelectors() > 100) ? -1 : 1;
    let selector = (stride === -1) ? maxSelector(r) : minSelector(r);
    selector.value = parseInt(selector.value) + stride;
  }
  document.querySelector('#amount').value = sumSelectors();
};


document.addEventListener('DOMContentLoaded', function() {
  getRanges().forEach(r => {
    r.addEventListener('change', (event) => {
      checkRanges(r);
    });
  });
  
  checkRanges();
});
.hrange {
  display: block;
}
<div class="hThree">
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input id="amount" type="number" value="100" min="0" max="100" />
</div>
Bibberty
  • 4,670
  • 2
  • 8
  • 23
  • This is really great Bibberty. The goal is to have it always equal 100. Never below or above 100. I think this is so close. If you exceed 100, the loop reduces the previous highest in a round robin model by 1 until you are 100 and if it goes below 100 the loop needs to increase the next in a round robin model by 1 until you are at 100. – Joe Neuman Jan 13 '19 at 15:40
  • now the code will always be 100. – Bibberty Jan 13 '19 at 16:42
  • How would a person get the first input and the third input to equal 50? When the first input is moved up only the second input needs to move down until it reaches 0 then the third input and so on. If the first input is moved down then the previous input, and only input 4 would move up. – Joe Neuman Jan 13 '19 at 18:33
  • Ok, they are really pairs. Do not think of them as 1 and 3, think group 1 and group 2. You need to change the code to take in the group class name. – Bibberty Jan 13 '19 at 19:43
  • My advice right now is to really sit down and document the control, what it should do in each circumstance. This will really help to get the code tight and doing what you need. I will move this code to a repl. And post link. Because I think the question itself is answered. Now you just need help. That is not best done here. – Bibberty Jan 13 '19 at 19:57
  • Have moved code to here: https://repl.it/@PaulThomas1/CombinationSliderControl – Bibberty Jan 13 '19 at 20:05
  • I really appreciate all your help Bibberty. I think the animated gif does a pretty good job documenting the control, and what it should do in each circumstance. Is there a circumstance it does not account for? – Joe Neuman Jan 13 '19 at 20:20
  • If you watch the gif, it seems to randomly pick a slider to move. – Bibberty Jan 13 '19 at 23:19
  • If you watch the gif more closely. When any slider is moved down the slider just before it moves up. If a slider is moved up the slider just after it moves down if it's not already at zero. – Joe Neuman Jan 14 '19 at 01:31
0

const getRanges = () => Array.from(document.querySelectorAll('.hrange'));

const minValue = (v1, v2) => {
  if(v1 < 0)  return Math.abs(v1) < v2 ? v1 : -v2;
  else return v1 < 100 - v2 ? v1 : 100 - v2;
}

const sumSelectors = (exclude) => getRanges()
    .filter((r) => (exclude) ? r !== exclude : true)
    .map(r => parseInt(r.value))
    .reduce((a, s) => a + s);

const objSelector = (selectorId) => {
    let result
    getRanges().forEach((object, index) => {
      if(index === selectorId) result = object
    })
    return result
  }

const checkRanges = (changed_obj, changed_idx) => {
  let stride = (sumSelectors() > 100) ? 1 : -1;
  let selectorId = changed_idx;
  while(true) {
    selectorId = (selectorId + stride + 4) % 4;
    let selector = objSelector(selectorId)
    let selectorValue = parseInt(selector.value);
    selector.value = selectorValue + minValue(100 - sumSelectors(), selectorValue);
    if(sumSelectors() !== 100) continue;
    else break;
  }
  document.querySelector('#amount').value = sumSelectors();
};

document.addEventListener('DOMContentLoaded', function() {
  getRanges().forEach((object, index) => {
    object.addEventListener('input', (event) => {
      event.target.value = (event.target.value < 0 ? 0 : (event.target.value > 100 ? 100: event.target.value));
      checkRanges(object, index);
    });
  });
});
.hrange {
  display: block;
}
<div class="hThree">
  <input type="range" orient="vertical" value="100" class="hrange" min="0" max="100" />
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input type="range" orient="vertical" value="0" class="hrange" min="0" max="100" />
  <input id="amount" type="number" value="100" min="0" max="100" />
</div>
0

<!DOCTYPE html>
<html>
<head>
  <title>Sliders</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
</head>

<body>
  <div id="Sliders">
    <div>
  <input type="range" id="0" name="1"
         min="0" max="100" v-model.number="countity_sliders[0]" step="1" @input="reorder">
  <label for="volume">First: {{ countity_sliders[0] }}</label>
</div>

<div>
  <input type="range" id="1" name="2"
         min="0" max="100" v-model.number="countity_sliders[1]" step="1" @input="reorder">
  <label for="volume">Second: {{ countity_sliders[1] }}</label>
</div>
<div>
  <input type="range" id="2" name="3"
         min="0" max="100" v-model.number="countity_sliders[2]" step="1" @input="reorder">
  <label for="volume">Third: {{ countity_sliders[2] }}</label>
</div>
<div>
  <input type="range" id="3" name="4"
         min="0" max="100" v-model.number="countity_sliders[3]" step="1" @input="reorder">
  <label for="volume">Fourth: {{ countity_sliders[3] }}</label>
</div>
<div>
  <hr>
  summ: {{ summa }} <br>
</div>
  </div>

  <script>
    var app = new Vue({
      el: '#Sliders',
      data: {
        countity_sliders: [100,0,0,0],
        sliders_order: [0,1,2,3],
        summa: 0
      },
      methods: {
        reorder($element) {
          this.sliders_order=[parseInt($element.target.id)].concat(this.sliders_order);
          this.sliders_order = Array.from(new Set(this.sliders_order));
          this.check_summ_rule()

        },
        check_summ_rule() {
          this.summa = (this.countity_sliders[0] + this.countity_sliders[1]+ this.countity_sliders[2] + this.countity_sliders[3])
          if (this.summa > 100) {
            if (this.countity_sliders[this.sliders_order[1]]>0) {
              this.countity_sliders[this.sliders_order[1]] = this.countity_sliders[this.sliders_order[1]]-(this.summa -100);
            } else if (this.countity_sliders[this.sliders_order[2]]>0) {
              this.countity_sliders[this.sliders_order[2]] = this.countity_sliders[this.sliders_order[2]]-(this.summa -100);
            } else if (this.countity_sliders[this.sliders_order[3]]>0) {
              this.countity_sliders[this.sliders_order[3]] = this.countity_sliders[this.sliders_order[3]]-(this.summa -100);
            }
          }
          this.summa = (this.countity_sliders[0] + this.countity_sliders[1]+ this.countity_sliders[2] + this.countity_sliders[3])
          if (this.summa < 100) {
            if (this.countity_sliders[this.sliders_order[1]]>-1) {
              this.countity_sliders[this.sliders_order[1]] = this.countity_sliders[this.sliders_order[1]]+(100 - this.summa);
            } else if (this.countity_sliders[this.sliders_order[2]]>-1) {
              this.countity_sliders[this.sliders_order[2]] = this.countity_sliders[this.sliders_order[2]]+(100 - this.summa);
            } else if (this.countity_sliders[this.sliders_order[3]]>-1) {
              this.countity_sliders[this.sliders_order[3]] = this.countity_sliders[this.sliders_order[3]]+(100 - this.summa);
            }
          }
          if (this.countity_sliders[0] < 0 ) this.countity_sliders[0] = 0
          if (this.countity_sliders[1] < 0 ) this.countity_sliders[1] = 0
          if (this.countity_sliders[2] < 0 ) this.countity_sliders[2] = 0
          if (this.countity_sliders[3] < 0 ) this.countity_sliders[3] = 0
          this.summa = (this.countity_sliders[0] + this.countity_sliders[1]+ this.countity_sliders[2] + this.countity_sliders[3])
          }

        }

      })
  </script>
</body>
</html>
0

<!--   Author : Mehran   -->
<!DOCTYPE html>
<html>
<head>
    <title>Four Sliders</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
</head>

<body>
    <div id="FourSliders">
        <input type="range" id="0" min="0" max="100" v-model="val[0]" @input="changed" /><br/>
        <input type="range" id="1" min="0" max="100" v-model="val[1]" @input="changed" /><br/>
        <input type="range" id="2" min="0" max="100" v-model="val[2]" @input="changed" /><br/>
        <input type="range" id="3" min="0" max="100" v-model="val[3]" @input="changed" /><br/>
        Sum : <input type="number" value="100" v-model="sum" />
    </div>

    <script>
        var app = new Vue({
            el: '#FourSliders',
            data: {
                val: [100, 0, 0, 0],
                sum: 100
            },
            methods : {
                changed(e) {
                    var i;
                    var dif = Number(this.val[0]) + Number(this.val[1]) + Number(this.val[2]) + Number(this.val[3]) - Number(this.sum);
                    for (i = 0; i < 4; i++) {
                        if (i == e.target.id) continue;
                        if (dif > 0) {
                        if (this.val[i] - dif > 0) {
                            this.val[i] -= dif;
                            break;
                        }
                        else
                            dif -= this.val[i], this.val[i] = 0;
                        }
                        else {
                        if (this.val[i] - dif <= 100) {
                            this.val[i] -= dif;
                            break;
                        }
                        else
                            dif += (100-this.val[i]), this.val[i] = 100;
                        }
                    }
                }
            }
        })
    </script>
</body>
</html>
MEHRAN
  • 1
  • Hi! I noticed that your answer uses Vue.js but there is no tag indicating that OP uses vue. Can you update your answer to be more vanilla JS? – pedrochaves Jan 14 '19 at 12:32