├── img ├── Thinking People Illustration Vector.png └── Hand Drawn Full Body Kid Jumping Showing Excitement.png ├── README.md ├── scripts ├── index.js ├── elements-helper.js └── quiz.js ├── index.html └── styles └── styles.css /img/Thinking People Illustration Vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasimreja/quizzler/HEAD/img/Thinking People Illustration Vector.png -------------------------------------------------------------------------------- /img/Hand Drawn Full Body Kid Jumping Showing Excitement.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasimreja/quizzler/HEAD/img/Hand Drawn Full Body Kid Jumping Showing Excitement.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quizzler 2 | 3 | > Quizzler is a fun new way to practice, improve, and test your Javascript skills. During this assessment you will presented with a series of random Javascript questions with 70 seconds to answer as many as you can. 4 | 5 | ## Tech Stack 6 | 7 | - HTML 8 | - CSS 9 | - JavaScript 10 | 11 | ## Screenshots 12 | 13 |

14 | 15 | 16 | 17 |

18 | -------------------------------------------------------------------------------- /scripts/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const app = document.getElementById("quiz-app"); 4 | const quizCard = document.getElementById("quiz-details"); 5 | const questionsCard = document.getElementById("questions-card"); 6 | const resultCard = document.getElementById("result-card"); 7 | 8 | let quiz; 9 | 10 | function initApp() { 11 | const questions = [ 12 | { 13 | title: "Which one is the type of a javascript file?", 14 | options: [".ts", ".js", ".jsx", ".j"] 15 | }, { 16 | title: "Inside which HTML element do we put the JavaScript?", 17 | options: ["", " 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /scripts/elements-helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | class QuizElementsHelper { 4 | constructor(app, quizCard, questionCard, resultCard, quiz) { 5 | this.app = app; 6 | this.quiz = quiz; 7 | this.quizCard = quizCard; 8 | this.questionCard = questionCard; 9 | this.resultCard = resultCard; 10 | 11 | // Find & assign elements 12 | this.assignElements(); 13 | 14 | // Initialize the listeners 15 | this.initListeners(); 16 | 17 | // Show quiz details card 18 | this.showQuizCard(); 19 | } 20 | 21 | assignElements() { 22 | // Quiz Card Elements 23 | this.quizCard.startBtn = this.quizCard.querySelector( 24 | ".quiz-details__start-btn" 25 | ); 26 | this.quizCard.titleElm = this.quizCard.querySelector( 27 | ".quiz-details__title" 28 | ); 29 | this.quizCard.descriptionElm = this.quizCard.querySelector( 30 | ".quiz-details__description" 31 | ); 32 | this.quizCard.metaQCElm = this.quizCard.querySelector( 33 | ".quiz-details__meta.--qc strong" 34 | ); 35 | this.quizCard.metaTimeElm = this.quizCard.querySelector( 36 | ".quiz-details__meta.--t strong" 37 | ); 38 | 39 | // Question Card Elements 40 | this.questionCard.progressRemainingTimeElm = document.querySelector( 41 | ".questions-card__remaining-time" 42 | ); 43 | this.questionCard.progressQuestionCountElm = document.querySelector( 44 | ".questions-card__q-count" 45 | ); 46 | this.questionCard.progressbarElm = document.querySelector( 47 | ".questions-card__progress .--value" 48 | ); 49 | this.questionCard.questionTitleElm = document.getElementById( 50 | "question-title" 51 | ); 52 | this.questionCard.optionOneElm = document.querySelector( 53 | "#option-one ~ label" 54 | ); 55 | this.questionCard.optionTwoElm = document.querySelector( 56 | "#option-two ~ label" 57 | ); 58 | this.questionCard.optionThreeElm = document.querySelector( 59 | "#option-three ~ label" 60 | ); 61 | this.questionCard.optionFourElm = document.querySelector( 62 | "#option-four ~ label" 63 | ); 64 | this.questionCard.nextBtn = this.app.querySelector("#next-btn"); 65 | this.questionCard.stopBtn = this.app.querySelector("#stop-btn"); 66 | 67 | // Result Card Elements 68 | this.resultCard.gotoHome = this.resultCard.querySelector("#go-to-home"); 69 | this.resultCard.scoreElm = this.resultCard.querySelector("#score"); 70 | } 71 | 72 | initListeners() { 73 | this.quizCard.startBtn.addEventListener( 74 | "click", 75 | this.showQuestionsCard.bind(this) 76 | ); 77 | this.questionCard.nextBtn.addEventListener( 78 | "click", 79 | this.nextBtnHandler.bind(this) 80 | ); 81 | this.questionCard.stopBtn.addEventListener( 82 | "click", 83 | this.stopBtnHandler.bind(this) 84 | ); 85 | this.resultCard.gotoHome.addEventListener( 86 | "click", 87 | this.hideResultCard.bind(this) 88 | ); 89 | } 90 | 91 | showQuizCard() { 92 | this.quizCard.titleElm.innerText = this.quiz.title; 93 | this.quizCard.descriptionElm.innerText = this.quiz.description; 94 | this.quizCard.metaQCElm.innerText = this.quiz._questions.length; 95 | this.quizCard.metaTimeElm.innerText = this.quiz._time; 96 | 97 | this.quizCard.classList.add("show"); 98 | } 99 | 100 | // Hide the quiz card 101 | hideQuizCard() { 102 | this.quizCard.classList.remove("show"); 103 | } 104 | 105 | // Show the question card 106 | showQuestionsCard() { 107 | this.hideQuizCard(); 108 | 109 | this.questionCard.classList.add("show"); 110 | this.questionCard.classList.remove("time-over"); 111 | 112 | this.startQuiz(); 113 | } 114 | 115 | // Hide the question card 116 | hideQuestionsCard() { 117 | this.questionCard.classList.remove("show"); 118 | } 119 | 120 | // Handle the visibility of the result card 121 | showResultCard(result) { 122 | this.hideQuestionsCard(); 123 | 124 | if (this.resultCard.scoreElm && result) 125 | this.resultCard.scoreElm.innerText = Math.floor(result.score * 10) / 10; 126 | 127 | this.resultCard.classList.add("show"); 128 | } 129 | 130 | // Hide the result card 131 | hideResultCard() { 132 | this.resultCard.classList.remove("show"); 133 | this.showQuizCard(); 134 | } 135 | 136 | // Handle the starting of the quiz and control the status of it 137 | startQuiz() { 138 | this.resetPreviousQuiz(); 139 | this.quiz.reset(); 140 | const firstQuestion = this.quiz.start(); 141 | if (firstQuestion) { 142 | this.parseNextQuestion(firstQuestion); 143 | } 144 | 145 | this.questionCard.nextBtn.innerText = "Next"; 146 | 147 | this._setProgressTicker(); 148 | } 149 | 150 | // Initialize the quiz time progress on every time that quiz starts 151 | // to control the progressbar and remaining time 152 | _setProgressTicker() { 153 | this.remainingTimeInterval = setInterval(() => { 154 | const qTime = this.quiz.timeDetails; 155 | if (qTime && qTime.remainingTime) { 156 | // Update remaining time span 157 | this.questionCard.progressRemainingTimeElm.innerText = 158 | qTime.remainingTime; 159 | 160 | // Update progressbar 161 | let progressPercent = 162 | ((qTime.quizTime - qTime.elapsedTime) * 100) / qTime.quizTime; 163 | if (progressPercent < 0) progressPercent = 0; 164 | this.questionCard.progressbarElm.style.width = progressPercent + "%"; 165 | } 166 | 167 | // Clear & stop interval when time over 168 | if (qTime.timeOver) { 169 | this.questionCard.classList.add("time-over"); 170 | this.questionCard.nextBtn.innerText = "Show Result"; 171 | clearInterval(this.remainingTimeInterval); 172 | } 173 | }, 1000); 174 | } 175 | 176 | // This method putting the question in the question card 177 | parseNextQuestion(question) { 178 | const selectedOption = document.querySelector( 179 | "input[name=question-option]:checked" 180 | ); 181 | 182 | this.questionCard.progressQuestionCountElm.innerText = `Question ${this.quiz 183 | ._currentQuestionIndex + 1}/${this.quiz._questions.length}`; 184 | this.questionCard.questionTitleElm.setAttribute( 185 | "data-qn", 186 | `Q ${this.quiz._currentQuestionIndex + 1}:` 187 | ); 188 | this.questionCard.questionTitleElm.innerText = question.title; 189 | 190 | this.questionCard.optionOneElm.innerText = question.options[0]; 191 | this.questionCard.optionTwoElm.innerText = question.options[1]; 192 | this.questionCard.optionThreeElm.innerText = question.options[2]; 193 | this.questionCard.optionFourElm.innerText = question.options[3]; 194 | 195 | // Reset pre selected options on every next 196 | if (selectedOption) selectedOption.checked = false; 197 | } 198 | 199 | // To reset the previous quiz status before restarting it 200 | resetPreviousQuiz() { 201 | this.quiz.stop(); 202 | clearInterval(this.remainingTimeInterval); 203 | 204 | this.resultCard.scoreElm.innerText = 0; 205 | this.questionCard.progressRemainingTimeElm.innerText = "00:00"; 206 | this.questionCard.progressbarElm.style.width = "100%"; 207 | } 208 | 209 | // This will call when next button clicked 210 | nextBtnHandler() { 211 | const selectedOption = document.querySelector( 212 | "input[name=question-option]:checked" 213 | ); 214 | 215 | let result; 216 | if (!selectedOption) { 217 | result = this.quiz.skipCurrentQuestion(); 218 | } else { 219 | result = this.quiz.answerCurrentQuestion(selectedOption.value); 220 | } 221 | 222 | if (result.finished || result.timeOver) { 223 | this.showResultCard(result.result); 224 | } else if (result) { 225 | this.parseNextQuestion(result.nextQ); 226 | } 227 | } 228 | 229 | // This will call when stop button clicked 230 | stopBtnHandler() { 231 | this.resetPreviousQuiz(); 232 | this.showResultCard(); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /styles/styles.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600;700;800;900&display=swap'); 2 | 3 | html { 4 | box-sizing: border-box; 5 | font-size: 16px; 6 | overflow-x: hidden; 7 | } 8 | 9 | *, *:before, *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body, h1, h2, h3, h4, h5, h6, p, ol, ul { 14 | margin: 0; 15 | padding: 0; 16 | font-weight: 400; 17 | } 18 | 19 | ol, ul { 20 | list-style: none; 21 | } 22 | 23 | img { 24 | max-width: 100%; 25 | height: auto; 26 | } 27 | 28 | body { 29 | font-family: 'Montserrat', sans-serif; 30 | } 31 | 32 | #quiz-app { 33 | position: fixed; 34 | left: 0; 35 | top: 0; 36 | height: 100vh; 37 | width: 100vw; 38 | background-image: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%); 39 | padding: 1rem; 40 | display: flex; 41 | justify-content: center; 42 | align-items: center; 43 | flex-wrap: wrap; 44 | } 45 | 46 | .center-card { 47 | height: 100%; 48 | width: 100%; 49 | max-width: 468px; 50 | max-height: 600px; 51 | overflow-y: auto; 52 | background: #fff; 53 | border-radius: .7rem; 54 | box-shadow: 1px 1px 15px 2px rgba(0, 0, 0, 0.16); 55 | padding: 2rem; 56 | visibility: hidden; 57 | opacity: 0; 58 | position: absolute; 59 | transform-origin: top; 60 | transform: translateY(-150px) scale(.6); 61 | transition: transform .5s, opacity .7s .1s, visibility .7s .1s; 62 | user-select: none; 63 | z-index: -1; 64 | } 65 | 66 | .center-card.show { 67 | position: relative; 68 | transform: translateY(0px) scale(1); 69 | opacity: 1; 70 | visibility: visible; 71 | transition: transform .6s, opacity .7s .2s, visibility .7s .2s; 72 | } 73 | 74 | .default-btn { 75 | outline: none; 76 | border: none; 77 | background: #14b25f; 78 | padding: .4rem 1rem; 79 | border-radius: 4px; 80 | color: #fff; 81 | cursor: pointer; 82 | min-width: 140px; 83 | height: 40px; 84 | text-transform: uppercase; 85 | font-size: 15px; 86 | font-weight: 500; 87 | box-shadow: 1px 1px 12px 2px rgba(0, 0, 0, .2); 88 | transition: all .3s; 89 | font-family: 'Montserrat', sans-serif; 90 | } 91 | 92 | .default-btn:hover { 93 | box-shadow: 1px 1px 6px 2px rgba(0, 0, 0, .3); 94 | } 95 | 96 | .default-btn:active { 97 | box-shadow: 1px 1px 6px 2px rgba(0, 0, 0, .3); 98 | transform: scale(0.95); 99 | } 100 | 101 | 102 | /** Quiz Card */ 103 | .quiz-details img { 104 | display: block; 105 | margin: 1rem auto 0; 106 | width: 50%; 107 | } 108 | 109 | .quiz-details__title { 110 | font-weight: 600; 111 | text-align: center; 112 | margin-top: 1.5rem; 113 | font-size: 2.5rem; 114 | color: #555; 115 | } 116 | 117 | .quiz-details__description { 118 | margin-top: 1.5rem; 119 | color: #888; 120 | line-height: 24px; 121 | margin-bottom: 1rem; 122 | text-align: center; 123 | } 124 | 125 | .quiz-details__meta { 126 | margin-top: .5rem; 127 | opacity: 0.6; 128 | color: #14b25f; 129 | text-align: center; 130 | } 131 | 132 | .quiz-details__meta span { 133 | margin-right: .3rem; 134 | } 135 | 136 | .quiz-details__start-btn-wrapper { 137 | display: flex; 138 | justify-content: center; 139 | align-items: center; 140 | margin-top: 2rem; 141 | position: sticky; 142 | width: 100%; 143 | top: 100%; 144 | left: 0; 145 | } 146 | 147 | .quiz-details__start-btn { 148 | width: 60%; 149 | height: 40px; 150 | } 151 | 152 | .quiz-details__start-btn:hover { 153 | box-shadow: 1px 1px 6px 2px rgba(0, 0, 0, .3); 154 | } 155 | 156 | .quiz-details__start-btn:active { 157 | box-shadow: 1px 1px 6px 2px rgba(0, 0, 0, .3); 158 | transform: scale(0.95); 159 | } 160 | 161 | 162 | /** Question Card */ 163 | 164 | .questions-card__progress-wrapper { 165 | display: flex; 166 | flex-wrap: wrap; 167 | justify-content: space-between; 168 | align-items: center; 169 | margin: .5rem 0 1rem; 170 | font-size: 14px; 171 | font-weight: 600; 172 | } 173 | 174 | .questions-card__progress { 175 | width: 100%; 176 | display: block; 177 | height: 8px; 178 | margin-top: .5rem; 179 | border-radius: 4px; 180 | background: #ddd; 181 | position: relative; 182 | overflow: hidden; 183 | } 184 | 185 | .questions-card__progress .--value { 186 | position: absolute; 187 | left: 0; 188 | height: 8px; 189 | width: 100%; 190 | display: inline-block; 191 | background: #14b25f; 192 | transition: all .3s; 193 | } 194 | 195 | .questions-card__q-title { 196 | font-size: 18px; 197 | line-height: 26px; 198 | font-weight: 400; 199 | margin-top: 3rem; 200 | display: block; 201 | position: relative; 202 | } 203 | 204 | .questions-card__q-title:before { 205 | content: attr(data-qn); 206 | display: block; 207 | font-size: 16px; 208 | margin-bottom: .25rem; 209 | color: #bbb; 210 | font-weight: bold; 211 | } 212 | 213 | .question-card__options { 214 | display: flex; 215 | justify-content: center; 216 | align-items: center; 217 | flex-direction: column; 218 | margin-top: 1rem; 219 | } 220 | 221 | .question-card__option { 222 | display: block; 223 | margin-top: .5rem; 224 | border: 1px solid #ddd; 225 | border-radius: 5px; 226 | width: 100%; 227 | cursor: pointer; 228 | text-align: left; 229 | } 230 | 231 | .question-card__option input[type="radio"] { 232 | display: none; 233 | } 234 | 235 | .question-card__option__value { 236 | display: block; 237 | width: 100%; 238 | padding: 1rem 1rem 1rem 3rem; 239 | position: relative; 240 | cursor: pointer; 241 | } 242 | 243 | .question-card__option__value:before, 244 | .question-card__option__value:after { 245 | content: ''; 246 | display: inline-block; 247 | position: absolute; 248 | left: 1rem; 249 | top: calc(50% - 10px); 250 | width: 20px; 251 | height: 20px; 252 | border-radius: 50%; 253 | border: 1px solid #ddd; 254 | } 255 | 256 | .question-card__option__value:after { 257 | display: none; 258 | width: 14px; 259 | height: 14px; 260 | left: calc(1rem + 3px); 261 | top: calc(50% - 7px); 262 | background-color: #bbb; 263 | } 264 | 265 | .question-card__option:hover, 266 | .question-card__option:hover .question-card__option__value:before { 267 | border-color: #bbb; 268 | } 269 | 270 | .question-card__option input[type="radio"]:checked ~ label:after { 271 | display: inline-block; 272 | } 273 | 274 | .question-card-buttons { 275 | display: flex; 276 | justify-content: space-between; 277 | align-items: center; 278 | margin-top: 2rem; 279 | position: sticky; 280 | width: 100%; 281 | top: 100%; 282 | left: 0; 283 | } 284 | 285 | .question-card-buttons__stop { 286 | background-color: #495464; 287 | } 288 | 289 | .questions-card.time-over .question-card-buttons__stop { 290 | display: none; 291 | } 292 | 293 | .questions-card.time-over .question-card-buttons { 294 | justify-content: flex-end; 295 | } 296 | 297 | 298 | /** Result Card */ 299 | .result-card { 300 | display: flex; 301 | justify-content: center; 302 | align-items: center; 303 | flex-direction: column; 304 | } 305 | 306 | .result-card img { 307 | display: block; 308 | margin-left: auto; 309 | margin-right: auto; 310 | width: 50%; 311 | } 312 | 313 | .result-card__score { 314 | font-size: 3rem; 315 | color: #555; 316 | } 317 | 318 | .result-card__score__lbl { 319 | color: #888; 320 | font-size: 20px; 321 | margin: 2rem 0 1rem; 322 | } 323 | 324 | .result-card-buttons { 325 | display: flex; 326 | justify-content: center; 327 | align-items: center; 328 | margin-top: 2rem; 329 | position: sticky; 330 | width: 100%; 331 | top: 100%; 332 | left: 0; 333 | } 334 | 335 | .result-card-buttons button { 336 | background: #14b25f; 337 | width: 60%; 338 | } 339 | 340 | 341 | -------------------------------------------------------------------------------- /scripts/quiz.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | let TIME_OVER_SYM = Symbol("TO"); 4 | let TIMER_INTERVAL_SYM = Symbol("TI"); 5 | 6 | class Quiz { 7 | 8 | constructor(title, description, time, questions = []) { 9 | 10 | if (!title) 11 | throw new Error("Title of quiz is required."); 12 | 13 | if (!description) 14 | throw new Error("Description of quiz is required."); 15 | 16 | if (!time || time < 10) 17 | throw new Error("Time is required and must be more than 10 sec."); 18 | 19 | this.title = title; 20 | this.description = description; 21 | this._time = time; 22 | this[TIME_OVER_SYM] = null; 23 | this[TIMER_INTERVAL_SYM] = null; 24 | this._questions = questions; 25 | } 26 | 27 | addQuestion(title, options) { 28 | if (this._startTime) { 29 | console.log("Question can not added on a started quiz."); 30 | return; 31 | } 32 | 33 | let id = this._questions.length; 34 | this._questions.push({ id, title, options }) 35 | } 36 | 37 | // Start the quiz 38 | start() { 39 | if (!this._questions.length) { 40 | console.log("There is not any question"); 41 | return; 42 | } 43 | 44 | if (this._startTime) { 45 | console.log("Already started."); 46 | return; 47 | } 48 | 49 | this.reset(); 50 | this._startTime = new Date().getTime(); 51 | 52 | this._setTicker(); 53 | 54 | return this.currentQuestion; 55 | } 56 | 57 | // Stop the running quiz 58 | stop() { 59 | this._endTime = new Date().getTime(); 60 | clearInterval(this[TIMER_INTERVAL_SYM]); 61 | this[TIMER_INTERVAL_SYM] = null; 62 | } 63 | 64 | // This will return the head question of running quiz 65 | get currentQuestion() { 66 | if (!this._startTime) { 67 | console.log("Quiz not started"); 68 | return; 69 | } 70 | 71 | return this._questions[this._currentQuestionIndex]; 72 | } 73 | 74 | // Get the result of running quiz 75 | result() { 76 | if (!this._startTime) { 77 | console.log("Quiz not started."); 78 | return; 79 | } 80 | 81 | let skipped = 0; 82 | let correct = 0; 83 | this._questions.map(q => { 84 | if (q.result) 85 | correct++; 86 | else if (q.skip) 87 | skipped++; 88 | }); 89 | 90 | let score = (100 * correct) / this._questions.length; 91 | 92 | return { 93 | questionsCount: this._questions.length, 94 | skipped, 95 | correct, 96 | score, 97 | timeOver: this[TIME_OVER_SYM], 98 | finished: this.isOnLastQuestion() || this[TIME_OVER_SYM] || this._endTime 99 | }; 100 | } 101 | 102 | // Reset the running quiz status and make it ready to start again 103 | reset() { 104 | if (this._startTime && !this._endTime) { 105 | console.log("Can not reset the running quiz."); 106 | return; 107 | } 108 | 109 | this._startTime = null; 110 | this._endTime = null; 111 | this._remainingTime = this._time; 112 | this._currentQuestionIndex = 0; 113 | this[TIME_OVER_SYM] = false; 114 | clearInterval(this[TIMER_INTERVAL_SYM]); 115 | 116 | this._questions = this._questions.map(q => ({ id: q.id, title: q.title, options: q.options })) 117 | } 118 | 119 | // Answer the head question of the running quiz with a selected option 120 | answerCurrentQuestion(option) { 121 | if (!this._startTime) { 122 | console.log("Start the quiz first"); 123 | return; 124 | } 125 | 126 | let response = { 127 | timeOver: this[TIME_OVER_SYM], 128 | finished: this.isOnLastQuestion() || this._endTime || this[TIME_OVER_SYM] 129 | }; 130 | 131 | if (!this[TIME_OVER_SYM]) { 132 | 133 | const currentQ = this.currentQuestion; 134 | if (currentQ.skip !== void (0)) { 135 | console.log("You already skipped this question"); 136 | return; 137 | } 138 | if (currentQ.answer !== void (0)) { 139 | console.log("You already answered this question"); 140 | return; 141 | } 142 | currentQ.answer = option; 143 | const answerResult = checkAnswerValidity(currentQ.id, option); 144 | currentQ.result = answerResult; 145 | 146 | response.answerResult = answerResult; 147 | 148 | if (!response.finished) { 149 | const nextQ = askNextQuestion.call(this); 150 | if (nextQ) { 151 | response.nextQ = nextQ; 152 | } 153 | } 154 | } 155 | 156 | if (response.finished) { 157 | response.result = this.result(); 158 | this.stop(); 159 | } 160 | 161 | return response; 162 | } 163 | 164 | // Skip the head question and pick next question if exist 165 | skipCurrentQuestion() { 166 | if (!this._startTime) { 167 | console.log("Start the quiz first"); 168 | return; 169 | } 170 | 171 | let response = { 172 | timeOver: this[TIME_OVER_SYM], 173 | finished: this.isOnLastQuestion() || this._endTime || this[TIME_OVER_SYM] 174 | }; 175 | 176 | if (!this[TIME_OVER_SYM]) { 177 | 178 | const currentQ = this.currentQuestion; 179 | if (currentQ.skip !== void (0)) { 180 | console.log("You already skipped this question"); 181 | return; 182 | } 183 | if (currentQ.answer !== void (0)) { 184 | console.log("You already answered this question"); 185 | return; 186 | } 187 | currentQ.skip = true; 188 | 189 | if (!response.finished) { 190 | const nextQ = askNextQuestion.call(this); 191 | if (nextQ) { 192 | response.nextQ = nextQ; 193 | } 194 | } 195 | } 196 | 197 | if (response.finished) { 198 | response.result = this.result(); 199 | this.stop(); 200 | } 201 | 202 | return response; 203 | } 204 | 205 | // Check if the head question is the last question of running quiz 206 | isOnLastQuestion() { 207 | return this._currentQuestionIndex + 1 >= this._questions.length 208 | } 209 | 210 | // Get the details of the timing of the quiz 211 | get timeDetails() { 212 | let now = new Date().getTime(); 213 | return { 214 | quizTime: this._time, 215 | start: this._startTime, 216 | end: this._endTime, 217 | elapsedTime: ((this._endTime || now) - this._startTime) / 1000, // ms to sec 218 | remainingTime: secToTimeStr(this._remainingTime), 219 | timeOver: this[TIME_OVER_SYM] 220 | } 221 | } 222 | 223 | // Control the ticker of the time of the running quiz 224 | _setTicker() { 225 | if (!this._startTime) { 226 | console.log("Quiz not started yet."); 227 | return; 228 | } 229 | 230 | if (this[TIMER_INTERVAL_SYM]) { 231 | console.log("The ticker has been set before"); 232 | return; 233 | } 234 | 235 | let privateRemainingTimeInSec = this._time; 236 | this[TIME_OVER_SYM] = false; 237 | this[TIMER_INTERVAL_SYM] = setInterval(() => { 238 | --privateRemainingTimeInSec; 239 | this._remainingTime = privateRemainingTimeInSec; 240 | if (privateRemainingTimeInSec <= 0) { 241 | this[TIME_OVER_SYM] = true; 242 | this.stop(); 243 | } 244 | }, 1000) 245 | } 246 | } 247 | 248 | // Private function to ask next question 249 | function askNextQuestion() { 250 | if (!this._startTime) { 251 | console.log("Quiz not started"); 252 | return; 253 | } 254 | 255 | const currentQ = this.currentQuestion; 256 | if (currentQ.answer === void (0) && currentQ.skip === void (0)) { 257 | console.log("Current question answered or skipped."); 258 | return; 259 | } 260 | 261 | if (this.isOnLastQuestion()) { 262 | console.log("No more question."); 263 | return; 264 | } 265 | 266 | return this._questions[++this._currentQuestionIndex]; 267 | } 268 | 269 | // Check the validity of the selected option 270 | function checkAnswerValidity(questionID, option) { 271 | 272 | // Every checking could be apply here but 273 | // the correct answer is the second option in 274 | // my questions because of its simplicity 275 | return +option === 1; 276 | } 277 | 278 | // Convert number (in second) to time-string 279 | function secToTimeStr(seconds) { 280 | 281 | let timeInHour = Math.floor(seconds / 3600); 282 | let timeInMin = Math.floor((seconds % 3600) / 60); 283 | let timeInSec = Math.floor(seconds % 60); 284 | 285 | if (timeInHour < 10) 286 | timeInHour = `0${timeInHour}`; 287 | 288 | if (timeInMin < 10) 289 | timeInMin = `0${timeInMin}`; 290 | 291 | if (timeInSec < 10) 292 | timeInSec = `0${timeInSec}`; 293 | 294 | let timeStr = `${timeInMin}:${timeInSec}`; 295 | if (parseInt(timeInHour)) 296 | timeStr = `${timeInHour}:${timeStr}`; 297 | 298 | return timeStr; 299 | } 300 | --------------------------------------------------------------------------------