diff --git a/amd/build/input.min.js b/amd/build/input.min.js index 34aa4116ff7..3a6bf42ba2d 100644 --- a/amd/build/input.min.js +++ b/amd/build/input.min.js @@ -19,6 +19,6 @@ * @copyright 2018 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("qtype_stack/input",["core/ajax","core/event"],(function(Ajax,CustomEvents){function StackInput(validationDiv,prefix,qaid,name,input){var TYPING_DELAY=1e3,delayTimeoutHandle=null,validationResults={},lastValidatedValue=getInputValue();function cancelTypingDelay(){delayTimeoutHandle&&clearTimeout(delayTimeoutHandle),delayTimeoutHandle=null}function valueChanging(){cancelTypingDelay(),showWaiting(),delayTimeoutHandle=setTimeout(valueChanged,TYPING_DELAY),setTimeout((function(){checkNoChange()}),0)}function checkNoChange(){getInputValue()===lastValidatedValue&&(cancelTypingDelay(),validationDiv.classList.remove("waiting"))}function valueChanged(){cancelTypingDelay(),showValidationResults()||validateInput()}function validateInput(){Ajax.call([{methodname:"qtype_stack_validate_input",args:{qaid:qaid,name:name,input:getInputValue()},done:function(response){validationReceived(response)},fail:function(response){showValidationFailure(response)}}]),showLoading()}function getInputValue(){return input.getValue()}function validationReceived(response){"invalid"!==response.status?(validationResults[response.input]=response,showValidationResults()):showValidationFailure(response)}function extractScripts(html,scriptCommands){for(var result,scriptregexp=/]*>([\s\S]*?)<\/script>/g;null!==(result=scriptregexp.exec(html));)scriptCommands.push(result[1]);return html.replace(scriptregexp,"")}function showValidationResults(){var val=getInputValue();if(!validationResults[val])return showWaiting(),!1;var results=validationResults[val];lastValidatedValue=val;var scriptCommands=[];validationDiv.innerHTML=extractScripts(results.message,scriptCommands);for(var i=0;i")}}function StackRadioInput(container){this.addEventHandlers=function(valueChanging){container.addEventListener("input",valueChanging)},this.getValue=function(){var selected=container.querySelector(":checked");return selected?selected.value:""}}function StackCheckboxInput(container){this.addEventHandlers=function(valueChanging){container.addEventListener("input",valueChanging)},this.getValue=function(){for(var selected=container.querySelectorAll(":checked"),result=[],i=0;i0?result.join(","):""}}function StackMatrixInput(idPrefix,container){var numcol=0,numrow=0;container.querySelectorAll("input[type=text]").forEach((function(element){if(element.name.slice(0,idPrefix.length+5)===idPrefix+"_sub_"){var bits=element.name.substring(idPrefix.length+5).split("_");numrow=Math.max(numrow,parseInt(bits[0],10)+1),numcol=Math.max(numcol,parseInt(bits[1],10)+1)}})),this.addEventHandlers=function(valueChanging){container.addEventListener("input",valueChanging)},this.getValue=function(){for(var values=new Array(numrow),i=0;i]*>([\s\S]*?)<\/script>/g;null!==(result=scriptregexp.exec(html));)scriptCommands.push(result[1]);return html.replace(scriptregexp,"")}function showValidationResults(){var val=getInputValue();if(!validationResults[val])return showWaiting(),!1;var results=validationResults[val];lastValidatedValue=val;var scriptCommands=[];validationDiv.innerHTML=extractScripts(results.message,scriptCommands);for(var i=0;i")}}function StackRadioInput(container){this.addEventHandlers=function(valueChanging){container.addEventListener("input",valueChanging)},this.getValue=function(){var selected=container.querySelector(":checked");return selected?selected.value:""}}function StackCheckboxInput(container){this.addEventHandlers=function(valueChanging){container.addEventListener("input",valueChanging)},this.getValue=function(){for(var selected=container.querySelectorAll(":checked"),result=[],i=0;i0?result.join(","):""}}function StackMatrixInput(idPrefix,container){var numcol=0,numrow=0;container.querySelectorAll("input[type=text]").forEach((function(element){if(element.name.slice(0,idPrefix.length+5)===idPrefix+"_sub_"){var bits=element.name.substring(idPrefix.length+5).split("_");numrow=Math.max(numrow,parseInt(bits[0],10)+1),numcol=Math.max(numcol,parseInt(bits[1],10)+1)}})),this.addEventHandlers=function(valueChanging){container.addEventListener("input",valueChanging)},this.getValue=function(){for(var values=new Array(numrow),i=0;i.\n\n/**\n * A javascript module to handle the real-time validation of the input the student types\n * into STACK questions.\n *\n * The overall way this works is as follows:\n *\n * - right at the end of this file are the init methods, which set things up.\n * - The work common to all input types is done by StackInput.\n * - Sending the Ajax request.\n * - Updating the validation display.\n * - The work specific to different input types (getting the content of the inputs) is done by\n * the classes like\n * - StackSimpleInput\n * - StackTextareaInput\n * - StackMatrixInput\n * objects of these types need to implement the two methods addEventHandlers and getValue().\n *\n * @module qtype_stack/input\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'core/ajax',\n 'core/event'\n], function(\n Ajax,\n CustomEvents\n) {\n\n \"use strict\";\n\n /**\n * Class constructor representing an input in a Stack question.\n *\n * @constructor\n * @param {HTMLElement} validationDiv The div to display the validation in.\n * @param {String} prefix prefix added to the input name to get HTML ids.\n * @param {String} qaid id of the question_attempt.\n * @param {String} name the name of the input we are validating.\n * @param {Object} input An object representing the input element for this input.\n */\n function StackInput(validationDiv, prefix, qaid, name, input) {\n /** @type {number} delay between the user stopping typing, and the ajax request being sent. */\n var TYPING_DELAY = 1000;\n\n /** @type {?int} if not null, the id of the timer for the typing delay. */\n var delayTimeoutHandle = null;\n\n /** @type {Object} cache of validation results we have already received. */\n var validationResults = {};\n\n /** @type {String} the last value that we sent to be validated. */\n var lastValidatedValue = getInputValue();\n\n /**\n * Cancel any typing pause timer.\n */\n function cancelTypingDelay() {\n if (delayTimeoutHandle) {\n clearTimeout(delayTimeoutHandle);\n }\n delayTimeoutHandle = null;\n }\n\n input.addEventHandlers(valueChanging);\n\n /**\n * Called when the input contents changes. Will validate after TYPING_DELAY if nothing else happens.\n */\n function valueChanging() {\n cancelTypingDelay();\n showWaiting();\n delayTimeoutHandle = setTimeout(valueChanged, TYPING_DELAY);\n setTimeout(function() {\n checkNoChange();\n }, 0);\n }\n\n /**\n * After a small delay, detect the case where the user has got the input back\n * to where they started, so no validation is necessary.\n */\n function checkNoChange() {\n if (getInputValue() === lastValidatedValue) {\n cancelTypingDelay();\n validationDiv.classList.remove('waiting');\n }\n }\n\n /**\n * Called to actually validate the input now.\n */\n function valueChanged() {\n cancelTypingDelay();\n if (!showValidationResults()) {\n validateInput();\n }\n }\n\n /**\n * Make an ajax call to validate the input.\n */\n function validateInput() {\n Ajax.call([{\n methodname: 'qtype_stack_validate_input',\n args: {qaid: qaid, name: name, input: getInputValue()},\n done: function(response) {\n validationReceived(response);\n },\n fail: function(response) {\n showValidationFailure(response);\n }\n }]);\n showLoading();\n }\n\n /**\n * Returns the current value of the input.\n *\n * @return {String}.\n */\n function getInputValue() {\n return input.getValue();\n }\n\n /**\n * Update the validation div to show the results of the validation.\n *\n * @param {Object} response The data that came back from the ajax validation call.\n */\n function validationReceived(response) {\n if (response.status === 'invalid') {\n showValidationFailure(response);\n return;\n }\n validationResults[response.input] = response;\n showValidationResults();\n }\n\n /**\n * Some browsers cannot execute JavaScript just by inserting script tags.\n * To avoid that problem, remove all script tags from the given content,\n * and run them later.\n *\n * @param {String} html HTML content\n * @param {Array} scriptCommands An array of script tags for later use.\n * @return {String} HTML with JS removed\n */\n function extractScripts(html, scriptCommands) {\n var scriptregexp = /]*>([\\s\\S]*?)<\\/script>/g;\n var result;\n while ((result = scriptregexp.exec(html)) !== null) {\n scriptCommands.push(result[1]);\n }\n return html.replace(scriptregexp, '');\n }\n\n /**\n * Update the validation div to show the results of the validation.\n *\n * @return {boolean} true if we could show the validation. false we we are we don't have it.\n */\n function showValidationResults() {\n /* eslint no-eval: \"off\" */\n var val = getInputValue();\n if (!validationResults[val]) {\n showWaiting();\n return false;\n }\n var results = validationResults[val];\n lastValidatedValue = val;\n var scriptCommands = [];\n validationDiv.innerHTML = extractScripts(results.message, scriptCommands);\n // Run script commands.\n for (var i = 0; i < scriptCommands.length; i++) {\n eval(scriptCommands[i]);\n }\n removeAllClasses();\n if (!results.message) {\n validationDiv.classList.add('empty');\n }\n // This fires the Maths filters for content in the validation div.\n CustomEvents.notifyFilterContentUpdated(validationDiv);\n return true;\n }\n\n /**\n * Update the validation div after an ajax validation call failed.\n *\n * @param {Object} response The data that came back from the ajax validation call.\n */\n function showValidationFailure(response) {\n lastValidatedValue = '';\n // Reponse usually contains backtrace, debuginfo, errorcode, link, message and moreinfourl.\n validationDiv.innerHTML = response.message;\n removeAllClasses();\n validationDiv.classList.add('error');\n // This fires the Maths filters for content in the validation div.\n CustomEvents.notifyFilterContentUpdated(validationDiv);\n }\n\n /**\n * Display the loader icon.\n */\n function showLoading() {\n removeAllClasses();\n validationDiv.classList.add('loading');\n }\n\n /**\n * Update the validation div to show that the input contents have changed,\n * so the validation results are no longer relevant.\n */\n function showWaiting() {\n removeAllClasses();\n validationDiv.classList.add('waiting');\n }\n\n /**\n * Strip all our class names from the validation div.\n */\n function removeAllClasses() {\n validationDiv.classList.remove('empty');\n validationDiv.classList.remove('error');\n validationDiv.classList.remove('loading');\n validationDiv.classList.remove('waiting');\n }\n }\n\n /**\n * Input type for inputs that are a single input or select.\n *\n * @constructor\n * @param {HTMLElement} input the HTML input that is this STACK input.\n */\n function StackSimpleInput(input) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n // The input event fires on any change in value, even if pasted in or added by speech\n // recognition to dictate text. Change only fires after loosing focus.\n // Should also work on mobile.\n input.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n return input.value.replace(/^\\s+|\\s+$/g, '');\n };\n }\n\n /**\n * Input type for textarea inputs.\n *\n * @constructor\n * @param {Object} textarea The input element wrapped in jquery.\n */\n function StackTextareaInput(textarea) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n textarea.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var raw = textarea.value.replace(/^\\s+|\\s+$/g, '');\n // Using
here is weird, but it gets sorted out at the PHP end.\n return raw.split(/\\s*[\\r\\n]\\s*/).join('
');\n };\n }\n\n /**\n * Input type for inputs that are a set of radio buttons.\n *\n * @constructor\n * @param {HTMLElement} container container
of this input.\n */\n function StackRadioInput(container) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n // The input event fires on any change in value, even if pasted in or added by speech\n // recognition to dictate text. Change only fires after loosing focus.\n // Should also work on mobile.\n container.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var selected = container.querySelector(':checked');\n if (selected) {\n return selected.value;\n } else {\n return '';\n }\n };\n }\n\n /**\n * Input type for inputs that are a set of checkboxes.\n *\n * @constructor\n * @param {HTMLElement} container container
of this input.\n */\n function StackCheckboxInput(container) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n // The input event fires on any change in value, even if pasted in or added by speech\n // recognition to dictate text. Change only fires after loosing focus.\n // Should also work on mobile.\n container.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var selected = container.querySelectorAll(':checked');\n var result = [];\n for (var i = 0; i < selected.length; i++) {\n result[i] = selected[i].value;\n }\n if (result.length > 0) {\n return result.join(',');\n } else {\n return '';\n }\n };\n }\n\n /**\n * Class constructor representing matrix inputs (one input).\n *\n * @constructor\n * @param {String} idPrefix input id, which is the start of the id of all the different text boxes.\n * @param {HTMLElement} container
of this input.\n */\n function StackMatrixInput(idPrefix, container) {\n var numcol = 0;\n var numrow = 0;\n container.querySelectorAll('input[type=text]').forEach(function(element) {\n if (element.name.slice(0, idPrefix.length + 5) !== idPrefix + '_sub_') {\n return;\n }\n var bits = element.name.substring(idPrefix.length + 5).split('_');\n numrow = Math.max(numrow, parseInt(bits[0], 10) + 1);\n numcol = Math.max(numcol, parseInt(bits[1], 10) + 1);\n });\n\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n container.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var values = new Array(numrow);\n for (var i = 0; i < numrow; i++) {\n values[i] = new Array(numcol);\n }\n container.querySelectorAll('input[type=text]').forEach(function(element) {\n if (element.name.slice(0, idPrefix.length + 5) !== idPrefix + '_sub_') {\n return;\n }\n var bits = element.name.substring(idPrefix.length + 5).split('_');\n values[bits[0]][bits[1]] = element.value.replace(/^\\s+|\\s+$/g, '');\n });\n return JSON.stringify(values);\n };\n }\n\n /**\n * Initialise all the inputs in a STACK question.\n *\n * @param {String} questionDivId id of the outer div of the question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} qaid Moodle question_attempt id.\n * @param {String[]} inputs names of all the inputs that should have instant validation.\n */\n function initInputs(questionDivId, prefix, qaid, inputs) {\n var questionDiv = document.getElementById(questionDivId);\n\n // Initialise all inputs.\n var allok = true;\n for (var i = 0; i < inputs.length; i++) {\n allok = initInput(questionDiv, prefix, qaid, inputs[i]) && allok;\n }\n\n // With JS With instant validation, we don't need the Check button, so hide it.\n if (allok && (questionDiv.classList.contains('dfexplicitvaildate') ||\n questionDiv.classList.contains('dfcbmexplicitvaildate'))) {\n questionDiv.querySelector('.im-controls input.submit').hidden = true;\n }\n }\n\n /**\n * Initialise one input.\n *\n * @param {HTMLElement} questionDiv outer
of this question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} qaid Moodle question_attempt id.\n * @param {String} name the input to initialise.\n * @return {boolean} true if this input was successfully initialised, else false.\n */\n function initInput(questionDiv, prefix, qaid, name) {\n var validationDiv = document.getElementById(prefix + name + '_val');\n if (!validationDiv) {\n return false;\n }\n\n var inputTypeHandler = getInputTypeHandler(questionDiv, prefix, name);\n if (inputTypeHandler) {\n new StackInput(validationDiv, prefix, qaid, name, inputTypeHandler);\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * Get the input type handler for a named input.\n *\n * @param {HTMLElement} questionDiv outer
of this question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} name the input to initialise.\n * @return {?Object} the input hander, if we can handle it, else null.\n */\n function getInputTypeHandler(questionDiv, prefix, name) {\n // See if it is an ordinary input.\n var input = questionDiv.querySelector('[name=\"' + prefix + name + '\"]');\n if (input) {\n if (input.nodeName === 'TEXTAREA') {\n return new StackTextareaInput(input);\n } else if (input.type === 'radio') {\n return new StackRadioInput(input.closest('.answer'));\n } else {\n return new StackSimpleInput(input);\n }\n }\n\n // See if it is a checkbox input.\n input = questionDiv.querySelector('[name=\"' + prefix + name + '_1\"]');\n if (input && input.type === 'checkbox') {\n return new StackCheckboxInput(input.closest('.answer'));\n }\n\n // See if it is a matrix input.\n var matrix = document.getElementById(prefix + name + '_container');\n if (matrix) {\n return new StackMatrixInput(prefix + name, matrix);\n }\n\n return null;\n }\n\n /** Export our entry point. */\n return {\n /**\n * Initialise all the inputs in a STACK question.\n *\n * @param {String} questionDivId id of the outer dic of the question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} qaid Moodle question_attempt id.\n * @param {String[]} inputs names of all the inputs that should have instant validation.\n */\n initInputs: initInputs\n };\n});\n"],"names":["define","Ajax","CustomEvents","StackInput","validationDiv","prefix","qaid","name","input","TYPING_DELAY","delayTimeoutHandle","validationResults","lastValidatedValue","getInputValue","cancelTypingDelay","clearTimeout","valueChanging","showWaiting","setTimeout","valueChanged","checkNoChange","classList","remove","showValidationResults","validateInput","call","methodname","args","done","response","validationReceived","fail","showValidationFailure","showLoading","getValue","status","extractScripts","html","scriptCommands","result","scriptregexp","exec","push","replace","val","results","innerHTML","message","i","length","eval","removeAllClasses","add","notifyFilterContentUpdated","addEventHandlers","StackSimpleInput","this","addEventListener","value","StackTextareaInput","textarea","split","join","StackRadioInput","container","selected","querySelector","StackCheckboxInput","querySelectorAll","StackMatrixInput","idPrefix","numcol","numrow","forEach","element","slice","bits","substring","Math","max","parseInt","values","Array","JSON","stringify","initInputs","questionDivId","inputs","questionDiv","document","getElementById","allok","initInput","contains","hidden","inputTypeHandler","getInputTypeHandler","nodeName","type","closest","matrix"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoCAA,OAAM,oBAAC,CACH,YACA,eACD,SACCC,KACAC,cAeA,SAASC,WAAWC,cAAeC,OAAQC,KAAMC,KAAMC,OAEnD,IAAIC,aAAe,IAGfC,mBAAqB,KAGrBC,kBAAoB,CAAA,EAGpBC,mBAAqBC,gBAKzB,SAASC,oBACDJ,oBACAK,aAAaL,oBAEjBA,mBAAqB,IACzB,CAOA,SAASM,gBACLF,oBACAG,cACAP,mBAAqBQ,WAAWC,aAAcV,cAC9CS,YAAW,WACPE,eACH,GAAE,EACP,CAMA,SAASA,gBACDP,kBAAoBD,qBACpBE,oBACAV,cAAciB,UAAUC,OAAO,WAEvC,CAKA,SAASH,eACLL,oBACKS,yBACDC,eAER,CAKA,SAASA,gBACLvB,KAAKwB,KAAK,CAAC,CACPC,WAAY,6BACZC,KAAM,CAACrB,KAAMA,KAAMC,KAAMA,KAAMC,MAAOK,iBACtCe,KAAM,SAASC,UACXC,mBAAmBD,SACtB,EACDE,KAAM,SAASF,UACXG,sBAAsBH,SAC1B,KAEJI,aACJ,CAOA,SAASpB,gBACL,OAAOL,MAAM0B,UACjB,CAOA,SAASJ,mBAAmBD,UACA,YAApBA,SAASM,QAIbxB,kBAAkBkB,SAASrB,OAASqB,SACpCN,yBAJIS,sBAAsBH,SAK9B,CAWA,SAASO,eAAeC,KAAMC,gBAG1B,IAFA,IACIC,OADAC,aAAe,qCAE2B,QAAtCD,OAASC,aAAaC,KAAKJ,QAC/BC,eAAeI,KAAKH,OAAO,IAE/B,OAAOF,KAAKM,QAAQH,aAAc,GACtC,CAOA,SAASjB,wBAEL,IAAIqB,IAAM/B,gBACV,IAAKF,kBAAkBiC,KAEnB,OADA3B,eACO,EAEX,IAAI4B,QAAUlC,kBAAkBiC,KAChChC,mBAAqBgC,IACrB,IAAIN,eAAiB,GACrBlC,cAAc0C,UAAYV,eAAeS,QAAQE,QAAST,gBAE1D,IAAK,IAAIU,EAAI,EAAGA,EAAIV,eAAeW,OAAQD,IACvCE,KAAKZ,eAAeU,IAQxB,OANAG,mBACKN,QAAQE,SACT3C,cAAciB,UAAU+B,IAAI,SAGhClD,aAAamD,2BAA2BjD,gBACjC,CACX,CAOA,SAAS4B,sBAAsBH,UAC3BjB,mBAAqB,GAErBR,cAAc0C,UAAYjB,SAASkB,QACnCI,mBACA/C,cAAciB,UAAU+B,IAAI,SAE5BlD,aAAamD,2BAA2BjD,cAC5C,CAKA,SAAS6B,cACLkB,mBACA/C,cAAciB,UAAU+B,IAAI,UAChC,CAMA,SAASnC,cACLkC,mBACA/C,cAAciB,UAAU+B,IAAI,UAChC,CAKA,SAASD,mBACL/C,cAAciB,UAAUC,OAAO,SAC/BlB,cAAciB,UAAUC,OAAO,SAC/BlB,cAAciB,UAAUC,OAAO,WAC/BlB,cAAciB,UAAUC,OAAO,UACnC,CAlKAd,MAAM8C,iBAAiBtC,cAmK3B,CAQA,SAASuC,iBAAiB/C,OAMtBgD,KAAKF,iBAAmB,SAAStC,eAI7BR,MAAMiD,iBAAiB,QAASzC,gBAQpCwC,KAAKtB,SAAW,WACZ,OAAO1B,MAAMkD,MAAMf,QAAQ,aAAc,IAEjD,CAQA,SAASgB,mBAAmBC,UAMxBJ,KAAKF,iBAAmB,SAAStC,eAC7B4C,SAASH,iBAAiB,QAASzC,gBAQvCwC,KAAKtB,SAAW,WAGZ,OAFU0B,SAASF,MAAMf,QAAQ,aAAc,IAEpCkB,MAAM,gBAAgBC,KAAK,QAE9C,CAQA,SAASC,gBAAgBC,WAMrBR,KAAKF,iBAAmB,SAAStC,eAI7BgD,UAAUP,iBAAiB,QAASzC,gBAQxCwC,KAAKtB,SAAW,WACZ,IAAI+B,SAAWD,UAAUE,cAAc,YACvC,OAAID,SACOA,SAASP,MAET,GAGnB,CAQA,SAASS,mBAAmBH,WAMxBR,KAAKF,iBAAmB,SAAStC,eAI7BgD,UAAUP,iBAAiB,QAASzC,gBAQxCwC,KAAKtB,SAAW,WAGZ,IAFA,IAAI+B,SAAWD,UAAUI,iBAAiB,YACtC7B,OAAS,GACJS,EAAI,EAAGA,EAAIiB,SAAShB,OAAQD,IACjCT,OAAOS,GAAKiB,SAASjB,GAAGU,MAE5B,OAAInB,OAAOU,OAAS,EACTV,OAAOuB,KAAK,KAEZ,GAGnB,CASA,SAASO,iBAAiBC,SAAUN,WAChC,IAAIO,OAAS,EACTC,OAAS,EACbR,UAAUI,iBAAiB,oBAAoBK,SAAQ,SAASC,SAC5D,GAAIA,QAAQnE,KAAKoE,MAAM,EAAGL,SAASrB,OAAS,KAAOqB,SAAW,QAA9D,CAGA,IAAIM,KAAOF,QAAQnE,KAAKsE,UAAUP,SAASrB,OAAS,GAAGY,MAAM,KAC7DW,OAASM,KAAKC,IAAIP,OAAQQ,SAASJ,KAAK,GAAI,IAAM,GAClDL,OAASO,KAAKC,IAAIR,OAAQS,SAASJ,KAAK,GAAI,IAAM,EAHlD,CAIJ,IAOApB,KAAKF,iBAAmB,SAAStC,eAC7BgD,UAAUP,iBAAiB,QAASzC,gBAQxCwC,KAAKtB,SAAW,WAEZ,IADA,IAAI+C,OAAS,IAAIC,MAAMV,QACdxB,EAAI,EAAGA,EAAIwB,OAAQxB,IACxBiC,OAAOjC,GAAK,IAAIkC,MAAMX,QAS1B,OAPAP,UAAUI,iBAAiB,oBAAoBK,SAAQ,SAASC,SAC5D,GAAIA,QAAQnE,KAAKoE,MAAM,EAAGL,SAASrB,OAAS,KAAOqB,SAAW,QAA9D,CAGA,IAAIM,KAAOF,QAAQnE,KAAKsE,UAAUP,SAASrB,OAAS,GAAGY,MAAM,KAC7DoB,OAAOL,KAAK,IAAIA,KAAK,IAAMF,QAAQhB,MAAMf,QAAQ,aAAc,GAF/D,CAGJ,IACOwC,KAAKC,UAAUH,QAE9B,CAUA,SAASI,WAAWC,cAAejF,OAAQC,KAAMiF,QAK7C,IAJA,IAAIC,YAAcC,SAASC,eAAeJ,eAGtCK,OAAQ,EACH3C,EAAI,EAAGA,EAAIuC,OAAOtC,OAAQD,IAC/B2C,MAAQC,UAAUJ,YAAanF,OAAQC,KAAMiF,OAAOvC,KAAO2C,MAI3DA,QAAUH,YAAYnE,UAAUwE,SAAS,uBACrCL,YAAYnE,UAAUwE,SAAS,4BACnCL,YAAYtB,cAAc,6BAA6B4B,QAAS,EAExE,CAWA,SAASF,UAAUJ,YAAanF,OAAQC,KAAMC,MAC1C,IAAIH,cAAgBqF,SAASC,eAAerF,OAASE,KAAO,QAC5D,IAAKH,cACD,OAAO,EAGX,IAAI2F,iBAAmBC,oBAAoBR,YAAanF,OAAQE,MAChE,QAAIwF,mBACA,IAAI5F,WAAWC,cAAeC,OAAQC,KAAMC,KAAMwF,mBAC3C,EAIf,CAUA,SAASC,oBAAoBR,YAAanF,OAAQE,MAE9C,IAAIC,MAAQgF,YAAYtB,cAAc,UAAY7D,OAASE,KAAO,MAClE,GAAIC,MACA,MAAuB,aAAnBA,MAAMyF,SACC,IAAItC,mBAAmBnD,OACR,UAAfA,MAAM0F,KACN,IAAInC,gBAAgBvD,MAAM2F,QAAQ,YAElC,IAAI5C,iBAAiB/C,OAMpC,IADAA,MAAQgF,YAAYtB,cAAc,UAAY7D,OAASE,KAAO,UAClC,aAAfC,MAAM0F,KACf,OAAO,IAAI/B,mBAAmB3D,MAAM2F,QAAQ,YAIhD,IAAIC,OAASX,SAASC,eAAerF,OAASE,KAAO,cACrD,OAAI6F,OACO,IAAI/B,iBAAiBhE,OAASE,KAAM6F,QAGxC,IACX,CAGA,MAAO,CASHf,WAAYA,WAEpB"} \ No newline at end of file +{"version":3,"file":"input.min.js","sources":["../src/input.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * A javascript module to handle the real-time validation of the input the student types\n * into STACK questions.\n *\n * The overall way this works is as follows:\n *\n * - right at the end of this file are the init methods, which set things up.\n * - The work common to all input types is done by StackInput.\n * - Sending the Ajax request.\n * - Updating the validation display.\n * - The work specific to different input types (getting the content of the inputs) is done by\n * the classes like\n * - StackSimpleInput\n * - StackTextareaInput\n * - StackMatrixInput\n * objects of these types need to implement the two methods addEventHandlers and getValue().\n *\n * @module qtype_stack/input\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'core/ajax',\n 'core/event'\n], function(\n Ajax,\n CustomEvents\n) {\n\n \"use strict\";\n\n /**\n * Class constructor representing an input in a Stack question.\n *\n * @constructor\n * @param {HTMLElement} validationDiv The div to display the validation in.\n * @param {String} prefix prefix added to the input name to get HTML ids.\n * @param {String} qaid id of the question_attempt.\n * @param {String} name the name of the input we are validating.\n * @param {Object} input An object representing the input element for this input.\n */\n function StackInput(validationDiv, prefix, qaid, name, input) {\n /** @type {number} delay between the user stopping typing, and the ajax request being sent. */\n var TYPING_DELAY = 1000;\n\n /** @type {?int} if not null, the id of the timer for the typing delay. */\n var delayTimeoutHandle = null;\n\n /** @type {Object} cache of validation results we have already received. */\n var validationResults = {};\n\n /** @type {String} the last value that we sent to be validated. */\n var lastValidatedValue = getInputValue();\n\n /**\n * Cancel any typing pause timer.\n */\n function cancelTypingDelay() {\n if (delayTimeoutHandle) {\n clearTimeout(delayTimeoutHandle);\n }\n delayTimeoutHandle = null;\n }\n\n input.addEventHandlers(valueChanging);\n\n /**\n * Called when the input contents changes. Will validate after TYPING_DELAY if nothing else happens.\n */\n function valueChanging() {\n cancelTypingDelay();\n showWaiting();\n delayTimeoutHandle = setTimeout(valueChanged, TYPING_DELAY);\n setTimeout(function() {\n checkNoChange();\n }, 0);\n }\n\n /**\n * After a small delay, detect the case where the user has got the input back\n * to where they started, so no validation is necessary.\n */\n function checkNoChange() {\n if (getInputValue() === lastValidatedValue) {\n cancelTypingDelay();\n validationDiv.classList.remove('waiting');\n }\n }\n\n /**\n * Called to actually validate the input now.\n */\n function valueChanged() {\n cancelTypingDelay();\n if (!showValidationResults()) {\n validateInput();\n }\n }\n\n /**\n * Make an ajax call to validate the input.\n */\n function validateInput() {\n Ajax.call([{\n methodname: 'qtype_stack_validate_input',\n args: {qaid: qaid, name: name, input: getInputValue()},\n done: function(response) {\n validationReceived(response);\n },\n fail: function(response) {\n showValidationFailure(response);\n }\n }]);\n showLoading();\n }\n\n /**\n * Returns the current value of the input.\n *\n * @return {String}.\n */\n function getInputValue() {\n return input.getValue();\n }\n\n /**\n * Update the validation div to show the results of the validation.\n *\n * @param {Object} response The data that came back from the ajax validation call.\n */\n function validationReceived(response) {\n if (response.status === 'invalid') {\n showValidationFailure(response);\n return;\n }\n validationResults[response.input] = response;\n showValidationResults();\n }\n\n /**\n * Some browsers cannot execute JavaScript just by inserting script tags.\n * To avoid that problem, remove all script tags from the given content,\n * and run them later.\n *\n * @param {String} html HTML content\n * @param {Array} scriptCommands An array of script tags for later use.\n * @return {String} HTML with JS removed\n */\n function extractScripts(html, scriptCommands) {\n var scriptregexp = /]*>([\\s\\S]*?)<\\/script>/g;\n var result;\n while ((result = scriptregexp.exec(html)) !== null) {\n scriptCommands.push(result[1]);\n }\n return html.replace(scriptregexp, '');\n }\n\n /**\n * Update the validation div to show the results of the validation.\n *\n * @return {boolean} true if we could show the validation. false we we are we don't have it.\n */\n function showValidationResults() {\n /* eslint no-eval: \"off\" */\n var val = getInputValue();\n if (!validationResults[val]) {\n showWaiting();\n return false;\n }\n var results = validationResults[val];\n lastValidatedValue = val;\n var scriptCommands = [];\n validationDiv.innerHTML = extractScripts(results.message, scriptCommands);\n // Run script commands.\n for (var i = 0; i < scriptCommands.length; i++) {\n eval(scriptCommands[i]);\n }\n removeAllClasses();\n if (!results.message) {\n validationDiv.classList.add('empty');\n }\n // This fires the Maths filters for content in the validation div.\n CustomEvents.notifyFilterContentUpdated(validationDiv);\n return true;\n }\n\n /**\n * Update the validation div after an ajax validation call failed.\n *\n * @param {Object} response The data that came back from the ajax validation call.\n */\n function showValidationFailure(response) {\n lastValidatedValue = '';\n // Reponse usually contains backtrace, debuginfo, errorcode, link, message and moreinfourl.\n validationDiv.innerHTML = response.message;\n removeAllClasses();\n validationDiv.classList.add('error');\n // This fires the Maths filters for content in the validation div.\n CustomEvents.notifyFilterContentUpdated(validationDiv);\n }\n\n /**\n * Display the loader icon.\n */\n function showLoading() {\n removeAllClasses();\n validationDiv.classList.add('loading');\n }\n\n /**\n * Update the validation div to show that the input contents have changed,\n * so the validation results are no longer relevant.\n */\n function showWaiting() {\n removeAllClasses();\n validationDiv.classList.add('waiting');\n }\n\n /**\n * Strip all our class names from the validation div.\n */\n function removeAllClasses() {\n validationDiv.classList.remove('empty');\n validationDiv.classList.remove('error');\n validationDiv.classList.remove('loading');\n validationDiv.classList.remove('waiting');\n }\n }\n\n /**\n * Input type for inputs that are a single input or select.\n *\n * @constructor\n * @param {HTMLElement} input the HTML input that is this STACK input.\n */\n function StackSimpleInput(input) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n // The input event fires on any change in value, even if pasted in or added by speech\n // recognition to dictate text. Change only fires after loosing focus.\n // Should also work on mobile.\n input.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n return input.value.replace(/^\\s+|\\s+$/g, '');\n };\n }\n\n /**\n * Input type for textarea inputs.\n *\n * @constructor\n * @param {Object} textarea The input element wrapped in jquery.\n */\n function StackTextareaInput(textarea) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n textarea.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var raw = textarea.value.replace(/^\\s+|\\s+$/g, '');\n // Using
here is weird, but it gets sorted out at the PHP end.\n return raw.split(/\\s*[\\r\\n]\\s*/).join('
');\n };\n }\n\n /**\n * Input type for inputs that are a set of radio buttons.\n *\n * @constructor\n * @param {HTMLElement} container container
of this input.\n */\n function StackRadioInput(container) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n // The input event fires on any change in value, even if pasted in or added by speech\n // recognition to dictate text. Change only fires after loosing focus.\n // Should also work on mobile.\n container.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var selected = container.querySelector(':checked');\n if (selected) {\n return selected.value;\n } else {\n return '';\n }\n };\n }\n\n /**\n * Input type for inputs that are a set of checkboxes.\n *\n * @constructor\n * @param {HTMLElement} container container
of this input.\n */\n function StackCheckboxInput(container) {\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n // The input event fires on any change in value, even if pasted in or added by speech\n // recognition to dictate text. Change only fires after loosing focus.\n // Should also work on mobile.\n container.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var selected = container.querySelectorAll(':checked');\n var result = [];\n for (var i = 0; i < selected.length; i++) {\n result[i] = selected[i].value;\n }\n if (result.length > 0) {\n return result.join(',');\n } else {\n return '';\n }\n };\n }\n\n /**\n * Class constructor representing matrix inputs (one input).\n *\n * @constructor\n * @param {String} idPrefix input id, which is the start of the id of all the different text boxes.\n * @param {HTMLElement} container
of this input.\n */\n function StackMatrixInput(idPrefix, container) {\n var numcol = 0;\n var numrow = 0;\n container.querySelectorAll('input[type=text]').forEach(function(element) {\n if (element.name.slice(0, idPrefix.length + 5) !== idPrefix + '_sub_') {\n return;\n }\n var bits = element.name.substring(idPrefix.length + 5).split('_');\n numrow = Math.max(numrow, parseInt(bits[0], 10) + 1);\n numcol = Math.max(numcol, parseInt(bits[1], 10) + 1);\n });\n\n /**\n * Add the event handler to call when the user input changes.\n *\n * @param {Function} valueChanging the callback to call when we detect a value change.\n */\n this.addEventHandlers = function(valueChanging) {\n container.addEventListener('input', valueChanging);\n };\n\n /**\n * Get the current value of this input.\n *\n * @return {String}.\n */\n this.getValue = function() {\n var values = new Array(numrow);\n for (var i = 0; i < numrow; i++) {\n values[i] = new Array(numcol);\n }\n container.querySelectorAll('input[type=text]').forEach(function(element) {\n if (element.name.slice(0, idPrefix.length + 5) !== idPrefix + '_sub_') {\n return;\n }\n var bits = element.name.substring(idPrefix.length + 5).split('_');\n values[bits[0]][bits[1]] = element.value.replace(/^\\s+|\\s+$/g, '');\n });\n return JSON.stringify(values);\n };\n }\n\n /**\n * Initialise all the inputs in a STACK question.\n *\n * @param {String} questionDivId id of the outer div of the question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} qaid Moodle question_attempt id.\n * @param {String[]} inputs names of all the inputs that should have instant validation.\n */\n function initInputs(questionDivId, prefix, qaid, inputs) {\n var questionDiv = document.getElementById(questionDivId);\n\n // Initialise all inputs.\n var allok = true;\n for (var i = 0; i < inputs.length; i++) {\n allok = initInput(questionDiv, prefix, qaid, inputs[i]) && allok;\n }\n\n // With JS With instant validation, we don't need the Check button, so hide it.\n if (allok && (questionDiv.classList.contains('dfexplicitvaildate') ||\n questionDiv.classList.contains('dfcbmexplicitvaildate'))) {\n if (!(questionDiv.querySelector('.im-controls input.submit') === null)) {\n questionDiv.querySelector('.im-controls input.submit').hidden = true;\n }\n }\n }\n\n /**\n * Initialise one input.\n *\n * @param {HTMLElement} questionDiv outer
of this question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} qaid Moodle question_attempt id.\n * @param {String} name the input to initialise.\n * @return {boolean} true if this input was successfully initialised, else false.\n */\n function initInput(questionDiv, prefix, qaid, name) {\n var validationDiv = document.getElementById(prefix + name + '_val');\n if (!validationDiv) {\n return false;\n }\n\n var inputTypeHandler = getInputTypeHandler(questionDiv, prefix, name);\n if (inputTypeHandler) {\n new StackInput(validationDiv, prefix, qaid, name, inputTypeHandler);\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * Get the input type handler for a named input.\n *\n * @param {HTMLElement} questionDiv outer
of this question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} name the input to initialise.\n * @return {?Object} the input hander, if we can handle it, else null.\n */\n function getInputTypeHandler(questionDiv, prefix, name) {\n // See if it is an ordinary input.\n var input = questionDiv.querySelector('[name=\"' + prefix + name + '\"]');\n if (input) {\n if (input.nodeName === 'TEXTAREA') {\n return new StackTextareaInput(input);\n } else if (input.type === 'radio') {\n return new StackRadioInput(input.closest('.answer'));\n } else {\n return new StackSimpleInput(input);\n }\n }\n\n // See if it is a checkbox input.\n input = questionDiv.querySelector('[name=\"' + prefix + name + '_1\"]');\n if (input && input.type === 'checkbox') {\n return new StackCheckboxInput(input.closest('.answer'));\n }\n\n // See if it is a matrix input.\n var matrix = document.getElementById(prefix + name + '_container');\n if (matrix) {\n return new StackMatrixInput(prefix + name, matrix);\n }\n\n return null;\n }\n\n /** Export our entry point. */\n return {\n /**\n * Initialise all the inputs in a STACK question.\n *\n * @param {String} questionDivId id of the outer dic of the question.\n * @param {String} prefix prefix added to the input names for this question.\n * @param {String} qaid Moodle question_attempt id.\n * @param {String[]} inputs names of all the inputs that should have instant validation.\n */\n initInputs: initInputs\n };\n});\n"],"names":["define","Ajax","CustomEvents","StackInput","validationDiv","prefix","qaid","name","input","TYPING_DELAY","delayTimeoutHandle","validationResults","lastValidatedValue","getInputValue","cancelTypingDelay","clearTimeout","valueChanging","showWaiting","setTimeout","valueChanged","checkNoChange","classList","remove","showValidationResults","validateInput","call","methodname","args","done","response","validationReceived","fail","showValidationFailure","showLoading","getValue","status","extractScripts","html","scriptCommands","result","scriptregexp","exec","push","replace","val","results","innerHTML","message","i","length","eval","removeAllClasses","add","notifyFilterContentUpdated","addEventHandlers","StackSimpleInput","this","addEventListener","value","StackTextareaInput","textarea","split","join","StackRadioInput","container","selected","querySelector","StackCheckboxInput","querySelectorAll","StackMatrixInput","idPrefix","numcol","numrow","forEach","element","slice","bits","substring","Math","max","parseInt","values","Array","JSON","stringify","initInputs","questionDivId","inputs","questionDiv","document","getElementById","allok","initInput","contains","hidden","inputTypeHandler","getInputTypeHandler","nodeName","type","closest","matrix"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/stackjsvle.min.js.map b/amd/build/stackjsvle.min.js.map index e6bfdebd42b..d12e452d714 100644 --- a/amd/build/stackjsvle.min.js.map +++ b/amd/build/stackjsvle.min.js.map @@ -1 +1 @@ -{"version":3,"file":"stackjsvle.min.js","sources":["../src/stackjsvle.js"],"sourcesContent":["/**\n * A javascript module to handle separation of author sourced scripts into\n * IFRAMES. All such scripts will have limited access to the actual document\n * on the VLE side and this script represents the VLE side endpoint for\n * message handling needed to give that access. When porting STACK onto VLEs\n * one needs to map this script to do the following:\n *\n * 1. Ensure that searches for target elements/inputs are limited to questions\n * and do not return any elements outside them.\n *\n * 2. Map any identifiers needed to identify inputs by name.\n *\n * 3. Any change handling related to input value modifications through this\n * logic gets connected to any such handling on the VLE side.\n *\n *\n * This script is intenttionally ordered so that the VLE specific bits should\n * be at the top.\n *\n *\n * This script assumes the following:\n *\n * 1. Each relevant IFRAME has an `id`-attribute that will be told to this\n * script.\n *\n * 2. Each such IFRAME exists within the question itself, so that one can\n * traverse up the DOM tree from that IFRAME to find the border of\n * the question.\n *\n * @module qtype_stack/stackjsvle\n * @copyright 2023 Aalto University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'core/event'\n], function(\n CustomEvents\n) {\n 'use strict';\n // Note the VLE specific include of logic.\n\n /* All the IFRAMES have unique identifiers that they give in their\n * messages. But we only work with those that have been created by\n * our logic and are found from this map.\n */\n let IFRAMES = {};\n\n /* For event handling, lists of IFRAMES listening particular inputs.\n */\n let INPUTS = {};\n\n /* For event handling, lists of IFRAMES listening particular inputs\n * and their input events. By default we only listen to changes.\n * We report input events as changes to the other side.\n */\n let INPUTS_INPUT_EVENT = {};\n\n /* A flag to disable certain things. */\n let DISABLE_CHANGES = false;\n\n\n /**\n * Returns an element with a given id, if an only if that element exists\n * inside a portion of DOM that represents a question.\n *\n * If not found or exists outside the restricted area then returns `null`.\n *\n * @param {String} id the identifier of the element we want.\n */\n function vle_get_element(id) {\n /* In the case of Moodle we are happy as long as the element is inside\n something with the `formulation`-class. */\n let candidate = document.getElementById(id);\n let iter = candidate;\n while (iter && !iter.classList.contains('formulation')) {\n iter = iter.parentElement;\n }\n if (iter && iter.classList.contains('formulation')) {\n return candidate;\n }\n\n return null;\n }\n\n /**\n * Returns an input element with a given name, if an only if that element\n * exists inside a portion of DOM that represents a question.\n *\n * Note that, the input element may have a name that multiple questions\n * use and to pick the preferred element one needs to pick the one\n * within the same question as the IFRAME.\n *\n * Note that the input can also be a select. In the case of radio buttons\n * returning one of the possible buttons is enough.\n *\n * If not found or exists outside the restricted area then returns `null`.\n *\n * @param {String} name the name of the input we want\n * @param {String} srciframe the identifier of the iframe wanting it\n */\n function vle_get_input_element(name, srciframe) {\n /* In the case of Moodle we are happy as long as the element is inside\n something with the `formulation`-class. */\n let initialcandidate = document.getElementById(srciframe);\n let iter = initialcandidate;\n while (iter && !iter.classList.contains('formulation')) {\n iter = iter.parentElement;\n }\n if (iter && iter.classList.contains('formulation')) {\n // iter now represents the borders of the question containing\n // this IFRAME.\n let possible = iter.querySelector('input[id$=\"_' + name + '\"]');\n if (possible !== null) {\n return possible;\n }\n // Radios have interesting ids, but the name makes sense\n possible = iter.querySelector('input[id$=\"_' + name + '_1\"][type=radio]');\n if (possible !== null) {\n return possible;\n }\n possible = iter.querySelector('select[id$=\"_' + name + '\"]');\n if (possible !== null) {\n return possible;\n }\n }\n // If none found within the question itself, search everywhere.\n let possible = document.querySelector('.formulation input[id$=\"_' + name + '\"]');\n if (possible !== null) {\n return possible;\n }\n // Radios have interesting ids, but the name makes sense\n possible = document.querySelector('.formulation input[id$=\"_' + name + '_1\"][type=radio]');\n if (possible !== null) {\n return possible;\n }\n possible = document.querySelector('.formulation select[id$=\"_' + name + '\"]');\n return possible;\n }\n\n /**\n * Triggers any VLE specific scripting related to updates of the given\n * input element.\n *\n * @param {HTMLElement} inputelement the input element that has changed\n */\n function vle_update_input(inputelement) {\n // Triggering a change event may be necessary.\n const c = new Event('change');\n inputelement.dispatchEvent(c);\n // Also there are those that listen to input events.\n const i = new Event('input');\n inputelement.dispatchEvent(i);\n }\n\n /**\n * Triggers any VLE specific scripting related to DOM updates.\n *\n * @param {HTMLElement} modifiedsubtreerootelement element under which changes may have happened.\n */\n function vle_update_dom(modifiedsubtreerootelement) {\n CustomEvents.notifyFilterContentUpdated(modifiedsubtreerootelement);\n }\n\n /**\n * Does HTML-string cleaning, i.e., removes any script payload. Returns\n * a DOM version of the given input string.\n *\n * This is used when receiving replacement content for a div.\n *\n * @param {String} src a raw string to sanitise\n */\n function vle_html_sanitize(src) {\n // This can be implemented with many libraries or by custom code\n // however as this is typically a thing that a VLE might already have\n // tools for we have it at this level so that the VLE can use its own\n // tools that do things that the VLE developpers consider safe.\n\n // As Moodle does not currently seem to have such a sanitizer in\n // the core libraries, here is one implementation that shows what we\n // are looking for.\n\n // TODO: look into replacing this with DOMPurify or some such.\n\n let parser = new DOMParser();\n let doc = parser.parseFromString(src, \"text/html\");\n\n // First remove all