diff --git a/README.md b/README.md index de5b10332d7f3a30a1afd37b5c5d53ee531f7935..dfba4373601fa37864302915ab948a56250e8f66 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ A moodle-based training area for maths aiming to prepare students for their stud - Create a new course in your moodle system. - Navigate to the course administration and pick the restore-option. -- Pick the `*.mbz`-file of this repository for restoring. It contains a moodle-course-backup-file. Perform the restore in your newly created moodle-course. +- Pick one of the `*.mbz`-files (according to your preferred language) of this repository for restoring. It contains a moodle-course-backup-file. Perform the restore in your newly created moodle-course. *Please choose one of the `*.mbz-files` according to your preferred language.* - The course you imported contains a single activity: The training area. Check it out in the dashboard of your course. - Be free to edit the quiz as you like and import it into other courses of your moodle-system. ## Troubleshooting ### Ressources won't load -It may appear that for security reasons, your moodle-system can't fetch the script `alquiz.js` from `https://marvin.hs-bochum.de/~mneugebauer/alquiz.js`. In this case the training area exists in your moodle-system, but will behave like any other test-element in moodle. For example, the maths-worlds-cluster and the endboss-symbols in the navigation bar (see screenshot above) won't appear. If so, download the script from this repository and move it to a place on your own server. Afterwards you have to change the `<script src="...">`-url in the beginning of each(!) question. -The same may appear for images used in the trigonometry-questions and the avatar-icon(s). Download them from this repository (`img`-directory), move them to your server and change the urls in the regarding questions. +It may appear that for security reasons, your moodle-system can't fetch the script from the server. In this case the training area exists in your moodle-system, but will behave like any other test-element in moodle. For example, the maths-worlds-cluster and the endboss-symbols in the navigation bar (see screenshot above) won't appear. If so, download the script (choose `script/alquiz.js` for german or `script/alquiz_en.js` for english) from this repository and move it to a place on your own server. Afterwards you have to change the `<script src="...">`-url in the beginning of each(!) question. +The same may appear for images used in the trigonometry-questions and the avatar-icon(s). Download them from this repository (`img`-directory), move them to your server and change the urls in the regarding questions. Please note, that for the english version the file with the ending `_en` ### Training area looks messy or does not work properly The script-file `alquiz.js` adds additional CSS-styling information to the loaded page. These stylings are optimized for the moodle-system of Hochschule Bochum. It may not fit to the stylings of your moodle-system. This may cause troubles for up to two cases: diff --git a/script/alquiz_en.js b/script/alquiz_en.js new file mode 100644 index 0000000000000000000000000000000000000000..d0ba85aebf3c77dbfec25fdefe70c12ec831c91a --- /dev/null +++ b/script/alquiz_en.js @@ -0,0 +1,1098 @@ +///START AL QUIZ SCRIPT/// +class QuestionGroup { + constructor(id, description, Questions, nextGroup) { + this.id = id; + this.description = description; + this.Questions = {}; + if (!!Questions) { + Questions.forEach(Question => { + this.Questions[Question.id] = Question; + }); + } + } +} + +class BubbleInfo { + constructor(stateStringAssociation) { + /*this.success = success; + this.validation = validation; + this.failure = failure; + this.redo = redo; + this.camebackaftersuccess = camebackaftersuccess; + this.error = error;*/ + for(let i in stateStringAssociation) { + this[i] = stateStringAssociation[i]; + } + } + + getText(state) { + if (this[state] != undefined) { + return this[state]; + } + return undefined; + } +} + +class Instruction { + constructor(id, description, onsuccess, onfailure, BubbleInfo, questionsOnPage) { + this.id = id; + /* success means: challenge accepted and go to endboss*/ + this.onsuccess = onsuccess; + /*failure means: give me the first easy question*/ + this.onfailure = onfailure; + this.description = description; + this.BubbleInfo = BubbleInfo; + + this.questionsOnPage = questionsOnPage == undefined ? 1 : questionsOnPage; + } + + isSolved() { + return false; + } + + isCurrentlySolved() { + return false; + } +} + +class Question extends Instruction { + constructor(id, description, needs, BubbleInfo, onsuccess, onfailure, askBeforeSkip, questionsOnPage) { + super(id, description, onsuccess, onfailure, BubbleInfo, questionsOnPage); + this.needs = needs; + this.solved = 0; + /*solved means: solved at least once in this attempt, currentlySolved means: solved actually now in the current scope of js variables*/ + this.currentlySolved = 0; + this.askBeforeSkip = askBeforeSkip == undefined ? true : askBeforeSkip; + } + + isSolved(onlyOnCurrentlySolved) { + if(onlyOnCurrentlySolved == undefined) { + onlyOnCurrentlySolved = false; + } + let solvedAtAskedTime = (!onlyOnCurrentlySolved ? this.solved : this.currentlySolved); + if (solvedAtAskedTime >= this.needs) { + return true; + } + return false; + } + + ifCurrentlySolved() { + if (this.currentlySolved >= this.needs) { + return true; + } + return false; + } +} + +class Quiz { + constructor(QuestionGroups, currentQuestionId) { + this.QuestionGroups = QuestionGroups; + this.currentQuestionId = currentQuestionId; + if (this.currentQuestionId == undefined) { + this.currentQuestionId = Object.keys(this.QuestionGroups[0].Questions)[0]; + } + + //auto assign onsuccess and onfailure for undefined + let grouplength = this.QuestionGroups.length; + for (let i = 0; i < grouplength; i++) { + let questionlength = Object.keys(this.QuestionGroups[i].Questions).length; + for (let j = 0; j < questionlength; j++) { + if (!this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onsuccess) { + //probably next question or next group + if (j < questionlength - 1) { + //next question + this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onsuccess = Object.keys(this.QuestionGroups[i].Questions)[j + 1]; + } else if (i < grouplength - 1) { + //console.log(Object.keys(this.QuestionGroups[i].Questions)[j] + " leads to next group onsuccess "); + //first question of next group + this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onsuccess = Object.keys(this.QuestionGroups[i + 1].Questions)[0]; + } else { + //console.log("no other group"); + this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onsuccess = "_finish"; + } + } + + if (!this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onfailure) { + //probably first question of next group + if (i < grouplength - 1) { + //console.log(Object.keys(this.QuestionGroups[i].Questions)[j] + " leads to next group onfailure "); + this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onfailure = Object.keys(this.QuestionGroups[i + 1].Questions)[0]; + } else { + //console.log("no other group"); + this.QuestionGroups[i].Questions[Object.keys(this.QuestionGroups[i].Questions)[j]].onfailure = "_finish"; + } + } + }; + } + } + + getQuestions() { + let objects = []; + this.QuestionGroups.forEach(QuestionGroup => { + for (let k in QuestionGroup.Questions) { + objects.push(QuestionGroup.Questions[k]); + } + }); + return objects; + } + + getQuestion(id) { + if (id == undefined) { + id = this.currentQuestionId; + } + for (let i = 0; i < this.QuestionGroups.length; i++) { + if (this.QuestionGroups[i].Questions[id] != undefined) { + return this.QuestionGroups[i].Questions[id]; + } + } + return false; + } + + getNextQuestionId(questionId) { + if (questionId == undefined) { + questionId = this.currentQuestionId; + } + let returnValue = false; + for (let i = 0; i < this.QuestionGroups.length; i++) { + if (QuestionGroups[i].Questions[questionId] != undefined) { + let nextStep = QuestionGroups[i].Questions[questionId].isSolved() ? QuestionGroups[i].Questions[questionId].onsuccess : QuestionGroups[i].Questions[questionId].onfailure; + switch (nextStep) { + case "_finish": + return -1; + default: + return nextStep; + break; + } + } + } + console.log("question id not found"); + return false; + } + + getPageURL(questionId) { + if (questionId == undefined) { + questionId = this.currentQuestionId; + } + + let firstQuestionNavElement = document.querySelector("#quiznavbutton1"); + if (!firstQuestionNavElement) { + return false; + } + let plainUrl = ""; + if (!firstQuestionNavElement.href || firstQuestionNavElement.href == "#") { + plainUrl = window.location.href; + } else { + plainUrl = firstQuestionNavElement.href; + } + + //let sanitizedUrl = plainUrl.replace(/(.*?)(?:#|&page=\d*#*|&scrollpos=\d*#*)/, "\1"); + let sanitizedUrl = plainUrl; + let relPos = plainUrl.indexOf("&scrollpos"); + if (relPos == -1) { + relPos = plainUrl.indexOf("&page"); + if (relPos == -1) { + relPos = plainUrl.indexOf("#"); + } + } + + if (relPos > -1) { + sanitizedUrl = plainUrl.substr(0, relPos); + } + + return sanitizedUrl + "&page=" + this.getQuestion(questionId).page + } + + getNextPageInfo(questionId, forceURL) { + if (questionId == undefined) { + questionId = this.currentQuestionId; + } + let Question = this.getQuestion(questionId); + + if(forceURL == undefined) { + forceURL = false; + } + + let nextPageUrl = ""; + let nextPageLinkText = ""; + let nextQuestionId = this.getNextQuestionId(); + let possibleNextPageUrl = this.getPageURL(nextQuestionId); + + if (nextQuestionId == -1) { + //finish + let finishAttemptElement = document.querySelector(".endtestlink.aalink"); + if (!finishAttemptElement || !finishAttemptElement.href) { + possibleNextPageUrl = "summary.php"; + } else { + possibleNextPageUrl = finishAttemptElement.href; + } + nextPageLinkText = "Finish"; + } + else { + nextPageLinkText = "Next question"; + } + + //actually means a real skip, no right or wrong + if(Question.askBeforeSkip == true && !forceURL && !Question.isSolved() && document.querySelector(".stackprtfeedback") == undefined) { + //ask before skip + nextPageUrl = "javascript:showAskBeforeSkipModal();"; + document.querySelector(".skip-yes").href = possibleNextPageUrl; + } + else { + nextPageUrl = possibleNextPageUrl; + } + return { + url: nextPageUrl, + linkText: nextPageLinkText + }; + } + + updateMoodleNavButtons() { + let buttonDivs = document.querySelectorAll(".submitbtns"); + buttonDivs.forEach(buttonDiv => { + buttonDiv.childNodes.forEach(buttonDivChild => { + buttonDivChild.style.visibility = "hidden"; + buttonDivChild.style.width = "0"; + }); + }); + + let cameFrom = sessionStorage.getItem("camefrom"); + if(cameFrom == undefined) { + let previousPageButton = document.getElementById("mod_quiz-prev-nav"); + if(previousPageButton != undefined) { + previousPageButton.value = "Back"; + previousPageButton.style.visibility = "visible"; + previousPageButton.style.width = "auto"; + } + } + else { + let prevButton = document.createElement("a"); + prevButton.classList.add("btn", "btn-primary", "btn-prev-question"); + prevButton.href = this.getPageURL(cameFrom); + prevButton.innerHTML = "Back"; + + buttonDivs.forEach(buttonDiv => { + buttonDiv.insertBefore(prevButton, buttonDiv.firstChild); + }); + + sessionStorage.removeItem("camefrom"); + } + + let nextButton = document.createElement("a"); + nextButton.classList.add("btn", "btn-primary", "btn-next-question"); + let nextPageInfos = this.getNextPageInfo(); + nextButton.href = nextPageInfos.url; + nextButton.innerHTML = nextPageInfos.linkText; + + buttonDivs.forEach(buttonDiv => { + buttonDiv.appendChild(nextButton); + }); + } + + updateSpeechBubbles(id) { + if (id == undefined) { + id = this.currentQuestionId; + } + let currQuestion = this.getQuestion(id); + if (!currQuestion || !currQuestion.BubbleInfo) { + return false; + } + let bubbles = document.querySelectorAll(".bubble:not(.in-modal)"); + if (bubbles.length != 1) { + console.log("bad amount of speech bubbles"); + return false; + } + let bubble = bubbles[0]; + let text = ""; + + let icon = document.querySelector(".dm-icon"); + let iconUrls = { + grin:"https://marvin.hs-bochum.de/~mneugebauer/dm-avatar-grin.svg", + think:"https://marvin.hs-bochum.de/~mneugebauer/dm-avatar-think.svg", + sad:"https://marvin.hs-bochum.de/~mneugebauer/dm-avatar-sad.svg", + happy:"https://marvin.hs-bochum.de/~mneugebauer/dm-avatar-happy.svg" + }; + let imgReaction = ""; + + //on instruction, the revisit property of the bubble info plays a special role, so... + if(currQuestion instanceof Instruction && !(currQuestion instanceof Question) && currQuestion.visited == true) { + let revisitText = currQuestion.BubbleInfo.getText("revisit"); + if(revisitText != "" && revisitText != undefined) { + text = revisitText; + } + } + + //get current state of question + let stackFeedback = document.querySelector(".stackprtfeedback"); + if (stackFeedback != undefined) { + //solved, false or partially correct + + //only move feedback to the speech bubble on one feedback field + let stackFeedbacks = document.querySelectorAll(".stackprtfeedback"); + + if (currQuestion.isSolved(true) == true) { + imgReaction = "happy"; + if (currQuestion.BubbleInfo.getText("success") == undefined) { + if (stackFeedbacks.length == 1) { + //move the stack feedback in the speech bubble by default and add "Nächste Frage/Übung beenden" + let stackFeedbackInWords = "";//stackFeedback.querySelector("div"); + let first = true; + //console.log(stackFeedback.childNodes); + stackFeedback.childNodes.forEach(function(childNode) { + //console.log(childNode.innerHTML); + if(first == false) { stackFeedbackInWords += " "; } else { first = false; } + if(childNode.innerHTML != undefined && childNode.innerHTML != "" && childNode.tagName != "SCRIPT") { + stackFeedbackInWords += childNode.innerHTML; + } + }); + if (stackFeedbackInWords == "" /*stackFeedbackInWords == undefined || stackFeedbackInWords.innerHTML == undefined || stackFeedbackInWords.innerHTML == ""*/) { + text = "Richtig!"; + console.log("no feedback found to move"); + } + let nextPageInfos = this.getNextPageInfo(); + text = stackFeedbackInWords + "If it gives you more confidence, you can <a href=\"javascript:;\" onclick=\"repeatQuestion();\">repeat this task</a>. Otherwise you are ready for the <a href=\"" + nextPageInfos.url + "\">" + nextPageInfos.linkText + "</a>."; + stackFeedback.style.display = "none"; + } + else { + text = "Richtig!"; + console.log("using standard speech-bubble-feedback because of undefined feedback text for solved state and too many feedback fields") + } + } + else { + text = currQuestion.BubbleInfo.getText("success"); + } + } + else { + //false or partially correct + imgReaction = "sad"; + if (currQuestion.BubbleInfo.getText("failure") == undefined) { + console.log("no fail text"); + if (stackFeedbacks.length == 1) { + //move the stack feedback in the speech bubble by default and add "Erneut versuchen" + let stackFeedbackInWords = "";/*stackFeedback.querySelector("div");*/ + let first = true; + stackFeedback.childNodes.forEach(function(childNode) { + //console.log(childNode);//.innerHTML); + if(first == false) { stackFeedbackInWords += " "; } else { first = false; } + if(childNode.nodeType == 3) { //if text node + stackFeedbackInWords += childNode.data; + } + else if(childNode.innerHTML != undefined && childNode.innerHTML != "" && childNode.tagName != "SCRIPT") { + stackFeedbackInWords += childNode.innerHTML; + } + }); + + if (stackFeedbackInWords == ""/* || stackFeedbackInWords.innerHTML == undefined || stackFeedbackInWords.innerHTML == ""*/) { + text = "Falsch!"; + console.log("no feedback found to move"); + } + + + let nextPageInfos = this.getNextPageInfo(); + text = stackFeedbackInWords + " Try again? <a href=\"#\" onclick=\"document.querySelector('input[name*=redoslot]').click();\">Yes</a> <a href=\"" + nextPageInfos.url + "\">No (" + nextPageInfos.linkText + ")</a>" + stackFeedback.style.display = "none"; + } + else { + text = "Falsch."; + console.log("using standard speech-bubble-feedback because of undefined feedback text for failure state and too many feedback fields") + } + } + else { + text = currQuestion.BubbleInfo.getText("failure"); + } + } + } + else { + //verfication, syntax error or came back to main question + if (document.querySelector(".validationerror") != undefined) { + //verification state + text = currQuestion.BubbleInfo.getText("validation") == undefined ? "Did the system interpret your input correctly? <a href=\"#\" onclick=\"document.querySelector('input[id*=q][name$=_-submit]').click();\">Yes</a> <a href=\"#\" onclick=\"this.parentNode.innerHTML='Ändere deine Eingabe und klicke erneut auf "Prüfen"'\">No</a>" : currQuestion.BubbleInfo.getText("validation"); + } + else if(document.querySelector(".alert") != undefined) { + //syntax error + imgReaction = "think"; + text = currQuestion.BubbleInfo.getText("error") == undefined ? "Oh! Apparently, there is a problem with your input. Please look at the note below the input field, correct it and click "Check" again." : currQuestion.BubbleInfo.getText("error"); + } + /*else { + //came back to main question after the last of this group is solved + console.log("start routine to check for coming back"); + let i = 0; + for (i in this.QuestionGroups) { + if (QuestionGroups[i].Questions[this.currentQuestionId] != undefined) { + break; + } + } + //console.log(this.currentQuestionId + " is in group " + i); + let keys = Object.keys(this.QuestionGroups[i].Questions); + if (keys[0] == this.currentQuestionId) { + //console.log(this.currentQuestionId + " is the first question of this group"); + if (this.QuestionGroups[i].Questions[keys[keys.length - 1]] != undefined && this.QuestionGroups[i].Questions[keys[keys.length - 1]].isSolved() == true) { + console.log("came back after solving last question of group"); + if (currQuestion.BubbleInfo.getText("camebackaftersuccess") == undefined) { + text = "Willkommen zurück zu dieser Aufgabe. Haben dir die letzten Aufgaben geholfen, diese Aufgabe zu verstehen? Versuche es doch nochmal! Gehe ansonsten weiter zur nächsten Aufgabe."; + } else { + text = currQuestion.BubbleInfo.getText("camebackaftersuccess"); + } + currQuestion.onfailure = currQuestion.onsuccess; + //this.updateMoodleNavButtons(); + } + } + }*/ + //if(in verification state) + //text = Question.BubbleInfo.getText("verify") == undefined ? "Wurde deine Eingabe richtig interpretiert? %check% <a onclick=\"...\">Nein</a>" : Question.BubbleInfo.getText("verify"); + //else if(in false state) + //else if(in syntax error state) + //else if(in redo state) + //else + //assume initial state + //text = Question.BubbleInfo.getText("initial") + } + + //bubble.classList.remove("hidden"); + + if (text != "") { + //append special links to text, e. g. %nextlink%, %linktoquestionid_id%, %check% + bubble.innerHTML = text; + } + if(imgReaction != "" && icon != undefined) { + icon.src = iconUrls[imgReaction]; + } + + return true; + } + + updateNavigation() { + let navPanel = document.querySelector(".qn_buttons"); + if(!navPanel) { + return false; + } + let buttons = document.querySelectorAll("[id*=quiznavbutton]"); + if(buttons.length == undefined || buttons.length < 1) { + return false; + } + //group + //let groupHeadingNodes = []; + this.QuestionGroups.forEach(QuestionGroup => { + let wrapper = document.createElement("span"); + wrapper.dataset.isFor = QuestionGroup.id; + //groupHeadingNodes.push(heading); + + let heading = document.createElement("h2"); + heading.innerHTML = QuestionGroup.description; + heading.style.clear = "left"; + wrapper.appendChild(heading); + + let j = 0; + let k = 1; + let questionAmount = Object.keys(QuestionGroup.Questions).length; + for(let i in QuestionGroup.Questions) { + //console.log("quiznavbutton"+(QuestionGroup.Questions[i].page+1)); + let questionCard = document.getElementById("quiznavbutton"+(QuestionGroup.Questions[i].page+1)); + + questionCard.querySelectorAll(".accesshide").forEach(slotMarker => { + if(slotMarker != undefined && slotMarker.nextSibling != undefined) { + if(!(QuestionGroup.Questions[i] instanceof Question) && QuestionGroup.Questions[i] instanceof Instruction) { + //assume first question to be instructions + slotMarker.nextSibling.data = "i"; + } + else if(j < questionAmount-1) { + slotMarker.nextSibling.data = k; + k++; + } + else { + slotMarker.nextSibling.data = ""; + let endbossImg = document.createElement("img"); + endbossImg.src = "https://marvin.hs-bochum.de/~mneugebauer/skull.svg"; + endbossImg.style.height = "20px"; + slotMarker.parentNode.insertBefore(endbossImg, slotMarker.nextSibling); + } + } + }); + if(!questionCard) { + console.log("bad question card id for question "+QuestionGroup.Questions[i].id); + } + else { + wrapper.appendChild(questionCard); + } + + if(QuestionGroup.Questions[i].questionsOnPage > 1) { + for(let l=QuestionGroup.Questions[i].questionsOnPage;l>0;l--) { + let questionCardToHide = document.getElementById("quiznavbutton"+(QuestionGroup.Questions[i].page+l+1)); + if(questionCardToHide != undefined) { + questionCardToHide.style.display = "none"; + } + } + } + + j++; + }; + + navPanel.appendChild(wrapper); + }); + + //put a flag instead of a skull, a number or an "i" in the very last question card + document.querySelectorAll(".qn_buttons span[data-is-for]:last-child a:last-child img").forEach(function(lastQuestionCard) { + lastQuestionCard.src = "https://marvin.hs-bochum.de/~mneugebauer/flag.svg"; + }); + + //show group navigation on instruction and update speech bubble navigation + let currentQuestion = this.getQuestion(); + if(!(currentQuestion instanceof Question) && currentQuestion instanceof Instruction) { + + let CurrentGroup; + for(let i in this.QuestionGroups) { + if(this.QuestionGroups[i].Questions[this.currentQuestionId] != undefined) { + CurrentGroup = this.QuestionGroups[i]; + break; + } + }; + + let groupNavigation = document.querySelector(".group-navigation"); + if(groupNavigation != undefined) { + //find current group + + if(CurrentGroup != undefined) { + + //show group navigation + let cards = document.querySelectorAll("[data-is-for="+CurrentGroup.id+"] a") + let cardAmount = cards.length; + let keys = Object.keys(CurrentGroup.Questions); + for(let i=cardAmount-1;i>=0;i--) { + let cardClone = cards[i].cloneNode(true); + let stepWrapper = document.createElement("div"); + stepWrapper.classList.add("wrap_nav_group"); + let stepHeadingAnchor = document.createElement("a"); + stepHeadingAnchor.href = cardClone.href; + let stepHeading = document.createElement("h2"); + stepHeading.innerHTML = CurrentGroup.Questions[keys[i]].description; + + stepHeadingAnchor.appendChild(stepHeading); + stepWrapper.appendChild(cardClone); + stepWrapper.appendChild(stepHeadingAnchor); + groupNavigation.appendChild(stepWrapper); + }; + + let groupNavCss = document.createElement("style");groupNavCss.type="text/css";groupNavCss.innerHTML = ".path-mod-quiz .group-navigation .qnbutton { text-decoration: none; font-size: 14px; line-height: 20px; font-weight: 400; background-color: #fff; background-image: none; height: 40px; width: 30px; border-radius: 3px; border: 0; overflow: visible; margin: 0 6px 6px 0;} .path-mod-quiz .group-navigation .qnbutton { background: none; background-color: rgba(0, 0, 0, 0); background-color: #eee; border: 0; border-radius: 4px; color: #000000 !important; font-size: 14px; font-weight: 700; height: 45px; line-height: 25px !important; margin: 0 5px 5px 0; width: 35px;} .group-navigation .qnbutton .thispageholder { border: 1px solid #999; border-radius: 4px; z-index: 1;}.group-navigation .qnbutton .thispageholder { border: 1px solid; border-radius: 3px; z-index: 1;}.group-navigation .qnbutton .trafficlight, group-navigation .qnbutton .thispageholder { display: block; position: absolute; top: 0; bottom: 0; left: 0; right: 0;} .path-mod-quiz .group-navigation .qnbutton.notyetanswered .trafficlight, .path-mod-quiz .group-navigation .qnbutton.invalidanswer .trafficlight { background-color: #fff;}.path-mod-quiz .group-navigation .qnbutton.notyetanswered .trafficlight, .path-mod-quiz .group-navigation .qnbutton.invalidanswer .trafficlight { background-color: #fff;}.path-mod-quiz .group-navigation .qnbutton .trafficlight { border: 0; background: #fff none center / 10px no-repeat scroll; height: 20px; margin-top: 20px; border-radius: 0 0 3px 3px;} .path-mod-quiz .group-navigation .qnbutton .trafficlight { background: #fff none center 4px / 10px no-repeat scroll; background-color: rgb(255, 255, 255); border: 0; border-radius: 0 0 4px 4px; height: 20px; margin-top: 25px;} .path-mod-quiz .group-navigation .qnbutton.correct .trafficlight { background-color: #8bc34a; background-image: url(/theme/image.php/adaptable/theme/1660635117/mod/quiz/checkmark); } .path-mod-quiz .group-navigation .qnbutton.notanswered .trafficlight, .path-mod-quiz .group-navigation .qnbutton.incorrect .trafficlight { background-color: #f44336; } .path-mod-quiz .group-navigation .qnbutton.partiallycorrect .trafficlight { background-color: #ff9800; background-image: url(/theme/image.php/adaptable/theme/1660635117/mod/quiz/whitecircle); } .path-mod-quiz .group-navigation .qnbutton.thispage .thispageholder { border: 3px solid #1f536b; } .wrap_nav_group { clear:left; }"; + document.getElementsByTagName("head")[0].appendChild(groupNavCss); + } + } + + let endbossLink = document.querySelector(".endboss-link"); + if(endbossLink != undefined) { + endbossLink.href = this.getPageURL(currentQuestion.onsuccess); + } + + if(CurrentGroup != undefined) { + let questionKeys = Object.keys(CurrentGroup.Questions); + let nextWorldLink = document.querySelector(".link-next-world"); + let nextWorldURL = this.getPageURL(CurrentGroup.Questions[questionKeys[questionKeys.length-1]].onsuccess); + + if(nextWorldLink != undefined) { + nextWorldLink.href = nextWorldURL; + } + + let endbossDefeat = CurrentGroup.Questions[questionKeys[questionKeys.length-1]].isSolved(); + let endbossStatePhrase = document.querySelector(".endboss-state"); + if(endbossStatePhrase != undefined) { + if(endbossDefeat == true) { + endbossStatePhrase.innerHTML = "bereits"; + } + /*else { + endbossStatePhrase.innerHTML = "noch nicht"; + }*/ + } + + let nextQuestionLink = document.querySelector(".link-next-question"); + if(nextQuestionLink != undefined) { + if(endbossDefeat == true) { + nextQuestionLink.innerHTML = "der nächsten Welt"; + nextQuestionLink.href = nextWorldURL; + //overwrite default next question + document.querySelector(".btn-next-question").href = nextWorldURL; + } + else { + let i; + let page; + for(i in CurrentGroup.Questions) { + if(!CurrentGroup.Questions[i].isSolved() && CurrentGroup.Questions[i] instanceof Question) { + page = CurrentGroup.Questions[i].page; + break; + } + } + //console.log(page); + nextQuestionLink.setAttribute("onclick", 'tutorialFocusElement(document.querySelector(\'.wrap_nav_group [data-quiz-page="'+page+'"]\'));'); + document.querySelector(".btn-next-question").href = this.getPageURL(i); + } + } + + let currentLevelPhrase = document.querySelector(".current-level"); + if(currentLevelPhrase != undefined) { + let amount = 0; + let solved = 0; + for(let i in CurrentGroup.Questions) { + if(CurrentGroup.Questions[i] instanceof Question) { + amount++; + if(CurrentGroup.Questions[i].isSolved()) { + solved++; + } + } + } + if(endbossDefeat == true) { + currentLevelPhrase.innerHTML = amount+" of "+amount; + } + else { + currentLevelPhrase.innerHTML = solved+" of "+amount; + } + } + } + + sessionStorage.setItem("camefrom", currentQuestion.id); + } + + } + + setCurrentQuestionId(id) { + this.currentQuestionId = id; + + //save question as visited, so next time on loading the script, question will carry the visited property with true + let visitedQuestionsAsString = sessionStorage.getItem("visited"); + if (visitedQuestionsAsString != undefined) { + let visitedQuestions = JSON.parse(visitedQuestionsAsString); + if (visitedQuestions != undefined && visitedQuestions.indexOf(id) < 0) { + visitedQuestions.push(id); + sessionStorage.setItem("visited", JSON.stringify(visitedQuestions)); + } + } else { + let visited = []; + sessionStorage.setItem("visited", JSON.stringify(visited)); + } + + } + + incrementSolved(id) { + if (id == undefined) { + id = this.currentQuestionId; + } + this.getQuestion(id).solved++; + this.getQuestion(id).currentlySolved++; + + if (this.getQuestion(id).isSolved()) { + let solvedQuestionsAsString = sessionStorage.getItem("solved"); + if (solvedQuestionsAsString != undefined) { + let solvedQuestions = JSON.parse(solvedQuestionsAsString); + if (solvedQuestions != undefined) { + if (solvedQuestions.indexOf(id) == -1) { + solvedQuestions.push(id); + sessionStorage.setItem("solved", JSON.stringify(solvedQuestions)); + } + } + } + } + + } +} + +let DefaultBubble = new BubbleInfo({}); + +let SynQuestions = []; +let FraQuestions = []; +let PQQuestions = []; +let RulQuestions = []; +let LogQuestions = []; +let TriQuestions = []; +let SurQuestions = []; + +let pythagorasPhrase = "The Pythagorean theorem states that the ancathete added to the square with the counter-cathete to the square gives the hypothenuse squared. In mathematical terms: \\(a^2+b^2=c^2\\;\\)."; + +SynQuestions.push(new Instruction("start_instructions", "Check-In Training Area", "syn_instructions", "syn_instructions")); +SynQuestions.push(new Instruction("syn_instructions", "Check-In Syntax", "syn_main", "syn_a1", new BubbleInfo({revisit:"You're back in the overview of this math world, which is all about <b>how to enter answers correctly</b>. Here you are in level <b class=\"current-level\">x of y</b>. You have <span class=\"endboss-state\">not yet</span> defeated the <a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.endboss-link'));\">final boss</a> in this world. Check out your progress below and jump to the question of your choice from here. From here you can also jump to the <a class=\"link-next-world\">next math world</a>. I would recommend you to continue with <a class=\"link-next-question\">the first unresolved question in this world</a>. Click on "<a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.submitbtns'));\">Next question</a>" to get there."}))); +SynQuestions.push(new Question("syn_a1", "Enter Equation ", 1, DefaultBubble, "syn_a2", "syn_a2", true)); +SynQuestions.push(new Question("syn_a2", "Enter Fraction & Division", 1, DefaultBubble, "syn_b1", "syn_b1", true)); +SynQuestions.push(new Question("syn_b1", "Powers", 1, DefaultBubble, "syn_b2", "syn_b2", true)); +SynQuestions.push(new Question("syn_b2", "Rational Expressions", 1, DefaultBubble, "syn_c", "syn_c", true)); +SynQuestions.push(new Question("syn_c", "Root Sign", 1, DefaultBubble, "syn_d", "syn_d", true)); +SynQuestions.push(new Question("syn_d", "Multiple Solutions", 1, DefaultBubble, "syn_e", "syn_e", true)); +SynQuestions.push(new Question("syn_e", "Solving equations in several steps", 1, DefaultBubble, "syn_f", "syn_f", true)); +SynQuestions.push(new Question("syn_f", "Greek Letters", 1, DefaultBubble, "syn_main", "syn_main")); +SynQuestions.push(new Question("syn_main", "Syntax Boss", 1, new BubbleInfo({success:"Very good! You have mastered the first world. You're ready for the next math world!", camebackaftersuccess:"Welcome back to the first task. With the knowledge you have collected along the way, can you now solve this task? Try again! Click on "Next question" to get to the next task."}), "fra_instructions", "syn_instructions")); +FraQuestions.push(new Instruction("fra_instructions", "Check-In Fractions & Binom. Formulas", "fra_main", "fra_a", new BubbleInfo({revisit:"You're back in the overview of the math world <b>Fractions & Binom. Formulas</b>. Here you are in level <b class=\"current-level\">x of y</b>. You have <span class=\"endboss-state\">not yet</span> defeated the <a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.endboss-link'));\">final boss</a> in this world. Check out your progress below and jump to the question of your choice from here. From here you can also jump to the <a class=\"link-next-world\">next math world</a>. I would recommend you to continue with <a class=\"link-next-question\">the first unresolved question in this world</a>. Click on "<a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.submitbtns'));\">Next question</a>" to get there."}))); +FraQuestions.push(new Question("fra_a", "Reduce Fraction", 1, DefaultBubble, "fra_b", "fra_b")); +FraQuestions.push(new Question("fra_b", "Add and Expand Fractions", 1, DefaultBubble, "fra_c", "fra_c")); +FraQuestions.push(new Question("fra_c", "Multiply Fractions", 1, DefaultBubble, "fra_d", "fra_d")); +FraQuestions.push(new Question("fra_d", "Divide Fractions", 1, DefaultBubble, "fra_e", "fra_e")); +FraQuestions.push(new Question("fra_e", "Compound Fraction", 1, DefaultBubble, "fra_f", "fra_f")); +FraQuestions.push(new Question("fra_f", "Fractions Interim Conclusion", 1, DefaultBubble, "bin_a", "bin_a")); +FraQuestions.push(new Question("bin_a", "First Binomial Formula", 1, DefaultBubble, "bin_b", "bin_b")); +FraQuestions.push(new Question("bin_b", "Second Binomial Formula", 1, DefaultBubble, "bin_c", "bin_c")); +FraQuestions.push(new Question("bin_c", "Third Binomial Formula", 1, DefaultBubble, "bin_main", "bin_main")); +FraQuestions.push(new Question("bin_main", "Application", 1, DefaultBubble, "fra_main", "fra_main")); +FraQuestions.push(new Question("fra_main", "Expand Fraction With Term", 1, DefaultBubble, "pq_instructions", "fra_instructions")); +/*FraQuestions.push(new Question("fra_main", "Bruch vereinfachen mithilfe binomischer Formeln ", 1, new BubbleInfo({camebackaftersuccess:"Willkommen zurück zur ersten Aufgabe. Kannst du mit dem Wissen, was du auf dem Weg gesammelt hast, diese Aufgabe jetzt lösen? Versuche es nochmal! Mit Klick auf "Nächste Frage" kommst du zur nächsten Aufgabe."}), "pq_instructions", "fra_instructions"));*/ +PQQuestions.push(new Instruction("pq_instructions", "Check-In p-q-Formel", "pq_main", "pq_a", new BubbleInfo({revisit:"You're back in the overview of the math world <b>Pq Formula</b>. Here you are in level <b class=\"current-level\">x of y</b>. You have <span class=\"endboss-state\">not yet</span> defeated the <a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.endboss-link'));\">final boss</a> in this world. Check out your progress below and jump to the question of your choice from here. From here you can also jump to the <a class=\"link-next-world\">next math world</a>. I would recommend you to continue with <a class=\"link-next-question\">the first unresolved question in this world</a>. Click on "<a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.submitbtns'));\">Next question</a>" to get there."}))); +PQQuestions.push(new Question("pq_a", "Reshap Term", 1, DefaultBubble, "pq_b", "pq_b")); +PQQuestions.push(new Question("pq_b", "Use Pq Formula", 1, DefaultBubble, "pq_main", "pq_main")); +PQQuestions.push(new Question("pq_main", "Boss ", 1, new BubbleInfo({camebackaftersuccess:"Welcome back to this task. Now, can you apply term reshaping and pq formula to solve this problem? Good luck!"}), "rul_instructions", "pq_instructions")); +RulQuestions.push(new Instruction("rul_instructions", "Check-In Power Laws", "rul_main", "rul_a", new BubbleInfo({revisit:"You're back in the overview of the math world <b>Power Laws</b>. Here you are in level <b class=\"current-level\">x of y</b>. You have <span class=\"endboss-state\">not yet</span> defeated the <a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.endboss-link'));\">final boss</a> in this world. Check out your progress below and jump to the question of your choice from here. From here you can also jump to the <a class=\"link-next-world\">next math world</a>. I would recommend you to continue with <a class=\"link-next-question\">the first unresolved question in this world</a>. Click on "<a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.submitbtns'));\">Next question</a>" to get there."}))); +RulQuestions.push(new Question("rul_a", "Add Powers", 1, DefaultBubble, "rul_b", "rul_b")); +RulQuestions.push(new Question("rul_b", "Subtract Powers", 1, DefaultBubble, "rul_c", "rul_c")); +RulQuestions.push(new Question("rul_c", "Representing Root As Potency", 1, DefaultBubble, "rul_d", "rul_d")); +RulQuestions.push(new Question("rul_d", "Multiply Powers", 1, DefaultBubble, "rul_e", "rul_e")); +RulQuestions.push(new Question("rul_e", "Use Power Laws", 1, DefaultBubble, "rul_main", "rul_main")); +RulQuestions.push(new Question("rul_main", "Boss", 1, new BubbleInfo({camebackaftersuccess:"Welcome back to the initial task on the power laws. Now you've learned every power calculation rule. Try again to solve this task. Click on "Next question" to get to the next task."}), "tri_instructions", "rul_instructions")); +/*LogQuestions.push(new Instruction("log_instructions", "Check-In Zinseszins und Logarithmus", "log_main", "log_a")); +LogQuestions.push(new Question("log_a", "Zinsrechnung ", 1, DefaultBubble, "log_b", "log_b")); +LogQuestions.push(new Question("log_b", "Zinseszinsrechnung ", 1, DefaultBubble, "log_c", "log_c")); +LogQuestions.push(new Question("log_c", "Logarithmus ", 1, DefaultBubble, "log_d", "log_d")); +LogQuestions.push(new Question("log_d", "Mithilfe von Logarithmus gesuchte Potenz bestimmen ", 1, DefaultBubble, "log_main", "log_main")); +LogQuestions.push(new Question("log_main", "Endboss ", 1, DefaultBubble, "tri_instructions", "log_instructions"));*/ + +TriQuestions.push(new Instruction("tri_instructions", "Check-In Trigonometry", "tri_main", "tri_a", new BubbleInfo({revisit:"You're back in the overview of the math world <b>Trigonometry</b>. Here you are in level <b class=\"current-level\">x of y</b>. You have <span class=\"endboss-state\">not yet</span> defeated the <a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.endboss-link'));\">final boss</a> in this world. Check out your progress below and jump to the question of your choice from here. From here you can also jump to the <a class=\"link-next-world\">next math world</a>. I would recommend you to continue with <a class=\"link-next-question\">the first unresolved question in this world</a>. Click on "<a href=\"javascript:;\" onclick=\"tutorialFocusElement(document.querySelector('.submitbtns'));\">Next question</a>" to get there."}))); +TriQuestions.push(new Question("tri_a", "Clarification of Triangle Terms", 3, new BubbleInfo({success:"Richtig!", failure:"False. Look at the graph below and repeat the task. Good luck!"}), "tri_b", "tri_b", true)); +TriQuestions.push(new Question("tri_b", "Pythagorean Theorem 1", 2, new BubbleInfo({success:"Correct! "+pythagorasPhrase, failure:"Falsch. "+pythagorasPhrase+" Feel free to try again. Good luck!"}), "tri_c", "tri_c")); +TriQuestions.push(new Question("tri_c", "Pythagorean Theorem 2 ", 2, new BubbleInfo({success:"Correct!", failure:"False. Feel free to try again. Good luck!"}), "tri_d", "tri_d")); +TriQuestions.push(new Question("tri_d", "Clarification of Trigonometric Functions", 3, new BubbleInfo({success:"Correct!", failure:"False. Feel free to try again. Good luck!"}), "tri_e", "tri_e")); +TriQuestions.push(new Question("tri_e", "Trigonometry", 1, DefaultBubble, "tri_f", "tri_f")); +TriQuestions.push(new Question("tri_f", "Reverse Trigonometric Functions 1", 1, DefaultBubble, "tri_g", "tri_g")); +TriQuestions.push(new Question("tri_g", "Reverse Trigonometric Functions 2", 1, DefaultBubble, "tri_main", "tri_main")); +TriQuestions.push(new Question("tri_main", "Endboss ", 1, new BubbleInfo({camebackaftersuccess:"Welcome back to this task. The last tasks were about competencies that you can use to solve this task. Can you solve them now? Good luck!"}), "sur_instructions"/*"_finish"*/, "tri_instructions")); + +SurQuestions.push(new Instruction("sur_instructions", "Start Survey", "sur_a", "sur_a", DefaultBubble)); +SurQuestions.push(new Question("sur_a", "Umfrage", 1, new BubbleInfo({beforeskip:"Please press "Check" before proceeding so that your answers to the survey are saved. Or do you want to skip the survey?"}), "_finsih", "_finish", true, 6)); + + +let AllQuestions = SynQuestions.concat(FraQuestions, PQQuestions, RulQuestions/*, LogQuestions*/, TriQuestions, SurQuestions); + +for (let i = 0; i < AllQuestions.length; i++) { + AllQuestions[i].page = i; +} + + +let QuestionGroups = []; +QuestionGroups.push(new QuestionGroup("syn", "Syntax", SynQuestions)); +QuestionGroups.push(new QuestionGroup("fra", "Fractions & Binom. Formulas", FraQuestions)); +QuestionGroups.push(new QuestionGroup("pq", "pq Formula", PQQuestions)); +QuestionGroups.push(new QuestionGroup("rul", "Power Laws", RulQuestions)); +//QuestionGroups.push(new QuestionGroup("log", "Zinsesinzs & Logarithmus", LogQuestions)); +QuestionGroups.push(new QuestionGroup("tri", "Trigonometry", TriQuestions)); +QuestionGroups.push(new QuestionGroup("sur", "Survey", SurQuestions)); + +let ALQuiz = new Quiz(QuestionGroups); + +let solvedQuestionsAsString = sessionStorage.getItem("solved"); +if (solvedQuestionsAsString != undefined) { + let solvedQuestions = JSON.parse(solvedQuestionsAsString); + if (solvedQuestions != undefined) { + solvedQuestions.forEach(function (solvedQuestion) { + let Question = ALQuiz.getQuestion(solvedQuestion); + Question.solved = Question.needs; + }); + } +} else { + let solved = []; + sessionStorage.setItem("solved", JSON.stringify(solved)); +} + +let visitedQuestionsAsString = sessionStorage.getItem("visited"); +if (visitedQuestionsAsString != undefined) { + let visitedQuestions = JSON.parse(visitedQuestionsAsString); + if (visitedQuestions != undefined) { + visitedQuestions.forEach(function (visitedQuestion) { + let Question = ALQuiz.getQuestion(visitedQuestion); + Question.visited = true; + }); + } +} else { + let visited = []; + sessionStorage.setItem("visited", JSON.stringify(visited)); +} + +document.addEventListener("DOMContentLoaded", function () { + + if (window.location.href.indexOf("review.php") < 0) { + + //add ask-before-skip modal + let modal = document.createElement("div"); + modal.classList.add("dmmodal"); + modal.addEventListener("click", function(event) { + if(!event.target.closest(".dmmodal-content")) { + this.style.display = "none"; + } + }); + let modalContent = document.createElement("div"); + modalContent.classList.add("dmmodal-content", "formulation"); + let closeSpan = document.createElement("span"); + closeSpan.classList.add("dmclose"); + closeSpan.innerHTML = "×"; + closeSpan.onclick = function() { this.closest(".dmmodal").style.display="none"; }; + let contentParagraph = document.createElement("p"); + let modalBubble = document.createElement("p"); + modalBubble.classList.add("bubble", "in-modal"); + modalBubble.innerHTML = "Are you sure to skip this question? "; + if(ALQuiz != undefined && ALQuiz.getQuestion() != undefined && ALQuiz.getQuestion().BubbleInfo != undefined && ALQuiz.getQuestion().BubbleInfo.getText("beforeskip") != undefined) { + modalBubble.innerHTML = ALQuiz.getQuestion().BubbleInfo.getText("beforeskip"); + } + modalBubble.style.marginTop = "50px"; + let dmicon = document.createElement("img"); + dmicon.classList.add("dm-icon"); + dmicon.src="https://marvin.hs-bochum.de/~mneugebauer/dm-avatar-grin.svg"; + let yesButton = document.createElement("a"); + yesButton.classList.add("skip-yes"); + yesButton.href = "javascript:;"; + yesButton.innerHTML = "Yes"; + let noButton = document.createElement("a"); + noButton.classList.add("skip-no"); + noButton.href = "javascript:;"; + noButton.innerHTML = "No"; + noButton.onclick = function() { this.closest(".dmmodal").style.display="none"; }; + + modalBubble.appendChild(yesButton); + modalBubble.innerHTML += " "; + modalBubble.appendChild(noButton); + + modalContent.appendChild(closeSpan); + modalContent.appendChild(modalBubble); + modalContent.appendChild(dmicon); + modalContent.appendChild(contentParagraph); + modal.appendChild(modalContent); + document.body.appendChild(modal); + + //in development state: interrupt button update to give the main page time to recognize the inline scripts like increment solved and set current question id + /*setTimeout('*/ALQuiz.updateSpeechBubbles(); ALQuiz.updateMoodleNavButtons(); ALQuiz.updateNavigation(); /*', 500)*/; + } + + + //add styles, e. g. speech bubble + let style = document.createElement("style"); + style.type = "text/css"; + //.que .outcome background-color is #fcefdc; + style.innerHTML = ".bubble { /* layout*/ position: relative; max-width: 30em; /* looks*/ background-color: #fcefdc; padding: 1.125em 1.5em; font-size: 1.25em; border-radius: 1rem; box-shadow: 0 0.125rem 0.5rem rgba(0, 0, 0, .3), 0 0.0625rem 0.125rem rgba(0, 0, 0, .2); } .bubble:not(.no-arrow)::before { /* layout*/ content: ''; position: absolute; width: 0; height: 0; top: 100%; left: 1.5em; /* offset should move with padding of parent*/ border: .75rem solid transparent; border-bottom: none; /* looks*/ border-top-color: #fcefdc; filter: drop-shadow(0 0.0625rem 0.0625rem rgba(0, 0, 0, .1)); } .formulation a { text-decoration:underline; } .mathsinput { position:fixed; display:flex; width:100vw; bottom:0px; z-index:1; } .mathsinput button { flex-grow:1; } table.trigonometry_table { border:1px solid black;width:100%; } table.trigonometry_table th, td { border:1px solid black;text-align:center; } .dm-icon { border:2px solid black; border-radius:50%; width: 7.5em; } .user-focus { background-color: #000; width: 100%; height: 100%; position: absolute; opacity: 0.5; overflow: none; display: block; left: 0; } .user-focus.hide-top { top:0; } .user-focus.hide-bottom { bottom:0; } .dmmodal { display: none; /* Hidden by default */ position: fixed; /* Stay in place */ z-index: 1; /* Sit on top */ padding-top: 100px; /* Location of the box */ left: 0; top: 0; width: 100%; /* Full width */ height: 100%; /* Full height */ overflow: auto; /* Enable scroll if needed */ background-color: rgb(0,0,0); /* Fallback color */ background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ } /* Modal Content */ .dmmodal-content { background-color: #fefefe; margin: auto; padding: 20px; border: 1px solid #888; width: 80%; } /* The Close Button */ .dmclose { color: #aaaaaa; float: right; font-size: 28px; font-weight: bold; } .close:hover, .close:focus { color: #000; text-decoration: none; cursor: pointer; } .mathsinput { position:fixed; display:flex; width:100vw; bottom:0px; z-index:1; } .mathsinput button { flex-grow:1; } .show-on-mobile-only { display:inline-block; } .show-on-desktop-only { display:none; } @media (min-width:991px) { .mathsinput { display:none; } .show-on-mobile-only { display:none; } .show-on-desktop-only { display:inline-block; } } .mathsbutton { border:1px solid black; border-radius:50%; height:2em; width:2em; float:right; background-color:#aaaaaa; background-image:url(\"https://marvin.hs-bochum.de/~mneugebauer/operators-white.svg\"); background-repeat:no-repeat; background-size:90%; background-position:50%; } .mathsbutton.active { background-color:#e2001a; }"; + document.getElementsByTagName('head')[0].appendChild(style); + + /*let butt = document.createElement("input"); + butt.value = "Try another question like this"; + butt.classList.add("btn"); + butt.classList.add("btn-secondary"); + butt.type = "submit"; + butt.name = "redoslot" + ALQuiz.getQuestion().page; + document.getElementById("responseform").appendChild(butt);*/ + + //show additional maths input if neccessary + document.querySelectorAll("input, textarea").forEach(function(inputElement) { + inputElement.addEventListener("focus", function() { + lastFocusedInputElement = this; + }); + }); + + let mathsInput = ["+", "-", "*", "/", "(", ")", "^", "="]; + let mathsButtons = []; + mathsInput.forEach(function(mathsInputSymbol) { + let button = document.createElement("button"); + button.type = "button"; + button.innerHTML = mathsInputSymbol; + button.onclick = function() { + if (!lastFocusedInputElement) { + console.log("no focused element to enter maths-symbol"); + return; + } + let caretPos = lastFocusedInputElement.selectionStart; + let currentContent = lastFocusedInputElement.value; + lastFocusedInputElement.value = currentContent.substring(0, caretPos) + mathsInputSymbol + currentContent.substring(caretPos); + lastFocusedInputElement.focus(); + lastFocusedInputElement.setSelectionRange(caretPos + 1, caretPos + 1); + }; + mathsButtons.push(button); + }); + + let mathsInputButtonDiv = document.createElement("div"); + mathsInputButtonDiv.classList.add("mathsinput"); + + mathsButtons.forEach(function(mathsButton) { + mathsInputButtonDiv.appendChild(mathsButton); + }); + + document.body.appendChild(mathsInputButtonDiv); + + //safari hack to show mathsinput at correct position + addSafariMathsInputAboveKeyboardSupport(); + + + addMathsOperatorButton(); +}); + +document.addEventListener("load", function () { + //ALQuiz.updateSpeechBubbles(); +}); + +function tutorialFocusElement(elem) { + if(!elem) { + console.log("element to focus not found"); + return; + } + + let userFocusTop = document.createElement("div"); + userFocusTop.classList.add("user-focus", "hide-top"); + userFocusTop.onclick = removeTutorialFocus; + let userFocusBottom = document.createElement("div"); + userFocusBottom.classList.add("user-focus", "hide-bottom"); + userFocusBottom.onclick = removeTutorialFocus; + + let position = elem.getBoundingClientRect(elem); + userFocusTop.style.height=Math.ceil(position.top+window.pageYOffset-20)+"px"; + + let pageHeight = document.documentElement.scrollHeight; + userFocusBottom.style.top = Math.ceil(position.bottom+window.pageYOffset+20)+"px"; + userFocusBottom.style.height=Math.ceil(pageHeight-(position.bottom+window.pageYOffset+20))+"px"; + + document.body.appendChild(userFocusTop); + document.body.appendChild(userFocusBottom); + + if(supportsSmoothScrolling() == true) { + elem.scrollIntoView({behavior:"smooth", block:"center", inline:"center"}); + } + else { + safariScrollTo(elem); + } +} + +function removeTutorialFocus() { + document.querySelectorAll(".user-focus").forEach(function(userFocusElement) { + userFocusElement.parentNode.removeChild(userFocusElement); + }); +} + +function repeatQuestion() { + document.querySelector('.mod_quiz-redo_question_button').click(); +} + +function showAskBeforeSkipModal() { + document.querySelector(".dmmodal").style.display = "block"; +} + +/*function addMathsOperatorButton() { + document.querySelectorAll(".formulation.clearfix input[type=text], textarea").forEach(function(inputElement) { let mathsButton = document.createElement("img"); mathsButton.src = "https://marvin.hs-bochum.de/~mneugebauer/operators.svg"; + mathsButton.addEventListener("click", function(event) { + event.preventDefault(); + //document.querySelector(".mathsinput").classList.toggle("hide"); + let mathsInput = document.querySelector(".mathsinput"); + if(mathsInput.style.display == "none") { + mathsInput.style.display = "flex"; + } + else { + mathsInput.style.display = "none"; + } + }); + inputElement.parentNode.appendChild(mathsButton); }); +}*/ + +function addMathsOperatorButton() { + document.querySelectorAll(".formulation.clearfix").forEach(function(questionElement) { + let mathsButtonDiv = document.createElement("div"); + mathsButtonDiv.classList.add("mathsbutton"); + + let mathsInput = document.querySelector(".mathsinput"); + let display = window.getComputedStyle(mathsInput).display; + if(display == "flex") { + mathsButtonDiv.classList.add("active"); + } + + mathsButtonDiv.addEventListener("click", function() { + //document.querySelector(".mathsinput").classList.toggle("hide"); + //let mathsInput = document.querySelector(".mathsinput"); + if(!mathsInput.style.display || mathsInput.style.display == "") { + display = window.getComputedStyle(mathsInput).display; + } + else { + display = mathsInput.style.display; + } + + if(display == "none") { + mathsInput.style.display = "flex"; + this.classList.add("active"); + } + else { + mathsInput.style.display = "none"; + this.classList.remove("active"); + } + }); + //mathsButtonDiv.appendChild(mathsButton); + questionElement.appendChild(mathsButtonDiv); + }); +} + + +//SAFARI HACKS BELOW +function addSafariMathsInputAboveKeyboardSupport() { + //alert("checking for safari"); + let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); + if(isSafari == true) { + + //alert("You are using Safari"); + + //sadly not working solution from stackoverflow + //style.innerHTML = ".testclass, .mathsinput { display:none; bottom:270px; } @media screen and (min-aspect-ratio:11/16) { .testclass, .mathsinput { display:none; } }"; + document.querySelectorAll("input.algebraic").forEach(function(inputElement) { + //console.log(inputElement); + inputElement.onfocus = function() { + let mathsInput = document.querySelector(".mathsinput"); + mathsInput.style.position = "absolute"; + mathsInput.style.bottom = "auto"; + let position = this.getBoundingClientRect(); + mathsInput.style.top = Math.ceil(position.bottom+window.pageYOffset)+"px"; + //console.log(position); + } + }); + } +} + +function supportsSmoothScrolling() { + let body = document.body; + let scrollSave = body.style.scrollBehavior; + body.style.scrollBehavior = 'smooth'; + let hasSmooth = getComputedStyle(body).scrollBehavior === 'smooth'; + body.style.scrollBehavior = scrollSave; + return hasSmooth; +}; + +//thanks to Jeff Starr from https://perishablepress.com/vanilla-javascript-scroll-anchor/ +function safariScrollTo(elem){ + + if(elem == undefined) { + return; + } + let position = elem.getBoundingClientRect(); + let to = (position.top+window.pageYOffset)-window.screen.availHeight/2; + //console.log(to); + + var i = parseInt(window.pageYOffset); + //console.log(i); + if ( i != to ) { + to = parseInt(to); + if (i < to) { + var int = setInterval(function() { + if (i > (to-20)) i += 1; + else if (i > (to-40)) i += 3; + else if (i > (to-80)) i += 8; + else if (i > (to-160)) i += 18; + else if (i > (to-200)) i += 24; + else if (i > (to-300)) i += 40; + else i += 60; + window.scroll(0, i); + if (i >= to) clearInterval(int); + }, 15); + } + else { + var int = setInterval(function() { + if (i < (to+20)) i -= 1; + else if (i < (to+40)) i -= 3; + else if (i < (to+80)) i -= 8; + else if (i < (to+160)) i -= 18; + else if (i < (to+200)) i -= 24; + else if (i < (to+300)) i -= 40; + else i -= 60; + window.scroll(0, i); + if (i <= to) clearInterval(int); + }, 15); + } + } +}; + +///END AL QUIZ SCRIPT/// \ No newline at end of file