Skip to content
Snippets Groups Projects
app.js 9.07 KiB
Newer Older
// 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}
 */
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'),
    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 };
}

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) + '%';

    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) {
    if (!hasProp(data, 'id')) {
    if (!hasProp(data, 'type')) {
        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!';
    }
    if (!hasProp(data,'label')) {
    if (!hasProp(data, 'answers')) {
        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) {
        if (!hasProp(data['answers'][i], 'id')) {
            return `Answer in slot ${i} has no ID!`;
        } else if (typeof data['answers'][i]['id'] !== 'number') {
            return `Answer in slot ${i} has invalid ID!`;
        }
        if (!hasProp(data['answers'][i], 'label')) {
            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']);

Leon's avatar
Leon committed
    label.innerText = data['label'] + ' (' + (isScq ? 'Single choice' : 'Multiple choice') + ')';

    answersContainer.setAttribute('id', 'answers_container');

    questionBlock.append(label);

    for (let i = 0; i < data['answers'].length; i += 1) {
Leon's avatar
Leon committed
        let container = document.createElement('div'),
            input = document.createElement('input'),
            label = document.createElement('label')
        ;

        container.setAttribute('class', 'input-container');

        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');
        }

        label.setAttribute('for', `answer_${i}`);
        label.innerText = data['answers'][i]['label'];

Leon's avatar
Leon committed
        container.append(input);
        container.append(label);
        answersContainer.append(container);
    }

    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'),
            });
        });
        let request = new XMLHttpRequest();
        request.overrideMimeType("application/json");
        request.open("POST", dataBlock.dataset.evalpath, true);
        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),
                    nxtBtn = document.createElement('button'),
                    resultMap = {
                        wrong: 'That\'s wrong.',
                        correct: 'Correct!'
                    };
                if (!hasProp(result, 'message') || !['correct', 'wrong'].includes(result['message'])) {
                    alert('Invalid or missing message');
                    return;
                }
                questionBlock.removeChild(evalBtn);
                resultP.setAttribute('class', result['message']);
                resultP.innerText = resultMap[result['message']];
                if (result['message'] === 'correct') {
                    dataset.c += 1;
                } else {
                    dataset.w += 1;
                }
                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 = '';

    let request = new XMLHttpRequest();
    request.overrideMimeType("application/json");
    request.open("POST", dataBlock.dataset.nextpath, true);
    request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
    request.onload = function() {
        if (this.readyState === 4 && this.status === 200) {
            dataset.a += 1;
            constructQuestion(JSON.parse(request.responseText));
        } else if (this.status === 404) {
            evaluate();
        } 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');
});

let msgSpan = q('p.wrong span[data-target="close"]');
if (null !== msgSpan) {
    msgSpan.addEventListener('click', function () {
        this.parentElement.parentElement.removeChild(this.parentElement);
    });
}
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');
});