Skip to content

Instantly share code, notes, and snippets.

@jverzani
Last active February 1, 2022 13:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jverzani/ab0762ab0c69c1928692e7698e2f18b8 to your computer and use it in GitHub Desktop.
Save jverzani/ab0762ab0c69c1928692e7698e2f18b8 to your computer and use it in GitHub Desktop.
# Multiple Choice
```julia; echo=false
using Mustache, Markdown, Random
abstract type Question end
mutable struct Numericq <: Question
val
tol
reminder
answer_text
m
M
units
hint
end
mutable struct Radioq <: Question
choices
answer
reminder
answer_text
values
labels
hint
inline
end
function numericq(val, tol=1e-3, reminder="", args...;
hint="", units::AbstractString="")
answer_text= "[$(round(val-tol,digits=5)), $(round(val+tol,digits=5))]"
Numericq(val, tol, reminder, answer_text, val-tol, val+tol, units, hint)
end
numericq(val::Int; kwargs...) = numericq(val, 0; kwargs...)
# for choice questions
function radioq(choices, answer, reminder="", answer_text=nothing;
hint="", inline::Bool=(hint!=""),
keep_order::Bool=false)
inds = collect(1:length(choices))
values = copy(inds)
labels = choices
!keep_order && shuffle!(inds)
Radioq(choices[inds], findfirst(isequal(answer), inds), reminder,
answer_text, values, labels[inds], hint, inline)
end
function booleanq(ans::Bool, reminder="", answer_text=nothing;labels::Vector=["true", "false"], hint::AbstractString="", inline::Bool=true)
choices = labels[1:2]
ans = 2 - ans
radioq(choices, ans, reminder, answer_text; hint=hint,
inline=inline, keep_order=true)
end
html_templates = Dict()
html_templates["Numericq"] = mt"""
<form class="mx-2 my-3" name='WeaveQuestion' data-id='{{ID}}' data-controltype='{{TYPE}}'>
<div class='form-group {{status}}'>
<div class='controls'>
{{{form}}}
{{#hint}}
<span class='help-inline'><i id='{{ID}}_hint' class='icon-gift'></i></span>
<script>$('#{{ID}}_hint').tooltip({title:'{{{hint}}}', html:true, placement:'right'});</script>
{{/hint}}
<div class="form-floating input-group">
<input id="{{ID}}" type="number" class="form-control" placeholder="Numeric answer">
<label for="{{ID}}">Numeric answer</label>
{{#units}}<span class="input-group-addon mx-2">{{{units}}}</span>{{/units}}
</div>
<div id='{{ID}}_message'></div>
</div>
</div>
</form>
<script text='text/javascript'>
document.getElementById("{{ID}}").addEventListener("change", function() {
var correct = {{{correct}}};
var msgBox = document.getElementById('{{ID}}_message');
if(correct) {
msgBox.innerHTML = "<div class='pluto-output admonition note alert alert-success'><span class='glyphicon glyphicon-thumbs-up'>👍&nbsp;Correct</span></div>";
} else {
msgBox.innerHTML = "<div class='pluto-output admonition alert alert-danger'><span class='glyphicon glyphicon-thumbs-down'>👎&nbsp; Incorrect</span></div>";
}
});
</script>
"""
html_templates["Radioq"] = mt"""
{{#items}}
<div class="form-check">
<input class="form-check-input" type="radio" name="radio_{{ID}}" id="radio_{{ID}}_{{value}}" value="{{value}}">
{{{label}}}
</div>
{{/items}}
"""
html_templates["radio_script_tpl"] = """
document.querySelectorAll('input[name="radio_{{ID}}"]').forEach(function(rb) {
rb.addEventListener("change", function() {
var correct = rb.value == {{correct_answer}};
var msgBox = document.getElementById('{{ID}}_message');
if (correct) {
msgBox.innerHTML = "<div class='pluto-output admonition note alert alert-success'><span class='glyphicon glyphicon-thumbs-up'>👍&nbsp;Correct</span></div>";
} else {
msgBox.innerHTML = "<div class='pluto-output admonition alert alert-danger'><span class='glyphicon glyphicon-thumbs-down'>👎&nbsp; Incorrect</span></div>";
}
})});
"""
html_templates["question_tpl"] = mt"""
<form class="mx-2 my-3" name="WeaveQuestion" data-id="{{ID}}" data-controltype="{{TYPE}}">
<div class="form-group {{status}}">
{{{form}}}
{{#hint}}
<i class="bi bi-gift-fill"></i>
<!--<script>$("#{{ID}}_hint").tooltip({title:"{{{hint}}}", html:true, placement:"right"});</script>-->
{{/hint}}
<div id="{{ID}}_message"></div>
</div>
</form>
<script text="text/javascript">
{{{script}}}
</script>
"""
function Base.show(io::IO, m::MIME"text/html", x::Numericq)
id = randstring()
Mustache.render(io, html_templates["Numericq"],
Dict("ID" => id,
"TYPE" => "numeric",
"selector" => "#" * id,
"status" => "",
"hint" => x.hint,
"units" => x.units,
"correct" => "Math.abs(this.value - $(x.val)) <= $(x.tol)"
))
end
function _markdown_to_html(x)
length(x) == 0 && return("")
x = Markdown.parse(x)
x = sprint(io -> Markdown.html(io, x))
return x
end
function _make_item(i, choice)
choice′ = sprint(io -> Markdown.html(io, Markdown.parse(choice)))
# strip <p> tag
choice′ = chomp(choice′)
choice′ = replace(choice′, r"^<p>" => "")
choice′ = replace(choice′, r"</p>$" => "")
item = Dict("no"=>i,
"label"=> choice′,
"value"=>i
)
end
function Base.show(io::IO, m::MIME"text/html", x::Radioq)
ID = randstring()
tpl = html_templates["Radioq"]
choices = string.(x.choices)
items = Dict[]
## make items
items = [_make_item(i, choice) for (i,choice) ∈ enumerate(choices)]
script = Mustache.render(html_templates["radio_script_tpl"],
Dict("ID"=>ID,
"correct_answer" => x.answer,
"selector" => "input:radio[name='radio_$ID']",
"correct" => "this.value == $(x.answer)"))
form = Mustache.render(tpl, Dict("ID"=>ID, "items"=>items,
"inline" => x.inline ? " inline" : ""
))
Mustache.render(io, html_templates["question_tpl"],
Dict("form" => form,
"script" => script,
"TYPE" => "radio",
"ID" => ID,
"hint"=> _markdown_to_html(x.hint)))
end
```
# Number
What is 2 + 2
```julia; echo=false
answer = 2 + 2
numericq(answer)
```
What is ``\sqrt{2}``?
```julia; echo=false
answer = sqrt(2)
numericq(answer, 1e-2) # tolerance
```
## select on
What is "one"?
```julia; echo=false
choices = ["``1``", "``2^2``", raw"``\sqrt{3}``"]
answer = 1
radioq(choices, answer, keep_order=true)
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment