Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
269e7df
Arrange global vars definition
davidbarna Oct 30, 2017
ff1b0f3
refactor with operators
davidbarna Oct 30, 2017
3518e33
Create a closure
davidbarna Oct 30, 2017
9a7d446
missing changes
davidbarna Oct 30, 2017
ae7e65e
reassign url
davidbarna Oct 30, 2017
35289a3
convert strings to template strings
davidbarna Nov 1, 2017
84e7ae2
function for multiple options fields
davidbarna Nov 20, 2017
e99403c
function for multiple inputs fields
davidbarna Nov 20, 2017
09a0389
function for input fields
davidbarna Nov 20, 2017
06bcc19
move quiz storage to functions
davidbarna Nov 22, 2017
ae0998b
move quiz DOM setup to functions
davidbarna Nov 22, 2017
da9703b
move progress to a func
davidbarna Nov 22, 2017
ac25a58
optimize updateProgress with partial application
davidbarna Nov 22, 2017
9bbc3bd
move question markup creation to function
davidbarna Nov 22, 2017
ba19d20
move question displaying to func
davidbarna Nov 22, 2017
bf0fb2d
move reset and submit buttons to setup funcs
davidbarna Nov 22, 2017
4a6ceaa
move end message to quiz status func
davidbarna Nov 22, 2017
7e59b8a
refactor submitResponse func
davidbarna Nov 28, 2017
9dc7f51
Pass response processing to functional
davidbarna Nov 28, 2017
dc9722e
Remove unnecessary response count from storage
davidbarna Nov 28, 2017
3ba74a5
base all partial applications on questions array
davidbarna Nov 28, 2017
a3d4e9a
Remove unnecesaary currentQuestion variable
davidbarna Nov 28, 2017
e0d5f78
move updateProgress to updateQuizViewStatus
davidbarna Nov 28, 2017
38c7c3d
improve performance mounting questions on demand
davidbarna Nov 28, 2017
18746ab
Fix error on final response
davidbarna Dec 11, 2017
77a7a4f
adapt tests to performance improvement
davidbarna Dec 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
348 changes: 136 additions & 212 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -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 '<div class="inline fields">'
+ options
.map(({label}, j) => {
const checked = !!response && response.includes(label) ? 'checked' : ''
const optionId = `${id}_${j}`
return `<div class="field">
<div class="ui checkbox ${type}">
<input type="${type}" ${checked} name="${id}" id="${optionId}" value="${label}">
<label for="${optionId}">${label}</label>
</div>
</div>`
}).join('')
+ '</div>'
}

const getInputsOptionsMarkup = options => id => response => {
return '<table>'
+ options.map(({label}, j) => {
const optionId = `${id}_${j}`
const value = response && response[j] || ''
return `<tr>
<td><label for="${optionId}">${label}</label></td>
<td width="15px"></td>
<td><div class="ui input">
<input type="text" placeholder="Response..." name="${id}" id="${optionId}" value="${value}" />
</div></td>
</tr>
<tr><td colspan="3">&nbsp;</tr></tr>` }).join('')
+ '</table>'
}

// Append the progress bar to DOM
$('body')
.append('<div style="position: fixed; bottom: 0; background: #eee; width: 100%; height: 6px; ">' +
'<div id="progress" style="background: #1678c2; width: 1%;">&nbsp;</div>' +
'</div>')

// Append title and form to quiz
$('#quiz')
.append('<h1 class="ui header">' + data.title + '</h1>')
.append('<form id="quiz-form" class="ui form"></form>')

// 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='') => `<div class="ui input fluid">
<input type="text" placeholder="Response..." name="${id}" value="${response}" />
</div>`

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 = $('<button class="ui button negative">Reset</button>')
const $submitButton = $('<button id="submit-response" class="ui primary button">Submit response</button>')
$resetButton.on('click', resetQuiz)
$submitButton.on('click', submitResponse(questions))

$(quizContainer)
.append(`<h1 class="ui header">${title}</h1>`)
.append('<form id="quiz-form" class="ui form"></form>')
.append($submitButton)
.append($resetButton)

$(document.body)
.append(`<div style="position: fixed; bottom: 0; background: #eee; width: 100%; height: 6px; ">
<div id="progress" style="background: #1678c2; width: 1%;">&nbsp;</div>
</div>`)
}
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 = '<div class="inline fields">'
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 += '<div class="field">' +
'<div class="ui checkbox ' + type + '">' +
'<input type="' + type + '" ' + checked + ' name="question_' + i + '" id="question_' + i + '_' + j + '" value="' + option.label + '">' +
'<label for="question_' + i + '_' + j + '">' + option.label + '</label>' +
'</div>' +
'</div>'
}
input += '</div>'
inputHtml = getRadiosMarkup(options)(name)(response)
break

// Set of inputs (composed response)
case 'inputs':
var input = '<table>'
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 += '<tr>' +
'<td><label for="question_' + i + '_' + j + '">' + option.label + '</label></td>' +
'<td width="15px"></td>' +
'<td><div class="ui input">' +
'<input type="text" placeholder="Response..." name="question_' + i + '" id="question_' + i + '_' + j + '" value="' + value + '" />' +
'</div></td>' +
'</tr>' +
'<tr><td colspan="3">&nbsp;</tr></tr>'
}
input += '</table>'
inputHtml = getInputsOptionsMarkup(options)(name)(response)
break

// Default: simple input
default:
if (!!responses[i]) {
var value = responses[i]
} else {
var value = ''
}
var input = '<div class="ui input fluid">' +
'<input type="text" placeholder="Response..." name="question_' + i + '" value="' + value + '" />' +
'</div>'
inputHtml = getInputMarkup(name)(response)
}

$question = $('<div id="question-' + i + '" class="ui card" style="width: 100%;">' +
'<div class="content">' +
'<div class="header">' + question.problem + '</div>' +
'</div>' +
'<div class="content">' +
input +
'</div>' +
'</div>'
).css('display', 'none')

$('#quiz-form')
.append($question)

// Show current question
$('#quiz-form')
.find('#question-' + currentQuestion)
.css('display', 'block')

// Update progress bar
return $(`<div id="${name}" class="ui card" style="width: 100%;">
<div class="content"><div class="header">${problem}</div></div>
<div class="content">${inputHtml}</div>
</div>`
).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('<button id="submit-response" class="ui primary button">Submit response</button>')
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('<div>Thank you for your responses.<br /><br /> </div>')
$('#quiz').append('<button class="ui primary button" onclick="window.print()" >Print responses</button>')
// Is case all questions have been responded
if (questions.length <= current ) {
$('#submit-response').css('display', 'none')
$('#quiz')
.append('<div>Thank you for your responses.<br /><br /> </div>')
.append('<button class="ui primary button" onclick="window.print()" >Print responses</button>')
} 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 = $('<button class="ui button negative">Reset</button>')
$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('<div>Thank you for your responses.<br /><br /> </div>')
$('#quiz').append('<button class="ui primary button" onclick="window.print()" >Print responses</button>')
}
// 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)
2 changes: 1 addition & 1 deletion test/helpers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading