0

I'm having some difficulties with passing functions around. I have this code:

  var options = [
        { name: "menu 1", func: function (element) { $(element).css("color", "Red"); } },
        { name: "menu 2", func: function (element) { alert("menu 2"); } },
        { name: "menu 3", func: function (element) { alert("menu 3"); } }
    ];

        // This basically creates a div with ul, and li element for each object in options, 
        // and attaches click event to each li that should fire provided func. 
        (function menuMaker(options) {
            var menuDiv = $("<div id='test' />");
            var menuUl = $("<ul>");
            menuDiv.append(menuUl);

            for (var i = 0; i < options.length; i++) {
                var li = $("<li>" + options[i].name + "</li>");
                // **li.click(function () { options[i].func(menuDiv); });**  
                //>> The line above gives "options[i] is undefined" error. Looks like a scoping issue.

                var userFunc = options[i].func;
                **li.click(function(){ userFunc(menuDiv);});**  
                //>> The line above always fires the last function (alert("menu 3"))

                menuUl.append(li);
            }
            $(document.body).append(menuDiv);
        })(options);

and I'm getting those errors I've commented. Any ideas what am I doing wrong ?

Thanks !

1
  • So I ended up rewriting the for loop like this: for (var i = 0; i < options.length; i++) { var li = $("<li>" + options[i].name + "</li>"); var func = function (uf) { return function () { uf(menuDiv); } } // change the scope li.click(func(options[i].func)); menuUl.append(li); } Closures and scope.. Duh! Commented Nov 2, 2011 at 15:44

4 Answers 4

1

Classic example of misunderstanding closures. See how to fix this here.

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

1 Comment

Great reference! Can't believe I missed it so far.
1

The problem is indeed a scoping one. The for loop does not create a new scope, so you have to create one inside it like:

for (var i=0; i<10; i++) {
    (function(i) {
        // "i" will be local here, and be accessible 
        // form any function defined inside this closure
    })(i);
}

Comments

1

Try this, which iterates over the array adding <li> elements. The function to be called is assigned to the local 'func' variable and then used within the click handler function.

$.each(options, function() {
    var func = this.func;

    menuUl.append(
        $('<li>').text(this.name).click(function () {
            func(menuDiv);
        })
    );
});

Comments

1

Remove the "options" argument from "(function menuMaker(options) { ". The options variable is already set outside of the function. By adding it as an argument the function looks for the argument of "options" and not the predefined variable of options.

Im not great at explaining stuff, but as for passing the function through the for loop and to each individual click event, create an empty function like below, that way it doesnt reference the last value used.

Tested! Works!

var options = [
    { name: "menu 1", func: function (element) { $(element).css("color", "Red"); } },
    { name: "menu 2", func: function (element) { alert("menu 2"); } },
    { name: "menu 3", func: function (element) { alert("menu 3"); } }
];

(function menuMaker() {
    var menuDiv = $('<div id="test" />'),
        menuUl  = $('<ul />').appendTo(menuDiv);

    for (var i=0; i<options.length; i++) {

        (function() {
            var func = options[i].func;
            $('<li>' + options[i].name + '</li>')
            .click(function(){ func(menuDiv); })
            .appendTo(menuUl);        
        })();

    }

    $(document.body).append(menuDiv);
})();

1 Comment

I'd rather have the version where the inner closure gets an i argument. Your solution works because he sets the intermediate "func" variable but will break if any other use of "i" is added in the event handler.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.