Welcome to the Treehouse Community

The Treehouse Community is a meeting place for developers, designers, and programmers of all backgrounds and skill levels to get support. Collaborate here on code errors or bugs that you need feedback on, or asking for an extra set of eyes on your latest project. Join thousands of Treehouse students and alumni in the community today. (Note: Only Treehouse students can comment or ask questions, but non-students are welcome to browse our conversations.)

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and a supportive community. Start your free trial today.

JavaScript Object-Oriented JavaScript (2015) Practice Project User Interface Code

Chris Pulver
Chris Pulver
7,624 Points

Object Oriented Quiz Project - How to make this reusable

Hi,

I've watched Andrew's completed project, and the way it is programmed, you can't reuse it for multiple quizzes. There are hard coded instances in some of the methods (see quiz.hasEnded()).

I've spent a bunch of time trying to figure out how to make this program work if you wanted to add multiple quizzes, but I'm stuck. I believe the issue comes in trying to determine the length of the current quiz.

Here is my workspace: https://w.trhou.se/s9pgsth249.

I've been able to get almost everything to look at the current instance, except in my quiz.ui.js file. I'm unable to figure out how to get my click event to point to the current instance without hard coding it ("quiz").

//User selects correct answer
correctAnswerButton.onclick = function() {
  quiz.userGuess("correct");
}

//When the wrong answer is clicked, alert
wrongAnswerButton.onclick = function() {
  quiz.userGuess();
}

Would appreciate any other advice on my code as well!

TIA! Chris

Chris Pulver
Chris Pulver
7,624 Points

That's awesome! It's great to have more experienced programmers on here to help out the newbies like me.

2 Answers

Steven Parker
Steven Parker
218,691 Points

The first thought that occurred to me:

Wrap the part of "quiz_ui" that establishes the handlers in a function, and call that function from the "quiz" constructor, passing the current instance to it as an argument.

Some other ideas:

  • remove the handlers when the results are shown (to avoid interfering with another instance)
  • create "running" and "idle" states so more than one instance can exist at the same time
  • add a "start" method to start or restart the session on a given instance
Chris Pulver
Chris Pulver
7,624 Points

Thanks for the reply! I've spent a few hours on this and I am just completely stuck. Something is just not clicking in my brain. I've tried multiple things and am not figuring out how to get the button click to call within the quiz constructor.

Here's what my code looks like now:

function Quiz() {
  this.questions = [];
}

Quiz.prototype.add = function (question) {
  this.questions.push(question);
}

//Changes quiz progress at bottom of page and question/answers for next question
Quiz.prototype.nextQuestion = function() {
  const progressElement = document.getElementById("progress");
  progressElement.innerHTML = "Question " + questionNumber + " of " + this.questions.length;
  questionDisplay.innerHTML = this.questions[questionNumber -1].question;
  answer1Display.innerHTML = this.questions[questionNumber - 1].wrongAnswer;
  answer2Display.innerHTML = this.questions[questionNumber - 1].correctAnswer;
}


//Function to display user's final score after quiz
Quiz.prototype.scorePage = function() {
  let scoreHTML = "You answered " + correctScore + " out of " + this.questions.length + " correct!";
  questionDisplay.innerHTML = scoreHTML;
  answer1Display.innerHTML = "";
  answer2Display.innerHTML = "";
  wrongAnswerButton.style.display = "none";
  correctAnswerButton.style.display = "none";
}

Quiz.prototype.isEnd = function() {
  if(questionNumber <= this.questions.length) {
    this.nextQuestion();
  } else {
    this.scorePage();
  }
}

//Starting score is 0. Keeps track of which question user is on with questionNumber

let correctScore = 0;
let questionNumber = 1;

//Displays question on page
const questionDisplay = document.getElementById("question");

//Displays wrong answer as first option
const answer1Display = document.getElementById("choice0");
const wrongAnswerButton = document.getElementById("guess0");

//Displays correct answer as second option
const answer2Display = document.getElementById("choice1");
const correctAnswerButton = document.getElementById("guess1");


function userGuess (isCorrect) {
  if(isCorrect) {
    correctScore++;
    alert("That is correct!");
    questionNumber++;
  } else {
    alert("Sorry that is incorrect");
    questionNumber++;
  }
}

correctAnswerButton.onclick = function () {
  userGuess("correct");
}
wrongAnswerButton.onclick = function () {
  userGuess();
}

Basically all I've done is changed the layout. Every issue I run into is trying to get the button click to call the isEnd() method. I can't for the life of me figure out how to get that button click to look at the current instance of Quiz.

I've tried adding this.isEnd() within the onclick function, but that doesn't work because "this" doesn't exist there. I've also tried adding the onlick into the isEnd method, but then clicks don't fire the function, which I'm guessing is because they are nested so the parent method would need to be called. Seems like it is circular reasoning.

I know I'm missing something and it's been really frustrating.

Steven Parker
Steven Parker
218,691 Points

Hee's what my first suggestion might look like, based on the original snapshot (where "userGuess" is method of the class prototype):

quiz.js
function Quiz() {
  this.questions = [];
  setHandlers(this);  // set the handlers for this instance
}
quiz_ui.js
function setHandlers(quiz) {    // use supplied instance
  //User selects correct answer
  correctAnswerButton.onclick = function() {
    quiz.userGuess("correct");
  }

  //When the wrong answer is clicked, alert
  wrongAnswerButton.onclick = function() {
    quiz.userGuess();
  }
}
Chris Pulver
Chris Pulver
7,624 Points

THANK YOU! I had been trying to figure out how to add the method to Quiz with "this.function" instead of just the function. It always makes so much sense once you see the correct way haha.

I'm curious. How long have you been programming? I see you answer lots of questions on here. I've only been doing it a few months, and trying to stay encouraged when I come up against issues like this one.

Appreciate your help!

Steven Parker
Steven Parker
218,691 Points

I shudder to think ... programming for about 40 years now. :older_man:

But you could also move that function into the class and make it be a method. I just made the suggestion that required the least changes.

So...I havent seen your html or your userGuess function. However, I can assume that this is your own project based on your learnings...

Assuming that this isnt a treehouse quiz, I assume there is no right/wrong answer, you just want to know how to do it. If thats the case, I have an idea.

Just taking a guess but...

First, add an attribute to the buttons. The attribute will contain the answer code "correct" or "false" or nothing at all. Remember that html5 finds custom attributes valid if they start with "data-"

Second, query for all buttons.

Third, iterate(loop through) each button.

Fourth, use a function that is non-specific to any one type of button.

Fifth, dummy-proof that bad-boy! We all make mistakes and forget to fill out an attribute or something. So make it check to see if the attribute exists.

Sixth, since you are passing through text, make it pass through text that the button gave it(the attribute value).

//get all answer buttons
var answerButtons = document.querySelectorAll(sharedSelector),
    i;

//go through each answer button
for (i = 0; i < answerButtons.length; ++i) {

    //this answer button .click starts function...
    answerButtons[i].onclick = function() {

        //store the attribute in case you want to use it later... - I added an attribute, btw :p
        var buttonType = this.getAttribute("data-button-type"),
            answer = ""; //sorry, I like to proto type :p

        //check to see if the attribute exists
        if(this.hasAttribute("data-button-type") == true) {

            //the answer becomes the attribute
            answer = buttonType;

        }

        //do your thing, bro!
        quiz.userGuess(answer);

    }

}

^^>>You could make that more efficient by removing "answer" and "button type" and just using your userguess function and query for the attribute in the function call. But, I think its easier to read this way....though bloated.

Working example... jsFiddle