Nadroev
Nadroev

Reputation: 363

How to swap values by changing one of them via javascript?

There are three fields with numbers from 1 to 3. I am trying to make it so if a person uses only the arrows there should always be one "1", one "2", and one "3". Why is it not always working and how could I make it work?

jQuery(document).ready(function($) {
  var prevNumber;
  $(".numbers").focus(function() {
    prevNumber = $(this).val();
  }).change(function() {
    curNumber = $(this).val();
    $('.numbers[value="' + curNumber + '"]:not(:focus)').first().val(prevNumber);
    prevNumber = curNumber;
  });
});
<input type="number" value="1" min="1" max="3" step="1" class="numbers" />
<input type="number" value="2" min="1" max="3" step="1" class="numbers" />
<input type="number" value="3" min="1" max="3" step="1" class="numbers" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Here is a jsfiddle.

Upvotes: 4

Views: 302

Answers (4)

Paul S.
Paul S.

Reputation: 66324

I would solve this by adding another property to each node.

jQuery(document).ready(function($) {
    var $numbers = $('.numbers'),
        off = false;
    $numbers.each(function () {
        this.prev = this.value;
    });
    $numbers.change(function () {
        var source = this;
        if (off) return; // this algorithm is already running
        off = true;
        $numbers.each(function () {
            if (this !== source && this.value === source.value) { // if conflict
                this.prev = this.value = source.prev; // swap with old value
            }
        });
        this.prev = this.value; // update for next time
        off = false; // re-enable
    });
});
<input type="number" value="1" min="1" max="3" step="1" class="numbers" />
<input type="number" value="2" min="1" max="3" step="1" class="numbers" />
<input type="number" value="3" min="1" max="3" step="1" class="numbers" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Upvotes: 1

Demurgos
Demurgos

Reputation: 1682

I'd like to propose you a more elaborate solution that might help you as your application grows: you should store the JS value apart from the <input /> controlling it (this way, adding multiple <input>s or modifying the value from your code becomes easier. The most important thing is that you should have a single trusted data storage independent from the DOM that is always in a valid state (in this case: without duplicates).

Given your problem (the values should be unique and only swaps should be possible), it's easier to handle it as a pure JS problem (and do not try to do everything in jQuery - though I agree it's a great lib, it's not necessarily the best tool for everything).

Here is my commented solution:

jQuery(document).ready(function($) {

  // This array the current values of the inputs
  var numbers = [1, 2, 3];

  // numbers should not be modified directly but trough
  // setNumber: this function ensures that numbers is ALWAYS
  // a swap of the original value ([1, 2, 3]).
  // When a value is set, this function returns the previous
  // index of the value
  function setNumber(index, newVal) {
    // find other index
    var prevIndex = numbers.indexOf(newVal);
    if (prevIndex < 0) {
      alert('Invalid value, please enter 1, 2 or 3');
    }
    // swap
    numbers[prevIndex] = numbers[index];
    numbers[index] = newVal;
    return prevIndex;
  }

  // This function updates the inputs to ensure
  // that their displayed value match the one stored in numbers
  function updateNumbersView() {
    $(".numbers").each(function(idx, elem) {
      elem = $(elem);
      if (parseInt(elem.val(), 10) !== numbers[idx]) {
        elem.val(numbers[idx]);
      }
    });
  }

  $(".numbers").change(function() {
    var self = $(this);
    var curNumber = parseInt(self.val(), 10);
    var curIndex = $(".numbers").index(self);
    if (curNumber === numbers[curIndex]) {
      return false; // no change
    }
    
    // update model:
    var changedIndex = setNumber(curIndex, curNumber);
    
    // updateView:
    $('.numbers').eq(changedIndex).val(numbers[changedIndex]);
    
    // or to be more generic (ie. multiple inputs for the same value):
    // updateNumbersView();
    
  });

});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="number" value="1" min="1" max="3" step="1" class="numbers" />
<input type="number" value="2" min="1" max="3" step="1" class="numbers" />
<input type="number" value="3" min="1" max="3" step="1" class="numbers" />

Upvotes: 1

AndreyKo
AndreyKo

Reputation: 911

The problem in your code is that the "value" attribute contains the initial value for the input. When you use the following selector:

$('.numbers[value="' + curNumber + '"]:not(:focus)')

you are selecting the element that initially had the given value, and not has this value now. Try this selector instead, and all will work fine:

$('.numbers:not(:focus)').filter(function(index, element){ 
    return $(element).val() == curNumber; 
  })

Here is a jsfiddle. ;)

Upvotes: 2

T.J. Crowder
T.J. Crowder

Reputation: 1074248

Why that approach doesn't work

The value attribute is not connected to the value of the input. I know that sound surprising. :-) The value attribute is the default value of the input. It doesn't change (unless you use setAttribute("value", x); or .defaultValue = x; to change it).

Your selector uses the attribute:

$('.numbers[value="' + curNumber + '"]')...

So it'll work on inputs whose value hasn't been changed by the user, but will fail once they have, selecting the wrong input.

How you could fix it

You could change the default value as well as the value by setting both defaultValue and value (being sure to update the defaultValue on the one that changed, too), like this (see comments):

jQuery(document).ready(function($) {
  var prevNumber;
  $(".numbers").focus(function() {
    prevNumber = $(this).val();
  }).change(function() {
    // Get the element wrapper
    var $this = $(this);
    
    // Get the current value
    var curNumber = $this.val();
    
    // Make sure the default value on this element is updated
    this.defaultValue = curNumber;
    
    // Update both the value and default value on the other
    // input that used to have this number
    $('.numbers[value="' + curNumber + '"]:not(:focus)').first().val(prevNumber).prop("defaultValue", prevNumber);
    prevNumber = curNumber;
  });
});
<input type="number" value="1" min="1" max="3" step="1" class="numbers" />
<input type="number" value="2" min="1" max="3" step="1" class="numbers" />
<input type="number" value="3" min="1" max="3" step="1" class="numbers" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

What I'd do instead (maybe -- your approach is growing on me)

I think I'd approach it without trying to remember state, e.g., just in the change: Get the number of the one that changed, then assign any other numbers to its siblings. See the comments:

var numbers = $(".numbers").map(function() { return this.value; }).get();
jQuery(document).ready(function($) {
  $(".numbers").change(function() {
    // Get a wrapper for this input
    var $this = $(this);
    // Get this number
    var thisNumber = $this.val();
    // Get the unused numbers
    var unused = numbers.filter(function(num) { return num != thisNumber; });
    // Assign them to the siblings, in order
    $this.siblings().val(function(index) {
      return unused[index];
    });
  });
});
<input type="number" value="1" min="1" max="3" step="1" class="numbers" />
<input type="number" value="2" min="1" max="3" step="1" class="numbers" />
<input type="number" value="3" min="1" max="3" step="1" class="numbers" />
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

I kept that general, rather than assuming the values would only be 1, 2, and 3 (and rather than assuming there'd only be three numbers).

Upvotes: 6

Related Questions