views:

98

answers:

5

For some reason no matter what, the pageNumber ends up being the last value in the loop for the loopCounter. Now I would understand that if I were directly using loopCounter in the closure itself, but I'm not. As you can see from the code below, I am creating a new variable within the closure to take the current value of loopCounter.

Only thing I can figure is (Assuming that javascript treats everything as a reference type) that pageNumber is taking the reference to loopCounter so no matter how many times I create a new pageNumber, it's always pointing at the loopCounter object. Therefore, whatever value loopCounter ends up with will be the value any pageNumber will point to.

How do I get it to not point at loopCounter but create a new pageNumber per iteration that holds the current loopCounter value?

for (var loopCounter = result.StartingPoint; loopCounter <= result.HighestPageCount; loopCounter++)
{
  ...
  var newDiv = document.createElement('div');
  ...
  //trying to remove the reference to loopCounter
  var pageNumber = loopCounter;
  newDiv.onclick = 
    function(event) 
 { //Right here ---V
   getResultUsingUrl(result.PagerPreLink + "&pageNumber=" + pageNumber);
    };

  ...
}

SOLUTION

Thanks to a couple answers below:

function createClickMethod(loopCounter, link)
{
    var pageNumber = loopCounter;

    return function(event) { getResultUsingUrl(link + "&pageNumber=" + pageNumber); };
}

and I can call like:

newDiv.onclick = createClickMethod(loopCounter, result.PagerPreLink);

Or if I want to use jQuery... suggested below:

jQuery(newDiv).click
(
    createClickMethod(loopCounter, result.PagerPreLink)
);
A: 

The onclick closure will not maintain its scope, so it won't be able to access result.

Take a look at dojo.hitch for an easy and powerful solution, so that you can control its scope.

Wahnfrieden
And at that point, you can just use dojo.forEach and dojo.connect
seth
+1  A: 

Is result.StartingPoint really a primitive type, e.g. an actual Number type? If not, then perhaps what's happening is that you are getting a reference to that object and then the string concatenation is doing a type-coercion for you. Try this instead:

var pageNumber = new Number(loopCounter); // force coercion

-Oisin

x0n
Won't this only make pageNumber reference that method and that method still point to loopCounter?
Programmin Tool
+3  A: 

You are not creating a new pageNumber each time. You only have one. Scope in JavaScript does not extend beyond function-scope. Any "var" you declare in a function -- in or out of loops -- works exactly as it would had you declared it right at the top of the function.

http://javascript.crockford.com/code.html

brownstone
Interesting, good old dynamic scoping.
Zoidberg
+4  A: 

Like everyone else said, it's a scoping problem. Without using a JS library, you can do something like this:

    newDiv.onclick = (function() {
                        var num = loopCounter;
                        return function(evt) {
                           console.log( num );
                        }
                   })();

You just need to create another closure around the value.

seth
So basically I need a method to create the event method to keep the scope clear.
Programmin Tool
Yep. Since JS scope is limited to functions, not blocks you need to create a new function. This is essentially what the various JS libs force you to do with their event connect methods.
seth
+1  A: 

Javascript closures store references to their variables, so all of your onclick handlers are using the same variable.

You need to capture the variable in an intermediate function, like this:

function buildClickHandler(pageNumber) {
    return function(event)  {    //Create and return a new function
        getResultUsingUrl(result.PagerPreLink + "&pageNumber=" + pageNumber);
    }
}

Then, use that function to create onclick handlers, like this:

for (var loopCounter = result.StartingPoint; loopCounter <= result.HighestPageCount; loopCounter++) { 
    //...

    var newDiv = document.createElement('div');

    newDiv.onclick = buildClickHandler(loopCounter);
}

Each call to buildClickHandler creates a separate closure that has its own variable.


As an aside, consider using jQuery to do DOM manipulation; it's much easier than raw DOM APIs.

In your example, you could write

$('<div />').click(buildClickHandler(loopCounter));
SLaks
Wish I could mark two as correct. I would most likely use jquery like you suggested, but I wanted this to be generic so more people would answer.
Programmin Tool