Set-up
There are two important set-up steps for pywebexercises
:
- Installing the
.js
and.css
files - Including the files in the front matter of our
.qmd file
.
Installing the .js
and .css
files
The pywebexercises
package contains functions which will generate html
. This html is processed by two scripts:
webex.js
webex.css
Which convert the html into interactive questions for users to answer.
A function is provided to install these files into your current directory into an assets/
folder.
# pylint: disable=missing-module-docstring
from pywebexercises.setup import setup_webex_assets
setup_webex_assets()
Alternatively…
These files can also be copied from below:
webex.js
<script>
/*
Acknowledgments. This file is from webexercises:
Barr D, DeBruine L (2023). webexercises: Create Interactive Web Exercises in
'R Markdown' (Formerly 'webex'). R package version 1.1.0.9000,
https://github.com/psyteachr/webexercises.
*/
/* update total correct if #webex-total_correct exists */
= function() {
update_total_correct console.log("webex: update total_correct");
var t = document.getElementsByClassName("webex-total_correct");
for (var i = 0; i < t.length; i++) {
= t[i].parentElement;
p var correct = p.getElementsByClassName("webex-correct").length;
var solvemes = p.getElementsByClassName("webex-solveme").length;
var radiogroups = p.getElementsByClassName("webex-radiogroup").length;
var selects = p.getElementsByClassName("webex-select").length;
.innerHTML = correct + " of " + (solvemes + radiogroups + selects) + " correct";
t[i]
}
}
/* webex-solution button toggling function */
= function() {
b_func console.log("webex: toggle hide");
var cl = this.parentElement.classList;
if (cl.contains('open')) {
.remove("open");
clelse {
} .add("open");
cl
}
}
/* check answers */
= function() {
check_func console.log("webex: check answers");
var cl = this.parentElement.classList;
if (cl.contains('unchecked')) {
.remove("unchecked");
clthis.innerHTML = "Hide Answers";
else {
} .add("unchecked");
clthis.innerHTML = "Show Answers";
}
}
/* function for checking solveme answers */
= function(e) {
solveme_func console.log("webex: check solveme");
var real_answers = JSON.parse(this.dataset.answer);
var my_answer = this.value;
var cl = this.classList;
if (cl.contains("ignorecase")) {
= my_answer.toLowerCase();
my_answer
}if (cl.contains("nospaces")) {
= my_answer.replace(/ /g, "")
my_answer
}
if (my_answer == "") {
.remove("webex-correct");
cl.remove("webex-incorrect");
clelse if (real_answers.includes(my_answer)) {
} .add("webex-correct");
cl.remove("webex-incorrect");
clelse {
} .add("webex-incorrect");
cl.remove("webex-correct");
cl
}
// match numeric answers within a specified tolerance
if(this.dataset.tol > 0){
var tol = JSON.parse(this.dataset.tol);
var matches = real_answers.map(x => Math.abs(x - my_answer) < tol)
if (matches.reduce((a, b) => a + b, 0) > 0) {
.add("webex-correct");
clelse {
} .remove("webex-correct");
cl
}
}
// added regex bit
if (cl.contains("regex")){
= RegExp(real_answers.join("|"))
answer_regex if (answer_regex.test(my_answer)) {
.add("webex-correct");
cl
}
}
update_total_correct();
}
/* function for checking select answers */
= function(e) {
select_func console.log("webex: check select");
var cl = this.classList
/* add style */
.remove("webex-incorrect");
cl.remove("webex-correct");
clif (this.value == "answer") {
.add("webex-correct");
clelse if (this.value != "blank") {
} .add("webex-incorrect");
cl
}
update_total_correct();
}
/* function for checking radiogroups answers */
= function(e) {
radiogroups_func console.log("webex: check radiogroups");
var checked_button = document.querySelector('input[name=' + this.id + ']:checked');
var cl = checked_button.parentElement.classList;
var labels = checked_button.parentElement.parentElement.children;
/* get rid of styles */
for (i = 0; i < labels.length; i++) {
.classList.remove("webex-incorrect");
labels[i].classList.remove("webex-correct");
labels[i]
}
/* add style */
if (checked_button.value == "answer") {
.add("webex-correct");
clelse {
} .add("webex-incorrect");
cl
}
update_total_correct();
}
window.onload = function() {
console.log("webex onload");
/* set up solution buttons */
var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {
if (buttons[i].parentElement.classList.contains('webex-solution')) {
.onclick = b_func;
buttons[i]
}
}
var check_sections = document.getElementsByClassName("webex-check");
console.log("check:", check_sections.length);
for (var i = 0; i < check_sections.length; i++) {
.classList.add("unchecked");
check_sections[i]
let btn = document.createElement("button");
.innerHTML = "Show Answers";
btn.classList.add("webex-check-button");
btn.onclick = check_func;
btn.appendChild(btn);
check_sections[i]
let spn = document.createElement("span");
.classList.add("webex-total_correct");
spn.appendChild(spn);
check_sections[i]
}
/* set up webex-solveme inputs */
var solveme = document.getElementsByClassName("webex-solveme");
for (var i = 0; i < solveme.length; i++) {
/* make sure input boxes don't auto-anything */
.setAttribute("autocomplete","off");
solveme[i].setAttribute("autocorrect", "off");
solveme[i].setAttribute("autocapitalize", "off");
solveme[i].setAttribute("spellcheck", "false");
solveme[i].value = "";
solveme[i]
/* adjust answer for ignorecase or nospaces */
var cl = solveme[i].classList;
var real_answer = solveme[i].dataset.answer;
if (cl.contains("ignorecase")) {
= real_answer.toLowerCase();
real_answer
}if (cl.contains("nospaces")) {
= real_answer.replace(/ /g, "");
real_answer
}.dataset.answer = real_answer;
solveme[i]
/* attach checking function */
.onkeyup = solveme_func;
solveme[i].onchange = solveme_func;
solveme[i]
.insertAdjacentHTML("afterend", " <span class='webex-icon'></span>")
solveme[i]
}
/* set up radiogroups */
var radiogroups = document.getElementsByClassName("webex-radiogroup");
for (var i = 0; i < radiogroups.length; i++) {
.onchange = radiogroups_func;
radiogroups[i]
}
/* set up selects */
var selects = document.getElementsByClassName("webex-select");
for (var i = 0; i < selects.length; i++) {
.onchange = select_func;
selects[i].insertAdjacentHTML("afterend", " <span class='webex-icon'></span>")
selects[i]
}
update_total_correct();
}
</script>
webex.css
/*
Acknowledgments. This file is from webexercises:
Barr D, DeBruine L (2023). webexercises: Create Interactive Web Exercises in
'R Markdown' (Formerly 'webex'). R package version 1.1.0.9000,
https://github.com/psyteachr/webexercises.
*/
:root {
--incorrect: #983E82;
--incorrect_alpha: #edaddd;
--correct: #59935B;
--correct_alpha: #c0edc2;
--highlight: #467AAC;
}
.webex-check {}
.webex-box {
border: 2px solid var(--highlight);
padding: 0.5em 0.25em;
margin: 1em 0;
border-radius: .25em;
background-color: rgba(127, 127, 127, 0.05);
}
.webex-total_correct {
margin-left: 1em;
}
.unchecked .webex-total_correct {
display: none;
}
.unchecked .webex-incorrect,
.unchecked .webex-correct {
border: 2px dotted grey !important;
background-color: white !important;
}
/* styles for webex-solveme */
.webex-select, input.webex-solveme,
.unchecked .webex-radiogroup label.webex-incorrect,
.unchecked .webex-radiogroup label.webex-correct{
border: 2px dotted grey;
background-color: white;
border-radius: 0.25em;
}
.webex-incorrect,
.webex-solveme.webex-incorrect,
input.webex-radiogroup label.webex-incorrect {
border: 2px dotted var(--incorrect);
background-color: var(--incorrect_alpha);
color: black;
border-radius: 0.25em;
}.webex-correct,
.webex-solveme.webex-correct,
input.webex-radiogroup label.webex-correct {
border: 2px solid var(--correct);
background-color: var(--correct_alpha);
color: black;
border-radius: 0.25em;
}
.unchecked .webex-incorrect span::before,
.unchecked .webex-incorrect + .webex-icon::after,
.unchecked .webex-correct span::before,
.unchecked .webex-correct + .webex-icon::after {
content: " ";
}
.webex-incorrect span::before,
.webex-incorrect + .webex-icon::after {
content: "\274C\00A0\00A0";
font-family: Arial, sans-serif;
}
.webex-correct span::before,
.webex-correct + .webex-icon::after {
content: "\2705\00A0\00A0";
}
/* styles for hidden solutions */
.webex-solution {
height: 2.5em;
overflow-y: hidden;
padding: 0.5em;
margin-bottom: 10px;
}.webex-solution.open {
height: auto;
border: 2px solid var(--highlight);
border-radius: 5px;
}.webex-solution button, .webex-check-button {
height: 2em;
margin-bottom: 0.5em;
border-radius: 0.5em;
background-color: var(--highlight);
color: white;
padding: 0 0.5em;
}.webex-solution pre.sourceCode {
border-color: var(--correct);
}
.webex-radiogroup label {
margin-left: 2em;
text-indent: -1em;
padding-left: 0.5em;
font-weight: 400;
display: block;
border: 2px solid rgba(255, 255, 255, 0);
background-color: inherit;
border-radius: 0.25em;
}
.webex-radiogroup label input {
position: relative;
left: -1em;
}
.webex-radiogroup {
margin: 1em 0;
}
Front matter
To render our exercises in our quarto site, we first need to include the following in the front matter of our .qmd
file:
format:
html:
css: [assets/webex.css]
include-after-body: [assets/webex.js]
embed-resources: true
The path to the files may be assets/...
, or changed to an appropriate path for your project.