0

The problem I've got is trying to dynamically generate answers into pre-made boxes for a JavaScript quiz. Each answer has an index and it's the index that determines if the answer is correct (this info comes from a JSON and can't be modified). For each question, the answers are all randomised. I also have a seperate function to feed back t the user if they were right or wrong.

The approach I initally took for to use a For Loop to dynamically build each answer, but this was loading the same index into each answer (a common problem when using variables inside for loops). So I decided to add a listener to each answer and pulled this out into a seperate function to ensure each answer had it's own index. That part worked great, each had it own listener passing the index to the answer function, but when the next question was loaded two listeners had been added to the answer as it was re-generated for the next question. So, logically I added a remove listener, but this doesn't seem to work.

I know what I need to do to make this quiz work, I'm just not sure how to do it. Here's the code I have already:

Loading Answers to screen:

// Load Answers
for (i=0; i<4; i++) {
    var answerBox = 'answer' + (i + 1);
    app.LoadInnerHTML(answerBox, answers[i].title);
    this.listenerFunc(i);
}

Adding the Listeners:

this.listenerFunc = function(j) { 
        var thisQuiz = this;
        var answers = this.CurrentAnswers;
        var answerBox = 'answer' + (j + 1);
        var answerIndex = answers[j].index;
        var answerElement = document.getElementById(answerBox);
        // If there already is an event listener then remove it
        answerElement.removeEventListener('click', function () {
                thisQuiz.AnswerQuestion(answerIndex);
        });
        // Add New Event Listener
        if (answerElement.addEventListener) {  // all browsers except IE before version 9
            answerElement.addEventListener("click", function () {
                thisQuiz.AnswerQuestion(answerIndex);
            }, false);
        } else {
            if (answerElement.attachEvent) {   // IE before version 9
                answerElement.attachEvent("click", function () {
                    thisQuiz.AnswerQuestion(answerIndex);
                });
            }
        }
    };

Answering the Question:

// Answer Questions
    this.AnswerQuestion = function (index) {
        // If Question is answered correctly
        if (index == 0) {
            alert('Question Answered Correctly ' + index);
        } else {
            alert('Question Answered Incorrectly ' + index);
        }
        // Call Next Question
        this.LoadNextQuestion();
    }

I just feel like I need to untangle everything becasue I've kept patching it to try and make it work. As a side note though I can only work with JavaScript, I can't use any frameworks like JQuery - I'm pretty sure theres a really easy solution in JQuery, but unfortunately I'm bound to just JavaScript.

3
  • Please provide a "Minimal, Complete, and Verifiable example": stackoverflow.com/help/mcve Commented Feb 14, 2017 at 10:09
  • That's all I have that's relevant to the problem, there's nothing else I can show you... Commented Feb 14, 2017 at 10:12
  • It's not really clear why you are doing things the way you do and whether there might be a better way to achieve what you try, without seeing the JSON structure and the HTML of a question/answer pair. Commented Feb 14, 2017 at 10:14

1 Answer 1

1

The problem is with passing closure to removeEventListener. It's a completely new reference to callback so browser is not able remove it, because it's not defined in listeners list.

You need to extract somewhere outside (away from listenerFunc) a list of listeners:

this.listenerCallbacks = [];

this.listenerFunc = function(j) { 
        // .. head of listenerFunc

        var answerIndex = answers[j].index;

        if (!this.listenerCallbacks[answerIndex]) {
                this.listenerCallbacks[answerIndex] = function () {
                        thisQuiz.AnswerQuestion(answerIndex);
                }
        }

        var answerElement = document.getElementById(answerBox);

        // If there already is an event listener then remove it
        answerElement.removeEventListener('click', this.listenerCallbacks[answerIndex]);

        // Add New Event Listener
        if (answerElement.addEventListener) {  // all browsers except IE before version 9
            answerElement.addEventListener("click", this.listenerCallbacks[answerIndex], false);
        } else if (answerElement.attachEvent) {   // IE before version 9
            answerElement.attachEvent("click", this.listenerCallbacks[answerIndex]);
        }
    };
Sign up to request clarification or add additional context in comments.

9 Comments

That still gives me multiple listeners unfortunately - it's the remove listener that isn't working for some reason
Did you check if listeners are stored in listenersCallbacks? Have you tried checking bound event listeners on element in developer console? Also - on which browser are you testing?
The listenersCallbacks contains four instances of QuizBase/this.listenerFunc/this.listenerCallbacks[answerIndex]() - I'm testing on most recent version of Firefox
Does the answerIndex change when calling listenerFunc with the same value for j argument? For me it looks like there's a problem with keeping reference for the callback of given answer.
Yeah answer index changes each time listenerFunc is called - so answer index will be out of sync in any random order of 0-3 but j is sequencial
|

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.