0

I have this weird situation and I don't know why do the output is not what I expect. This is only a simple for-loop function. Can somebody explain me why this happens?

var pm = 2; 
for (var i = 0; i < pm; i++) {
     $("#specialty_pm_"+i).mouseenter(function(){
            alert(i);
    });
};

I have 2 divs in my html that has id="specialty_pm_<?php echo $countPM; ?>" and that div is inside a for loop function in php. foreach ($employee_info as $emp_info){ $countPM++; } I expect that the alert on hover in the 1st div is '1' and the 2nd div is '2'. but when I hover the 1st div, it will alert '2'.

3

4 Answers 4

3

you should use JavaScript closure:

var pm = 2; 
for (var i = 0; i < pm; i++) {
    var func = (function(i){
        return function(){
            alert(i);
        }
    })(i);
    $("#specialty_pm_"+i).mouseenter(func);
};

The point is in your code all the mouseenter functions use the same variable i, and after your loop ends, it has its last value which is 2. Using a scope chain, with nested functions and their closures in JavaScript, you can create safe scopes for your variables. Basically what nested functions do, is to provide a outer LexicalEnvironment for the inner function. You can find more information in this post:

Scope Chain in Javascript.

Sign up to request clarification or add additional context in comments.

2 Comments

"Using closures in JavaScript you can create safe scopes for your variables." No no no! The way closures work in JS is the reason for this problem in the first place. The event handlers that are created in the loop are already closures. Closures themselves don't create scope, only function invocation creates a new scope. And that's the solution to the problem: Execute a function to create a new scope. Whether that function is a closure or not doesn't matter.
@FelixKling: My bad in describing the whole concept and thanks for your informative feedback. I try to be more descriptive.
2

You alert can't wirks because i has only one instance. Yoi can check variable i inside your div in this case.

try this:

$("#specialty_pm_"+i).mouseenter(function(){
     var id = $(this).attr('id');
     alert(id.substring(0,13));
});

2 Comments

Why should the OP try this? Care to provide an explanation?
yes the instance is only one of that variable @user2864740
1

The reason, as already mentioned, is that the scope of i is the same for both eventhandlers, and as such it will have the same value for both of them.

There is a couple of solution for this problem.

Solution 1: create a new scope via a immediate function

var pm = 2; 
for (var i = 0; i < pm; i++) {
     $("#specialty_pm_"+i).mouseenter(function(instance){
        return function() {  alert(instance); };
    }(i));
};

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/

Solution 2: use jQuerys data method to store the value

var pm = 2; 
for (var i = 0; i < pm; i++) {
     $("#specialty_pm_"+i).data('instance',i).mouseenter(function(){
            alert($(this).data('instance'));
    });
};

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/1/

Solution 3: bind the instance number to the eventhandler

var pm = 2; 
for (var i = 0; i < pm; i++) {
     $("#specialty_pm_"+i).mouseenter(function(instance){
        alert(instance);
    }.bind(null,i));
};

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/2/

Solution 3 has a few caveats - this is being bound as null, and thus it can no longer be used as a reference to the dom element, like jQuery eventhandlers nomrally do. Also bind isn't supported by older browsers, but this can be mitigated by usinga polyfill, a good one can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Solution 4: Be smart and use a delegate instead of binding event handlers in a loop

     $(document.body).on('mouseenter','.specialty_pm',function(){
        alert($(this).data('id'));
    });

You can see a fiddle of it in action here: http://jsfiddle.net/LP6ZQ/4/

Solution 4 is the "right way" to do it, but it will require you to change the way you build your markup

Comments

0

You could get rid of the for loop in your JavaScript and use jQuery's Starts With Selector to select ALL elements whose id begins with 'specialty_pm_'

$("[id^='specialty_pm_']").on("mouseenter",function(){
     var id = $(this).attr('id');
     alert(id.substring(13));
});

E.G: http://jsfiddle.net/6bTJ3/

Alternatively you could add a class to each of these specialty_pm_[n] items to make it even simpler to select them in jQuery.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.