0

This is my html form:

<form method="post" id="new_message" action="?action=changeRate">

              <div class="form-group">
                <label for="from" class="col-sm-2 control-label">From</label>
                <div class="col-sm-3">
                <select name="from" id="from" class="form-control">
                    <option value=""></option>
                    </select>
                </div>
              </div>

              <div class="form-group">
                <label for="to" class="col-sm-2 control-label">To</label>
                <div class="col-sm-3">
                <select name="to" id="to" class="form-control">
                    <option value=""></option>
                  </select>
                </div>
              </div>

              <div class="form-group">
                <label for="value" class="col-sm-2 control-label">Rate</label>
                <div class="col-sm-3">
                  <input type="text" class="form-control" name="ra" id="ra" placeholder="result" value="0">
                </div>
              </div>


                <p><input type="button" value="Submit" name="commit" id ="commit"/> or <a class="close" href="/">Cancel</a></p>
              </form>

Here is my jquery code:

$('#to').on('change', function () {
                var fr = $('#from').val();
                var to = $('#to').val();
                for (i = 0; i < rates.length; ++i){
                if ((fr==rates[i].from) && to==rates[i].to) {

                    //the first i value is 0
                    console.log("i in if" + i);
                      $('input[name=ra]').val(rates[i].val);


                      $('#commit').click(function() {

                        //the second i value is always 3(same as array length)
                        console.log("i in commit " + i);

                      });
                  }
                }
              });

I am trying to access the same [i] property when I click the #commit button but the [i] index is increased to the maximum. How can I get the same index when the button is clicked?

Spy
  • 149
  • 10

3 Answers3

2

You have a closure over the i variable. This will cause i to be the value of the maximum when click callback fires.

What you can try is this:

$('#to').on('change', function () {
            var fr = $('#from').val();
            var to = $('#to').val();
            for (i = 0; i < rates.length; ++i){
            if ((fr==rates[i].from) && to==rates[i].to) {

                //the first i value is 0
                console.log("i in if" + i);
                  $('input[name=ra]').val(rates[i].val);

                  (function(index) {
                    $('#commit').click(function() {

                    //the second i value is always 3(same as array length)
                    console.log("index in commit " + index);

                  });
                  })(i);

              }
            }
          });
JohanP
  • 5,252
  • 2
  • 24
  • 34
1

After your loop ends, i will have become maximum, but your click handler is still referring to that same i. The value of the i during the loop when you create your click handler is not "saved", unless you pass it through a function.

function makeCommitFunction(i) {
    return function () {
        console.log("i in commit " + i);
    };
}

$('#commit').click(makeCommitFunction(i));
Bemmu
  • 17,849
  • 16
  • 76
  • 93
1

You are attaching your click handler inside the for loop. This is a bad idea even if you are sure that the if condition will be true just once. Move the click handler out of the change handler to avoid multiple binding and for easier reading.

Once you find the value inside the loop you can have a variable to store the range value (or index) that sits in the upper block as seen in the example below. As you can see foundIdx will cover both the click handler and the change handler.

Alternatively you can attach the index (or the object at that index) to the input element using .data() so that you can access it wherever you need. This is something like val() but invisible.

Your code is failing because you cannot guarantee you will click the button at exactly the same time your code finds the rate. It is much faster :) That's why you need to store the value as soon as you find it then use the value some other time since it will be there stored waiting for you.

Having a closure wrapping the click handler is another way to 'store' the value as it copies the variable's value to the newly created scope. But this is not the correct way as every change on the #to element will cause a new function to be bind to the button's click event.

Example:

// One of the options 1, 2, 3 can be used
var myInput = $('input[name=ra]');
var foundIdx = null; // option 1
var i = null; // option 3

$('#commit').click(function() {
  console.log("i (option 1) = " + myInput.data('i'));
  console.log("i (option 2) = " + foundIdx);
  console.log("i (option 3) = " + i);
  console.log("the rate : " + JSON.stringify(myInput.data('rate')));
});

$('#to').on('change', function() {
  var fr = $('#from').val();
  var to = $('#to').val();
  for (i = 0; i < rates.length; ++i) {
    if ((fr == rates[i].from) && to == rates[i].to) {
      myInput.val(rates[i].val);
      foundIdx = i; // option 1
      myInput.data('i', i); // option 2
      // option 3 is already there as long as the i is defined above
    }
  }
});

And here is a full rewrite of the code above with recommended changes and using ES2015 updates:

// declare the input variable here in the upper scope
// to access it from the both handlers
const myInput = $('input[name=ra]');

$('#commit').click(function() {
  console.log("the rate : " + JSON.stringify(myInput.data('rate')));
  // do we want to use the index?
  // console.log("the index = " + myInput.data('idx'));
});

// we can assign the handler to 
// multiple elements in case if we want it to run
// on both from & to change
$('#from, #to').on('change', function() {
  const fr = $('#from').val();
  const to = $('#to').val();
  rates.forEach( (rate, idx) => {
    if (rate.from == fr && rate.to == to) {
      myInput.val(rate.val);
      myInput.data('rate', rate);
      // do we want to store the index?
      // myInput.data('idx', idx);
    }
  }
});
Bulent Vural
  • 2,630
  • 1
  • 13
  • 18
  • That's helpful. I just dont quite understand how I get the [i] on the commit function since you declare [i] on the second function – Spy Feb 28 '17 at 00:36
  • Edited the answer to fix an issue may cause confusion. You have several options to make the i accessible from the other handler. Assign it to a variable in the wider scope, store it in element's data attribute or declare the i itself in the wider scope. – Bulent Vural Mar 01 '17 at 02:36