public/js/quiz.js
(function (window, document, $) {
var logoOffsetCounter = 0;
let defaultQuizConfig = {
'channel': 'individual',
'company': null,
'partner': null,
'language': 'nl',
'hideCountdownOnMobile': 'no',
'imageDirectory': 'https://s3-eu-west-1.amazonaws.com/verkeersquiz-2021/',
'teams': {
'922391c4-fc5b-4148-b69d-d347d48caaef': {
'name': 'Club Brugge KV',
'primary': '#387cda',
'secondary': '#3c3c3c'
}
},
'apiUrl': '/api',
'email': '',
'team': '',
'startquestion': null,
'contestClosed': false
};
let translations = {
nl: {
STARTING: 'De quiz start over',
START_QUIZ: 'Start!',
CONTINUE: 'Verdergaan',
QUESTION: 'Vraag',
TIME_LEFT: 'Resterende tijd',
NEXT_QUESTION: 'Volgende vraag >',
VIEW_SCORE: 'Bekijk score',
SCORE: 'Score',
PLAY_AGAIN: 'Speel nog eens',
PLAY_CONTEST: 'Neem deel aan de wedstrijd',
ANSWERED_CORRECT: 'Juist',
ANSWERED_WRONG: 'Fout',
ANSWERED_LATE: 'Te laat',
CHOOSE_TEAM: 'Selecteer een club naar keuze',
RECRUITMENT_TITLE: 'Test je verkeerskennis en ga aan de haal met 1 van de vele topprijzen. 5 minuutjes, 10 vragen en misschien win jij wel een reischeque van 1000 euro!',
EMAIL: 'E-mail:',
EMAIL_CONFIRM: 'Bevestiging e-mail:',
SHARE_TITLE: 'Goede of slechte score?',
SHARE_TITLE_VERY_BAD: 'Oei, dat ziet er niet goed uit. Probeer het nog eens en fris je verkeerskennis op. Vanaf 7/10 maak je trouwens kans op 1 van de geweldige prijzen.',
SHARE_TITLE_BAD: 'Dat kan beter. Probeer nog eens en ga voor een hogere score. Vanaf 7/10 maak je kans op een reischeque van 1000 euro. Wie niet waagt...',
SHARE_TITLE_GOOD: 'Goed bezig! Maar alles kan beter đ. Probeer nog eens en laat zien dat je een Ă©chte verkeersexpert bent.',
SHARE_TITLE_VERY_GOOD: 'Wat een topscore! Jij hebt duidelijk een echt verkeersbrein.',
SHARE_SUB_TITLE: 'Deel je score met je vrienden en daag hen uit om beter te doen.',
},
fr: {
STARTING: 'Vous ĂȘtes prĂȘt ?',
START_QUIZ: 'Commencer',
CONTINUE: 'Continuez',
QUESTION: 'Question',
TIME_LEFT: 'Temps restant',
NEXT_QUESTION: 'Question suivante >',
VIEW_SCORE: 'Voir mon score',
SCORE: 'Score',
PLAY_AGAIN: 'Rejouer',
PLAY_CONTEST: 'Participer au concours',
ANSWERED_CORRECT: 'Correct',
ANSWERED_WRONG: 'Faux',
ANSWERED_LATE: 'Trop tard',
CHOOSE_TEAM: 'Choisissez votre club',
RECRUITMENT_TITLE: 'Testez vos connaissances du code de la route et gagnez un de nos 200 prix! 5 minutes, 10 questions et vous gagnerez peut-ĂȘtre un chĂšque-voyage de 1000 euros!',
EMAIL: 'Email:',
EMAIL_CONFIRM: 'Confirmation email:',
SHARE_TITLE: 'Bon ou mauvais score?',
SHARE_TITLE_VERY_BAD: 'AĂŻe, retentez vite votre chance! Vous devez obtenir 7/10 minimum pour tenter de gagner un des prix mis en jeu.',
SHARE_TITLE_BAD: 'Vous pouvez faire mieux! Retentez vite votre chance! Vous devez obtenir 7/10 minimum pour tenter de gagner un des prix mis en jeu.',
SHARE_TITLE_GOOD: 'Bien jouĂ© ! C\'est suffisant pour participer au concours mais pas encore pour ĂȘtre un vĂ©ritable expert! Serez-vous capable de faire mieux?',
SHARE_TITLE_VERY_GOOD: 'Quel score! Le code de la route n\'a pas de secret pour vous, bravo.',
SHARE_SUB_TITLE: 'Partagez votre score avec vos amis et défiez-les de faire mieux!',
}
};
let cachedConfig = {};
function loadTemplate(name, language) {
let template = $('div[data-template="'+name+'"]');
template
.find('[data-translate]')
.each(function () {
let translatable = $(this);
let reference = translatable.attr('data-translate');
translatable.text(translations[language][reference]);
});
return template.prop('outerHTML');
}
function Quiz (quizConfig) {
quizConfig = $.extend({}, defaultQuizConfig, quizConfig || cachedConfig);
cachedConfig = quizConfig;
let view = $('#gvq-quiz .gvq-quiz-view');
let oldView = $('#gvq-quiz .gvq-quiz-old-view');
let cupModeOn = ('cup' === quizConfig.channel);
function renderView (viewName, quizId, questionNr, answerId, scroll) {
let oldContent = view.children();
let newContent = $(views[viewName].template).hide();
function showNewContent () {
oldContent.remove();
newContent.show();
sendQuizHeight(50, scroll);
}
oldView.append(oldContent);
view.append(newContent);
views[viewName].controller(quizId, questionNr, answerId).done(showNewContent);
}
function setViewValue (name, value) {
view.find('[data-value="' + name + '"]').text(value);
}
function setViewHtmlValue (name, value) {
view.find('[data-value="' + name + '"]').html(value);
}
function renderTeamBanner (teamId) {
let banner = $('#gvq-quiz .gvq-team-banner');
let form = $('.participation-form');
let team = false;
if (false === teamId) {
banner.remove();
return;
}
if ('' !== teamId) {
team = quizConfig['teams'][teamId];
}
let colorPrimary = team ? team['primary'] : 'white';
let colorSecondary = team ? team['secondary'] : 'white';
var teamColor = colorPrimary;
if(colorPrimary == '#fff') {
teamColor = colorSecondary;
}
banner.find('img').attr('src', team ? (quizConfig.imageDirectory+'teams/'+teamId+'.png') : '');
banner.css({
'border-bottom': '10px solid ' + teamColor,
});
if (logoOffsetCounter < 2) {
sendQuizHeight(150);
logoOffsetCounter++;
}
}
let views = {
startTimer: {
controller: function () {
var sequences = [
{pct: 20, label: '4'},
{pct: 40, label: '3'},
{pct: 60, label: '2'},
{pct: 80, label: '1'},
{pct: 100, label: quizConfig['language'] == 'nl' ? 'start!' : 'Câest parti', css_class: 'start'}
];
function progress(val, label, css_class, bar, cont) {
var r = bar.attr('r');
var c = Math.PI*(r*2);
if (val < 0) { val = 0;}
if (val > 100) { val = 100;}
var offset = ((100-val)/100)*c;
console.log(offset);
bar.css({ strokeDashoffset: offset});
cont.attr('data-pct',label)
if (css_class) {
cont.addClass(css_class);
}
}
function start () {
$.post(quizConfig.apiUrl + '/quiz', JSON.stringify({
channel: quizConfig['channel'],
company: quizConfig['company'],
partner: quizConfig['partner'],
language: quizConfig['language'],
first_question: quizConfig['startquestion'],
team: null
}))
.done(function (data) {
renderView('askQuestion', data.id, 1, null, true);
})
.fail(function (data) {
alert(data.responseText);
});
}
let cont = view.find('.cont');
let bar = view.find('.bar');
for (var i = 0; i < sequences.length; i++) {
setTimeout(progress, 500 * (i+1), sequences[i].pct, sequences[i].label, sequences[i].css_class ?? null, bar, cont);
}
setTimeout(start, (500 * sequences.length+1) + 300);
return $.Deferred().resolve().promise();
},
template: loadTemplate('start-timer', quizConfig.language)
},
participationForm: {
controller: function () {
let startButton = view.find('button.gvq-start-button');
let teamSelect = view.find('select[name="choose-team"]');
let recruitment = view.find('#recruitment');
function start (team) {
$.post(quizConfig.apiUrl + '/quiz', JSON.stringify({
channel: quizConfig['channel'],
company: quizConfig['company'],
partner: quizConfig['partner'],
language: quizConfig['language'],
first_question: quizConfig['startquestion'],
team: team
}))
.done(function (data) {
renderView('askQuestion', data.id, 1, null, true);
})
.fail(function (data) {
alert(data.responseText);
});
quizConfig['team'] = team;
}
if (cupModeOn) {
recruitment.remove();
$.each(quizConfig['teams'], function (id, team) {
teamSelect.append($('<option>', {value: id, text: team.name}));
});
teamSelect.val(quizConfig['team']);
teamSelect
.on('change', function () {
startButton.prop('disabled', (checkTeamSelect()) === false);
renderTeamBanner(teamSelect.val());
})
.trigger('change');
} else {
renderTeamBanner(false);
teamSelect.remove();
}
function checkTeamSelect () {
return teamSelect.val() !== '';
}
startButton.on('click', function () {
start(
cupModeOn ? teamSelect.val() : null
);
});
return $.Deferred().resolve().promise();
},
template: loadTemplate('participation-form', quizConfig.language)
},
askEmail: {
controller: function (quizId, score, totalQuestions) {
setViewValue('score', score);
setViewValue('totalQuestions', totalQuestions);
function checkEmail () {
let emailRegex = new RegExp('^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$');
var email = emailInput.val().trim();
var email_confirmation = emailInputConfirmation.val().trim();
return emailRegex.test(email) &&
email === email_confirmation;
}
let registerEmailButton = view.find('button.gvq-register-email-button');
let emailInput = view.find('input#gvq-participant-email');
let emailInputConfirmation = view.find('input#gvq-participant-email-confirm');
emailInput.val(quizConfig['email']);
emailInput.on('keyup change', function () {
registerEmailButton.prop('disabled', checkEmail() === false);
}).trigger('change');
emailInputConfirmation.on('keyup change', function () {
registerEmailButton.prop('disabled', checkEmail() === false);
}).trigger('change');
function showResult() {
if (score >= 7) {
let contestUrl = quizConfig.language+'/view/contest/'+quizId;
$(location).attr("href", contestUrl);
}
else {
renderView('showResult', quizId, score, totalQuestions, null, true);
}
}
registerEmailButton.on('click', function () {
$.post(quizConfig.apiUrl + '/quiz/' + quizId + '/email/' + emailInput.val().toLowerCase().trim()).done(
showResult
);
});
return $.Deferred().resolve().promise();
},
template: loadTemplate(quizConfig['company'] ? 'ask-email-company' : 'ask-email', quizConfig.language)
},
askQuestion: {
controller: function (quizId, questionNr) {
let deferredRender = $.Deferred();
let counterInterval;
function startCountdown () {
if (quizConfig.hideCountdownOnMobile === 'yes') {
view.find('.gvq-countdown').addClass('hide-on-mobile');
}
let counter = view.find('.gvq-time-left');
sendCounterState('started');
counterInterval = window.setInterval(function () {
let secondsLeft = parseInt(counter.text(), 10) - 1;
if (0 === secondsLeft) {
clearInterval(counterInterval);
view.find('.gvq-countdown').addClass('finished');
sendCounterState('finished');
renderView('showAnswer', quizId, questionNr, null, false);
}
counter.text(secondsLeft);
sendCounter(secondsLeft);
}, 1000);
}
function renderQuestion (data) {
let imageLocation = quizConfig.imageDirectory + data.question.imageFileName;
let questionImage = new Image();
setViewValue('questionText', data.question.text);
for (var i = 1; i <= 3; i++) {
view.find('[data-value="answer' + i + '"]').hide();
}
$.each(data.question.answers, function (index, answer) {
setViewValue('answer' + answer.index, answer.text);
view.find('[data-value="answer' + answer.index + '"]').show();
});
view
.find('ul.gvq-answers')
.on('click', 'li', function () {
let chosenAnswer = data.question.answers[$(this).index()];
clearInterval(counterInterval);
sendCounterState('finished');
renderView('showAnswer', quizId, questionNr, chosenAnswer.id, false);
});
questionImage.onload = function () {
view
.find('.gvq-question-image')
.attr('src', imageLocation);
deferredRender.resolve();
startCountdown();
};
questionImage.onerror = function () {
deferredRender.resolve();
startCountdown();
};
questionImage.src = imageLocation;
}
setViewValue('questionNr', questionNr);
$.get(quizConfig.apiUrl + '/quiz/' + quizId + '/question').done(renderQuestion);
return deferredRender.promise();
},
template: loadTemplate('ask-question', quizConfig.language)
},
showAnswer: {
controller: function (quizId, questionNr, answerId) {
let deferredRender = $.Deferred();
function renderAnsweredQuestion (data) {
let answerResult = 'wrong';
for (var i = 1; i <= 3; i++) {
view.find('[data-value="answer' + i + '"]').hide();
}
setViewValue('questionText', data.question.text);
$.each(data.question.answers, function (index, answer) {
if (answerId && answer.correct && answerId === answer.id) {
answerResult = 'correct';
}
setViewValue('answer' + answer.index, answer.text);
view.find('[data-value="answer' + answer.index + '"]').show();
view.find('[data-value="answer' + answer.index + '"]')
.toggleClass('selected-answer', answer.id === answerId)
.toggleClass('is-correct', answer.correct);
});
view
.find('.gvq-answer-result')
.filter(function () {
return false === $(this).hasClass(answerResult);
})
.remove();
view
.find('.gvq-question-image')
.attr('src', quizConfig.imageDirectory + data.question.imageFileName);
setViewValue('questionText', data.question.text);
//replace newlines with line breaks as pragmatic solution for bullet lists in feedback
let feedbackWithLineBreaks = data.question.feedback.replace(/\n/g, '<br>');
setViewHtmlValue('feedback', feedbackWithLineBreaks);
if (typeof data.score === 'number') {
view.find('button.gvq-view-score')
.on('click', function () {
let viewName = ((quizConfig['company'] || data.score >= 7) && quizConfig['contestClosed'] === false) ? 'askEmail': 'showResult';
renderView(viewName, quizId, data.score, questionNr, null, true);
})
.show();
} else {
view.find('button.gvq-next-question')
.on('click', function () {
renderView('askQuestion', quizId, ++questionNr, null, true);
})
.show();
}
deferredRender.resolve();
}
setViewValue('questionNr', questionNr);
answerId = answerId || 'late';
$.post(quizConfig.apiUrl + '/quiz/' + quizId + '/question/' + answerId).done(renderAnsweredQuestion);
return deferredRender.promise();
},
template: loadTemplate('show-answer', quizConfig.language)
},
showResult: {
controller: function (quizId, score, totalQuestions) {
setViewValue('score', score);
setViewValue('totalQuestions', totalQuestions);
view.find('button.gvq-play-again').on('click', function () {
Quiz();
});
if (cupModeOn) {
view.find('#share-quiz').remove();
} else {
view.find('#share-cup').remove();
if (score <= 4) {
view.find('#share-title-very-bad').show();
view.find('#share-title-bad').hide();
view.find('#share-title-good').hide();
view.find('#share-title-very-good').hide();
} else if (score <= 6) {
view.find('#share-title-very-bad').hide();
view.find('#share-title-bad').show();
view.find('#share-title-good').hide();
view.find('#share-title-very-good').hide();
} else if (score <= 8) {
view.find('#share-title-very-bad').hide();
view.find('#share-title-bad').hide();
view.find('#share-title-good').show();
view.find('#share-title-very-good').hide();
} else {
view.find('#share-title-very-bad').hide();
view.find('#share-title-bad').hide();
view.find('#share-title-good').hide();
view.find('#share-title-very-good').show();
}
}
return $.Deferred().resolve().promise();
},
template: loadTemplate('show-result', quizConfig.language)
}
};
renderView('startTimer', null, null, null, false);
}
window.Quiz = Quiz;
}(window, document, jQuery));