Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Object Oriented JavaScript quiz

4.13/5 (6 votes)
11 Nov 2006CPOL3 min read 1   11  
Object oriented JavaScript quiz which can easily be customized to add more features.

Introduction

This is basically a log of a dynamic JavaScript quiz I created. It makes extensive use of JavaScript's object oriented features. Anyone interested in doing really advanced stuff in JavaScript can learn a lot from the methods used in this application. Doing this from scratch took me approximately two days.

What does this application do? It allows someone to add as many modules containing as many questions with as many choices as you like. All this is done without editing core files. It is also possible to replace the questions.js with AJAX calls....

What type of quiz is this? This is a multiple choice quiz.

File structure:

  • index.html - the file loaded into the browser which will display the quiz.
  • quiz.js - JavaScript code file containing the core quiz functions.
  • test.js - JavaScript code file containing the core interface functions.
  • quiz.css - Cascading styleheets file containing the directives that control the visual elements of the quiz, like font and color.
  • questions.js - JavaScript code file where the questions can be added with a specific syntax.

Index.html

I know it does not look like much, but believe it or not, this is what brings it all together:

HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Quiz</title>
<script type="text/javascript" src="quiz.js"></script>
<script type="text/javascript" src="questions.js"></script>
<script type="text/javascript" src="test.js"></script>
<link rel="stylesheet" href="quiz.css" />
</head>
 
<body onLoad="displayset(0,0)">
<form name="submitquestions" action="index.html" method="get">
<div id="headdiv" class="layout">
<h1>
QUIZ: 
<script>
document.writeln(quiz1.name)
</script>
</h1>
</div>
 
<div id="modulediv" class="layout" >
<table align="center"><tr>
<script>
 
 
totalmodules = quiz1.total()
for(i = 0;i < totalmodules;i++)
{
    currentmodule = quiz1.get(i)
    document.writeln('<td class="module" cellspacing=0>')    
    totalsets = currentmodule.total()
    for(x=0;x<totalsets;x++)
    {
        currentset = currentmodule.get(x)
        document.writeln('<a href="javascript:displayset('+ i +','+ x +')">'+ 
             currentmodule.name +'(' + currentset.name + ')</a>|<br>')
 
    }
    document.writeln('</td>')
}
 
</script>
</tr></table>
</div>
<div id="sectionheader" class="layout">
<h3 id="hl"> </h3>
</div>
 
<div id="bodydiv" class="layout">
</div>
<div id="footer" class="layout">
<input type="button" onclick="displayresults()" value="Submit" />
</div>
</form>
</body>
</html>

quiz.js

You can call this file the brain of the application. This is where the object classes used all over the application are defined.

JavaScript
/* *************************************************************************
GLOBALS AND UTILITY FUNCTIONS
Purpose of utility functions are to allow 
a novice user to add questions to questions.js
without having to worry about naming objects
**************************************************************************/
var globalmodule
var globalset
var globalquiz //really not necessary but put for completeness
var chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
function nextmodule(name)
{
    globalquiz.add(name)
}
function nextset(name)
{
    globalmodule.add(name)
}
function addproblem(question,answer)
{
    globalset.add(arguments) 
}
 
/* ***********************************************************************
MAIN QUIZ class
**************************************************************************/
function quiz(name)
{
    this.right = 0
    this.wrong = 0
    this.name = name
    this.modules = new Array()
}
quiz.prototype.add = function(name)
{
    this.modules.push(new module(name)) 
    globalmodule = this.modules[this.modules.length-1]
}
quiz.prototype.get = function(module)
{
    return this.modules[module] 
}
quiz.prototype.total = function()
{
    return this.modules.length 
}

/* *********************************************************************
MODULE CLASS
************************************************************************/
function module(name)
{
    this.name = name
    this.sets = new Array()
}
module.prototype.add = function(name)
{
    this.sets.push(new set(name))
    globalset = this.sets[this.sets.length-1]
}
module.prototype.get = function(set)
{
    return this.sets[set] 
}
module.prototype.total = function()
{
    return this.sets.length 
}
/*******************************************************************
SET CLASS
********************************************************************/
function set(name)
{
    this.name = name
    this.rightanswers = 0
    this.problems = new Array()
}
set.prototype.add = function(args)
{
    this.problems.push(new problem(args)) 
}
set.prototype.get = function(problem)
{
    return this.problems[problem] 
}
set.prototype.total = function()
{
    return this.problems.length 
}
/*****************************************************************
SETUP QUESTIONS AND ANSWERS
PROBLEM CLASS
******************************************************************/
function problem(args)
{
    this.choices = new Array() 
    this.selection = -1 //have not chosen an answer
    this.question = args[0]
    this.answer = chars.indexOf(args[1])
    for (var i = 2; i < args.length; i++) 
    {
        this.choices.push(args[i])
    }
}
problem.prototype.totalchoices = function()
{
    return this.choices.length 
}
problem.prototype.getchoice = function(choice)
{
    return this.choices[choice] 
}

Test.js

This file contains the code that dynamically renders the quiz using the routines from quiz.js based on the content of questions.js, which I will show next.

JavaScript
/********************************************** JavaScript Document
Interface functions responsible for displaying 
quiz elements and general flow of quiz
*******************************************************************/
 
function displayset(module,set)
{
    getmodule = quiz1.get(module)
    getset = getmodule.get(set)
    globalset = getset

    //set subheader for set
    var sheader = document.getElementById('hl')
    headertext = getmodule.name + " - " + getset.name + 
                 ": Answer the following questions"
    sheader.childNodes[0].nodeValue = headertext

    // clear questions
    questionsdiv = document.getElementById('bodydiv')
    if(questionsdiv.childNodes)
    {
        for(var i = 0;i<questionsdiv.childNodes.length;i++)
        {
            questionsdiv.removeChild(questionsdiv.childNodes[i]) 
        }
    }
    //write out questions and choices for current selected set
    totalproblems = getset.total()
    questionlist = document.createElement('OL')

    for(var i=0;i<totalproblems;i++)
    {
        currentproblem = getset.get(i)
        listitem = document.createElement('LI')
        questionbreak = document.createElement('BR')
        listquestion = document.createTextNode(currentproblem.question)

        //list choices for current problem
        choicelist = document.createElement('UL')

        uniqueid = uniquename(5)
        totalchoices = currentproblem.totalchoices()
        for(var x = 0;x < totalchoices;x++)
        {
            choiceitem = document.createElement('LI') 
            listradio = document.createElement('INPUT')
            listradio.setAttribute('type','radio')
            listradio.setAttribute('onClick', 
              'selectoption('+module+','+set+','+i+','+x+')')

            listradio.setAttribute('name',uniqueid)
            listradio.setAttribute('value',uniqueid) 
            choiceitemchar = document.createTextNode(chars.charAt(x) + ". ")
            choiceitemtext = 
               document.createTextNode(currentproblem.getchoice(x))
            choiceitem.appendChild(listradio)
            choiceitem.appendChild(choiceitemchar)
            choiceitem.appendChild(choiceitemtext)
            choicelist.appendChild(choiceitem)
        }
        listitem.appendChild(listquestion)
        listitem.appendChild(choicelist)
        questionlist.appendChild(listitem)
    }
    questionsdiv.appendChild(questionlist) 
    iefix()
}
function selectoption(module,set,problem,roption)
{
    //alert(module +"/"+ set +"/"+ problem +"/"+ roption)
    getmodule = quiz1.get(module)
    getset = getmodule.get(set)
    getproblem = getset.get(problem)
    getproblem.selection = roption
}
function uniquename(length)
{
    name = "";
    for(x=0;x<length;x++)
    {
        i = Math.floor(Math.random() * 52);
        name += chars.charAt(i);
    }
    return name;
}
function displayresults()
{
    //alert(document.getElementById('bodydiv').innerHTML)
    var noanswer = new Array()
    globalset.rightanswers = 0
    for(var i = 0;i<globalset.total();i++)
    {
        getproblem = globalset.get(i)
        if(getproblem.selection == -1)
        {
            noanswer.push(i + 1)
        }
        if(getproblem.selection == getproblem.answer)
        {
            globalset.rightanswers++ 
        }
    }
    switch(noanswer.length)
    {
        case 0 :
            percentage = Math.round(100 * 
                        (globalset.rightanswers/globalset.total()))
            //totalwrong = (globalset.total() - globalset.rightanswers)
            var resultstring = globalmodule.name +' - '+ globalset.name + '\n\n'
            resultstring+= 'Your Score is ' + percentage + '%'
            resultstring+= ' ('+ globalset.rightanswers+'/'+ globalset.total() +')\n\n'
            resultstring+= 'Question Your Answer Correct Answer Result \n'
            for(var x=0;x<globalset.total();x++)
            {
                question = x+1
                getproblem = globalset.get(x)
                resultstring+=' '+ question + ' '
                resultstring+=chars.charAt(getproblem.selection) + ' '
                resultstring+=chars.charAt(getproblem.answer) + ' '
                resultstring+= 
                  (getproblem.selection == getproblem.answer)? 'o\n':'x\n'
            }

            alert(resultstring);
            break;
        default :
        var noanswerstring =""
        for(var j=0;j<noanswer.length;j++)
        {
            noanswerstring += 'Question (' + noanswer[j] + ')\n' 
        }
        alert("The follwoing questions were not answered:\n\n" + noanswerstring)
    }
}

/*****************************************************************************
Function created to address issue with internet explorer after experiencing 
problems dynamically setting the name attribute of radio buttons. 
N.B. Other browsers don't need this.
******************************************************************************/
function iefix()
{
    if(document.all)
    {
        rawdata = document.getElementById('bodydiv').innerHTML
        fixdata = rawdata.replace(/value/g,"name")
        document.getElementById('bodydiv').innerHTML = fixdata
    }
}

questions.js

The following are simple examples showing how questions can be added. Although currently the index.html page is hard coded for a single quiz named quiz1, it is possible to define multiple quizzes.

Follow these instructions to add new modules, sets, and questions:

  1. To add a new module, simply put in: nextmodule('Module Name'). 'Module Name' will be displayed in the module selection.
  2. To add a new set, simply put in: nextset('Set Name'). 'Set Name' will be displayed in the set selection.
  3. To add a question, observe the following pattern: addproblem("your question",position of the correct answer,"answer1","answer2","answer3" etc.).
JavaScript
var quiz1 = new quiz('Sample Name')
globalquiz = quiz1
nextmodule('m1')
nextset('set 1')
addproblem("How old are you",'a',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'c',"Jan","mar","may","jul","dec")
addproblem("How old are you",'b',"10 to 13 years","14 to 20 years","21 to 25 years")
nextset('set 2')
addproblem("How old are you",'a',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'a',"Jan","mar","may","jul","dec")
addproblem("How old are you",'b',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'d',"Jan","mar","may","jul","dec")
nextmodule('m2')
nextset('set 1')
addproblem("How old are you",'b',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'b',"Jan","mar","may","jul","dec")
addproblem("How old are you",'a',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'c',"Jan","mar","may","jul","dec")
nextset('set 2')
addproblem("How old are you",'c',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'b',"Jan","mar","may","jul","dec")
addproblem("How old are you",'b',"10 to 13 years","14 to 20 years","21 to 25 years")
addproblem("your birth month?",'d',"Jan","mar","may","jul","dec")

quiz.css

This file can be modified to change the look and feel of the quiz elements.

CSS
/* CSS Document */
div.layout { margin:0px 0px 0px 0px; }
#modulediv { text-align:center; padding:0px 0px 0px 0px; }
#headdiv { text-align:center ; }
td.module { max-width:80px;}
#bodydiv { overflow:scroll;height:350px; max-height:350px; 
           overflow-x:hidden;border:thin inset black;}
#footer{text-align:center;}
body {margin-top:0px;}
#sectionheader {padding:0px 0px 0px 10px;}
ul {list-style-type:none;}

This script can be seen in action here.

Final Comments

The idea behind this is the ability to take quiz data and:

  1. Dynamically create the interface required for a user to take the quiz.
  2. Provide information about the user's choices and send them back to be verified.

So, given the object oriented structure, it is also possible to load the quiz data from another source without changing anything. Let's say, for example, your quiz is stored in XML files. You could add your own xml_question_reader.js, then you could say the following in questions.js:

JavaScript
xml_test_source = load_xml_source_tree("path/to/quiz.xml")

for(x in xml_test_source.modules)
{
    xml_module = xml_test_source.modules[x]
    nextmodule(xml_module.name)

    for(y in xml_module.sets)
    {
        xml_set = xml_module.sets[y]
        nextset(xml_set.name)

        for(z in xml_set.problems)
        {
           xml_problem = xml_set.problems[z]
           addproblem(xml_problem.question,xml_problem.answer,xml_problem.options)
        }
    }
}

It is important to note that the XML source could also be generated by any server-side language. However, the script may have to be changed slightly, so you won't have to provide the correct answer in JavaScript at the time of initializing a problem.

This script demonstrates many key concepts including dynamically adding form elements to a page, and has also been tested in IE, Opera, and Firefox. Enjoy.

N.B. The previous script has no real error testing, and should be used for educational purposes only.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)