Monjury
Monjury

Reputation: 13

JavaScript closure onclick

I saw many examples and tried to follow but none of them would work. So I am finally posting them here. I cannot understand what I am doing wrong.

There is a list of FAQ questions /answers with show/hide feature to answers.

Here is my code without applying closure (this targets only the last question of course)

for(var i = 1; i <= faqCount; i++) {
    question = '#' + i + ' .faq_question';
    answer = '#' + i + ' .faq_answer';
    $(question).click(function () {
        $(answer).toggle();
        $(question).toggleClass('down');
    });
}

But looking at the other examples I tried to do this but did not work:

var funcs = [];
function createfunc(i) {
    return function() {
        question = '#' + i + ' .faq_question';
        answer = '#' + i + ' .faq_answer';
        $(question).click(function () {
            $(answer).toggle();
            $(question).toggleClass('down');
        }); 
    };
}   
for (var i = 0; i < faqCount; i++) {
    funcs[i] = createfunc(i);
}
for (var j = 1; j < faqCount; j++) {
    funcs[j]();                        
}

Any help would be appreciated. Thank you.

Upvotes: 1

Views: 1348

Answers (3)

jfriend00
jfriend00

Reputation: 707486

There are several ways to approach this using closures. I find this method using a self executing function the easiest to remember how to do. Only the answer string has to be in the closure because the question is executed immediately and you can then use $(this) inside the event handler. Here's a closure using a self executing function:

for(var i = 1; i <= faqCount; i++) {
    (function(a) {
        $('#' + i + ' .faq_question').click(function () {
            $(a).toggle();
            $(this).toggleClass('down');
        });
    })('#' + i + ' .faq_answer');
}

A non-closure way that I sometimes find makes more readable code stores the index as a .data() item on the question and works like this:

for(var i = 1; i <= faqCount; i++) {
    $('#' + i + ' .faq_question').data("answerIndex", i).click(function () {
        $('#' + $(this).data("answerIndex") + ' .faq_answer').toggle();
        $(this).toggleClass('down');
    });
}

Upvotes: 3

James Kyburz
James Kyburz

Reputation: 14453

The problem with first example was when the event triggers i has the last value in the loop.

And the second one had to do with global variables I think as I saw no var.

I would solve it using a closure like so:-

This calls a function using the current value of i in the loop, encapsuling it with a closure.

for(var i = 1; i <= faqCount; i++) {
  $(question).click((function(question, answer) {
    return function() {
      $(answer).toggle();
      $(question).toggleClass('down');
    }
  })('#' + i + ' .faq_question', '#' + i + ' .faq_answer' ))
}

I would suggest checking out http://ejohn.org/apps/learn/ which is great learning material! One of the examples has just this problem.

Upvotes: 0

gen_Eric
gen_Eric

Reputation: 227270

You need to add var to question and answer inside the closure. Otherwise you are just overwriting global variables each time.

var funcs = [];
function createfunc(i) {
    return function() {
        var question = '#' + i + ' .faq_question';
        var answer = '#' + i + ' .faq_answer';
        $(question).click(function () {
            $(answer).toggle();
            $(question).toggleClass('down');
        }); 
    };
}  

Upvotes: 1

Related Questions