ArticleTemplates/assets/js/modules/quiz.js (396 lines of code) (raw):

import { updateMPUPosition } from 'modules/ads'; import { getStringFromUnicodeVal, getElementOffset } from 'modules/util'; import { initPositionPoller } from 'modules/cards'; let numAnswered; let questionCount; let isPersonalityQuiz; let moveMPU; let score; let scoreMessages; let personalityQuizBuckets; function init() { let quiz = document.querySelector('.element-atom .quiz'); if (quiz) { numAnswered = 0; questionCount = 0; isPersonalityQuiz = document.getElementsByClassName('quiz__buckets')[0]; moveMPU = isAdBelowQuiz(quiz); if (isPersonalityQuiz) { quiz.classList.add('personality-quiz'); setupPersonalityQuizBuckets(); setupPersonalityQuizQuestions(); removePersonalityQuizAnswers(); buildResultsPanel(quiz); } else { quiz.classList.add('news-quiz'); score = 0; scoreMessages = getScoreMessages(); setupNewsQuizQuestions(); removeNewsQuizAnswers(); buildScoresPanel(quiz); } quiz.classList.add('loaded'); } } function setupNewsQuizQuestions() { let i; const questionsAndAnswers = getQuestionsAndAnswers(); let question; const questions = document.getElementsByClassName('quiz__question'); let questionObj; for (i = 0; i < questions.length; i++) { question = questions[i]; questionObj = questionsAndAnswers[i+1]; questionObj.elem = question; wrapQuestion(question); setupNewsQuizAnswers(questionObj); questionCount++; } } function setupNewsQuizAnswers(questionObj) { let i; let answerCode; const answers = questionObj.elem.getElementsByClassName('question__answer'); for (i = 0; i < answers.length; i++) { answerCode = getStringFromUnicodeVal(65 + i); if (answerCode === questionObj.correctAnswer) { if (questionObj.revealText) { answers[i].dataset.correctAnswerExplanation = questionObj.revealText; } answers[i].dataset.correct = 'true'; } styleAnswer(answers[i]); answers[i].addEventListener('click', onNewsAnswerClick.bind(null, answers[i], questionObj.elem, answers[i].getElementsByTagName('img')[0])); } } function removeNewsQuizAnswers() { let i; const answers = document.querySelectorAll('.quiz__correct-answers-title, .quiz__correct-answers'); for (i = 0; i < answers.length; i++) { answers[i].parentNode.removeChild(answers[i]); } } function setupPersonalityQuizBuckets() { let i; let bucketCode; const buckets = document.getElementsByClassName('quiz__bucket'); const quizBuckets = {}; for (i = 0; i < buckets.length; i++) { bucketCode = getStringFromUnicodeVal(65 + i); quizBuckets[bucketCode] = { count: 0, title: buckets[i].dataset.title, description: buckets[i].dataset.description }; } personalityQuizBuckets = quizBuckets; } function setupPersonalityQuizQuestions() { let i; let question; const questions = document.getElementsByClassName('quiz__question'); let questionObj; for (i = 0; i < questions.length; i++) { question = questions[i]; questionObj = {}; questionObj.elem = question; wrapQuestion(question); setupPersonalityQuizAnswers(questionObj); questionCount++; } } function setupPersonalityQuizAnswers(questionObj) { let i; const answers = questionObj.elem.getElementsByClassName('question__answer'); for (i = 0; i < answers.length; i++) { styleAnswer(answers[i]); answers[i].addEventListener('click', onPersonalityAnswerClick.bind(null, answers[i], questionObj.elem)); } } function removePersonalityQuizAnswers() { let i; const answers = document.querySelectorAll('.quiz__buckets-title, .quiz__buckets'); for (i = 0; i < answers.length; i++) { answers[i].parentNode.removeChild(answers[i]); } } function getQuestionsAndAnswers() { let i; let key; let answerMatch; let question; let answer; const answers = {}; const correctAnswerWordList = document.getElementsByClassName('quiz__correct-answers')[0].innerHTML.split(' '); for (i = 0; i < correctAnswerWordList.length; i++) { // Check if word in this format: 1:A answerMatch = correctAnswerWordList[i].match(/(\d+):([A-Z])/g); if (answerMatch && answerMatch.length) { answer = answerMatch[0]; question = answer.split(':')[0]; answers[question] = { correctAnswer: answer.split(':')[1] }; } else { if (!answers[question].revealText || answers[question].revealText === '- ') { answers[question].revealText = ''; } answers[question].revealText += `${correctAnswerWordList[i]} `; } } for (key in answers) { if (answers.hasOwnProperty(key) && answers[key].revealText) { // Remove trailing comma in revealText answers[key].revealText = answers[key].revealText.replace(/,\s*$/, ''); } } return answers; } function getScoreMessages() { let i; let message; let minScore; const scoreElems = document.querySelectorAll('.quiz__scores > li'); const messages = {}; for (i = 0; i < scoreElems.length; i++) { message = scoreElems[i].dataset.title; minScore = scoreElems[i].dataset.minScore; messages[Math.max(minScore, 0)] = message; } return messages; } function buildScoresPanel(quiz) { const scoresPanel = document.createElement('div'); scoresPanel.classList.add('quiz-scores'); scoresPanel.id = 'quiz-scores'; scoresPanel.innerHTML = `<p class="quiz-scores__score"><span class="quiz-scores__correct"></span> / <span class="quiz-scores__questions">${questionCount}</span></p><p class="quiz-scores__message"></p>`; quiz.appendChild(scoresPanel); } function buildResultsPanel(quiz) { const resultPanel = document.createElement('div'); resultPanel.classList.add('quiz-results'); resultPanel.id = 'quiz-results'; resultPanel.innerHTML = '<h1 class="quiz-results__title"></h1><p class="quiz-results__description"></p>'; quiz.appendChild(resultPanel); } function wrapQuestion(question) { let i; const questionWrapper = document.createElement('div'); const questionAnswerList = question.getElementsByClassName('question__answers'); const questionImages = question.querySelectorAll(':scope > img'); const questionText = question.getElementsByClassName('question__text'); questionWrapper.classList.add('question__wrapper'); question.insertBefore(questionWrapper, questionAnswerList[0]); // Does this question have text for (i = 0; i < questionText.length; i++) { adjustText(questionWrapper, questionText[i]); } // Does this question have an image for (i = 0; i < questionImages.length; i++) { adjustImage(question, questionWrapper, questionImages[i], true); } } function styleAnswer(answer) { const answerImages = answer.querySelectorAll(':scope > img'); const answerMarker = document.createElement('div'); const answerMessage = document.createElement('div'); const answerWrapper = document.createElement('div'); const answerText = answer.getElementsByClassName('answer__text'); let i; // Wrap answer in a div for styling answerWrapper.classList.add('answer__wrapper'); answer.appendChild(answerWrapper); // Add an answer message div to wrap text answer, correct/wrong message and explanation response answerMessage.classList.add('answer__message'); answerWrapper.appendChild(answerMessage); // Add a marker icon span answerMarker.classList.add('answer__marker'); if (isPersonalityQuiz) { answerMarker.innerHTML = '<div class="answer__marker__inner"></div>'; } answerWrapper.appendChild(answerMarker); // Does this answer have text for (i = 0; i < answerText.length; i++) { adjustText(answerMessage, answerText[i]); } // Does this answer have an image for (i = 0; i < answerImages.length; i++) { adjustImage(answer, answerWrapper, answerImages[i], false); } } function adjustImage(parent, wrapper, image, isQuestion) { const src = image.getAttribute('src'); if (src !== '') { parent.classList.add('has-image'); if (isQuestion) { wrapper.parentNode.classList.add('question__img'); // if the text height is greater than the image height resize the wrapper checkWrapperHeight(src, image, wrapper); } else { image.classList.add('answer__img'); } wrapper.appendChild(image); } else { image.parentNode.removeChild(image); } } function checkWrapperHeight(src, image, wrapper) { let dummyImage; const text = wrapper.getElementsByClassName('question__text')[0]; if (!text) { return; } dummyImage = document.createElement('img'); dummyImage.addEventListener('load', adjustWrapperHeight.bind(null, image, text, wrapper)); dummyImage.src = src; } function adjustWrapperHeight(image, text, wrapper) { if (image.offsetHeight < text.offsetHeight) { wrapper.style.height = `${text.offsetHeight}px`; } } function adjustText(parent, text) { parent.appendChild(text); } function isAdBelowQuiz(quiz) { let adBelowQuiz = false; const mpu = document.getElementsByClassName('advert-slot__wrapper')[0]; let mpuOffset; let quizOffset; if (mpu) { mpuOffset = getElementOffset(mpu).top; quizOffset = getElementOffset(quiz).top; if (mpuOffset > quizOffset) { adBelowQuiz = true; } } return adBelowQuiz; } function adjustAdPosition(yPos, startTime, timeStamp) { let newYPos; let progress; if (!startTime) { startTime = timeStamp; } progress = timeStamp - startTime; newYPos = updateMPUPosition(yPos); if (progress < 2000) { window.requestAnimationFrame(adjustAdPosition.bind(null, newYPos, startTime)); } } function onNewsAnswerClick(answer, question, isImage) { initPositionPoller(); let answerPara; let correctAnswerWrapper; const startTime = null; const yPos = null; if (question.classList.contains('answered')) { return; } if (answer.dataset.correct === 'true') { question.classList.add('is-correct'); score++; } else { answer.classList.add('wrong-answer'); question.classList.add('is-wrong'); answer = question.querySelector('[data-correct="true"]'); } answer.classList.add('correct-answer'); if (answer.dataset.correctAnswerExplanation) { answerPara = document.createElement('p'); answerPara.classList.add('answer__explanation'); answerPara.innerHTML = answer.dataset.correctAnswerExplanation.trim(); correctAnswerWrapper = answer.getElementsByClassName('answer__message')[0]; correctAnswerWrapper.appendChild(answerPara); } question.classList.add('answered'); numAnswered++; // If necessary set up a call to check mpu position if (moveMPU) { window.requestAnimationFrame(adjustAdPosition.bind(null, yPos, startTime)); } // When we have an image answer we need to move the positioning of the explanation and marker if (isImage) { showMarkedAnswer(question); } // If all questions have been answered display the score if (questionCount === numAnswered) { showScore(); } } function onPersonalityAnswerClick(answer, question) { initPositionPoller(); let highlightedAnswer; let answerBuckets; let highlightedAnswerBuckets; // if the question has been answered already // and the the new answer selected is not the highlighted answer // remove answer from highlighted answer buckets if (question.classList.contains('answered') && !(answer.classList.contains('highlight-answer') || questionCount === numAnswered)) { highlightedAnswer = question.getElementsByClassName('highlight-answer')[0]; highlightedAnswer.classList.remove('highlight-answer'); highlightedAnswerBuckets = highlightedAnswer.dataset.buckets.split(','); highlightedAnswerBuckets.forEach(bucketId => { bucketId = bucketId.trim(); if (personalityQuizBuckets[bucketId]) { personalityQuizBuckets[bucketId].count--; } }); numAnswered--; } // update answer buckets answerBuckets = answer.dataset.buckets.split(','); answerBuckets.forEach(bucketId => { bucketId = bucketId.trim(); if (personalityQuizBuckets[bucketId]) { personalityQuizBuckets[bucketId].count++; } }); // mark question as answered question.classList.add('answered'); answer.classList.add('highlight-answer'); numAnswered++; // If all questions have been answered display the score if (questionCount === numAnswered) { showResult(); } } function showMarkedAnswer(question) { let i; let markedAnswer; const markedAnswers = question.querySelectorAll('.correct-answer, .wrong-answer'); let thisMessage; let thisHeight; let thisMarker; for (i = 0; i < markedAnswers.length; i++) { markedAnswer = markedAnswers[i]; thisMessage = markedAnswer.getElementsByClassName('answer__message')[0]; thisHeight = thisMessage.offsetHeight; thisMarker = markedAnswer.getElementsByClassName('answer__marker')[0]; // position explanation to the bottom of wrapper thisMessage.style.top = `calc(100% - ${thisHeight}px)`; thisMarker.style.top = `calc(100% - ${thisHeight - 7}px)`; } } function showScore() { let i; let scoreDisplayMessage = ''; for (i = 0; i <= score; i++) { if (scoreMessages[i]) { scoreDisplayMessage = scoreMessages[i]; } } document.getElementsByClassName('quiz-scores__correct')[0].innerHTML = score.toString(); document.getElementsByClassName('quiz-scores__message')[0].innerHTML = scoreDisplayMessage; document.getElementsByClassName('quiz-scores')[0].classList.add('open'); initPositionPoller(); // Scroll score panel into view const scoresElement = document.getElementById('quiz-scores') if (scoresElement !== undefined) { scoresElement.scrollIntoView() } } function showResult() { let key; let bucket; let result; let resultTitle; let resultDescription; for (key in personalityQuizBuckets) { if (personalityQuizBuckets.hasOwnProperty(key)) { bucket = personalityQuizBuckets[key]; if (!result || (bucket.count > result.count)) { result = bucket; resultDescription = result.description; resultTitle = result.title; } } } document.getElementsByClassName('quiz-results__description')[0].innerHTML = resultDescription; document.getElementsByClassName('quiz-results__title')[0].innerHTML = resultTitle; document.getElementsByClassName('quiz-results')[0].classList.add('open'); initPositionPoller(); // Scroll result panel into view const scroll = new SmoothScroll(); scroll.animateScroll('#quiz-results', null, {speed: 1500, offset: 40}); } export { init }