summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/zelfoverhoor/.gitignore4
-rw-r--r--modules/zelfoverhoor/docent.html57
-rw-r--r--modules/zelfoverhoor/docent.js173
-rw-r--r--modules/zelfoverhoor/index.html15
-rw-r--r--modules/zelfoverhoor/notfound.html12
-rw-r--r--modules/zelfoverhoor/qs.html64
-rw-r--r--modules/zelfoverhoor/qs.js76
-rw-r--r--modules/zelfoverhoor/style.css3
-rw-r--r--modules/zelfoverhoor/zelfoverhoor.js189
9 files changed, 593 insertions, 0 deletions
diff --git a/modules/zelfoverhoor/.gitignore b/modules/zelfoverhoor/.gitignore
new file mode 100644
index 0000000..948b29c
--- /dev/null
+++ b/modules/zelfoverhoor/.gitignore
@@ -0,0 +1,4 @@
+accounts.json
+questiondb.json
+questionsets.json
+userlists.json
diff --git a/modules/zelfoverhoor/docent.html b/modules/zelfoverhoor/docent.html
new file mode 100644
index 0000000..224384d
--- /dev/null
+++ b/modules/zelfoverhoor/docent.html
@@ -0,0 +1,57 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Zelfoverhoor: Docent (<!--###NAME###-->)</title>
+<link rel="stylesheet" href="/zelfoverhoor/style.css">
+<script src="/zelfoverhoor/docent/docent.js"></script>
+<style>
+table {
+ border-collapse: collapse;
+}
+td, th {
+ border: 1px #333 solid;
+ padding: 4px;
+}
+.invisible {
+ display: none;
+}
+#newqsetform {
+ border: 1px #333 solid;
+ padding: 4px;
+}
+.newquestioncontainer {
+ margin-bottom: 10px;
+}
+.newquestioncontainer > span {
+ margin-right: 5px;
+}
+.newquestioncontainer textarea {
+ width: 300px;
+ height: 60px;
+}
+</style>
+</head>
+<body>
+<h1>Zelfoverhoor: Docent (<!--###NAME###-->)</h1>
+<h2>Vragensets</h2>
+<table>
+ <thead><tr><th>ID</th><th>Naam</th><th>Beschrijving</th><th>#vragen</th></tr></thead>
+ <tbody id="qsets"></tbody>
+</table>
+<br>
+<input type="button" id="newqsetvisible" onclick="doNewQSet()" value="Nieuwe vragenset maken">
+<br>
+<div id="newqsetform" class="invisible">
+ <h2>Nieuwe vragenset</h2>
+ <b>Naam</b>:<br><input type="text" id="newqsetname" size="40"><br><br>
+ <b>Beschrijving</b>:<br><textarea id="newqsetdescr" style="width:300px;height:60px"></textarea><br><br>
+ <div id="newquestions"></div>
+ <input type="button" onclick="addNewQuestion()" value="Nog een vraag!"><br>
+ <br>
+ <div style="background-color:#d8d8d8;padding:10px;display:inline-block">
+ <input type="button" onclick="submitQSet()" value="Vragenset klaar">
+ </div>
+</div>
+</body>
+</html>
diff --git a/modules/zelfoverhoor/docent.js b/modules/zelfoverhoor/docent.js
new file mode 100644
index 0000000..7987351
--- /dev/null
+++ b/modules/zelfoverhoor/docent.js
@@ -0,0 +1,173 @@
+var questionsets=null;
+
+function getQuestionSets(){
+ var xhr=new XMLHttpRequest();
+ xhr.onreadystatechange=function(){
+ if(xhr.readyState==4){
+ questionsets=JSON.parse(xhr.responseText);
+ updateQuestionSetsList();
+ }
+ };
+ xhr.responseType="text";
+ xhr.open("GET","/zelfoverhoor/docent/sets");
+ xhr.send();
+}
+
+function updateQuestionSetsList(){
+ var tbody=document.getElementById("qsets");
+ clearElement(tbody);
+ if(questionsets.length==0){
+ var tr=document.createElement("tr");
+ var td=document.createElement("td");
+ td.appendChild(document.createTextNode("Nog geen vragensets..."));
+ td.setAttribute("colspan","4");
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
+ for(var i=0;i<questionsets.length;i++){
+ var tr=document.createElement("tr");
+ td=document.createElement("td");
+ var a=document.createElement("a");
+ a.setAttribute("href",location.origin+"/zelfoverhoor/qs/"+questionsets[i].id);
+ a.setAttribute("target","_blank");
+ a.innerHTML=questionsets[i].id;
+ td.appendChild(a);
+ tr.appendChild(td);
+ td=document.createElement("td");
+ td.appendChild(document.createTextNode(questionsets[i].name));
+ tr.appendChild(td);
+ td=document.createElement("td");
+ td.appendChild(document.createTextNode(questionsets[i].description));
+ tr.appendChild(td);
+ td=document.createElement("td");
+ td.appendChild(document.createTextNode(questionsets[i].questions.length));
+ tr.appendChild(td);
+ td=document.createElement("td");
+ var input=document.createElement("input");
+ input.setAttribute("type","button");
+ input.setAttribute("value","verwijder");
+ input.addEventListener("click",(function(id,name){return function(){
+ if(confirm("Weet je zeker dat je de vragenset '"+name+"' wilt verwijderen?")){
+ var xhr=new XMLHttpRequest();
+ xhr.onreadystatechange=function(){
+ if(xhr.readyState==4){
+ if(xhr.status==200){
+ getQuestionSets();
+ } else if(xhr.status==404){
+ alert("Vragenset kon niet worden gevonden... Misschien even de pagina herladen?");
+ } else {
+ alert("Onbekende error: "+xhr.responseText);
+ }
+ }
+ };
+ xhr.open("POST","/zelfoverhoor/docent/deleteset");
+ xhr.send(id);
+ }
+ };})(questionsets[i].id,questionsets[i].name));
+ td.appendChild(input);
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ }
+}
+
+function clearElement(el){
+ var c;
+ while((c=el.lastChild))el.removeChild(c);
+}
+
+function doNewQSet(){
+ document.getElementById("newqsetname").value="";
+ document.getElementById("newqsetdescr").value="";
+ clearElement("newquestions");
+ document.getElementById("newqsetform").classList.remove("invisible");
+ document.getElementById("newqsetvisible").classList.add("invisible");
+ addNewQuestion();
+}
+
+function closeQSetForm(){
+ document.getElementById("newqsetform").classList.add("invisible");
+ document.getElementById("newqsetvisible").classList.remove("invisible");
+}
+
+function addNewQuestion(){
+ var parent=document.getElementById("newquestions");
+
+ var div=document.createElement("div");
+ div.classList.add("newquestioncontainer");
+
+ var span=document.createElement("span");
+ span.setAttribute("style","display:inline-block");
+ var b=document.createElement("b");
+ b.appendChild(document.createTextNode("Vraag:"));
+ span.appendChild(b);
+ span.appendChild(document.createElement("br"));
+ var qta=document.createElement("textarea");
+ span.appendChild(qta);
+ div.appendChild(span);
+
+ span=document.createElement("span");
+ span.setAttribute("style","display:inline-block");
+ b=document.createElement("b");
+ b.appendChild(document.createTextNode("Antwoord:"));
+ span.appendChild(b);
+ span.appendChild(document.createElement("br"));
+ span.appendChild(document.createElement("textarea"));
+ div.appendChild(span);
+
+ span=document.createElement("span");
+ var input=document.createElement("input");
+ input.setAttribute("type","button");
+ input.setAttribute("value","verwijder vraag");
+ input.setAttribute("style","vertical-align:50px");
+ input.addEventListener("click",function(){
+ if(confirm("Weet je zeker dat je de volgende vraag wil verwijderen?\n"+qta.value)){
+ parent.removeChild(div);
+ }
+ });
+ span.appendChild(input);
+ div.appendChild(span);
+
+ parent.appendChild(div);
+}
+
+function submitQSet(){
+ var name=document.getElementById("newqsetname").value.trim();
+ var description=document.getElementById("newqsetdescr").value.trim();
+ if(name==""||description==""){
+ alert("Naam en beschrijving zijn nodig.");
+ return;
+ }
+
+ var div=document.getElementById("newquestions");
+ var ch=div.children,nq=ch.length;
+ var questions=[];
+ for(var i=0;i<nq;i++){
+ var tas=ch[i].getElementsByTagName("textarea");
+ var q={"q": tas[0].value.trim(), "a": tas[1].value.trim()};
+ if(q.q==""||q.a==""){
+ alert("Een van de vragen of antwoorden is leeg; graag even vullen of de vraag verwijderen.");
+ return;
+ }
+ questions.push(q);
+ }
+
+ var obj={"name": name, "description": description, "questions": questions};
+
+ var xhr=new XMLHttpRequest();
+ xhr.onreadystatechange=function(){
+ if(xhr.readyState==4){
+ if(xhr.status==200){
+ getQuestionSets();
+ closeQSetForm();
+ } else {
+ alert("Vragenset lijkt niet succesvol toegevoegd te zijn...\n"+xhr.responseText);
+ }
+ }
+ };
+ xhr.open("POST","/zelfoverhoor/docent/addset");
+ xhr.send(JSON.stringify(obj));
+}
+
+window.addEventListener("load",function(){
+ getQuestionSets();
+});
diff --git a/modules/zelfoverhoor/index.html b/modules/zelfoverhoor/index.html
new file mode 100644
index 0000000..d8f951f
--- /dev/null
+++ b/modules/zelfoverhoor/index.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Zelfoverhoor</title>
+<link rel="stylesheet" href="/zelfoverhoor/style.css">
+</head>
+<body>
+<div style="float:right">
+ <a href="/zelfoverhoor/docent">(docent)</a>
+</div>
+<h1>Zelfoverhoor</h1>
+<p>Welkom bij Zelfoverhoor! Als het goed is heb je van de docent een link van de vorm <b>https://tomsmeding.com/zelfoverhoor/qs/abcdef</b> gekregen. Daarmee kom je op de juiste pagina terecht. Hier is dus verder weinig te vinden. ;)</p>
+</body>
+</html>
diff --git a/modules/zelfoverhoor/notfound.html b/modules/zelfoverhoor/notfound.html
new file mode 100644
index 0000000..8d79c10
--- /dev/null
+++ b/modules/zelfoverhoor/notfound.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Zelfoverhoor: niet gevonden</title>
+<link rel="stylesheet" href="/zelfoverhoor/style.css">
+</head>
+<body>
+<h1>Zelfoverhoor</h1>
+Die vragenset kon niet worden gevonden. Misschien heb je de link niet goed gekopieerd?
+</body>
+</html>
diff --git a/modules/zelfoverhoor/qs.html b/modules/zelfoverhoor/qs.html
new file mode 100644
index 0000000..32c9099
--- /dev/null
+++ b/modules/zelfoverhoor/qs.html
@@ -0,0 +1,64 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Zelfoverhoor: <!--###ID###--></title>
+<link rel="stylesheet" href="/zelfoverhoor/style.css">
+<script>
+// Gefeliciteerd, je hebt de antwoorden gevonden.
+// De vraag is echter, wil je die gebruiken? :)
+// Deze overhoor-applicatie is er immers om JEZELF te verbeteren...
+var questionset=/*###QUESTIONSET###*/;
+</script>
+<script src="/zelfoverhoor/qs.js"></script>
+<style>
+#qsetdescr {
+ margin-top: 5px;
+ display: inline-block;
+ background-color: #eee;
+ padding: 5px;
+}
+#qcontainer {
+ border: 1px #666 solid;
+ border-radius: 10px;
+ padding: 10px;
+ display: inline-block;
+}
+#question, #answer {
+ display: inline-block;
+ background-color: #eef;
+ border-radius: 10px;
+ padding: 10px;
+ margin-top: 5px;
+}
+/* HACK */
+#qcontainer.invisible, .invisible {
+ display: none;
+}
+</style>
+</head>
+<body>
+<h1>Zelfoverhoor: vragenset "<span id="qsetname"></span>"</h1>
+<b>Beschrijving:</b><br>
+<div id="qsetdescr"></div><br>
+<br><br>
+<div id="qcontainer">
+ <b>Vraag:</b><br>
+ <div id="question"></div><br>
+ <br>
+ <input type="button" id="showAnswerButton" onclick="showAnswer()" value="Laat antwoord zien!">
+ <div id="answercontainer" class="invisible">
+ <b>Antwoord:</b><br>
+ <div id="answer"></div><br>
+ <input type="button" onclick="advance(true)" value="Goed!"><span style="margin-left:20px"></span>
+ <input type="button" onclick="advance(false)" value="Fout">
+ </div>
+</div>
+<div id="rescontainer">
+ <b>Je hebt alle vragen gehad!</b><br>
+ <p>Je had <span id="numcorrect"></span> vragen goed van de in totaal <span id="numtotal"></span> vragen.</p>
+ <p id="allcorrectp"><b>Gefeliciteerd met je prestatie! :D</b></p>
+ <input type="button" onclick="startQuiz()" value="Nog een keer?">
+</div>
+</body>
+</html>
diff --git a/modules/zelfoverhoor/qs.js b/modules/zelfoverhoor/qs.js
new file mode 100644
index 0000000..cc68af9
--- /dev/null
+++ b/modules/zelfoverhoor/qs.js
@@ -0,0 +1,76 @@
+var setname=questionset.name;
+var setdescription=questionset.description;
+var questions=questionset.questions;
+
+var currentidx=0;
+var numcorrect=0;
+
+function clearElement(el){
+ while(el.lastChild)el.removeChild(el.lastChild);
+}
+
+function shuffle(a){
+ var j,v,i;
+ for(var i=a.length-1;i>0;i--){
+ j=Math.floor(Math.random()*(i+1));
+ v=a[i]; a[i]=a[j]; a[j]=v;
+ }
+}
+
+function startQuiz(){
+ currentidx=0;
+ numcorrect=0;
+ showCurrent();
+}
+
+function showCurrent(){
+ document.getElementById("qcontainer").classList.remove("invisible");
+ document.getElementById("rescontainer").classList.add("invisible");
+ document.getElementById("answercontainer").classList.add("invisible");
+ document.getElementById("showAnswerButton").classList.remove("invisible");
+
+ var qdiv=document.getElementById("question");
+ clearElement(qdiv);
+ qdiv.appendChild(document.createTextNode(questions[currentidx].q));
+}
+
+function showAnswer(){
+ document.getElementById("answercontainer").classList.remove("invisible");
+ document.getElementById("showAnswerButton").classList.add("invisible");
+
+ var adiv=document.getElementById("answer");
+ clearElement(adiv);
+ adiv.appendChild(document.createTextNode(questions[currentidx].a));
+}
+
+function finishQuiz(){
+ document.getElementById("qcontainer").classList.add("invisible");
+ document.getElementById("rescontainer").classList.remove("invisible");
+ document.getElementById("numcorrect").innerHTML=numcorrect;
+ document.getElementById("numtotal").innerHTML=questions.length;
+ if(numcorrect==questions.length){
+ document.getElementById("allcorrectp").classList.remove("invisible");
+ } else {
+ document.getElementById("allcorrectp").classList.add("invisible");
+ }
+}
+
+function advance(corr){
+ if(corr)numcorrect++;
+ currentidx++;
+ if(currentidx<questions.length){
+ showCurrent();
+ } else {
+ finishQuiz();
+ }
+}
+
+window.addEventListener("load",function(){
+ document.getElementById("qsetname").appendChild(document.createTextNode(setname));
+ document.getElementById("qsetdescr").appendChild(document.createTextNode(setdescription));
+ if(questions.length==0){
+ alert("Deze lijst heeft geen vragen, dus er kan weinig overhoord worden, eerlijk gezegd...");
+ return;
+ }
+ startQuiz();
+});
diff --git a/modules/zelfoverhoor/style.css b/modules/zelfoverhoor/style.css
new file mode 100644
index 0000000..a326381
--- /dev/null
+++ b/modules/zelfoverhoor/style.css
@@ -0,0 +1,3 @@
+body {
+ font-family: sans-serif;
+}
diff --git a/modules/zelfoverhoor/zelfoverhoor.js b/modules/zelfoverhoor/zelfoverhoor.js
new file mode 100644
index 0000000..c55f0e0
--- /dev/null
+++ b/modules/zelfoverhoor/zelfoverhoor.js
@@ -0,0 +1,189 @@
+var cmn=require("../$common.js"),
+ fs=require("fs");
+
+var moddir;
+
+var accounts=require("./accounts.json");
+
+// {<id>: {q: "question", a: "answer"}}
+var questiondb={};
+// {<id>: {id, name, description, questions: [<question_id>]}}
+var questionsets={};
+// {<username>: [<set_id>]}
+var userlists={};
+
+function uniqidstr(validp){
+ for(var len=6;;len++){
+ var str="";
+ for(var i=0;i<len;i++)str+=String.fromCharCode(97+Math.random()*26|0);
+ if(validp(str))return str;
+ }
+}
+
+var persistDB=(function(){
+ var qdbCache=null,qsetsCache=null,ulCache=null;
+ return function persistDB(){
+ var s=JSON.stringify(questiondb);
+ if(s!=qdbCache){
+ qdbCache=s;
+ fs.writeFile(moddir+"/questiondb.json",JSON.stringify(questiondb),function(err){
+ if(err)console.log("write questiondb.json:",err);
+ });
+ }
+ s=JSON.stringify(questionsets);
+ if(s!=qsetsCache){
+ qsetsCache=s;
+ fs.writeFile(moddir+"/questionsets.json",JSON.stringify(questionsets),function(err){
+ if(err)console.log("write questionsets.json:",err);
+ });
+ }
+ s=JSON.stringify(userlists);
+ if(s!=ulCache){
+ ulCache=s;
+ fs.writeFile(moddir+"/userlists.json",JSON.stringify(userlists),function(err){
+ if(err)console.log("write userlists.json:",err);
+ });
+ }
+ };
+})();
+
+function shuffle(a){
+ var j,v,i;
+ for(var i=a.length-1;i>0;i--){
+ j=Math.floor(Math.random()*(i+1));
+ v=a[i]; a[i]=a[j]; a[j]=v;
+ }
+}
+
+module.exports=function(app,io,_moddir){
+ moddir=_moddir;
+
+ try {
+ questiondb=JSON.parse(fs.readFileSync(moddir+"/questiondb.json"));
+ questionsets=JSON.parse(fs.readFileSync(moddir+"/questionsets.json"));
+ userlists=JSON.parse(fs.readFileSync(moddir+"/userlists.json"));
+ } catch(e){
+ console.log("error on reading zelfoverhoor db");
+ questiondb={};
+ questionsets={};
+ userlists={};
+ persistDB();
+ }
+
+ app.get("/zelfoverhoor",function(req,res){
+ res.sendFile(moddir+"/index.html");
+ });
+
+ app.get("/zelfoverhoor/qs/:id",function(req,res){
+ var qset=questionsets[req.params.id];
+ if(qset==null){
+ res.status(404).sendFile(moddir+"/notfound.html");
+ return;
+ }
+ var list=[];
+ for(var i=0;i<qset.questions.length;i++){
+ list.push(questiondb[qset.questions[i]]);
+ }
+ var resset={"id": qset.id, "name": qset.name, "description": qset.description, "questions": list};
+ shuffle(resset.questions);
+ fs.readFile(moddir+"/qs.html",function(err,data){
+ if(err)throw err;
+ res.send(String(data)
+ .replace("<!--###ID###-->",req.params.id)
+ .replace("/*###QUESTIONSET###*/",JSON.stringify(resset)));
+ });
+ });
+
+ app.use(["/zelfoverhoor/docent","/zelfoverhoor/docent/*"],cmn.authgen(accounts));
+ app.get("/zelfoverhoor/docent",function(req,res){
+ fs.readFile(moddir+"/docent.html",function(err,data){
+ if(err)throw err;
+ res.send(String(data)
+ .replace(/<!--###NAME###-->/g,req.authuser));
+ });
+ });
+ app.get("/zelfoverhoor/docent/sets",function(req,res){
+ if(userlists[req.authuser]==null){
+ userlists[req.authuser]=[];
+ }
+ var ul=userlists[req.authuser];
+ var list=[];
+ for(var i=0;i<ul.length;i++){
+ list.push(questionsets[ul[i]]);
+ }
+ res.send(JSON.stringify(list));
+ });
+ app.post("/zelfoverhoor/docent/addset",function(req,res){
+ var qset;
+ try {
+ qset=JSON.parse(req.body);
+ } catch(e){
+ res.status(400).send("Invalid json received");
+ return;
+ }
+ if(!qset.name||typeof qset.name!="string"||
+ !qset.description||typeof qset.description!="string"||
+ !qset.questions||!Array.isArray(qset.questions)){
+ res.status(400).send("Invalid data received");
+ return;
+ }
+ var i;
+ for(i=0;i<qset.questions.length;i++){
+ if(!qset.questions[i].q||typeof qset.questions[i].q!="string"||
+ !qset.questions[i].a||typeof qset.questions[i].a!="string"){
+ res.status(400).send("Invalid question data received");
+ return;
+ }
+ }
+ var ids=[],id;
+ for(i=0;i<qset.questions.length;i++){
+ id=uniqidstr(function(s){return questiondb[s]==null;});
+ questiondb[id]={"q": qset.questions[i].q, "a": qset.questions[i].a};
+ ids.push(id);
+ }
+ var setid=uniqidstr(function(s){return questionsets[s]==null;});
+ questionsets[setid]={"id": setid, "name": qset.name, "description": qset.description, "questions": ids};
+ if(userlists[req.authuser]==null){
+ userlists[req.authuser]=[];
+ }
+ userlists[req.authuser].push(setid);
+ persistDB();
+ res.send(setid);
+ });
+ app.post("/zelfoverhoor/docent/deleteset",function(req,res){
+ var setid=req.body;
+ if(!setid||typeof setid!="string"){
+ res.status(400).send("Invalid id received");
+ return;
+ }
+ if(!userlists[req.authuser]){
+ userlists[req.authuser]=[];
+ }
+ var ul=userlists[req.authuser];
+ var found=false;
+ for(var i=0;i<ul.length;i++){
+ if(ul[i]==setid){
+ ul.splice(i,1);
+ found=true;
+ break;
+ }
+ }
+ if(!found){
+ res.status(404).send("ID not found");
+ return;
+ }
+ persistDB();
+ res.send();
+ });
+
+ app.get("/zelfoverhoor/docent/docent.js",function(req,res){
+ res.sendFile(moddir+"/docent.js");
+ });
+
+ app.get("/zelfoverhoor/qs.js",function(req,res){
+ res.sendFile(moddir+"/qs.js");
+ });
+ app.get("/zelfoverhoor/style.css",function(req,res){
+ res.sendFile(moddir+"/style.css");
+ });
+};