diff --git a/Project.toml b/Project.toml index feb4b9e..e3b5085 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "QuizQuestions" uuid = "612c44de-1021-4a21-84fb-7261cf5eb2d4" authors = ["jverzani and contributors"] -version = "0.2.1" +version = "0.3.0" [deps] Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" diff --git a/docs/src/index.md b/docs/src/index.md index ec9657c..764d0a6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -75,7 +75,24 @@ Who was the first president? stringq(r"^Washington", label="last name") ``` +* Here is an example of matching +For each question, select the correct answer. + +```@example quiz_question +questions = ("Select a Volvo", "Select a Mercedes", "Select an Audi") +choices = ("XC90", "A4", "GLE 350", "X1") # may be more than questions +answer = (1,3,2) # indices of correct +matchq(questions, choices, answer) +``` + +The above shows that the number of choices need not match the number of questions. When they do, a dictionary can be used to specify the choices and the answers will be computed: + +```@example quiz_question +d = Dict("Select a Volvo" => "XC90", "Select a Mercedes" => "GLE 350", + "Select an Audi" => "A4") +matchq(d) +``` ---- @@ -88,4 +105,5 @@ radioq yesnoq booleanq multiq +matchq ``` diff --git a/src/QuizQuestions.jl b/src/QuizQuestions.jl index 4c21483..7ff4575 100644 --- a/src/QuizQuestions.jl +++ b/src/QuizQuestions.jl @@ -8,6 +8,6 @@ include("question_types.jl") include("html_templates.jl") include("show_methods.jl") -export numericq, radioq, multiq, booleanq, yesnoq, stringq +export numericq, radioq, multiq, booleanq, yesnoq, stringq, matchq end diff --git a/src/html_templates.jl b/src/html_templates.jl index 9e42f5b..340cef9 100644 --- a/src/html_templates.jl +++ b/src/html_templates.jl @@ -116,3 +116,43 @@ rb.addEventListener("change", function() { $(grading_partial) })}); """ + +## ---- + +html_templates["Matchq"] = mt""" + +{{#:ITEMS}} + + + + +{{/:ITEMS}} +
+ + + +
+""" + +html_templates["matchq_grading_script"] = """ + function callback(element, iterator) { + element.addEventListener("change", function() { + var a = []; + var selectors = document.querySelectorAll('[id ^= "select_{{:ID}}"]'); + Array.prototype.forEach.call(selectors, function (element, iterator) { + a.push(element.value); + }) + b = {{{:CORRECT_ANSWER}}}; + // https://stackoverflow.com/questions/7837456/how-to-compare-arrays-in-javascript + correct = (a.length === b.length && a.find((v,i) => v !== b[i]) === undefined) + var msgBox = document.getElementById('{{:ID}}_message'); + $(grading_partial) + }) + } + Array.prototype.forEach.call(document.querySelectorAll('[id ^= "select_{{:ID}}"]'), callback); +""" diff --git a/src/question_types.jl b/src/question_types.jl index e9e18f3..35c0330 100644 --- a/src/question_types.jl +++ b/src/question_types.jl @@ -160,6 +160,68 @@ function multiq(choices, answers; values, labels[inds], label, hint, inline) end +## +mutable struct Matchq <: Question + questions + choices + answer + label + hint +end + +""" + matchq(questions, choices, answers; label="", hint="") + matchq(d::Dictionary; label="", hint="") + +Use a drop down to select the right match for each question. + +Arguments: + +* `questions`: Indexable collection of questions. + +* `choices`: indexable collection of choices for each question. As seen in the example, choices can be formatted with markdown. + +* `answers`: collection of correct indices for each question. + +* `d`: As an alternative, a dictionary of questions and answers can be specified. The choices will be taken from the values then randomized, the answers will be computed. + +* `label`: optional label for the form element + +* `hint`: optional plain-text hint that can be seen on hover + +## Examples: + +``` +questions = ("Select a Volvo", "Select a Mercedes", "Select an Audi") +choices = ("XC90", "A4", "GLE 350", "X1") # may be more than questions +answer = (1,3,2) # indices of correct +matchq(questions, choices, answer) + +d = Dict("Select a Volvo" => "XC90", "Select a Mercedes" => "GLE 250") +matchq(d) +``` + +""" +function matchq(questions, choices, answers; + label="", hint="") + + @assert length(questions) == length(answers) + + Matchq(questions, choices, answers, + label, hint) +end + +function matchq(d::AbstractDict; kwargs...) + + questions = collect(keys(d)) + choices = collect(values(d)) + n = length(questions) + inds = shuffle(1:n) + + matchq(questions, choices[inds], sortperm(inds); kwargs...) +end + + """ booleanq(ans; [label, hint]) diff --git a/src/show_methods.jl b/src/show_methods.jl index ceb9726..95d67e0 100644 --- a/src/show_methods.jl +++ b/src/show_methods.jl @@ -138,3 +138,33 @@ function Base.show(io::IO, m::MIME"text/html", x::Multiq) ) end + + +function Base.show(io::IO, m::MIME"text/html", x::Matchq) + + ID = randstring() + BLANK = "Choose..." + ANSWER_CHOICES = [(INDEX=i, LABEL=_markdown_to_html(label)) for (i, label) in enumerate(x.choices)] + items = [(ID=ID, NO=i, BLANK=BLANK, QUESTION=_markdown_to_html(question), ANSWER_CHOICES=ANSWER_CHOICES) for (i,question) ∈ enumerate(x.questions)] + GRADING_SCRIPT = Mustache.render(html_templates["matchq_grading_script"]; + ID = ID, + CORRECT_ANSWER = collect(string.(x.answer)), + INCORRECT = "Not yet", + CORRECT = "Correct" + ) + FORM = Mustache.render(html_templates["Matchq"]; + ID = ID, + ITEMS = items, + + ) + + Mustache.render(io, html_templates["question_tpl"], + ID = ID, + TYPE = "radio", + FORM = FORM, + GRADING_SCRIPT = GRADING_SCRIPT, + LABEL=_markdown_to_html(x.label), + HINT = x.hint # use HINT in question + ) + +end diff --git a/test/runtests.jl b/test/runtests.jl index 035b852..ba927b7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -12,4 +12,11 @@ using Test @test radioq(1:3, 1, keep_order=true).answer == 1 @test multiq(("one","two","three"), (2, 3), label="Primes?").label == "Primes?" + + d = Dict("Select a Volvo" => "XC90", "Select a Mercedes" => "GLE 350", + "Select an Audi" => "A4") + r = matchq(d) + i = rand(1:3) + @test d[r.questions[i]] == r.choices[r.answer[i]] + end