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 }