const timeOutHandler = new Object(); const inputPrefix = "stackapi_input_"; const feedbackPrefix = "stackapi_fb_"; const validationPrefix = "stackapi_val_"; const stackstring = { "teacheranswershow_mcq": "A correct answer is: {$a->display}", "api_which_typed": "which can be typed as follows", "api_valid_all_parts": "Please enter valid answers for all parts of the question.", "api_out_of": "out of", "api_marks_sub": "Marks for this submission", "api_submit": "Submit Answers", "generalfeedback": "General feedback", "score": "Score", "api_response": "Response summary", "api_correct": "Correct answers" }; function wrap_math(content) { content = content.replace(/(?$1'); return content.replace(/(?$1'); } async function collectData(qfile, qname, qprefix) { let res = ""; await getQuestionFile(qfile, qname).then((response) => { if (response.questionxml != "\nnull\n") { res = { questionDefinition: response.questionxml, answers: collectAnswer(qprefix), seed: response.seed, renderInputs: qprefix + inputPrefix, readOnly: false }; } ; }); return res; } function collectAnswer(qprefix) { const inputs = document.getElementsByTagName("input"); const textareas = document.getElementsByTagName("textarea"); const selects = document.getElementsByTagName("select"); let res = {}; res = processNodes(res, inputs, qprefix); res = processNodes(res, textareas, qprefix); res = processNodes(res, selects, qprefix); return res; } function processNodes(res, nodes, qprefix) { for (let i = 0; i < nodes.length; i++) { const element = nodes[i]; if (element.name.indexOf(qprefix + inputPrefix) === 0 && element.name.indexOf("_val") === -1) { if (element.type === "checkbox" || element.type === "radio") { if (element.checked) { res[element.name.slice((qprefix + inputPrefix).length)] = element.value; } } else { res[element.name.slice((qprefix + inputPrefix).length)] = element.value; } } } return res; } function send(qfile, qname, qprefix) { const http = new XMLHttpRequest(); const url = stack_api_url + "/render"; http.open("POST", url, true); http.setRequestHeader("Content-Type", "application/json"); http.onreadystatechange = function() { if (http.readyState == 4) { try { const json = JSON.parse(http.responseText); if (json.message) { console.log(json); document.getElementById(`${qprefix + "errors"}`).innerText = json.message; return; } else { document.getElementById(`${qprefix + "errors"}`).innerText = ""; } renameIframeHolders(); let question = json.questionrender; const inputs = json.questioninputs; const seed = json.questionseed; let correctAnswers = ""; for (const [name, input] of Object.entries(inputs)) { question = question.replace(`[[input:${name}]]`, input.render); question = question.replace(`[[validation:${name}]]`, ``); question = question.replace(/javascript:download\(([^,]+?),([^,]+?)\)/, `javascript:download($1,$2, '${qfile}', '${qname}', '${qprefix}', ${seed})`); question = wrap_math(question); if (input.samplesolutionrender && name !== "remember") { correctAnswers += `

${stackstring["teacheranswershow_mcq"]} \\[{${input.samplesolutionrender}}\\], ${stackstring["api_which_typed"]}: `; for (const [name2, solution] of Object.entries(input.samplesolution)) { if (name2.indexOf("_val") === -1) { correctAnswers += `${wrap_math(solution)}`; } } correctAnswers += ".

"; } else if (name !== "remember") { for (const solution of Object.values(input.samplesolution)) { if (input.configuration.options) { correctAnswers += `

${input.configuration.options[solution]}

`; } } } } for (const [name, file] of Object.entries(json.questionassets)) { const plotUrl = getPlotUrl(file); question = question.replace(name, plotUrl); json.questionsamplesolutiontext = json.questionsamplesolutiontext.replace(name, plotUrl); correctAnswers = correctAnswers.replace(name, plotUrl); } question = replaceFeedbackTags(question, qprefix); qoutput = document.getElementById(`${qprefix + "output"}`); qoutput.innerHTML = question; document.getElementById(`${qprefix + "stackapi_qtext"}`).style.display = "block"; document.getElementById(`${qprefix + "stackapi_correct"}`).style.display = "block"; for (const inputName of Object.keys(inputs)) { const inputElements = document.querySelectorAll(`[name^=${qprefix + inputPrefix + inputName}]`); for (const inputElement of Object.values(inputElements)) { inputElement.oninput = (event) => { const currentTimeout = timeOutHandler[event.target.id]; if (currentTimeout) { window.clearTimeout(currentTimeout); } timeOutHandler[event.target.id] = window.setTimeout(validate.bind(null, event.target, qfile, qname, qprefix), 1e3); }; } } let sampleText = json.questionsamplesolutiontext; if (sampleText) { sampleText = replaceFeedbackTags(sampleText, qprefix); document.getElementById(`${qprefix + "generalfeedback"}`).innerHTML = wrap_math(sampleText); document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "block"; } else { document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "none"; } document.getElementById(`${qprefix + "stackapi_score"}`).style.display = "none"; document.getElementById(`${qprefix + "stackapi_validity"}`).innerText = ""; const innerFeedback = document.getElementById(`${qprefix + "specificfeedback"}`); innerFeedback.innerHTML = ""; innerFeedback.classList.remove("feedback"); document.getElementById(`${qprefix + "formatcorrectresponse"}`).innerHTML = correctAnswers; document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "none"; document.getElementById(`${qprefix + "stackapi_correct"}`).style.display = "none"; createIframes(json.iframes); MathJax.typeset(); } catch (e) { console.log(e); document.getElementById(`${qprefix + "errors"}`).innerText = http.responseText; return; } } }; collectData(qfile, qname, qprefix).then((data) => { let submitbutton = document.getElementById(`${qprefix + "stackapi_qtext"}`).querySelector('input[type="button"]'); submitbutton.addEventListener("click", function() { answer(qfile, qname, qprefix, data.seed); }); http.send(JSON.stringify(data)); let questioncontainer = document.getElementById(`${qprefix + "stack"}`).parentElement; if (questioncontainer.getBoundingClientRect().top < 0) { questioncontainer.scrollIntoView({ behavior: "smooth", block: "start" }); } ; }); } function validate(element, qfile, qname, qprefix) { const http = new XMLHttpRequest(); const url = stack_api_url + "/validate"; http.open("POST", url, true); const answerNamePrefixTrim = (qprefix + inputPrefix).length; const answerName = element.name.slice(answerNamePrefixTrim).split("_", 1)[0]; http.setRequestHeader("Content-Type", "application/json"); http.onreadystatechange = function() { if (http.readyState == 4) { try { const json = JSON.parse(http.responseText); if (json.message) { document.getElementById(`${qprefix + "errors"}`).innerText = json.message; return; } else { document.getElementById(`${qprefix + "errors"}`).innerText = ""; } renameIframeHolders(); const validationHTML = json.validation; const element2 = document.getElementsByName(`${qprefix + validationPrefix + answerName}`)[0]; element2.innerHTML = wrap_math(validationHTML); if (validationHTML) { element2.classList.add("validation"); } else { element2.classList.remove("validation"); } createIframes(json.iframes); MathJax.typeset(); } catch (e) { document.getElementById(`${qprefix + "errors"}`).innerText = http.responseText; return; } } }; collectData(qfile, qname, qprefix).then((data) => { data.inputName = answerName; http.send(JSON.stringify(data)); }); } function answer(qfile, qname, qprefix, seed) { const http = new XMLHttpRequest(); const url = stack_api_url + "/grade"; http.open("POST", url, true); if (!document.getElementById(`${qprefix + "output"}`).innerText) { return; } http.setRequestHeader("Content-Type", "application/json"); http.onreadystatechange = function() { if (http.readyState == 4) { try { const json = JSON.parse(http.responseText); if (json.message) { document.getElementById(`${qprefix + "errors"}`).innerText = json.message; return; } else { document.getElementById(`${qprefix + "errors"}`).innerText = ""; } if (!json.isgradable) { document.getElementById(`${qprefix + "stackapi_validity"}`).innerText = " " + stackstring["api_valid_all_parts"]; return; } renameIframeHolders(); document.getElementById(`${qprefix + "score"}`).innerText = (json.score * json.scoreweights.total).toFixed(2) + " " + stackstring["api_out_of"] + " " + json.scoreweights.total; document.getElementById(`${qprefix + "stackapi_score"}`).style.display = "block"; document.getElementById(`${qprefix + "response_summary"}`).innerText = json.responsesummary; document.getElementById(`${qprefix + "stackapi_generalfeedback"}`).style.display = "block"; document.getElementById(`${qprefix + "stackapi_summary"}`).style.display = "none"; const feedback = json.prts; const specificFeedbackElement2 = document.getElementById(`${qprefix + "specificfeedback"}`); if (json.specificfeedback) { for (const [name, file] of Object.entries(json.gradingassets)) { json.specificfeedback = json.specificfeedback.replace(name, getPlotUrl(file)); } json.specificfeedback = replaceFeedbackTags(json.specificfeedback, qprefix); specificFeedbackElement2.innerHTML = wrap_math(json.specificfeedback); specificFeedbackElement2.classList.add("feedback"); } else { specificFeedbackElement2.classList.remove("feedback"); } for (let [name, fb] of Object.entries(feedback)) { for (const [name2, file] of Object.entries(json.gradingassets)) { fb = fb.replace(name2, getPlotUrl(file)); } const elements = document.getElementsByName(`${qprefix + feedbackPrefix + name}`); if (elements.length > 0) { const element = elements[0]; if (json.scores[name] !== void 0) { fb = fb + `
${stackstring["api_marks_sub"]}: ${(json.scores[name] * json.scoreweights[name] * json.scoreweights.total).toFixed(2)} / ${(json.scoreweights[name] * json.scoreweights.total).toFixed(2)}.
`; } element.innerHTML = wrap_math(fb); } } createIframes(json.iframes); MathJax.typeset(); } catch (e) { console.log(e); document.getElementById(`${qprefix + "errors"}`).innerText = http.responseText; return; } } }; const specificFeedbackElement = document.getElementById(`${qprefix + "specificfeedback"}`); specificFeedbackElement.innerHTML = ""; specificFeedbackElement.classList.remove("feedback"); document.getElementById(`${qprefix + "response_summary"}`).innerText = ""; document.getElementById(`${qprefix + "stackapi_summary"}`).style.display = "none"; const inputElements = document.querySelectorAll(`[name^=${qprefix + feedbackPrefix}]`); for (const inputElement of Object.values(inputElements)) { inputElement.innerHTML = ""; inputElement.classList.remove("feedback"); } document.getElementById(`${qprefix + "stackapi_score"}`).style.display = "none"; document.getElementById(`${qprefix + "stackapi_validity"}`).innerText = ""; collectData(qfile, qname, qprefix).then((data) => { data.seed = seed; http.send(JSON.stringify(data)); }); } function download(filename, fileid, qfile, qname, qprefix, seed) { const http = new XMLHttpRequest(); const url = stack_api_url + "/download"; http.open("POST", url, true); http.setRequestHeader("Content-Type", "application/json"); http.filename = filename; http.fileid = fileid; http.onreadystatechange = function() { if (http.readyState == 4) { try { const blob = new Blob([http.responseText], { type: "application/octet-binary", endings: "native" }); const selector = CSS.escape(`javascript:download('${http.filename}', ${http.fileid}, '${qfile}', '${qname}', '${qprefix}', ${seed})`); const linkElements = document.querySelectorAll(`a[href^=${selector}]`); const link = linkElements[0]; link.setAttribute("href", URL.createObjectURL(blob)); link.setAttribute("download", filename); link.click(); } catch (e) { document.getElementById("errors").innerText = http.responseText; return; } } }; collectData(qfile, qname, qprefix).then((data) => { data.filename = filename; data.fileid = fileid; data.seed = seed; http.send(JSON.stringify(data)); }); } function saveState(key, value) { if (typeof Storage !== "undefined") { localStorage.setItem(key, value); } } function loadState(key) { if (typeof Storage !== "undefined") { return localStorage.getItem(key) || ""; } return ""; } function renameIframeHolders() { for (const iframe of document.querySelectorAll(`[id^=stack-iframe-holder]:not([id$=old]`)) { iframe.id = iframe.id + "_old"; } } function createIframes(iframes) { const corsFragment = "/cors.php?name="; for (const iframe of iframes) { create_iframe( iframe[0], iframe[1].replaceAll(corsFragment, stack_api_url + corsFragment), ...iframe.slice(2) ); } } function replaceFeedbackTags(text, qprefix) { let result = text; const feedbackTags = text.match(/\[\[feedback:.*?\]\]/g); if (feedbackTags) { for (const tag of feedbackTags) { result = result.replace(tag, `
`); } } return result; } async function getQuestionFile(questionURL, questionName) { let res = ""; if (questionURL) { await fetch(questionURL).then((result) => result.text()).then((result) => { res = loadQuestionFromFile(result, questionName); }); } return res; } function loadQuestionFromFile(fileContents, questionName) { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(fileContents, "text/xml"); let thequestion = null; let randSeed = ""; for (const question of xmlDoc.getElementsByTagName("question")) { if (question.getAttribute("type").toLowerCase() === "stack" && (!questionName || question.querySelectorAll("name text")[0].textContent === questionName)) { thequestion = question.outerHTML; let seeds = question.querySelectorAll("deployedseed"); if (seeds.length) { randSeed = parseInt(seeds[Math.floor(Math.random() * seeds.length)].textContent); } break; } } return { questionxml: setQuestion(thequestion), seed: randSeed }; } function setQuestion(question) { return "\n" + question + "\n"; } function createQuestionBlocks() { questionBlocks = document.getElementsByClassName("que stack"); let i = 0; for (questionblock of questionBlocks) { i++; let questionPrefix = "q" + i.toString() + "_"; var qfile = questionblock.dataset.qfile; var qname = questionblock.dataset.qname || ""; questionblock.innerHTML = `
`; } } function addCollapsibles() { var collapsibles = document.querySelectorAll(".level2>h2, .stack>h2"); for (let i = 0; i < collapsibles.length; i++) { collapsibles[i].addEventListener("click", () => collapseFunc(this)); } } function collapseFunc(e) { e.classList.toggle("collapsed"); } function stackSetup() { createQuestionBlocks(); addCollapsibles(); } function getPlotUrl(file) { return `${stack_api_url}/plots/${file}`; } //# sourceMappingURL=stackapicalls.js.map