diff --git a/src/index.js b/src/index.js index 9258781..156416b 100644 --- a/src/index.js +++ b/src/index.js @@ -1,249 +1,173 @@ -var responseCount, currentQuestion, options, -questions, responses, quizData, question, j, -$question, $resetButton, isQuestionAnswered - -responseCount = 0 -currentQuestion = 0 -options = { - url: 'data/quiz.json?' + Date.now() -} - -$.ajax({ - url: options.url -}).done(function(data) { - questions = data.questions - - // Load data from past reponses - try { - quizData = JSON.parse(localStorage.getItem('quiz')) - responses = quizData.responses || [] - currentQuestion = quizData.currentQuestion || -1 - responseCount = quizData.responseCount || -1 - } catch (e) {} - - if (quizData == null) { - quizData = { - responses: [] - } - responses = quizData.responses +(function($, JSON, localStorage){ + console.clear() + const options = { + url: `data/quiz.json?${Date.now()}` + } + const {url} = options + + const getOptionsMarkup = type => options => id => response => { + return '
' + + options + .map(({label}, j) => { + const checked = !!response && response.includes(label) ? 'checked' : '' + const optionId = `${id}_${j}` + return `
+
+ + +
+
` + }).join('') + + '
' } + const getInputsOptionsMarkup = options => id => response => { + return '' + + options.map(({label}, j) => { + const optionId = `${id}_${j}` + const value = response && response[j] || '' + return ` + + + + + ` }).join('') + + '
+ +
 
' + } - // Append the progress bar to DOM - $('body') - .append('
' + - '
 
' + - '
') - - // Append title and form to quiz - $('#quiz') - .append('

' + data.title + '

') - .append('
') - - // For each question of the json, - for (var i = 0; i < data.questions.length; i++) { - question = data.questions[i] - - if (question.input === undefined) { - question.input = { - type: 'input' - } - } + const getInputMarkup = id => (response='') => `
+ +
` + + const getCheckboxesMarkup = getOptionsMarkup('checkbox') + const getRadiosMarkup = getOptionsMarkup('radio') + + const getQuiz = () => JSON.parse(localStorage.getItem('quiz') || null) || {} + const setQuiz = data => localStorage.setItem('quiz', JSON.stringify(data)) + const setupQuizElement = ({title, questions}) => (quizContainer) => { + const $resetButton = $('') + const $submitButton = $('') + $resetButton.on('click', resetQuiz) + $submitButton.on('click', submitResponse(questions)) + + $(quizContainer) + .append(`

${title}

`) + .append('
') + .append($submitButton) + .append($resetButton) + + $(document.body) + .append(`
+
 
+
`) + } + const createQuestionElement = ({problem, input, input: {type, options}}) => name => response => { + let inputHtml // Construct the input depending on question type - switch (question.input.type) { - + switch (type) { // Multiple options case 'checkbox': + inputHtml = getCheckboxesMarkup(options)(name)(response) + break case 'radio': - var input = '
' - for (j = 0; j < question.input.options.length; j++) { - var option = question.input.options[j] - var type = question.input.type - - if (!!responses[i] && responses[i].indexOf(option.label) !== -1) { - var checked = 'checked' - } else { - var checked = '' - } - - input += '
' + - '
' + - '' + - '' + - '
' + - '
' - } - input += '
' + inputHtml = getRadiosMarkup(options)(name)(response) break - // Set of inputs (composed response) case 'inputs': - var input = '' - for (j = 0; j < question.input.options.length; j++) { - var option = question.input.options[j] - var type = 'checkbox' - - if (!!responses[i]) { - var value = responses[i][j] - } else { - var value = '' - } - - input += '' + - '' + - '' + - '' + - '' + - '' - } - input += '
' + - '' + - '
 
' + inputHtml = getInputsOptionsMarkup(options)(name)(response) break - // Default: simple input default: - if (!!responses[i]) { - var value = responses[i] - } else { - var value = '' - } - var input = '
' + - '' + - '
' + inputHtml = getInputMarkup(name)(response) } - $question = $('
' + - '
' + - '
' + question.problem + '
' + - '
' + - '
' + - input + - '
' + - '
' - ).css('display', 'none') - - $('#quiz-form') - .append($question) - - // Show current question - $('#quiz-form') - .find('#question-' + currentQuestion) - .css('display', 'block') - - // Update progress bar + return $(`
+
${problem}
+
${inputHtml}
+
` + ).get(0) + } + const appendToQuizForm = element => $('#quiz-form').append(element) + const removeFromQuizForm = element => $(element).remove() + + const updateProgress = questions => responses => { + const responseCount = countValidResponses(responses) $('#progress') .css('width', (responseCount / questions.length * 100) + '%') } - // Add button to submit response - $('#quiz') - .append('') + const getQuestionId = id => `question_${id}` + + const updateQuizViewStatus = questions => responses => { + const current = countValidResponses(responses) + const previousQuestion = document.getElementById(getQuestionId(current-1)) + + previousQuestion && removeFromQuizForm(previousQuestion) - // Is case all questions have been responded - if (responseCount === questions.length) { - $('#submit-response').css('display', 'none') - $('#quiz').append('
Thank you for your responses.

') - $('#quiz').append('') + // Is case all questions have been responded + if (questions.length <= current ) { + $('#submit-response').css('display', 'none') + $('#quiz') + .append('
Thank you for your responses.

') + .append('') + } else { + const question = questions[current] + question.input = question.input || { input: {type:'input'} } + + const nextQuestion = createQuestionElement(question)(getQuestionId(current))(responses[current]) + nextQuestion && appendToQuizForm(nextQuestion) + } + + updateProgress(questions)(responses) } - // Add a reset button that will redirect to quiz start - $resetButton = $('') - $resetButton.on('click', function() { + const resetQuiz = () => { localStorage.removeItem('quiz') location.reload(); - }) - $('#quiz').append($resetButton) + } - // Actions on every response submission - $('#submit-response').on('click', function() { - var $inputs = $('[name^=question_' + currentQuestion + ']') - var question = questions[currentQuestion] + const getInputsByName = form => { + const inputs = Array.from(form) + return name => inputs.filter( input => input.name.includes(name) ) + } - // Behavior for each question type to add response to array of responses - switch (question.input.type) { - case 'checkbox': - case 'radio': - responses[currentQuestion] = [] - $('[name=' + $inputs.attr('name') + ']:checked').each(function(i, input) { - responses[currentQuestion].push(input.value) - }) - if (responses[currentQuestion].length === 0) { - responses[currentQuestion] = null - } - break - case 'inputs': - responses[currentQuestion] = [] - $inputs.each(function(i, input) { - responses[currentQuestion].push(input.value) - }) - break - default: - responses[currentQuestion] = $inputs.val() - } + const isValidResponse = response => !!response // has a value + && !!Array.from(response).length // not an empty array + && !!Array.from(response).reduce((value, item) => value && !!item, true) // no value is empty - // Set the current responses counter - var responseCount = 0 - for (i = 0; i < responses.length; i++) { - question = questions[i] - switch (question.input.type) { - case 'checkbox': - case 'radio': - case 'inputs': - if (!!responses[i] && !!responses[i].join('')) { - responseCount++ - } - break - default: - if (!!responses[i]) { - responseCount++ - } - } - } + const countValidResponses = responses => responses + .reduce( ((value, response) => value + isValidResponse(response)), 0 ) - // Update progress bar - $('#progress') - .css('width', (responseCount / questions.length * 100) + '%') + const submitResponse = questions => () => { + let {responses=[]} = getQuiz() + const currentQuestion = responses.length + const getFormInputs = getInputsByName(document.getElementById('quiz-form')) + const inputs = getFormInputs(getQuestionId(currentQuestion)) + const response = inputs + .filter(({checked, type}) => type === 'text' || checked ) // has checked and it's false + .map(({value}) => value) // map to actual values - // Check if question had a valid answer - isQuestionAnswered = true - if (!responses[currentQuestion]) { - isQuestionAnswered = false - } - if (!!responses[currentQuestion] && !!responses[currentQuestion].length) { - for (j = 0; j < responses[currentQuestion].length; j++) { - if (!responses[currentQuestion][j]) { - isQuestionAnswered = false - } - } - } + // Set the current responses counter + responses.push(response) - if (!isQuestionAnswered) { + if (!isValidResponse(response)) { // Alert user of missing response alert('You must give a response') } else { - - // Display next question - $('#quiz-form') - .find('#question-' + currentQuestion).css('display', 'none') - currentQuestion = currentQuestion + 1 - - $('#quiz-form') - .find('#question-' + currentQuestion).css('display', 'block') - - // If it was the las question, display final message - if (responseCount === questions.length) { - $('#submit-response').css('display', 'none') - $('#quiz').append('
Thank you for your responses.

') - $('#quiz').append('') - } + // Count valid responses + updateQuizViewStatus(questions)(responses) + setQuiz({responses}) } + } + + $.ajax({ url }).done(function(data) { + let {questions} = data + let {responses=[]} = getQuiz() - // Save current state of the quiz - quizData.responses = responses - quizData.responseCount = responseCount - quizData.currentQuestion = currentQuestion - localStorage.setItem('quiz', JSON.stringify(quizData)) + setupQuizElement(data)(document.getElementById('quiz')) + updateQuizViewStatus(questions)(responses) }) -}) +})($, JSON, localStorage) diff --git a/test/helpers/index.js b/test/helpers/index.js index bf48672..70f9328 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -3,7 +3,7 @@ const BUTTON_SELECTOR = '.primary.button' const RESET_SELECTOR = '.negative.button' const PROGRESS_SELECTOR = '#progress' const RESPONSE_MSG = 'You must give a response' -const questionSelector = num => `.card:nth-child(${num})` +const questionSelector = num => `#question_${num-1}` module.exports = { data, diff --git a/test/quiz.spec.js b/test/quiz.spec.js index bc16d06..7bdf03e 100644 --- a/test/quiz.spec.js +++ b/test/quiz.spec.js @@ -40,9 +40,9 @@ describe('Quiz', () => { client.expect.element(questionSelector(1)) .to.be.visible client.expect.element(questionSelector(2)) - .to.not.be.visible + .to.not.be.present client.expect.element(questionSelector(3)) - .to.not.be.visible + .to.not.be.present }) it('should display description of the question', (client) => { @@ -81,7 +81,7 @@ describe('Quiz', () => { it('should display the next question', (client) => { client.expect.element(questionSelector(1)) - .to.not.be.visible + .to.not.be.present client.expect.element(questionSelector(2)) .to.be.visible }) @@ -102,7 +102,7 @@ describe('Quiz', () => { it('should display the last non answered question', (client) => { client.expect.element(questionSelector(1)) - .to.not.be.visible + .to.not.be.present client.expect.element(questionSelector(2)) .to.be.visible }) @@ -120,7 +120,7 @@ describe('Quiz', () => { client.expect.element(questionSelector(1)) .to.be.visible client.expect.element(questionSelector(2)) - .to.not.be.visible + .to.not.be.present }) }) })