├── .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 |
27 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
Start Quiz
52 |
53 |
54 |
55 | Question of
56 |
59 |
60 | Previous
61 | Show Hint
62 | Show Answer
63 | Next
64 | Calculate Score
65 |
66 |
67 | Quiz Results:
68 |
69 |
70 | Questions:
71 | Date:
72 | Overall Score: %
73 | Correct Questions:
74 |
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 |
7 |
8 | 100 Longer answerwith 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 |
21 |
22 |
23 |
24 | This is an answer description with a hyperlink .
25 |
26 |
27 |
28 |
29 | Which is the number One Hundred
?
30 |
31 |
32 |
33 |
34 |
37 |
38 |
39 |
--------------------------------------------------------------------------------