Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

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

Brendan Moran
Brendan Moran
14,052 Points

My Big Question: The best way to make the Quiz UI work for multiple quizzes.

Here's what I did:

Quiz.prototype.RunQuizUI = function () {

  var quiz = this;

  var object = {
    displayNext : function () {
      if (quiz.hasEnded()) {
        this.displayFinalScore();
      } else {
        this.displayQuestion();
        this.displayChoices();
        this.displayProgress();
        this.displayScoreCounter();
      }
    },

    displayQuestion : function () {
      this.populateIdWithHTML("question", quiz.getCurrentQuestion().question);
    },

    displayChoices : function () {
      var choices = quiz.getCurrentQuestion().choices;
      for (var i = 0; i < choices.length; i++) {
        this.populateIdWithHTML("choice" + i, choices[i]);
        this.answerHandler("button" + i, choices[i]);
      }
    },

    displayProgress : function () {
      var progressHTML = "Question " + (quiz.currentQuestionIndex + 1);
      progressHTML += "  of " + quiz.questions.length;
      this.populateIdWithHTML("progress", progressHTML);
    },

    displayScoreCounter : function () {
      var scoreCounterHTML = "Score: " + quiz.score + "/" + quiz.questions.length;
      this.populateIdWithHTML("scoreCounter", scoreCounterHTML);
    },

    displayFinalScore : function () {
      var quizCompletedHTML = '<h1>Quiz Completed</h1>';
      quizCompletedHTML += '<h2>You scored ' + quiz.score;
      quizCompletedHTML += ' out of ' + quiz.questions.length + '</h2>';
      this.populateIdWithHTML('quiz', quizCompletedHTML);
    },

    populateIdWithHTML : function (id, string) {
      var element = document.getElementById(id);
      element.innerHTML = string;
    },

    answerHandler : function (id, answer) {
      var UI = this;
      var button = document.getElementById(id);
      button.onclick = function () {
        quiz.checkAnswer(answer);
        UI.displayNext();
      };
    }
  };

  object.displayNext();

};

And in the app.js:

var quiz = new Quiz (questions);
quiz.RunQuizUI();

But... that's very non-conventional, and kind of leaves me asking: "But what does it do?" It's kind of scary, because I am not sure exactly what it is doing behind the scenes. Is the fact that a big old object is being created (temporarily) every time an instance runs the UI a deal breaker? Or should it be avoided because it is just too non-conventional and confusing to look at?

Here is another solution, which a friend of mine proposed. I have been having a lot of trouble getting it to work:

var QuizUI = {

  setCurrentQuiz: function (quizInstance) {
    this.currentQuiz = quizInstance;
  };

//every reference to "quiz" below will need to be changed to this.currentQuiz,
//which will make the code quite verbose. I am not a fan of this,
//but if the performance benefit is worth it (as compared to my other solution above)
//then so be it.

  displayNext : function () {
    if (quiz.hasEnded()) {
      this.displayFinalScore();
    } else {
      this.displayQuestion();
      this.displayChoices();
      this.displayProgress();
      this.displayScoreCounter();
    }
  },

  displayQuestion : function () {
    this.populateIdWithHTML("question", quiz.getCurrentQuestion().question);
  },

  displayChoices : function () {
    var choices = quiz.getCurrentQuestion().choices;
    for (var i = 0; i < choices.length; i++) {
      this.populateIdWithHTML("choice" + i, choices[i]);
      this.answerHandler("button" + i, choices[i]);
    }
  },

  displayProgress : function () {
    var progressHTML = "Question " + (quiz.currentQuestionIndex + 1);
    progressHTML += "  of " + quiz.questions.length;
    this.populateIdWithHTML("progress", progressHTML);
  },

  displayScoreCounter : function () {
    var scoreCounterHTML = "Score: " + quiz.score + "/" + quiz.questions.length;
    this.populateIdWithHTML("scoreCounter", scoreCounterHTML);
  },

  displayFinalScore : function () {
    var quizCompletedHTML = '<h1>Quiz Completed</h1>';
    quizCompletedHTML += '<h2>You scored ' + quiz.score;
    quizCompletedHTML += ' out of ' + quiz.questions.length + '</h2>';
    this.populateIdWithHTML('quiz', quizCompletedHTML);
  },

  populateIdWithHTML : function (id, string) {
    var element = document.getElementById(id);
    element.innerHTML = string;
  },

  answerHandler : function (id, answer) {
    var UI = this;
    var button = document.getElementById(id);
    button.onclick = function () {
      quiz.checkAnswer(answer);
      UI.displayNext();
    };
  }
};

//and add this to the quiz prototype:

Quiz.prototype.runQuizUI = function () {
  QuizUI.setCurrentQuiz(this);
  QuizUI.displayNext();
};

//app.js remains the same as in the above example, just call quiz.runQuizUI(); on your new quiz instance

So, what do you all think of these ideas? Andrew Chalkley, would especially love your input here.

Steven Parker
Steven Parker
231,269 Points

I wanted to try these out, but there's some missing bits. You could post the rest of it, or make a snapshot of your workspace and post the link to that.

Brendan Moran
Brendan Moran
14,052 Points

Steven Parker, that would be great! I'll do that this afternoon and tag you again once I have done it. Again, I had a lot of trouble getting the second idea to work. I'll post a snapshot of my workspace with my solution, and you can try to get the other idea working if you want (I couldn't get it going even after an hour of fiddling around with it).

Brendan Moran
Brendan Moran
14,052 Points

Whew, I got the second idea working. Stephen Parker, here are the snapshots. "quiz" has my non-conventional object-in-a-function thing going on (it's just used to easily define what "quiz" means in the object, and make the UI easy to call on whatever quiz). The only thing it changes from Andrew's code is putting the quiz UI into a function where quiz get's redefined according to "this", as well as a score counter I added at the bottom of each question.

The second idea is called "quiz-alt". It does the same thing, a different way. I haven't added any more quizzes yet; I am just trying to set it up so that it will be able to work with multiple quizzes of the same type. I have inserted single-line comments here detailing the changes (in the quiz.js, quiz-ui.js, and app.js).

In both files, I have also used an index instead of a string to pass the correct answer to the question constructor.

Lastly, I have a friend (currently a mid-level developer at his company), insisting that the questions should not have their own constructor, but instead be object literals, saying that as little constructing should be done as possible. Thoughts on that?

quiz: https://w.trhou.se/23px6gxi3t

quiz-alt: https://w.trhou.se/db0tcan90v

Steven Parker
Steven Parker
231,269 Points

FYI: Stephen (with "ph") Parker is a different student. :smirk: He may be wondering about the mysterious email he got.

Are you watcing for errors in the console? I got these from your "first" version:

  • Quiz is not defined
  • fooQuiz.runUI is not a function
  • quiz.hasEnded is not a function

The second version seems to run OK.