Bloodlex
Bloodlex

Reputation: 487

Make group of inputs' values don't exceed a specified value

I'm making a game and using JavaScript/jQuery to spend points of character's attributes. I have six inputs like this one, each representing one attribute:

<input class="attributeVal" type="number" min="1" max="36" step="1" value="1">

And one input like this (the "pool"):

<input id="pool" type="number" min="0" max="36" step="1" value="36" readonly>

The last input functions as a "pool", which means that it is the total of remaining points to be spent on the group of the six inputs. I would like to have a following:

I've tried different approaches, but couldn't prevent player from exceeding the values beyond the pool maximum, mostly because I don't know how to prevent the change event.

This is what I've tried so far:

<script>
    let attributeVal = $(".attributeVal");

    attributeVal.on("change paste keyup", function (e) {
        let prev = $(this).data('val');
        let maxPointsOverall = 42;
        let spentPoints = 0;
        let poolInput = $('#poolInput');

        $(".attributeVal").each(function () {
            spentPoints += +$(this).val();
        });

        if ((maxPointsOverall - spentPoints) < 0) {
            $(this).val(prev);
            poolInput.val(0);
        } else {
            poolInput.val(42 - spentPoints);
        }
    });

    attributeVal.on("cut copy paste", function (e) {
        e.preventDefault();
    });
</script>

Could you please give me any hints, how I can make this one work?

let attributeVal = $(".attributeVal");

attributeVal.on("change paste keyup", function(e) {
  let prev = $(this).data('val');
  let maxPointsOverall = 42;
  let spentPoints = 0;
  let poolInput = $('#poolInput');

  $(".attributeVal").each(function() {
    spentPoints += +$(this).val();
  });

  if ((maxPointsOverall - spentPoints) < 0) {
    $(this).val(prev);
    poolInput.val(0);
  } else {
    poolInput.val(42 - spentPoints);
  }
});

attributeVal.on("cut copy paste", function(e) {
  e.preventDefault();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label class="font-weight-bold unselectable mb-3">Points available
  <input id="poolInput" type="number" min="0" max="36" step="1" value="36" readonly>
</label>

<div class="form-group">
  <label>Strength
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Endurance
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Dexterity
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Perception
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Intelligence
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Charisma
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>

Here is JSFiddle to illustrate the problem

Upvotes: 1

Views: 44

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370679

An issue is that, once the input event has been fired, you can't reset it to the previous number without saving the state of every input somewhere, which is a bit messy. You might consider first validating the value:

  • Make sure it's an integer between 0 and 36
  • Count up the number of points spent on other attributes. Make sure the current value, when added to that, is no more than the maxPointsOverall. (If it overflows, reduce the current value to the point such that pointsSpentOnOtherAttribs + newVal === maxPointsOverall)

Then set the current value to the validated newVal, and calculate / populate the value for the #poolInput.

To make sure the inputs get fixed immediately, rather than on a short delay, add an input listener (which will fire around keypress, rather than on keyup):

const attributeVal = $(".attributeVal");

attributeVal.on("change paste keyup input", function(e) {
  let newVal = Math.floor($(this).val());
  newVal = Math.max(1, newVal);
  newVal = Math.min(36, newVal);
  const maxPointsOverall = 42;
  let pointsSpentOnOtherAttribs = 0;
  $(".attributeVal").not(this).each(function() {
    pointsSpentOnOtherAttribs += +$(this).val();
  });
  if (newVal + pointsSpentOnOtherAttribs > maxPointsOverall) {
    // Invalid, reset these points to the maximum allowable:
    newVal = maxPointsOverall - pointsSpentOnOtherAttribs;
  }
  // New value has been validated, put it into the DOM:
  $(this).val(newVal);
  const pointsLeft = maxPointsOverall - (pointsSpentOnOtherAttribs + newVal);
  $('#poolInput').val(pointsLeft);
});

attributeVal.on("cut copy paste", function(e) {
  e.preventDefault();
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label class="font-weight-bold unselectable mb-3">Points available
  <input id="poolInput" type="number" min="0" max="36" step="1" value="36" readonly>
</label>

<div class="form-group">
  <label>Strength
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Endurance
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Dexterity
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Perception
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Intelligence
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>
<div class="form-group">
  <label>Charisma
    <input class="attributeVal" type="number" min="1" max="36" step="1" value="1">
  </label>
</div>

Upvotes: 1

Related Questions