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.
Archived Comments
Perfect timing for this post because I may be creating a Quiz app for a city using Phonegap!! This will definitely help out! Thanks!
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/jsteenka...
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.
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
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.
Zip is located here: http://www.raymondcamden.co....
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.
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.)
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!!!!
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.
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
@Tim: Check the console - do you see an error?
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 =/
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?
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" );
Darn good point there Captain Obvious. Thanks. (Hopefully the Captain Obvious wasn't a dig at me for missing something obvious. ;)
No no - that is just my gmail handle :)
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.
The code is just HTML/JS/CSS. It should run just fine in PhoneGap.
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
Albert - you will get that error if "data" is a string rather than a json object, note my comment above.
Captain Obvious - thanks for the reminding again :) the changes in 141 line of quiz.js solved the problem :)
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...
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.
Raymond - that worked, thanks!
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:///.
I am not able to get correct answers count on my result page.
Can you please guide me.
Where is your copy of the app so I can test it?
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 ??
Well, that's certainly doable. :) I don't have that written but it is definitely something you could do.
can you show me how to do it ? may be initiate i variable by Math.random() .!!
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.
When i click on start quiz , the quiz section is not loading, i checked in browser also. I am doing for phone gap
Please open the console and tell me if you see an error.
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 :)
Can you put it online where I can see?
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/muse... the only thing changed from your code is one word in a question on the json file.
Thanks so much.
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/muse... 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.
I found the error, thank you so much, I never even thought of using a validator, Thanks again you've saved my project.
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?
@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!
I dont know how to use remote debugger. Can you please make me understand about the error which may be occuring in my code.
when i am running the code on desktop browser its running fine but app is not working
@Saurya: Go to the About Me link on top and look at my list of articles. I've got two articles on remote debugging.
I found my mistakes. Thank you so much
@Raymond Camden heres the link to the quiz.. hope u like it :)
www.style85.com/museumkids
Very cool! FYI, typo here: They Asian Elephant and?
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....
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}
]
}
]
My code doesn't support that. :) It is definitely doable, but you would need to modify the code.
yes sir, it's working fine ...thank u sir
where i include the json file. its not working me. please include q1.json file in the html.
The JSON file is loaded by the JavaScript code, not HTML.
I extracted the file and place in server http://education.pelicanbro.... 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.
When you open up your dev tools in your browser, do you see any issues?
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 ??
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.
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 !??
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.
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?
You could just generate the JSON data from PHP.
As for building this for Android, you could do it with PhoneGap/Cordova.
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
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.
Is there an address where I can download the files?
I have some errors and I want to compare to the original script.
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.
You mean the app.js and quiz.js?
Just use your Dev Tools. :)
app.js - http://www.raymondcamden.co...
quiz.js - http://www.raymondcamden.co...
I modified the questions in q1.json but they did not alter the application which can be
Try clearing your browser cache.
Good day I link back to this application as another I'm doing all the files were loaded only q1.json file was not loaded and not changed anything in the file app.js. What can be done? Is to make the direct manipulation of the questions in the app.js file? If so, how should it be done? Ja the app.js file loads and the q1.json not charging. Thanks in advance
I'm sorry, but I cannot understand your English. Can you try again?
Well what I meant and that direct testing this application it works because it loads the file q1.json. However I created an application and a redirect link to this quiz app. The files are all loaded in the q1.json exception. So quiz.html of the page is blank. What can it be? Is to store the contents of the file within q1.json app.js?
I'm afraid I still can't understand your English.
What is a "redirect link to this quiz app" - are you saying your app literally sends people back to my code?
"The files are all loaded in the q1.json exception."
This makes no sense at all.
It sounds like you *may* be trying to load q1.json from a different domain than the one hosting your app. If so, you can't, unless you make use of CORS or JSON/P.
I downloaded the quiz, but when i tried it (before changing anything) with Chrome it didn't work. I open index.html, clicked start quiz, and than i got a message that says: Error Loading Page.
How can i fix this, because i need it for a school assignment (very soon).
Are you running it on a local web server, or from the file system?
Hi
This is really interesting.
I get a zero kb 'download' file (without extension). Is there any other links to the source for the demo, please?
Thank you.
Sorry, add index.html to the URL. Unfortunately some of the scripts won't load because they use http. Not sure when I'll fix that, but at least for right now you can view source and download.
And it should be working online now. I'll fix the link here too, but that won't online until I blog again.
Thanks - now working with the index.html suffix.
URL changes to a suffix of quiz.html once it's run. and index.html needs to be reloaded.
Probably me - on a steep learning curve!