├── .github └── FUNDING.yml ├── LICENSE ├── README.md └── src ├── Default.html ├── css ├── jsQuizEngine.css ├── jsQuizEngine.css.map ├── jsQuizEngine.less └── jsQuizEngine.min.css ├── js └── jsQuizEngine.js ├── jsQuizEngine.sln ├── jsQuizEngine.userprefs ├── jsQuizEngine.v12.suo └── quiz └── default.htm /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://paypal.me/simplovation 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chris Pietschmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsQuizEngine 2 | A simple HTML / Javascript based quiz engine 3 | 4 | Projects that use jsQuizEngine: 5 | 6 | - [Developing Azure Solutions (70-532) Practice Test](http://crpietschmann.github.io/Azure-70-532-Practice-Test/) 7 | 8 | - [Implementing Azure Solutions (70-533) Practice Test](http://crpietschmann.github.io/Azure-70-533-Practice-Test/) 9 | 10 | - [Architecting Azure Solutions (70-534) Practice Test](http://crpietschmann.github.io/Azure-70-534-Practice-Test/) 11 | 12 | 13 | The following link contains a brief description of why I created this project, including a screenshot of what it looks likes so far: 14 | 15 | [jsQuizEngine - lightweight javascript quiz engine](http://pietschsoft.com/post/2015/04/14/jsQuizEngine-lightweight-javascript-quiz-engine) 16 | 17 | This is a sister project to the [jsFlashEngine](http://github.com/crpietschmann/jsFlashEngine) project. 18 | -------------------------------------------------------------------------------- /src/Default.html: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | jsQuizEngine 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 44 |
45 | 46 |
47 |
48 |
49 |

50 |

51 |

52 |
53 |
54 |
55 |
Question of
56 |
57 |
58 |
59 |
60 | 61 | 62 | 63 | 64 | 65 |
66 |
67 |

Quiz Results:

68 |

69 |

70 |
Questions:
71 |
Date:
72 |
Overall Score: %
73 |
Correct Questions:
74 |
75 |
76 |
77 |
78 |

79 | PASS 80 | FAIL 81 |

82 |
83 |
84 |
85 |
86 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/css/jsQuizEngine.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5em; 3 | } 4 | footer { 5 | margin-top: 2em; 6 | } 7 | .question-pool { 8 | margin-bottom: 1em; 9 | } 10 | .question-pool .question .text { 11 | margin-bottom: 0.75em; 12 | } 13 | .question-pool .question label.answer { 14 | display: block; 15 | font-weight: normal; 16 | margin: 0; 17 | padding: 0.75em; 18 | border-bottom: dashed 0.1em transparent; 19 | border-top: dashed 0.1em transparent; 20 | } 21 | .question-pool .question label.answer > input { 22 | display: inline-block; 23 | float: left; 24 | margin-right: 0.5em; 25 | } 26 | .question-pool .question label.answer > div { 27 | display: inline-block; 28 | } 29 | .question-pool .question label.answer.nth-child(1) { 30 | border-top: dashed 0.1em transparent; 31 | } 32 | .question-pool .question .answer[data-correct].highlight { 33 | background-color: rgba(70, 197, 65, 0.35); 34 | border-color: #008000; 35 | } 36 | .question-pool .question .answer:hover { 37 | background-color: rgba(173, 216, 230, 0.1); 38 | } 39 | .question-pool .question .description { 40 | display: none; 41 | margin-top: 1em; 42 | } 43 | 44 | .hint { 45 | display: none; 46 | background-color: rgba(173, 216, 230, 0.1); 47 | padding: 0.75em; 48 | border-bottom: dashed 0.1em transparent; 49 | border-top: dashed 0.1em transparent; 50 | border-color: #008000; 51 | margin-top: 1em; 52 | } 53 | 54 | .score .pass-indicator h1 { 55 | font-size: 10em; 56 | } 57 | /*# sourceMappingURL=jsQuizEngine.css.map */ 58 | -------------------------------------------------------------------------------- /src/css/jsQuizEngine.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["jsQuizEngine.less"],"names":[],"mappings":"AAIA;EACI,gBAAA;;AAGJ;EACI,eAAA;;AAGJ;EACI,kBAAA;;AADJ,cAGI,UACI;EACI,qBAAA;;AALZ,cAGI,UAKI,MAAK;EACD,cAAA;EACA,mBAAA;EACA,SAAA;EACA,eAAA;EACA,uCAAA;EACA,oCAAA;;AAdZ,cAGI,UAKI,MAAK,OAOD;EACI,qBAAA;EACA,WAAA;EACA,mBAAA;;AAlBhB,cAGI,UAKI,MAAK,OAYD;EACI,qBAAA;;AArBhB,cAGI,UAqBI,MAAK,OAAO,UAAU;EAClB,oCAAA;;AAzBZ,cAGI,UAwBI,QAAO,cAAc;EACjB,uCAAA;EACA,qBAAA;;AA7BZ,cAGI,UA6BI,QAAO;EACH,0CAAA;;AAjCZ,cAGI,UAiCI;EACI,aAAA;;AAIZ,MACI,gBACI;EACI,eAAA","file":"jsQuizEngine.css"} -------------------------------------------------------------------------------- /src/css/jsQuizEngine.less: -------------------------------------------------------------------------------- 1 | // jsQuizEngine https://github.com/crpietschmann/jsQuizEngine 2 | // Copyright (c) 2015 Chris Pietschmann http://pietschsoft.com 3 | // Licensed under MIT License https://github.com/crpietschmann/jsQuizEngine/blob/master/LICENSE 4 | 5 | body { 6 | padding-top: 5em; 7 | } 8 | 9 | footer { 10 | margin-top: 2em; 11 | } 12 | 13 | .question-pool { 14 | margin-bottom: 1em; 15 | 16 | .question { 17 | .text { 18 | margin-bottom: 0.75em; 19 | } 20 | 21 | label.answer { 22 | display: block; 23 | font-weight: normal; 24 | margin: 0; 25 | padding: 0.75em; 26 | border-bottom: dashed 0.1em transparent; 27 | border-top: dashed 0.1em transparent; 28 | > input { 29 | display: inline-block; 30 | float: left; 31 | margin-right: 0.5em; 32 | } 33 | > div { 34 | display: inline-block; 35 | } 36 | } 37 | label.answer.nth-child(1) { 38 | border-top: dashed 0.1em transparent; 39 | } 40 | .answer[data-correct].highlight { 41 | background-color: rgba(70, 197, 65, 0.35); 42 | border-color: rgb(0, 128, 0); 43 | } 44 | 45 | .answer:hover { 46 | background-color: rgba(173, 216, 230, 0.1); 47 | } 48 | 49 | .description { 50 | display: none; 51 | } 52 | } 53 | } 54 | .score { 55 | .pass-indicator { 56 | h1 { 57 | font-size: 10em; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/css/jsQuizEngine.min.css: -------------------------------------------------------------------------------- 1 | body{padding-top:5em}footer{margin-top:2em}.question-pool{margin-bottom:1em}.question-pool .question .text{margin-bottom:.75em}.question-pool .question label.answer{display:block;font-weight:normal;margin:0;padding:.75em;border-bottom:dashed .1em transparent;border-top:dashed .1em transparent}.question-pool .question label.answer>input{display:inline-block;float:left;margin-right:.5em}.question-pool .question label.answer>div{display:inline-block}.question-pool .question label.answer.nth-child(1){border-top:dashed .1em transparent;}.question-pool .question .answer[data-correct].highlight{background-color:rgba(70,197,65,0.35);border-color:green}.question-pool .question .answer:hover{background-color:rgba(173,216,230,.1)}.question-pool .question .description{display:none}.score .pass-indicator h1{font-size:10em} -------------------------------------------------------------------------------- /src/js/jsQuizEngine.js: -------------------------------------------------------------------------------- 1 | // jsQuizEngine https://github.com/crpietschmann/jsQuizEngine 2 | // Copyright (c) 2017 Chris Pietschmann http://pietschsoft.com 3 | // Licensed under MIT License https://github.com/crpietschmann/jsQuizEngine/blob/master/LICENSE 4 | (function (window, $) { 5 | 6 | function getCurrentQuiz(container) { 7 | return container.find('.question-pool > .quiz'); 8 | } 9 | function getAllQuestions(container) { 10 | return container.find('.question-pool > .quiz .question'); 11 | } 12 | function getQuestionByIndex(container, index) { 13 | return container.find('.question-pool > .quiz .question:nth-child(' + index + ')'); 14 | } 15 | function getNowDateTimeStamp() { 16 | var dt = new Date(); 17 | return dt.getMonth() + '/' + dt.getDate() + '/' + dt.getFullYear() + ' ' + dt.getHours() + ':' + (dt.getMinutes() >= 10 ? dt.getMinutes() : '0' + dt.getMinutes()); 18 | } 19 | 20 | var ViewModel = function (elem, options) { 21 | var self = this; 22 | self.element = $(elem); 23 | self.options = $.extend({}, engine.defaultOptions, options); 24 | 25 | self.element.find('.question-pool').load(self.options.quizUrl, function () { 26 | // quiz loaded into browser from HTML file 27 | 28 | getCurrentQuiz(self.element).find('.question').each(function (i, e) { 29 | var question = $(this), 30 | questionIndex = i, 31 | answers = question.find('.answer'), 32 | correctAnswerCount = question.find('.answer[data-correct]').length; 33 | 34 | question.find('.hint a, .description a').attr('target','_blank'); 35 | 36 | answers.each(function (ai, ae) { 37 | var answer = $(this), 38 | newAnswer = $('').addClass('answer').append('').append($('
').html(answer.html())); 39 | if (answer.is('[data-correct]')) { 40 | newAnswer.attr('data-correct', '1'); 41 | } 42 | if (correctAnswerCount <= 1){ 43 | newAnswer.find('input').attr('type','radio').attr('name','question' + questionIndex); 44 | } 45 | answer.replaceWith(newAnswer); 46 | }); 47 | }); 48 | 49 | 50 | self.questionCount(getAllQuestions(self.element).length); 51 | self.quizTitle(getCurrentQuiz(self.element).attr('data-title')); 52 | self.quizSubTitle(getCurrentQuiz(self.element).attr('data-subtitle')); 53 | }); 54 | 55 | self.quizStarted = ko.observable(false); 56 | self.quizComplete = ko.observable(false); 57 | 58 | self.quizTitle = ko.observable(''); 59 | self.quizSubTitle = ko.observable(''); 60 | self.questionCount = ko.observable(0); 61 | 62 | self.currentQuestionIndex = ko.observable(0); 63 | self.currentQuestionIndex.subscribe(function (newValue) { 64 | if (newValue < 1) { 65 | self.currentQuestionIndex(1); 66 | } else if(newValue > self.questionCount()) { 67 | self.currentQuestionIndex(self.questionCount()); 68 | } else { 69 | getAllQuestions(self.element).hide() 70 | getQuestionByIndex(self.element, newValue).show(); 71 | } 72 | 73 | if (self.questionCount() !== 0) { 74 | self.currentProgress(self.currentQuestionIndex() / self.questionCount() * 100); 75 | } 76 | }); 77 | self.currentProgress = ko.observable(0); 78 | 79 | self.currentQuestionIsFirst = ko.computed(function () { 80 | return self.currentQuestionIndex() === 1; 81 | }); 82 | self.currentQuestionIsLast = ko.computed(function () { 83 | return self.currentQuestionIndex() === self.questionCount(); 84 | }); 85 | self.currentQuestionHasHint = ko.computed(function () { 86 | var q = getQuestionByIndex(self.element, self.currentQuestionIndex()); 87 | return (q.find('.hint').length > 0); 88 | }); 89 | 90 | self.startQuiz = function () { 91 | // reset quiz to start state 92 | self.currentQuestionIndex(0); 93 | self.currentQuestionIndex(1); 94 | 95 | self.quizStarted(true); 96 | } 97 | 98 | self.moveNextQuestion = function () { 99 | self.currentQuestionIndex(self.currentQuestionIndex() + 1); 100 | }; 101 | self.movePreviousQuestion = function () { 102 | self.currentQuestionIndex(self.currentQuestionIndex() - 1); 103 | }; 104 | self.showCurrentQuestionHint = function () { 105 | var q = getQuestionByIndex(self.element, self.currentQuestionIndex()); 106 | q.find('.hint').slideDown(); 107 | }; 108 | self.showCurrentQuestionAnswer = function () { 109 | var q = getQuestionByIndex(self.element, self.currentQuestionIndex()); 110 | q.find('.answer[data-correct]').addClass('highlight'); 111 | q.find('.description').slideDown(); 112 | }; 113 | 114 | 115 | 116 | self.calculateScore = function () { 117 | var correctQuestions = []; 118 | getAllQuestions(self.element).each(function (i, e) { 119 | var q = $(this); 120 | if (q.find('.answer').length === ( 121 | q.find('.answer[data-correct] > input:checked').length + q.find('.answer:not([data-correct]) > input:not(:checked)').length 122 | )) { 123 | correctQuestions.push(q); 124 | } 125 | }); 126 | self.totalQuestionsCorrect(correctQuestions.length); 127 | 128 | if (self.questionCount() !== 0) { 129 | self.calculatedScore( Math.round( (self.totalQuestionsCorrect() / self.questionCount() * 100) * 10 ) / 10 ); 130 | } 131 | 132 | self.calculatedScoreDate(getNowDateTimeStamp()); 133 | 134 | self.quizComplete(true); 135 | }; 136 | self.totalQuestionsCorrect = ko.observable(0); 137 | self.calculatedScore = ko.observable(0); 138 | self.calculatedScoreDate = ko.observable(''); 139 | self.quizPassed = ko.computed(function () { 140 | return self.calculatedScore() >= 50; 141 | }); 142 | }; 143 | 144 | 145 | var engine = window.jsQuizEngine = function (elem, options) { 146 | return new engine.fn.init(elem, options); 147 | }; 148 | engine.defaultOptions = { 149 | quizUrl: 'original.htm' 150 | }; 151 | engine.fn = engine.prototype = { 152 | version: 0.2, 153 | init: function (elem, options) { 154 | var vm = new ViewModel(elem[0], options); 155 | ko.applyBindings(vm, elem[0]); 156 | } 157 | }; 158 | engine.fn.init.prototype = engine.fn; 159 | 160 | 161 | })(window, jQuery); 162 | -------------------------------------------------------------------------------- /src/jsQuizEngine.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.31101.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "src", "http://localhost:16027", "{C3804993-C5A1-405A-80C0-7D98A9020E8D}" 7 | ProjectSection(WebsiteProperties) = preProject 8 | UseIISExpress = "true" 9 | TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.0" 10 | Debug.AspNetCompiler.VirtualPath = "/localhost_16027" 11 | Debug.AspNetCompiler.PhysicalPath = "..\src\" 12 | Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_16027\" 13 | Debug.AspNetCompiler.Updateable = "true" 14 | Debug.AspNetCompiler.ForceOverwrite = "true" 15 | Debug.AspNetCompiler.FixedNames = "false" 16 | Debug.AspNetCompiler.Debug = "True" 17 | Release.AspNetCompiler.VirtualPath = "/localhost_16027" 18 | Release.AspNetCompiler.PhysicalPath = "..\src\" 19 | Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_16027\" 20 | Release.AspNetCompiler.Updateable = "true" 21 | Release.AspNetCompiler.ForceOverwrite = "true" 22 | Release.AspNetCompiler.FixedNames = "false" 23 | Release.AspNetCompiler.Debug = "False" 24 | SlnRelativePath = "..\src\" 25 | EndProjectSection 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{51DF6276-F6A6-4A51-9688-C0E3C1C8CFA6}" 28 | ProjectSection(SolutionItems) = preProject 29 | ..\LICENSE = ..\LICENSE 30 | ..\README.md = ..\README.md 31 | EndProjectSection 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {C3804993-C5A1-405A-80C0-7D98A9020E8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {C3804993-C5A1-405A-80C0-7D98A9020E8D}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /src/jsQuizEngine.userprefs: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/jsQuizEngine.v12.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crpietschmann/jsQuizEngine/5ab7f0c538d75464e1cb514c5f18c6c27d010db0/src/jsQuizEngine.v12.suo -------------------------------------------------------------------------------- /src/quiz/default.htm: -------------------------------------------------------------------------------- 1 | 
2 |
3 |
4 | Which are the number One Hundred? 5 |
6 |
100
7 |
1,000
8 |
100
Longer answer
with line breaks.
9 |
10 | There is more than one correct answer. 11 |
12 |
13 | Both '100' and '100' are correct answers. 14 |
15 |
16 |
17 |
18 | Which is the letter A? 19 |
20 |
B
21 |
A
22 |
C
23 |
24 | This is an answer description with a hyperlink. 25 |
26 |
27 |
28 |
29 | Which is the number One Hundred? 30 |
31 |
1,000,000
32 |
1,000
33 |
100
34 |
35 | This is a hint with a hyperlink. 36 |
37 |
38 |
39 | --------------------------------------------------------------------------------