Newer
Older

Leon
committed
// any CSS you import will output into a single css file (app.css in this case)
import '../scss/app.scss';
const SINGLE_CHOICE = 'scq';
const MULTIPLE_CHOICE = 'mcq';
const MULTIPLE_CHOICE_RANDOM_WRONGS = 'mcqrw';
const VALID_QS_TYPES = [SINGLE_CHOICE, MULTIPLE_CHOICE, MULTIPLE_CHOICE_RANDOM_WRONGS];
let dataset = {
t: null,
ids: [],
a: 0,
c: 0,
w: 0
};
/**
* @param {string} slctr
* @return {Element|null}
*/
function q(slctr) {
return document.querySelector(slctr);
}
/**
* @param {string} slctr
* @return {NodeListOf<Element>}
*/
function qa(slctr) {
return document.querySelectorAll(slctr);
}
/**
* @param {string} id
* @return {HTMLElement|null}

Leon
committed
*/
function id(id) {
return document.getElementById(id);
}
/**
* @param {object} obj
* @param {string} pName
* @return {boolean}
*/
function hasProp(obj, pName) {
return obj.hasOwnProperty(pName);
}
let dataBlock = id('data-block'),
loginForm = q('.admin'),
startBlock = q('.startblock'),

Leon
committed
questionBlock = q('.questionblock'),
evalBlock = q('.evalblock'),
allQuestions = id('all_questions'),
correctAnswers = id('correct_answers'),
wrongAnswers = id('wrong_answers'),
percentage = id('percentage'),
resultMessage = id('result_message')
;
function resetDataset() {
dataset = { t: null, ids: [], a: 0, c: 0, w: 0 };

Leon
committed
}
function resetEvalBlock() {
allQuestions.innerText = '';
correctAnswers.innerText = '';
wrongAnswers.innerText = '';
percentage.innerText = '';
resultMessage.innerText = '';
}
function evaluate() {
let percentageVal = (dataset.c / dataset.a) * 100,
msgMap = {
0: "Ouf. Get a book!",
25: 'Try again!',
50: 'I wouldn\'t be satisfied with that.',
75: 'Looks good, aim for something more',
90: 'That\'s actually impressive!',
100: 'I think you\'re ready. Go get \'em, champ!'
},
msgKeys = Object.keys(msgMap),
i = 1,
msg = msgMap['0']
;
allQuestions.innerText = dataset.a.toString(10);
correctAnswers.innerText = dataset.c.toString(10);
wrongAnswers.innerText = dataset.w.toString(10);
percentage.innerText = percentageVal.toString(10) + '%';

Leon
committed
for (i; i < msgKeys.length; i += 1) {
if (parseInt(msgKeys[i], 10) < percentageVal) {
msg = msgMap[msgKeys[i]];
} else {
i = msgKeys.length;
}
}
resultMessage.innerText = msg;
startBlock.setAttribute('style', 'display:none');
questionBlock.setAttribute('style', 'display:none');
evalBlock.removeAttribute('style');
}
/**
* @param {object} data
* @return {string}
*/
function validateQuestionData(data) {

Leon
committed
return 'Question ID was not provided!';
}

Leon
committed
return 'Question structure type was not provided!';
} else if (typeof data['type'] !== 'string' || !VALID_QS_TYPES.includes(data['type'])) {
return 'Question structure type is invalid!';
}

Leon
committed
return 'Question label was not provided!';
}

Leon
committed
return 'No answers provided!';
} else if (typeof data['answers'] !== 'object') {
return 'Answers dataset invalid!';
} else if (data['answers'].length < 5) {
return 'Insufficient amount of answers provided!';
}
for (let i = 0; i < data['answers'].length; i += 1) {

Leon
committed
return `Answer in slot ${i} has no ID!`;
} else if (typeof data['answers'][i]['id'] !== 'number') {
return `Answer in slot ${i} has invalid ID!`;
}

Leon
committed
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
return `Answer in slot ${i} has no label!`;
} else if (typeof data['answers'][i]['label'] !== 'string') {
return `Answer in slot ${i} has invalid label!`;
}
}
return '';
}
function constructQuestion(data) {
let validation = validateQuestionData(data);
if (validation.length > 0) {
alert(`Error while reading question data: ${validation}`);
}
let label = document.createElement('p'),
answersContainer = document.createElement('div'),
evalBtn = document.createElement('button'),
resultP = document.createElement('p'),
isScq = data['type'] === SINGLE_CHOICE
;
dataset.ids.push(data['id']);
label.innerText = data['label'];
answersContainer.setAttribute('id', 'answers_container');
questionBlock.append(label);
for (let i = 0; i < data['answers'].length; i += 1) {
let input = document.createElement('input'),
label = document.createElement('label');
input.setAttribute('type', isScq ? 'radio' : 'checkbox');
input.setAttribute('id', `answer_${i}`);
input.setAttribute('answerId', `${data['answers'][i]['id']}`);
if (isScq) {
input.setAttribute('name', 'scq-answer');
}

Leon
committed
label.setAttribute('for', `answer_${i}`);
label.innerText = data['answers'][i]['label'];
answersContainer.append(input);
answersContainer.append(label);
}
questionBlock.append(answersContainer);
questionBlock.append(resultP);
evalBtn.addEventListener('click', function () {
let answerData = [];
qa('#answers_container input').forEach(function (elem) {
answerData.push({
id: elem.getAttribute('answerId'),

Leon
committed
checked: elem.checked || false

Leon
committed
});
});
let request = new XMLHttpRequest();
request.overrideMimeType("application/json");
request.open("POST", dataBlock.dataset.evalpath, true);

Leon
committed
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
request.onload = function() {
if (this.readyState === 4 && this.status === 200) {
let result = JSON.parse(request.responseText),

Leon
committed
nxtBtn = document.createElement('button'),
resultMap = {
wrong: 'That\'s wrong.',
correct: 'Correct!'
};
if (!hasProp(result, 'message') || !['correct', 'wrong'].includes(result['message'])) {

Leon
committed
alert('Invalid or missing message');
return;
}
questionBlock.removeChild(evalBtn);
resultP.setAttribute('class', result['message']);

Leon
committed
resultP.innerText = resultMap[result['message']];
if (result['message'] === 'correct') {
dataset.c += 1;
} else {
dataset.w += 1;
}

Leon
committed
nxtBtn.addEventListener('click', getNextQuestion);
nxtBtn.innerText = 'Next question';
questionBlock.append(nxtBtn);
} else {
alert(`Error! Returned status ${this.status.toString(10)}`);
}
};
request.send(JSON.stringify({ question: data['id'], answers: answerData }));
});
evalBtn.innerText = 'Send';
questionBlock.append(evalBtn);
}
function getNextQuestion() {
if ((dataset.a + 1) >= 10) {
evaluate();
return;
}
questionBlock.innerHTML = '';

Leon
committed
let request = new XMLHttpRequest();
request.overrideMimeType("application/json");
request.open("POST", dataBlock.dataset.nextpath, true);

Leon
committed
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
request.onload = function() {
if (this.readyState === 4 && this.status === 200) {

Leon
committed
constructQuestion(JSON.parse(request.responseText));
} else if (this.status === 404) {
evaluate();

Leon
committed
} else {
alert('Error! Returned status ' + this.status.toString(10));
}
};
request.send(JSON.stringify({ t: dataset.t, ids: dataset.ids }));
}
function startQuiz() {
dataset.t = id('topic').value;
startBlock.setAttribute('style', 'display:none');
questionBlock.removeAttribute('style');
getNextQuestion();
}
q('header nav a[data-target="admin"]').addEventListener('click', function () {
loginForm.classList.add('open');
});
q('.admin > a[data-target="close"]').addEventListener('click', function () {
loginForm.classList.remove('open');
});

Leon
committed
let msgSpan = q('p.wrong span[data-target="close"]');
if (null !== msgSpan) {
msgSpan.addEventListener('click', function () {
this.parentElement.parentElement.removeChild(this.parentElement);
});
}

Leon
committed
id('start').addEventListener('click', startQuiz);
id('retry').addEventListener('click', function () {
resetEvalBlock();
resetDataset();
questionBlock.setAttribute('style', 'display:none');
evalBlock.setAttribute('style', 'display:none');
id('topic').value = 'all topics';
startBlock.removeAttribute('style');
});