${stackstring['teacheranswershow_mcq']} \\[{${input.samplesolutionrender}}\\], ${stackstring['api_which_typed']}: `; for (const [name, solution] of Object.entries(input.samplesolution)) { if (name.indexOf('_val') === -1) { correctAnswers += `${solution}`; } } correctAnswers += '.
'; } else if (name !== 'remember') { // For dropdowns, radio buttons, etc, only the correct option is displayed. for (const solution of Object.values(input.samplesolution)) { if (input.configuration.options) { correctAnswers += `${input.configuration.options[solution]}
`; } } } } // Convert Moodle plot filenames to API filenames. 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; // Only display results sections once question retrieved. document.getElementById(`${qprefix+'stackapi_qtext'}`).style.display = 'block'; document.getElementById(`${qprefix+'stackapi_correct'}`).style.display = 'block'; // Setup a validation call on inputs. Timeout length is reset if the input is updated // before the validation call is made. 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), 1000); }; } } let sampleText = json.questionsamplesolutiontext; if (sampleText) { sampleText = replaceFeedbackTags(sampleText,qprefix); document.getElementById(`${qprefix+'generalfeedback'}`).innerHTML = sampleText; document.getElementById(`${qprefix+'stackapi_generalfeedback'}`).style.display = 'block'; } else { // If the question is updated, there may no longer be general feedback. 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; // Hide General feedback and correct answers for now document.getElementById(`${qprefix+'stackapi_generalfeedback'}`).style.display = 'none'; document.getElementById(`${qprefix+'stackapi_correct'}`).style.display = 'none'; createIframes(json.iframes); MathJax.Hub.Queue(["Typeset", MathJax.Hub]); } 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' }); }; }); } // Validate an input. Called a set amount of time after an input is last updated. function validate(element, qfile, qname, qprefix) { const http = new XMLHttpRequest(); const url = stack_api_url + '/validate'; http.open("POST", url, true); // Remove API prefix and subanswer id. 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 element = document.getElementsByName(`${qprefix+validationPrefix + answerName}`)[0]; element.innerHTML = validationHTML; if (validationHTML) { element.classList.add('validation'); } else { element.classList.remove('validation'); } createIframes(json.iframes); MathJax.Hub.Queue(["Typeset", MathJax.Hub]); } catch(e) { document.getElementById(`${qprefix+'errors'}`).innerText = http.responseText; return; } } }; collectData(qfile, qname, qprefix).then((data)=>{ data.inputName = answerName; http.send(JSON.stringify(data)); }); } // Submit answers. 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; // Show General feedback and correct answers, hide summary document.getElementById(`${qprefix+'stackapi_generalfeedback'}`).style.display = 'block'; document.getElementById(`${qprefix+'stackapi_summary'}`).style.display = 'none'; const feedback = json.prts; const specificFeedbackElement = document.getElementById(`${qprefix+'specificfeedback'}`); // Replace tags and plots in specific feedback and then display. 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); specificFeedbackElement.innerHTML = json.specificfeedback; specificFeedbackElement.classList.add('feedback'); } else { specificFeedbackElement.classList.remove('feedback'); } // Replace plots in tagged feedback and then display. for (let [name, fb] of Object.entries(feedback)) { for (const [name, file] of Object.entries(json.gradingassets)) { fb = fb.replace(name, getPlotUrl(file)); } const elements = document.getElementsByName(`${qprefix+feedbackPrefix + name}`); if (elements.length > 0) { const element = elements[0]; if (json.scores[name] !== undefined) { fb = fb + `