4

I have a loop to run through my buttons to give it a hover state but I can't seem to know how to fix the 'i' inside the functions to when it's looped. Okay, that's a bad way of explaining what I'm trying to achieve, maybe the code will be clearer.

for ( i = 1; i < 4; i++ ) {
  $( '#buttons #'+i ).hover( function() {
 $( this ).text( i );
  }, function() {
 $( this ).text( 'Button' );
  });
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="buttons">
  <h1 id="1">Button</h1>
  <h1 id="2">Button</h1>
  <h1 id="3">Button</h1>
</div>

The hover state of each button is supposed to show 1, 2, 3 respectively.

Kelvin Zhao
  • 2,243
  • 3
  • 22
  • 31

5 Answers5

3

Use this to solve it:

for ( i = 1; i < 4; i++ ) {
      $( '#scene-3 .selection img:nth-child('+i+')' ).hover(
       (function (value){
          return function (){
            $( this ).attr( 'src', 'img/s3-b'+value+'b.png' );
          }
       })(i)
       ,(function (value){
           return function (){
             $( this ).attr( 'src', 'img/s3-b'+value+'.png' );
           }
         })(i));
    };

For more details see this or this issue on Stack overflow

Community
  • 1
  • 1
Milan Tomeš
  • 384
  • 2
  • 10
  • Wow, awesome, nice article. This is really an interesting problem/issue! – Kelvin Zhao Jul 29 '16 at 07:25
  • You can simplify this further. `(function (value){return function (){ ... }})(i)` could be `(value)=>function(){ ... }(i)` – Xeren Narcy Jul 29 '16 at 07:28
  • Looks like lambda in C# – Milan Tomeš Jul 29 '16 at 07:32
  • @MilanTomeš it more or less is. `()=>expr` is a function that takes no arguments and when executed, evaluates and returns `expr`. The one thing it can't do like `function` is have a `this` object, so when you don't need that, you can use the lambda syntax to save on space / readability. It is a proper function syntax, you could also do `()=>{return expr}` but that isn't as fun :) – Xeren Narcy Jul 29 '16 at 07:33
3

If you'd like to do this in a way that is similar to your current code, it's very easy. Simply call a function in your loop, pass it the i variable, and set up the hover() callbacks inside that function. The only change to your code is the function call and function definition:

for ( i = 1; i < 4; i++ ) {
    setupHover( i );
}

function setupHover( i ) {
  $( '#buttons #' + i ).hover( function() {
 $( this ).text( i );
  }, function() {
 $( this ).text( 'Button' );
  });
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="buttons">
  <h1 id="1">Button</h1>
  <h1 id="2">Button</h1>
  <h1 id="3">Button</h1>
</div>

Why does this work where the original doesn't? Note that the two hover() callbacks are not called at the time your loop runs. They are called later, when you roll the mouse over the elements.

So the original version the for loop has already run to completion, long before the hover() callbacks get called in response to mouse movement. The i variable already has its final value of 4 before those callbacks run.

However, by calling a function inside the loop and passing i into that function, the called function captures a reference to the variable you pass in. Note that the i inside the setupHover() function is a separate variable from the i in your for loop. It's only coincidental that it has the same name, but it really is a separate variable that is unique to each invocation of the function.

Read up on "JavaScript closures" for more information.

You can also do the same thing in other ways, such as the technique in another answer where a function returns another function. This is a useful approach in some situations, but if you simply need a closure it is usually overkill.

Any function call can create a closure, so the only one you need is a simple replacement of the loop body with a function call - either a named function outside the loop as in my example, or as you noted in your comment, an anonymous function called inline in the loop. It's the act of calling the function that creates a closure, whichever way you define and call the function.

Michael Geary
  • 28,450
  • 9
  • 65
  • 75
  • 1
    I learned so much in this question alone! Thanks everyone for the suggestions and links. @Michael Greary Actually in your example, instead of calling the setupHover function, I could also plonk that in as a unnamed function and run it as it loops right? Is there any technical reasons why that shouldn't be done? – Kelvin Zhao Jul 29 '16 at 09:25
  • 1
    Indeed you are correct! You could use an anonymous function and invoke it directly in the loop. You're doing exactly the same thing whether you call a named function as in my example, or call an anonymous function inline. In both cases it's the act of calling the function that creates the closure. (And glad you found my answer along with the others helpful - it's great to hear multiple views and see different ways of solving a problem.) – Michael Geary Jul 29 '16 at 09:29
0

The hover gives you the number 4 because the variable i value is 4 at the end of the loop so when hover is triggered you are getting the 4th image. You can check this by initialize variable i after the loop as 5 then on hover the image will be s3-b5b.png.

instead of using loop you can try this,

Refer get the nth-child number of an element in jquery

$('#scene-3 .selection img').hover(function () {
    $( this ).attr( 'src', 'img/s3-b'+parseInt($(this).index() + 1)+'b.png' );
}, function () {
    $( this ).attr( 'src', 'img/s3-b'+parseInt($(this).index() + 1)+'.png' );
});
Community
  • 1
  • 1
Karthik N
  • 921
  • 8
  • 16
  • Nice way of achieving the result! It saves/cut out the looping too. But I'm still wondering how to make the i static when it runs into the hover function created by the loop. Hmm hmm.. – Kelvin Zhao Jul 29 '16 at 06:25
  • "But I'm still wondering how to make the i static when it runs into the hover function created by the loop." pls I can't understand. Your variable i should be dynamic based on the elements you hover so it can be used in the function created by the loop. – Karthik N Jul 29 '16 at 06:43
  • Thanks for your replies! I'll be using your method for the hover. What I meant is, is there any way to make each i fixed inside the hover function so that it's 1, 2, 3, instead of all being 4. – Kelvin Zhao Jul 29 '16 at 06:46
  • one way is by saving the variable i in the element as attribute during loop and on hover you can use it. – Karthik N Jul 29 '16 at 06:49
  • I personally think it is a bad idea to depend on its index as it could change in later improvements on the application, making it difficult and not so flexible to implement other elements between these buttons – Flying Gambit Jul 29 '16 at 07:24
  • The question I answer was now updated. Previously their was html content was given. Based on give info I answered it @FlyingGambit – Karthik N Jul 29 '16 at 07:32
0

This might not be what you are looking for but this is another way to fix your code.

In the iteration the "i" would be 4 in the end, thats why all buttons were displaying 4. If you change it to $( this ).text( this.id ); then it will work

for ( i = 1; i < 4; i++ ) {
  $( '#buttons #'+i ).hover( function() {
 $( this ).text( this.id );
  }, function() {
 $( this ).text( 'Button' );
  });
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="buttons">
  <h1 id="1">Button</h1>
  <h1 id="2">Button</h1>
  <h1 id="3">Button</h1>
</div>
Flying Gambit
  • 1,238
  • 1
  • 15
  • 32
0

Here is another way to make it more dynamically:

$(function() {
  var buttonsSelectorString = '#buttons > h1';
  var defaultButtonText = 'Button';

  var iterator = function(index, element) {
    var mouseEnterAction = function(event) {
      $(event.target).text(index + 1);
    };

    var mouseLeaveAction = function(event) {
      $(event.target).text(defaultButtonText);
    };

    $(element)
      .on('mouseenter', mouseEnterAction)
      .on('mouseleave', mouseLeaveAction);
  };

  $(buttonsSelectorString).each(iterator);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="buttons">
  <h1>Button</h1>
  <h1>Button</h1>
  <h1>Button</h1>
</div>

You can find the commented source code here: http://codepen.io/anon/pen/WxKAKm