Skip to content

Instantly share code, notes, and snippets.

@imhamad
Forked from katjad/index.html
Created August 25, 2021 12:49
Show Gist options
  • Save imhamad/46dea504946f287300e3b197e59d33b3 to your computer and use it in GitHub Desktop.
Save imhamad/46dea504946f287300e3b197e59d33b3 to your computer and use it in GitHub Desktop.
One page JS Quiz App
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Quiz built in JS</title>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 16px;
line-height: 1.6;
}
.container {width: 80%; max-width: 900px; margin: 0 auto}
section { padding: 10px 20px; }
button { background: #3cf; padding: 5px 8px; border: none; font-size: 1.2em; margin: 5px 8px 15px 0; min-width: 120px; }
button#start { background: #dedede; width: 320px; padding: 8px 13px;}
button#submit { background: #69f; }
.hidden { display: none; }
.inactive { background: #dedede; color: #555; }
.success { background: #0c6; }
.error {background: #f97;}
input {margin-bottom: 15px;}
#next {float: right;}
</style>
</head>
<body>
<div class="container">
<h1>The best little JavaScript Quiz</h1>
<p>(Questions are from this <a href="http://dmitrysoshnikov.com/ecmascript/the-quiz/">post</a> by Dimitry Soshnikov)
<p>&nbsp;</p>
<section id="message"></section>
<button id="start" >Start the Quiz</button>
<h2 id="titleEl">Welcome to my little JavaScript Quiz</h2>
<section id="question"></section>
<section>
<form id="answers" action=""></form>
</section>
<button id="submit" type="submit" class="hidden">Submit</button><br />
<button id="prev" tabindex="0" class="inactive">&laquo; Previous</button>
<button id="next" tabindex="0">Next &raquo;</button>
</div>
<script>
const quiz = [
{question: "What's the result of \"typeof typeof(null)\"?",
choices: ["\"undefined\"","SyntaxError","\"string\"","\"object\"","TypeError"], correctAnswer:2},
{question: "Are the algorithms of the following checks completely equivalent?: \"typeof foo == 'undefined'\" and \"typeof foo === 'undefined'\"",
choices: ["Yes","No"], correctAnswer:0},
{question: "What's the result of \"100['toString']['length']\"?",
choices: ["100","3","1","8","0"], correctAnswer:2},
{question: "What's the value of a, when \"var a = (1,5 - 1) * 2\"?",
choices: ["0.99999999","1","0.5","8","-0.5","4"], correctAnswer:3},
{question: "What's the result of \"1..z\"?",
choices: ["SyntaxError","New Range object (equivalent to new Range(1, 'z')) including all numbers and letters","undefined","Error of Range object (incompatible types for range: number and string)","ReferenceError \"z\" is not defined"], correctAnswer:2}
]
// check for valid page querystring
const validQs = [...Array(quiz.length + 2).keys()]
const validParams = validQs.map(i => i.toString())
const validP = () => {
const urlParams = new URLSearchParams(window.location.search);
const qparam = urlParams.get('page');
return (qparam && validParams.includes(qparam)) ? qparam : false;
}
// baseURL
const base = window.location.href.split('/').slice(0, -1).join('/')
const setDOMprops = (type, page) => {
const domFuncs = {
start: () => {
titleEl.innerHTML = Qz.msgstrings.welcome;
start.classList.remove("hidden");
submitbtn.classList.add("hidden");
prev.classList.add("inactive");
next.classList.remove("inactive");
},
questions: page => {
titleEl.innerHTML = `Question ${page}`;
start.classList.add("hidden");
message.className = "";
message.innerHTML ="";
submitbtn.classList.remove("hidden");
prev.classList.remove("inactive");
next.classList.remove("inactive");
},
end: () => {
titleEl.innerHTML = `End of Quiz`;
start.classList.add("hidden");
prev.classList.remove("inactive");
next.classList.add("inactive");
},
resetQA: () => {
questionEl.innerHTML = "";
answersEl.innerHTML = "";
},
resetMsg: () => {
message.className = "";
message.innerHTML = "";
}
}
return domFuncs[type]
}
const showPage = page => {
page = parseInt(page)
if (page === 0){
Qz.setDOMprops("resetQA")();
Qz.setDOMprops("resetMsg")();
Qz.setDOMprops("start")();
} else if (page > quiz.length) {
Qz.setDOMprops("resetQA")();
Qz.setDOMprops("resetMsg")();
Qz.setDOMprops("end")();
//console.log("final answer count", Qz.submittedAnswers)
Qz.getFinishingMsg()
} else {
Qz.setDOMprops("resetQA")();
Qz.setDOMprops("resetMsg")();
Qz.setDOMprops("questions")(page);
}
if (page > 0 && page < quiz.length + 1) {
q = (page >= 1) ? page-1 : page;
if(q < quiz.length) {
const { question, choices, correctAnswer } = quiz[q];
questionEl.innerHTML = question;
Qz.displayAndHandleAnswers(choices, correctAnswer);
}
}
}
const displayAndHandleAnswers = (choices, correct) => {
const addRadioBtn = (acc, choice, index) => {
return `${acc}<input type="radio" name="answer" value="${index}" tabindex="0">${choice}<br />\n`
}
const answersStr = choices.reduce(addRadioBtn, "")
answersEl.innerHTML = answersStr;
const handler = e => {
e.preventDefault()
let selected = false;
const q = Qz.page-1;
const answers = document.querySelectorAll("input");
answers.forEach(answer => {
if(answer.checked){
selected = answer.value;
}
})
if(!selected){
message.className = "error";
message.innerHTML = Qz.msgstrings.noselect;
} else {
let output = (correct === parseInt(selected))
message.className = (output) ? "success" : "error";
message.innerHTML = (output) ? `This is the correct answer to question ${Qz.page}! Click 'Next' to go to the next question.` : Qz.msgstrings.incorrect;
Qz.submittedAnswers = {...Qz.submittedAnswers, [`${q}`]: output}
}
}
submitbtn.addEventListener("click", handler);
answersEl.addEventListener("keydown", e => {if (e.keyCode === 13) {handler(e)} })
}
const getFinishingMsg = () => {
const entries = Object.entries(Qz.submittedAnswers)
let counterCorrect = 0;
let counterIncorrect = 0;
let counterUnanswered = 0;
let unanswered = [];
entries.forEach(entry => {
[key, value] = entry;
if (value === true){ ++counterCorrect }
if (value === false){ ++counterIncorrect }
if (value === "unanswered"){
++counterUnanswered;
unanswered.push(key)
}
})
const unansweredQs = unanswered.map(q => parseInt(q) + 1);
const unansweredStr = unansweredQs.slice().join(", ")
let msg = "";
if (counterCorrect === quiz.length){
msg += "Congratulations, you answered all questions correctly!";
} else {
if(counterCorrect){
msg += `You answered ${counterCorrect} out of ${quiz.length} questions correctly.`
}
if(counterUnanswered){
msg += `You did not answer the following questions: ${unansweredStr}.`
}
}
answersEl.innerHTML = msg;
}
const Qz = {
// initialise variables
submittedAnswers: {},
page: 0,
msgstrings: {
welcome: `Welcome to my little JavaScript Quiz`,
noselect: `You have not selected an answer yet`,
incorrect: `This answer is not correct. Try again or click 'Next' for the next question.`
},
// functions
setDOMprops: setDOMprops,
showPage: showPage,
displayAndHandleAnswers: displayAndHandleAnswers,
getFinishingMsg: getFinishingMsg
}
const start = document.getElementById("start");
const titleEl = document.getElementById("titleEl");
const questionEl = document.getElementById("question");
const submitbtn = document.getElementById("submit");
const message = document.getElementById("message");
const answersEl = document.getElementById("answers");
const prev = document.getElementById("prev");
const next = document.getElementById("next");
if (validP()){
Qz.page = parseInt(validP())
Qz.showPage(Qz.page)
}
start.addEventListener("click", () => {
Qz.page = 1
Qz.showPage(Qz.page);
history.pushState({page: Qz.page}, `Page ${Qz.page}`, `${base}/index.html?page=${Qz.page}`)
})
next.addEventListener("click", () => {
if(Qz.page < quiz.length + 1){
if(Qz.page > 0){
console.log("type", typeof(Qz.page))
q = Qz.page - 1
if(Qz.submittedAnswers[`${q}`] === undefined){
Qz.submittedAnswers = {...Qz.submittedAnswers, [`${q}`]:"unanswered"}
}
console.log("answers after check", Qz.submittedAnswers)
}
Qz.page += 1;
Qz.showPage(Qz.page)
}
history.pushState({page: Qz.page}, `Page ${Qz.page}`, `${base}/index.html?page=${Qz.page}`)
})
prev.addEventListener("click", () => {
Qz.page = (Qz.page > 1) ? Qz.page-1 : 0;
showPage(Qz.page)
history.pushState({page: Qz.page}, `Page ${Qz.page}`, `${base}/index.html?page=${Qz.page}`)
})
window.onpopstate = () => {
if (history.state && history.state.page){
Qz.page = history.state.page;
Qz.showPage(Qz.page)
} else {
Qz.showPage(0)
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment