Home > OS >  Javascript oop undefined reading length in loop
Javascript oop undefined reading length in loop

Time:01-05

Im creating an oop project with multiple functions in a class. I have a piece of code, but since I'm using bind it doesn't work anymore.

    document.getElementById("finalLink").innerHTML  =
        "<a id='FLink' href='https://www.voetbalshop.nl/voetbalschoenen.html#' onclick='location.href=this.href getLink(url);return false;'>Result</a>";

    class QuestionControl{
        /**
         * @param {QuizPart[]} quiz
         */
        constructor(quiz) {
            this.quiz = quiz;
            this.url = [];
            this.questionNumber = -1;
            this.button = document.getElementById('answer');
            this.questionName = document.getElementById('question');
            this.nextbtn = document.getElementById('nextbtn');
            this.prevbtn = document.getElementById('prevbtn')
            this.resultbtn = document.getElementById('FLink');
        }

        Initialize(){
            this.NextQuestion();
        }

        /**
         *
         * @param {int} question
         * @returns {QuizPart}
         */
        SetQuestion(question){
            if (this.questionNumber >= 0){
                let oldAnswerButton = document.querySelectorAll('.filter_anwser');

                // Deletes old question when the next question is clicked
                for (let answerButton of oldAnswerButton) {
                    answerButton.style.display = 'none';
                }
            }

            this.questionNumber = question;

            let q = this.quiz[question];
            // Check if your at the last question so the next button will stop being displayed.
            if (this.questionNumber === Quiz.length-1) {
                this.nextbtn.style.display = 'none';
                this.prevbtn.style.display = 'block';
                this.resultbtn.style.display = 'grid';
            } else if (this.questionNumber === 0 ) {
                this.nextbtn.style.display = 'block';
                this.prevbtn.style.display = 'none';
                this.resultbtn.style.display = 'none';
            } else{
                this.nextbtn.style.display = 'block';
                this.prevbtn.style.display = 'block';
                this.resultbtn.style.display = 'none';
            }

            // Displays Question
            this.questionName.textContent = q.questionDescription;
            this.questionName.id = "questionID";

            return q;
        }

        NextQuestion() {
            let question = this.SetQuestion.bind(this.questionNumber   1);

            // Displays answers of the questions
            for (let y = 0; y < question.chosenAnswer.length; y  ) {
                let item = question.chosenAnswer[y];
                // Display answer buttons
                let btn = document.createElement('button');
                btn.value = item.id;
                btn.className = "filter_anwser";
                btn.textContent = item.name;
                this.button.appendChild(btn);
            }
        }

        PrevQuestion() {
            let question = this.SetQuestion(this.questionNumber - 1);

            // Displays answers of the questions
            for (let y = 0; y < question.chosenAnswer.length; y  ) {
                let item = question.chosenAnswer[y];
                // Display answer buttons

                let btn = document.querySelector('button[value="'   item.id   '"]');
                btn.style.display = 'block';
                console.log(btn);
            }
        }

        /**
         * Returns the parameters for the URL.
         *
         * @returns {string}
         */
        getLink() {
            let tmp = [];
            for (let i = 0; i < this.url.length; i  ) {
                // Check if question is from the same quiz part and adds a , between chosen answers and add the right prefix at the beginning
                if (this.url[i].length > 0){
                    tmp.push(""   Quiz[i].prefix   this.url[i].join(","))
                }
            }
            /// If answers are from different quiz parts add a & between answers.
            console.log(this.url, this.questionNumber);
            return ""   tmp.join("&");
        };
    }

    class QuizPart{
        constructor(questionDescription, chosenAnswer, prefix){
            this.questionDescription = questionDescription;
            this.chosenAnswer = chosenAnswer;
            this.prefix = prefix;
        }
    }

    class ChosenAnswer{
        constructor(id, name){
            this.id = id;
            this.name = name;
        }
    }

    let Quiz = [
        new QuizPart('Whats your size?', [
            new ChosenAnswer('6595', '41'),
            new ChosenAnswer('6598', '42'),
            new ChosenAnswer('6601', '43'),
        ], 'bd_shoe_size_ids='),

        new QuizPart('What color would you like?', [
            new ChosenAnswer('6053', 'Red'),
            new ChosenAnswer('6044', 'Blue'),
            new ChosenAnswer('6056', 'Yellow'),
            new ChosenAnswer('6048', 'Green'),
        ], 'color_ids='),

        new QuizPart('What brand would you like?', [
            new ChosenAnswer('5805', 'Adidas'),
            new ChosenAnswer('5866', 'Nike'),
            new ChosenAnswer('5875', 'Puma'),
        ], 'manufacturer_ids='),
    ]

    let control = new QuestionControl(Quiz);
    control.Initialize();

    document.getElementById('nextbtn').addEventListener("click", control.NextQuestion);
    document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion);

    control.button.addEventListener("click", function (e) {
        const tgt = e.target;

        // clear the url array if there's nothing clicked
        if (control.url.length === control.questionNumber) {
            control.url.push([]);
        }

        let quizUrl = control.url[control.questionNumber];

        // Check if a button is clicked. Changes color and adds value to the url array.
        if (quizUrl.indexOf(tgt.value) === -1) {
            quizUrl.push(tgt.value);
            e.target.style.backgroundColor = "orange";
            // Check if a button is clicked again. If clicked again changes color back and deletes value in the url array.
        } else {
            quizUrl.splice(quizUrl.indexOf(tgt.value), 1);
            e.target.style.backgroundColor = "white";
        }
        console.log(control.getLink());
    })

The problem is that when i started using bind to connect the function SetQuestion to NextQuestion, it gave me the following error:

Uncaught TypeError: Cannot read properties of undefined (reading 'length')

At the for loop in nextQuestion, but if I stop using bind it won't recognize the function SetQuestion. Does anybody know why this happens?

CodePudding user response:

This is not the correct use of bind:

let question = this.SetQuestion.bind(this.questionNumber   1);

bind returns a function; it doesn't execute it. The argument you pass to bind should be a value to be used for this when eventually that returned function is called.

As this is not the intention here, you should remove .bind here, and just have:

let question = this.SetQuestion(this.questionNumber   1);

The problem really originates here:

document.getElementById('nextbtn').addEventListener("click", control.NextQuestion);
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion);

These click handler functions will be called without a specific this value (it will be undefined in strict mode, or else the global object). This is not what you want, since your code expects this to represent an instance of QuestionControl.

This can be solved with bind as follows:

document.getElementById('nextbtn').addEventListener("click", control.NextQuestion.bind(control));
document.getElementById('prevbtn').addEventListener("click", control.PrevQuestion.bind(control));

So it is not as you write:

using bind to connect the function SetQuestion to NextQuestion

bind is used to connect a function to the object that must act as the this value during the execution of the function. In your case that is control.

Alternatively the binding function can be provided explicitly:

document.getElementById('nextbtn').addEventListener("click", () => control.NextQuestion());
document.getElementById('prevbtn').addEventListener("click", () => control.PrevQuestion());
  •  Tags:  
  • Related