Twitter: raymondcamden


Address: Lafayette, LA, USA

Building a Quiz Manager for jQuery Mobile

12-05-2013 10,490 views Mobile, jQuery, JavaScript, HTML5 69 Comments

A few weeks ago a reader asked if I had ever designed a quiz for jQuery Mobile. While I had not, I spent some time thinking about how a quiz could be designed as well as how a generic library could help automate it. I've built a demo I'd like to share with folks. It is definitely "First Draft" (but hey, it lints!) so feel free to tear it apart and suggest improvements.

I began by thinking how quiz data could be represented. I figured either XML or JSON. JSON has the benefit of being really easy to work with in JavaScript. XML has the benefit of being really easy to write, even for non-devs. At the end of the day though I settled on JSON. My library could be updated to handle both though. Here is an example quiz I used for my demo.

{
	"introduction":"This quiz tests you about foo and goo", 
	"questions":[
		{"question":"Why is the sky blue?", 
		 "answers":["Unicorns","Fairies","Boring Science","Kittens"],
		 "correct":2},
		{"question":"Why are kittens so cute?", 
		 "answers":["Magic","Fur","Meow","More Kittens!"],
		 "correct":3}
	]
}

The scheme consists of an optional introduction and an array of questions. Each question has a question value (the actual text), an array of answers, and a correct index. This is 0-based but I'm thinking it may make sense to be 1-based. My design only allows for multiple choice questions with one answer, but you could also do true/false of course.

On the jQuery Mobile side, the library is used by running an execute method. The execute method takes the URL of a quiz, a DOM element to render the quiz within, and a success callback. My jQuery Mobile application uses this app.js file to handle that aspect:

/* global $,document,console,quizMaster */
$(document).ready(function() {
	
	$(document).on("pageshow", "#quizPage", function() {
		console.log("Page show");
		//initialize the quiz
		quizMaster.execute("q1.json", ".quizdisplay", function(result) {
			console.log("SUCESS CB");
			console.dir(result);	
		});
	});
}); 

I whipped up a quick jQuery Mobile application with two pages. The first simply links over to the quiz page.

Once you load the quiz page, the code you see above runs the library. Here is how the quiz displays the introduction:

And this is the first question:

Ok, now let's look at the library.

/* global $,window */
var quizMaster = (function () {
	var name;
	var data;
	var loaded = false;
	var displayDom;
	var successCbAlias;

	function nextHandler(e) {
		e.preventDefault();

		var status = getUserStatus();

		//if we aren't on the intro, then we need to ensure you picked something
		if(status.question >= 0) {
			var checked = $("input[type=radio]:checked", displayDom);
			if(checked.length === 0) {
				//for now, an ugly alert
				window.alert("Please answer the question!");
				return;
			} else {
				status.answers[status.question] = checked.val();	
			}
		} 
		status.question++;
		storeUserStatus(status);
		displayQuiz(successCbAlias);
	}

	function displayQuiz(successCb) {

		//We copy this out so our event can use it later. This feels wrong
		successCbAlias = successCb;
		var current = getQuiz();
		var html;

		if(current.state === "introduction") {
			html = "<h2>Introduction</h2><p>" + current.introduction + "</p>" + nextButton();
			displayDom.html(html).trigger('create');
		} else if(current.state === "inprogress") {
			
			html = "<h2>" + current.question.question + "</h2><form><div data-role='fieldcontain'><fieldset data-role='controlgroup'>";
			for(var i=0; i<current.question.answers.length; i++) {
				html += "<input type='radio' name='quizMasterAnswer' id='quizMasterAnswer_"+i+"' value='"+i+"'/><label for='quizMasterAnswer_"+i+"'>" + current.question.answers[i] + "</label>";
			}
			html += "</fieldset></div></form>" + nextButton();
			displayDom.html(html).trigger('create');
		} else if(current.state === "complete") {
			html = "<h2>Complete!</h2><p>The quiz is now complete. You got "+current.correct+" correct out of "+data.questions.length+".</p>";
			displayDom.html(html).trigger('create');
			removeUserStatus();
			successCb(current);
		}
		
		
		//Remove previous if there...
		//Note - used click since folks will be demoing in the browser, use touchend instead
		displayDom.off("click", ".quizMasterNext", nextHandler);
		//Then restore it
		displayDom.on("click", ".quizMasterNext", nextHandler);
		
	}
	
	function getKey() {
		return "quizMaster_"+name;	
	}
	
	function getQuestion(x) {
		return data.questions[x];	
	}
	
	function getQuiz() {
		//Were we taking the quiz already?
		var status = getUserStatus();
		if(!status) {
			status = {question:-1,answers:[]};
			storeUserStatus(status);
		}
		//If a quiz doesn't have an intro, just go right to the question
		if(status.question === -1 && !data.introduction) {
			status.question = 0;
			storeUserStatus(status);
		}

		var result = {
			currentQuestionNumber:status.question
		};
		
		if(status.question == -1) {
			result.state = "introduction";
			result.introduction = data.introduction;	
		} else if(status.question < data.questions.length) {
			result.state = "inprogress";
			result.question = getQuestion(status.question);	
		} else {
			result.state = "complete";
			result.correct = 0;
			for(var i=0; i < data.questions.length; i++) {
				if(data.questions[i].correct == status.answers[i]) {
					result.correct++;	
				}
			}
		}
		return result;
	}
	
	function getUserStatus() {
		var existing = window.sessionStorage.getItem(getKey());
		if(existing) {
			return JSON.parse(existing);
		} else {
			return null;
		}
	}
	
	function nextButton() {
		return "<a href='' class='quizMasterNext' data-role='button'>Next</a>";	
	}
	
	function removeUserStatus(s) {
		window.sessionStorage.removeItem(getKey());	
	}
	
	function storeUserStatus(s) {
		window.sessionStorage.setItem(getKey(), JSON.stringify(s));
	}
	
	return {
		execute: function( url, dom, cb ) {
			//We cache the ajax load so we can do it only once 
			if(!loaded) {
				
				$.get(url, function(res, code) {
					//Possibly do validation here to ensure basic stuff is present
					name = url;
					data = res;
					displayDom = $(dom);
					//console.dir(res);
					loaded = true;
					displayQuiz(cb);
				});
				
			} else {
				displayQuiz(cb);
			}
		}
	};
}());

There's a lot here and I'll try to explain it bit by bit. The end of the code is the public API which - for now - has one method, execute. Note how we detect if the quiz is already loaded. This way we can cache the JSON and not load it after we've fetched it once in the request.

displayQuiz is the main handler for rendering the quiz. It begins (ignore the copy statement) by calling getQuiz. getQuiz handles interfacing with the quiz data as well as the user data. I'm using sessionStorage to remember where you are in the quiz. This is useful if you leave the quiz before finishing it. getQuiz also does some intelligent handling of state. So for example, if there isn't an introduction it ensures you go right into the first question. It also recognizes when you're done and checks your work.

Back in displayQuiz we use the result of getQuiz to render one of three states - the introduction, the quiz itself, or the completion. By the way, the success callback is used to allow your calling code to record the results to your server via AJAX, or do anything really.

All in all this was fun to write, but as I said, feels very much like a first draft. Want to try it yourself? Hit the demo link below.

69 Comments

  • Commented on 12-05-2013 at 11:43 AM
    Perfect timing for this post because I may be creating a Quiz app for a city using Phonegap!! This will definitely help out! Thanks!
  • Commented on 12-05-2013 at 10:27 PM
    I built something similar a while back using jQuery but not mobile. It shows correct/incorrect as you proceed and allows timed quiz. The quiz and settings are stored in Google spreadsheet. You may get a error on first load - just reload - it's due to a bug in connecting to YQL which parses spreadsheet.

    https://github.com/jsteenkamp/jqwiz
  • Commented on 12-06-2013 at 7:15 AM
    A bit OT - but YQL is cool. I remember finding it years ago and it gets almost no press. I rarely hear developers talk about it.
  • Okezie Michael #
    Commented on 12-06-2013 at 8:36 AM
    Hello Ray,

    Nice work, i think we should have something like a review after the Quiz, and a button to re-take the Quiz again. and re-taking the Quiz, the question should shuffle , so i think if you can increase the number of Questions in the Question bank to 5, to check that it shuffles on re-taking the Quiz.

    if you can please email me your source code and let me add these features, i don't know why am getting an error.

    thanks

    Michael

    thanks
  • Commented on 12-06-2013 at 8:38 AM
    Okezie: Yeah, I was a bit torn between how much the "Manager" should do versus how much you (you being the person using the manager) should do too. I thought about providing a way to let you do the 'Your done' message yourself, but decided against it.

    Anyway - there is no need for me to send you the code... just view source and save. :) (Obviously you will need to grab the JS files manually.)

    Ok screw it - give me a minute and I'll post up a zip.
  • Commented on 12-06-2013 at 8:39 AM
    Zip is located here: http://www.raymondcamden.com/demos/2013/dec/5/quiz....
  • Commented on 12-06-2013 at 11:57 AM
    I teach an online class, and have had an observation the other night about students taking quizzes online. I think that the traditional thought of quizzing doesn't apply to online students any longer. #1 it's always been assumed that an online quiz is an open book, open Google search assignment.

    But what I found was that students were simply answering questions without even reading the textbook OR doing a Google search. In other words, the quiz itself needs to do the teaching and become the verification that the teaching has been understood. So instead of the normal Question and Answer, I think online quizzes need to be: teaching and verifying.

    Might seem obvious, but just something that I wanted to put on the table.
    Run it up the flag pole and see who salutes it.
    Swing for the fences.
    I'm just throwing something up against the wall and seeing what sticks.
    A penny saved is a penny earned.
  • Commented on 12-06-2013 at 12:01 PM
    Interesting comment there, Phillip. Do you have an example of one online that does this? (Hopefully something easy for folks to see - ie no sign up.)
  • Jack #
    Commented on 12-06-2013 at 1:07 PM
    I know this is just a demo, but it seems to me that from a quiz-creation standpoint it would be better to have the answer be the actual answer (as a string) rather than a numeric index:
    {"question":"Why is the sky blue?",
    "answers":["Unicorns","Fairies","Boring Science","Kittens"],
    "correct":"Fairies"},


    One drawback of this approach is that a typo in the answer would mean that the code would never find a correct match. (Presumably this would be detected through testing or a quiz validation function.)

    However, it comes with several advantages
    -- Would make it easier to change the order of the multiple choice responses. This would be really helpful if you reshuffled the questions and presented them again and didn't want them to look exactly the same.
    -- Would allow another reviewer to easily validate a test created by a colleague.
    -- Would eliminate all off-by-one errors!!!!
  • Commented on 12-06-2013 at 1:20 PM
    Jack and I discussed this in email and I asked him to bring here as I thought it would be good for the conversation. I was opposed to this because of the concern with possibly mistyping the answer. But - if there was validation at the quiz library level I think this would be a good change actually.
  • Tim #
    Commented on 12-10-2013 at 3:46 AM
    I downloaded the script, and when I run it in firefox and click "Start Quiz!", it shows the quiz.html page but has no questions or footer content there, why is that :S
  • Commented on 12-10-2013 at 5:19 AM
    @Tim: Check the console - do you see an error?
  • Commented on 12-10-2013 at 9:57 PM
    I'm seeing the same problem as the other Tim. Downloaded the .zip file, extracted, double-clicked on index.html, click the Start Quiz button - then only see the header and footer, nothing in the content area. I notice sometimes when viewing the demo on your site it does the same thing - where the actual quiz should be, only a header and footer show up.

    Console will log out "Page show" then gives a "not well-formed" for both q1.json and quiz.html; it never seems to make it to log out the "SUCESS CB" line. This is on Firefox 26; tried on IE and Chrome - IE will work on site, but not when downloaded to my computer. Chrome gives the following console error:

    No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

    This is why javascript so often tastes like headache =/
  • Commented on 12-11-2013 at 7:14 AM
    Lets focus on Chrome for now. When you run it, are you running it from a local web server, or did you just double click on the file?
  • Captain Obvious #
    Commented on 12-16-2013 at 10:00 AM
    For those getting an error add "json" mime type to web server config or change line #141 in quiz.js from this

    });

    to this

    }, "json" );
  • Commented on 12-16-2013 at 12:30 PM
    Darn good point there Captain Obvious. Thanks. (Hopefully the Captain Obvious wasn't a dig at me for missing something obvious. ;)
  • Captain Obvious #
    Commented on 12-16-2013 at 12:57 PM
    No no - that is just my gmail handle :)
  • Albert #
    Commented on 01-04-2014 at 3:50 PM
    Great tutorial as usual, thanks. I'm going to use it in my phonegap app and therefore wanted to ask you about some advices for adapting your code for running it locally rather than from web server. Thanks in advance.
  • Commented on 01-05-2014 at 4:46 PM
    The code is just HTML/JS/CSS. It should run just fine in PhoneGap.
  • Albert #
    Commented on 01-06-2014 at 5:13 PM
    I've tried already to run it on phonegap. It causes Uncaught TypeError: Cannot read property 'length' of undefined at file:///android_asset/www/quiz.js:92
  • Captain Obvious #
    Commented on 01-07-2014 at 7:11 AM
    Albert - you will get that error if "data" is a string rather than a json object, note my comment above.
  • Albert #
    Commented on 01-07-2014 at 8:03 AM
    Captain Obvious - thanks for the reminding again :) the changes in 141 line of quiz.js solved the problem :)
  • Kane #
    Commented on 02-17-2014 at 6:04 AM
    Tim, were you able to fix the No 'Access-Control-Allow-Origin'' error?

    Raymond - thank you for putting this together.
    Downloaded the .zip and clicked index.html, it runs perfectly fine in Firefox, however in Chrome and IE I get the following errors as soon as clicking the "Start Quiz" button:

    1) Failed to load resource: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

    2) XMLHttpRequest cannot load file:///C:/Users/Kane/Desktop/New%20folder/quiz/quiz.html. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.

    Any idea's? I made the change to 141 on quiz.js...
  • Commented on 02-17-2014 at 6:06 AM
    The answer is simple - don't run it from the file system. Just run it on a 'real' web server, like Apache, or IIS. Or try httpster.
  • Kane #
    Commented on 02-17-2014 at 8:25 AM
    Raymond - that worked, thanks!
  • Commented on 02-17-2014 at 8:28 AM
    As an FYI, I strongly recommend doing all HTML work on a real web server. A lot of stuff acts differently when opening via file:///.
  • Sachin Tehare #
    Commented on 03-13-2014 at 8:27 AM
    I am not able to get correct answers count on my result page.
    Can you please guide me.
  • Commented on 03-14-2014 at 10:37 AM
    Where is your copy of the app so I can test it?
  • saad #
    Commented on 03-15-2014 at 8:37 AM
    it works thank you, but I need two things :
    1- get a random question every time and increment randomly too ?
    2- display correct answer before quitting the current question ??
  • Commented on 03-15-2014 at 9:07 AM
    Well, that's certainly doable. :) I don't have that written but it is definitely something you could do.
  • saad #
    Commented on 03-15-2014 at 10:26 AM
    can you show me how to do it ? may be initiate i variable by Math.random() .!!
  • Commented on 03-15-2014 at 10:03 PM
    Sorry - no - it isn't something I'll have time to do. You can do it yourself - you already have an idea about using Math.random - just keep at it.
  • Commented on 05-13-2014 at 1:50 AM
    When i click on start quiz , the quiz section is not loading, i checked in browser also. I am doing for phone gap
  • Commented on 05-13-2014 at 8:45 AM
    Please open the console and tell me if you see an error.
  • Commented on 05-19-2014 at 8:43 PM
    Hi, I can't seem to get this working, When i follow the instructions and copy the code t all works perfectly but if I change any questions then nothing shows up at all, even just changing the first question from 'blue' to 'red' makes nothing show up, I'm probably missing something very easy. this is my first time using json any help would be very welcome. Thanks :)
  • Commented on 05-19-2014 at 8:51 PM
    Can you put it online where I can see?
  • Commented on 05-19-2014 at 9:06 PM
    Hi sorry it's the website I entered on the comments form :) I thought it would show up, here it is http://www.style85.com/museumkids the only thing changed from your code is one word in a question on the json file.

    Thanks so much.
  • Commented on 05-19-2014 at 10:03 PM
    I copied the URL of your JSON file to a JSON validator, and it said there was an error. Go here and paste in http://www.style85.com/museumkids/q1.json and validate. You will see.

    And.... I see it now. Go the URL (the JSON url) in a new tab and you will see a special character at the end of the first question value. You may have pasted something there by accident.
  • Commented on 05-19-2014 at 11:02 PM
    I found the error, thank you so much, I never even thought of using a validator, Thanks again you've saved my project.
  • Saurya #
    Commented on 05-20-2014 at 4:54 AM
    I used your code and developed an app with phonegap build but the app is not working after i clicked on quiz . I think this is because phonegap doesnot support json file.Am i right?
  • Commented on 05-20-2014 at 6:23 AM
    @Sasha: It looked pretty cool, would you mind posting here when it is done so I can see it?

    @Saurya: PhoneGap definitely supports JSON. Perhaps you made the same mistake as Sasha? Try using remote debug to see if you see an error.

    @Sasha and All: If I had added a proper error handler to $.get, this would have shown up. Sorry!
  • Saurya #
    Commented on 05-20-2014 at 6:36 AM
    I dont know how to use remote debugger. Can you please make me understand about the error which may be occuring in my code.
  • Saurya #
    Commented on 05-20-2014 at 6:49 AM
    when i am running the code on desktop browser its running fine but app is not working
  • Commented on 05-20-2014 at 6:55 AM
    @Saurya: Go to the About Me link on top and look at my list of articles. I've got two articles on remote debugging.
  • Saurya #
    Commented on 05-20-2014 at 11:20 AM
    I found my mistakes. Thank you so much
  • Commented on 05-20-2014 at 8:14 PM
    @Raymond Camden heres the link to the quiz.. hope u like it :)

    www.style85.com/museumkids
  • Commented on 05-20-2014 at 8:18 PM
    Very cool! FYI, typo here: They Asian Elephant and?
  • denny #
    Commented on 05-28-2014 at 9:15 AM
    thank u so much sir for this guide...
    but i want to add multiple subject wise quiz , let say, two questions of english subjet & two for maths....now if i select english from drop menu then it should show english's questions otherwise it shouls show as continously....
  • denny #
    Commented on 05-28-2014 at 9:17 AM
    My json file is -
    [
    {
       "introduction":"This quiz tests you about foo and goo",
    "subject":"english",
       "questions":[
          {"question":"Why is the sky blue?",
           "answers":["Unicorns","Fairies","Boring Science","Kittens"],
           "correct":2},
          {"question":"Why are kittens so cute?",
           "answers":["Magic","Fur","Meow","More Kittens!"],
           "correct":3}
       ]
    },
    {
       "introduction":"This quiz tests you about foo and goo",
    "subject":"maths",
       "questions":[
          {"question":"Why is the sky blue?",
           "answers":["Unicorns","Fairies","Boring Science","Kittens"],
           "correct":2},
          {"question":"Why are kittens so cute?",
           "answers":["Magic","Fur","Meow","More Kittens!"],
           "correct":3}
       ]
    }
    ]
  • Commented on 05-28-2014 at 9:19 AM
    My code doesn't support that. :) It is definitely doable, but you would need to modify the code.
  • denny #
    Commented on 05-28-2014 at 9:24 AM
    yes sir, it's working fine ...thank u sir
  • dhas #
    Commented on 07-30-2014 at 6:21 AM
    where i include the json file. its not working me. please include q1.json file in the html.
  • Commented on 07-30-2014 at 7:08 AM
    The JSON file is loaded by the JavaScript code, not HTML.
  • dhas #
    Commented on 07-30-2014 at 10:38 PM
    I extracted the file and place in server http://education.pelicanbrown.com/myquiz/. its working. the same setup i place it in apache server in the LAN environment. The quiz is not viewing. please help me is there any constraint is there.
  • Commented on 07-31-2014 at 6:11 AM
    When you open up your dev tools in your browser, do you see any issues?
  • saad #
    Commented on 08-03-2014 at 8:13 AM
    Why it doen't works when include remote json file in app.js :
    quizMaster.execute("http://localhost/Q.json";, ".quizdisplay", function(result) {
    do you have any idea why it doesn't works ??
  • Commented on 08-03-2014 at 9:58 AM
    That's browser security rules. If you want to call a JS file on another server, you have to either use JSON/P or CORS.
  • saad #
    Commented on 08-03-2014 at 1:57 PM
    When I use an external file like json or even a php file that echo a json output ... not working either ... do I need to change something in quiz.js ?? if I use this application in intellxdk or phonegap, will it works on mobile devices !??
  • Commented on 08-03-2014 at 2:34 PM
    Please read my last comment as I told you exactly why it would fail and 2 possible work arounds.

    In Cordova this won't be an issue as Cordova apps can get JSON data via XHR to any domain.
  • Rodrigo da Costa Barros Macedo #
    Commented on 08-08-2014 at 9:49 AM
    I really liked this example. I wonder if this has a similar loading questions from a php server instance? And another, how to generate build this application for android?
  • Commented on 08-08-2014 at 9:53 AM
    You could just generate the JSON data from PHP.

    As for building this for Android, you could do it with PhoneGap/Cordova.
  • Rodrigo Macedo #
    Commented on 08-08-2014 at 12:35 PM
    Have an example with php server or at least know where you have indicated?
    For understand the Sencha Touch framework but still could not make a quiz application I want to see if I can with JQuery Mobile
  • Commented on 08-08-2014 at 1:15 PM
    I don't use PHP so I can't help you with that. Did you google for PHP and JSON? I'm sure there is a simple way to do it.
  • Omer Rosenbaum #
    Commented on 08-09-2014 at 12:59 PM
    Is there an address where I can download the files?
    I have some errors and I want to compare to the original script.
  • Commented on 08-09-2014 at 1:11 PM
    Unfortunately no - but you can right click/download from the demo. And grab the JS file referenced. It's all there - just have to manually grab em.
  • Omer Rosenbaum #
    Commented on 08-09-2014 at 1:18 PM
    You mean the app.js and quiz.js?
  • Commented on 08-09-2014 at 1:36 PM
    Just use your Dev Tools. :)

    app.js - http://www.raymondcamden.com/demos/2013/dec/5/app....
    quiz.js - http://www.raymondcamden.com/demos/2013/dec/5/quiz...
  • Rodrigo Macedo #
    Commented on 08-26-2014 at 6:57 AM
    I modified the questions in q1.json but they did not alter the application which can be
  • Commented on 08-26-2014 at 7:24 AM
    Try clearing your browser cache.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty