Skip to content

Commit

Permalink
Merge pull request #2 from jverzani/clean_up
Browse files Browse the repository at this point in the history
clean up; close issue #1
  • Loading branch information
jverzani authored Feb 3, 2022
2 parents d88f022 + 7c81489 commit 27e48d5
Show file tree
Hide file tree
Showing 8 changed files with 139 additions and 57 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "QuizQuestions"
uuid = "612c44de-1021-4a21-84fb-7261cf5eb2d4"
authors = ["jverzani <jverzani@gmail.com> and contributors"]
version = "0.1.0"
version = "0.2.0"

[deps]
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# QuizQuestions

[![](https://img.shields.io/badge/docs-stable-blue.svg)](https://juliahub.com/docs/QuizQuestions/)

A simple means to make basic web pages using Markdown with self-grading quiz questions. Question types are for numeric response, text response (graded with a regular expression), or a selection of one from many. Can be used with Weave, Documenter, or Pluto.


The package creates `show` methods for mime type `text/html` for a few objects that produce HTML showing an input widget with attached javascript code to grade the input once the widget loses focus.
2 changes: 2 additions & 0 deletions docs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build/
site/
6 changes: 6 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using Documenter
using QuizQuestions

makedocs(sitename="QuizQuestions documentation",
format = Documenter.HTML(ansicolor=true)
)
61 changes: 61 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# QuizQuestions

[QuizQuestions](https://github.com/jverzani/QuizQuestions.jl) allows the inclusion of self-grading quiz questions within a `Documenter`, `Weave`, or `Pluto` HTML page.


The basic idea is:

* load the package:

```@example quiz_question
using QuizQuestions
```

* create a question:

```@example quiz_question
choices = ["one", "``2``", raw"``\sqrt{9}``"]
question = "Which is largest?"
answer = 3
radioq(choices, answer; label=question, hint="A hint")
```

* repeat as desired.

----

The `show` method of the object for the `text/html` mime type produced by `radioq` inserts the necessary HTML and JavaScript code to show the input widget and grading logic.


The above question uses radio buttons for allowing one choice from many.

The `hint` argument allows an optional text-only hint available to the user on hover. The `label` is used to flag the question. This is also optional. For example, the question can be asked in the body of the document (the position of any hint will be different):

What is ``\sqrt{2}``?

```@example quiz_question
answer = sqrt(2)
tol = 1e-3
numericq(answer, tol, hint="you need to be within 1/1000")
```


The quiz questions are written in markdown, as would be the rest of the Documenter or Weave document containing the questions. The above code cells would be enclosed in triple-backtick blocks and would have their contents hidden from the user. How this is done varies from `Documenter`, `Weave`, and `Pluto`. The `examples` directory shows examples of each. Here is an example of a numeric question:

```@example quiz_question
answer = 1 + 1
numericq(answer; label="``1 + 1``?", hint="Do the math")
```


----

Currently only a few question types are available:

```@docs
numericq
stringq
radioq
yesnoq
booleanq
```
70 changes: 33 additions & 37 deletions src/html_templates.jl
Original file line number Diff line number Diff line change
@@ -1,60 +1,60 @@
## Could tidy up this HTML to make it look nicer
html_templates = Dict()

const grading_partial = """
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>";
}
"""

## input form questions
## Basic question
## has label and hint option.
## Hint is put with label when present; otherwise, it appears at bottom of form.
## this is overridden with input widget in how show method is called
html_templates["question_tpl"] = mt"""
<form class="mx-2 my-3" name='WeaveQuestion' data-id='{{:ID}}' data-controltype='{{:TYPE}}'>
<div class='form-group {{:STATUS}}'>
<div class='controls'>
<div class="form-floating input-group" id="controls_{{:ID}}">
{{#:LABEL}}<label for="controls_{{:ID}}">{{{:LABEL}}}</label>{{/:LABEL}}
<div class="form-floating input-group" id="controls_{{:ID}}">
{{#:LABEL}}
<label for="controls_{{:ID}}">{{{:LABEL}}}{{#:HINT}}<span href="#" title='{{{:HINT}}}'>&nbsp;🎁</span>{{/:HINT}}
</label>
{{/:LABEL}}
<div style="padding-top: 5px">
{{{:FORM}}}
{{#:HINT}}
<span href="#" title='{{{:HINT}}}'>&nbsp;🎁</span>
<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>
<div id='{{:ID}}_message'></div>
{{^:LABEL}}{{#:HINT}}<label for="controls_{{:ID}}"><span href="#" title='{{{:HINT}}}'>&nbsp;🎁</span></label>{{/:HINT}}{{/:LABEL}}
</div>
</div>
<div id='{{:ID}}_message' style="padding-bottom: 15px">
</div>
</div>
</div>
</form>
<script text='text/javascript'>
{{{:GRADING_SCRIPT}}
</script>
"""

html_templates["input_grading_script"] = mt"""
html_templates["input_grading_script"] = jmt"""
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>";
}
$(grading_partial)
});
"""

##
html_templates["Numericq_form"] = mt"""
{{#:LABEL}}<label for="{{:ID}}">{{{:LABEL}}}</label>{{/:LABEL}}
<input id="{{:ID}}" type="number" class="form-control u-full-width" placeholder="{{:PLACEHOLDER}}">
{{#:UNITS}}<span class="input-group-addon mx-2">{{{:UNITS}}}</span>{{/:UNITS}}
"""

## Regular expression
html_templates["Stringq_form"] = mt"""
{{#:LABEL}}<label for="{{:ID}}">{{{:LABEL}}}</label>{{/:LABEL}}
<input id="{{:ID}}" type="text" class="form-control u-full-width" placeholder="{{:PLACEHOLDER}}">
html_templates["inputq_form"] = mt"""
<div class="row">
<span style="width:90%">
<input id="{{:ID}}" type="{{:TYPE}}" class="form-control" placeholder="{{:PLACEHOLDER}}">
</span>
<span style="width:10%">{{#:UNITS}}{{{:UNITS}}}{{/:UNITS}}{{#:HINT}}<span href="#" title='{{{:HINT}}}'>&nbsp;🎁</span>{{/:HINT}}
</span>
</div>
"""

## Multiple choice (one of many)
Expand All @@ -79,10 +79,6 @@ 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>";
}
$(grading_partial)
})});
"""
25 changes: 15 additions & 10 deletions src/question_types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ Match string answer with regular expression
Arguments:
* `re`: A regular expression
* `re`: a regular expression for grading
* `label`: optional label for the form element
* `hint`: optional plain text hint that can be seen on hover
* `hint`: optional plain-text hint that can be seen on hover
"""
stringq(re::Regex; label="", hint="") = Stringq(re, label, hint)
Expand All @@ -40,7 +40,7 @@ Arguments:
* `value`: the numeric answer
* `tol`: ``|answer - value| \\le tol`` is used to determine correctness
* `label`: optional label for the form element
* `hint`: optional plain text hint that can be seen on hover
* `hint`: optional plain-text hint that can be seen on hover
* `units`: a string indicating expected units.
"""
Expand Down Expand Up @@ -73,22 +73,22 @@ Multiple choice question (one of several)
Arguments:
* `choices`: indeable collection of choices.
* `choices`: indexable collection of choices. As seen in the example, choices can be formatted with markdown.
* `answer::Int`: index of correct choice
* `keep_order::Boolean`
* `keep_order::Boolean`: if `true` keeps display order of choices, otherwise they are shuffled.
* `inline::Bool`: hint to render inline (or not) if supported
* `label`: optional label for the form element
* `hint`: optional plain text hint that can be seen on hover
* `hint`: optional plain-text hint that can be seen on hover
Example:
```
choices = ["beta", raw"``\beta``", "`beta`"]
choices = ["beta", raw"``\\beta``", "`beta`"]
answer = 2
radioq(choices, answer; hint="Which is the Greek symbol?")
```
Expand All @@ -108,13 +108,13 @@ end


"""
booleanq(ans, [reminder])
booleanq(ans; [label, hint])
True of false questions:
Example:
```
booleanq(true, reminder="Does it hurt...")
booleanq(true; label="Does it hurt...")
```
"""
Expand All @@ -133,7 +133,12 @@ end
Boolean question with `yes` or `no` labels.
Examples: `yesnoq("yes")` or `yesnoq(true)`
Examples:
```
yesnoq("yes")
yesnoq(true)
```
"""
yesnoq(ans::AbstractString, args...; kwargs...) = radioq(["Yes", "No"], ans == "yes" ? 1 : 2, args...; keep_order=true, kwargs...)
Expand Down
25 changes: 16 additions & 9 deletions src/show_methods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ function _markdown_to_html(x)
length(x) == 0 && return("")
x = Markdown.parse(x)
x = sprint(io -> Markdown.html(io, x))
x = replace(x, r"^<p>"=>"", r"</p>$"=>"")
return x
end

function Base.show(io::IO, m::MIME"text/html", x::Numericq)

ID = randstring()

FORM = Mustache.render(html_templates["Numericq_form"];
FORM = Mustache.render(html_templates["inputq_form"];
ID=ID,
PLACEHOLDER = "Numeric answer",
UNITS=x.units)
UNITS=x.units,
TYPE="number",
HINT = length(x.label) == 0 ? x.hint : ""
)

GRADING_SCRIPT =
Mustache.render(html_templates["input_grading_script"];
Expand All @@ -27,9 +31,9 @@ function Base.show(io::IO, m::MIME"text/html", x::Numericq)
TYPE = "text",
STATUS = "",
LABEL=_markdown_to_html(x.label),
HINT = length(x.label) > 0 ? x.hint : "",
FORM = FORM,
GRADING_SCRIPT = GRADING_SCRIPT,
HINT = x.hint,
GRADING_SCRIPT = GRADING_SCRIPT
)

end
Expand All @@ -39,9 +43,12 @@ function Base.show(io::IO, m::MIME"text/html", x::Stringq)

ID = randstring()

FORM = Mustache.render(html_templates["Stringq_form"];
FORM = Mustache.render(html_templates["inputq_form"];
ID=ID,
PLACEHOLDER = "Text answer")
PLACEHOLDER = "Text answer",
TYPE="text",
HINT = length(x.label) == 0 ? x.hint : ""
)

GRADING_SCRIPT =
Mustache.render(html_templates["input_grading_script"];
Expand All @@ -54,9 +61,9 @@ function Base.show(io::IO, m::MIME"text/html", x::Stringq)
TYPE = "text",
STATUS = "",
LABEL=_markdown_to_html(x.label),
HINT = length(x.label) > 0 ? x.hint : "",
FORM = FORM,
GRADING_SCRIPT = GRADING_SCRIPT,
HINT = x.hint
GRADING_SCRIPT = GRADING_SCRIPT
)

end
Expand Down Expand Up @@ -97,7 +104,7 @@ function Base.show(io::IO, m::MIME"text/html", x::Radioq)
FORM = FORM,
GRADING_SCRIPT = GRADING_SCRIPT,
LABEL=_markdown_to_html(x.label),
HINT = x.hint
HINT = x.hint # use HINT in question
)

end

0 comments on commit 27e48d5

Please sign in to comment.