Juan Juan - 2 months ago 7
Javascript Question

jQuery function ignoring incremented value

I can't seem to fix this issue, and after two hours or so I gave up. I am not good with javascript at all.

Here's a jfiddle with a nice jquery I found around here:

[html]
<button id="addProduct">Add Product</button>
<div id="someContainer"></div>
<span></span>


[javascript]
var i = 1;
$("#addProduct").click(function() {
$("<div />", { "class":"wrapper", id:"product"+i })
.append($("<input />", { type: "text", id:"name"+i }))
.append($("<input />", { type: "text", id:"property"+i }))
.appendTo("#someContainer");
i++;
});

$("input").live("click", function() {
$("span").text("Clicked ID: " + this.id);
});


It appends two new inputs with a unique id. Now what I want to do is use on() to reset the value of a different input when on "focus". The thing is, I want it to clear its corresponding input so for example focusing on "id=name1" would reset "id=property2" and vice verse, focusing "id=name2" would reset "id=property2" vice versa and so on...

So I added to it:

var i = 1;
$("#addProduct").click(function() {
$("<div />", { "class":"wrapper", id:"product"+i })
.append($("<input />", { type: "text", class:"name"+i }))
.append($("<input />", { type: "text", class:"property"+i }))
.appendTo("#someContainer");

$("#product"+i).on("focus", $(".name"+i), function() {
$(".property"+i).val("");
});

$("#product"+i).on("focus", $(".property"+i), function() {
$(".name"+i).val("");
});

i++;

});


But for whatever reason, I cannot get these inputs to reset. The function seems to be ignoring the incrementing values. if I remove var i it seems to work but then it clears all my inputs. Logically adding "+i" to the end of the class should fix the issue but it doesn't. I have tried using different form of selection the class:

$("#product"+i).find(".name"+i);


but this doesn't seem to help either. I really don't understand what is wrong with this. If someone could help I'd greatly appreciate thanks.

Answer

Don't try to use incremental Ids for something like this. Instead, use classes. It's much easier to work with dynamic elements this way. See the below example:

$("#addProduct").click(function() {
   $("<div />", { "class":"wrapper", class:"product"})
   .append($("<input />", { type: "text", class:"name" }))
   .append($("<input />", { type: "text", class:"property" }))
   .appendTo("#someContainer");
});

$('#someContainer').on('click', '.name, .property', function() {
  var $this = $(this);
  var $otherInput = $this.parent().find('.name, .property').not($this).val('');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="addProduct">Add Product</button>
<div id="someContainer"></div>
<span></span>


So, how does this work? Well....

First, this handler listens for any clicks inside the #someContainer element. When a click is heard, it checks if the clicked element has either the name or property class, if it does, the function is called.

$('#someContainer').on('click', '.name, .property', function() {

});

Next, we cache this as a jQuery object. This is the clicked element.

var $this = $(this);

Now we have:

$this.parent().find('.name, .property').not($this).val('');
  • $this.parent() gets the parent of the clicked element
  • .find('.name, .property') finds any children of that parent that have either the name or property class
  • .not($this) excludes the clicked element from the matched elements
  • val('') clears the value of all the matched elements, only one in this case

Note that madalin ivascu's use of .siblings().val(""); is more concise. I honestly forgot about it, though this way would be more flexible if you have more elements later and didnt want to clear some.


Now that we know a better way, let's talk about why your attempt did not work.

The first issue is the binding of your handlers. You have:

$("#product"+i).on("focus", $(".name"+i), function() {
   //.....
});

If you take a look at the docs for .on() it says the following about the second parameter:

selector

Type: String

A selector string to filter the descendants of the selected elements that trigger the event. If the selector is null or omitted, the event is always triggered when it reaches the selected element.

You are passing a jQuery object $(".name"+i) but this should be a string, so just ".name"+i like this:

$("#product"+i).on("focus", ".name"+i, function() {
   //.....
});

The second issue is the logic that increments i is flawed. Let's look at why:

var i = 1; // here we set i to 1, cool

$("#addProduct").click(function() {
   $("<div />", { "class":"wrapper", id:"product"+i })
   .append($("<input />", { type: "text", class:"name"+i }))
   .append($("<input />", { type: "text", class:"property"+i }))
   .appendTo("#someContainer");

   // the 3 lines above all use i and i=1, so far so good

  $("#product" + i).on("focus", ".name" + i, function() { // for clarity, I have corrected this line, again, here i=1 so we're fine 
    $(".property" + i).val(""); // here is the issue, but we'll come back to this...
  });

  //..... other code....

  i++; // here we increment i so now, i=2 , seems ok, but it's not

});

So we now know the issue is with the nested handler. Here is what happens:

When we bind the handler, i is evaluated right away and i=1 on this line

$("#product" + i).on("focus", ".name" + i, function() { 
    //....
});

However, the code inside the handler does not get executed until the user clicks on $("#product1") which will be after we have incremented i

This means, that when the below line runs, i will always be one number higher than the last element added to the page. So even if you fix the selectors, your going to be trying to clear a non-existent element

$(".property" + i).val(""); // i here will always be the newest value for i 
                            // NOT the value of i when the handler was attached

Run the below snippet and watch the console output to see what I mean:

var i = 1;
$("#addProduct").click(function() {
   $("<div />", { "class":"wrapper", id:"product"+i })
   .append($("<input />", { type: "text", class:"name"+i }))
   .append($("<input />", { type: "text", class:"property"+i }))
   .appendTo("#someContainer");
    
  $("#product" + i).on("focus", ".property" + i, function() {
    console.log(".property" + i);
  }); 
  $("#product" + i).on("focus", ".name" + i, function() {
    console.log(".name" + i);
  });  
  i++;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="addProduct">Add Product</button>
<div id="someContainer"></div>
<span></span>