From 3859a7bf991fb5e112da705e2573d69fe7652c6e Mon Sep 17 00:00:00 2001 From: Richard Lobb Date: Wed, 10 Jul 2024 20:47:50 +1200 Subject: [PATCH] Update userinterfacewrapper to remove all use of jQuery. --- amd/build/authorform.min.js | 2 +- amd/build/authorform.min.js.map | 2 +- amd/build/multilanguagequestion.min.js | 2 +- amd/build/multilanguagequestion.min.js.map | 2 +- amd/build/userinterfacewrapper.min.js | 2 +- amd/build/userinterfacewrapper.min.js.map | 2 +- amd/src/authorform.js | 2 +- amd/src/multilanguagequestion.js | 2 +- amd/src/userinterfacewrapper.js | 223 ++++++++++++--------- tests/behat/scratchpad_ui_params.feature | 4 +- 10 files changed, 143 insertions(+), 100 deletions(-) diff --git a/amd/build/authorform.min.js b/amd/build/authorform.min.js index 9068f3a6d..064c7ec62 100644 --- a/amd/build/authorform.min.js +++ b/amd/build/authorform.min.js @@ -5,6 +5,6 @@ * @copyright Richard Lobb, 2015, The University of Canterbury * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("qtype_coderunner/authorform",["jquery","qtype_coderunner/userinterfacewrapper","core/str"],(function($,ui,str){let currentQtype="";var JSON_TO_FORM_MAP={template:["#id_template","value",""],iscombinatortemplate:["#id_iscombinatortemplate","checked","",function(value){return"1"===value}],cputimelimitsecs:["#id_cputimelimitsecs","value",""],memlimitmb:["#id_memlimitmb","value",""],sandbox:["#id_sandbox","value","DEFAULT"],sandboxparams:["#id_sandboxparams","value",""],testsplitterre:["#id_testsplitterre","value","",function(splitter){return splitter.replace("\n","\\n")}],allowmultiplestdins:["#id_allowmultiplestdins","checked","",function(value){return"1"===value}],grader:["#id_grader","value","EqualityGrader"],resultcolumns:["#id_resultcolumns","value",""],language:["#id_language","value",""],acelang:["#id_acelang","value",""],uiplugin:["#id_uiplugin","value","ace"]};return{initEditForm:function(){var typeCombo=$("#id_coderunnertype"),prototypeDisplay=$("#id_isprototype"),template=$("#id_template"),evaluatePerStudent=$("#id_templateparamsevalpertry"),globalextra=$("#id_globalextra"),prototypeextra=$("#id_prototypeextra"),useace=$("#id_useace"),language=$("#id_language"),acelang=$("#id_acelang"),customise=$("#id_customise"),isCombinator=$("#id_iscombinatortemplate"),testSplitterRe=$("#id_testsplitterre"),allowMultipleStdins=$("#id_allowmultiplestdins"),customisationFieldSet=$("#id_customisationheader"),advancedCustomisation=$("#id_advancedcustomisationheader"),isCustomised=customise.prop("checked"),prototypeType=$("#id_prototypetype"),preloadHdr=$("#id_answerpreloadhdr"),courseId=$('input[name="courseid"]').prop("value"),questiontypeHelpDiv=$("#qtype-help"),precheck=$("select#id_precheck"),testtypedivs=$("div.testtype"),testsection=$("#id_testcasehdr"),brokenQuestion=$("#id_broken_question"),badQuestionLoad=$("#id_bad_question_load"),uiplugin=$("#id_uiplugin"),uiparameters=$("#id_uiparameters");function setUi(taId,uiname){var lang,uiWrapper,ta=$(document.getElementById(taId)),paramsJson=ta.attr("data-params"),params={};ta.attr("data-prototypeextra",prototypeextra.val()),ta.attr("data-globalextra",globalextra.val()),ta.attr("data-test0",$("#id_testcode_0").val());try{params=JSON.parse(paramsJson)}catch(err){}"none"===(uiname=uiname.toLowerCase())&&(uiname=""),"id_templateparams"==taId||"id_uiparameters"==taId?lang="":(lang=language.prop("value"),"id_template"!==taId&&acelang.prop("value")&&(lang=function(acelang){var langs,i;if(acelang.indexOf(",")<0)return acelang;for(langs=acelang.split(","),i=0;i0?langs[0]:""}(acelang.prop("value")))),uiWrapper=ta.data("current-ui-wrapper"),ta.attr("data-lang",lang),uiWrapper?(params.lang=lang,uiWrapper.loadUi(uiname,params)):uiWrapper=new ui.InterfaceWrapper(uiname,taId)}function setUis(){let uiname=uiplugin.val(),answer=$("#id_answer"),enableUi=!0;if("html"===uiname&&""!==answer.attr("data-params"))try{!1===JSON.parse(answer.attr("data-params")).enable_in_editor&&(enableUi=!1)}catch(error){alert("Invalid UI parameters.")}enableUi&&(setUi("id_answer",uiname),setUi("id_answerpreload",uiname))}function setCustomisationVisibility(isVisible){var display=isVisible?"block":"none";customisationFieldSet.css("display",display),advancedCustomisation.css("display",display),isVisible&&useace.prop("checked")&&setUi("id_template","ace")}function copyFieldsFromQuestionType(newType,response){var formspecifier,attrval,isCombinatorEnabled;for(var key in function(stateOn){var uiWrapper,taIds=["id_template","id_uiparameters"];if(useace.prop("checked"))for(var i=0;i3&&(attrval=(0,formspecifier[3])(attrval)),$(formspecifier[0]).prop(formspecifier[1],attrval);customise.prop("checked",!1),str.get_string("coderunner_question_type","qtype_coderunner").then((function(s){var title,coderunner_descr,html,resultHtml;questiontypeHelpDiv.html((title=newType,coderunner_descr=s,html=response.questiontext,resultHtml='

',resultHtml+=coderunner_descr,resultHtml+=title+"

\n"+html))})),setCustomisationVisibility(!1),isCombinatorEnabled=isCombinator.prop("checked"),testSplitterRe.prop("disabled",!isCombinatorEnabled),allowMultipleStdins.prop("disabled",!isCombinatorEnabled)}function langStringAlert(key,extra){window.hasOwnProperty("behattesting")&&window.behattesting||str.get_string(key,"qtype_coderunner").then((function(s){var message=s.replace(/\n/g," ");extra&&(message+="\n"+extra),alert(message)}))}function loadCustomisationFields(){let newType=typeCombo.children("option:selected").text();""!==newType&&"Undefined"!==newType&&(typeCombo.children("option:first-child").prop("disabled","disabled"),$.getJSON(M.cfg.wwwroot+"/question/type/coderunner/ajax.php",{qtype:newType,courseid:courseId,sesskey:M.cfg.sesskey},(function(outcome){if($("#id_qtype_coderunner_warning_div").empty(),outcome.success)copyFieldsFromQuestionType(newType,outcome),setUis(),loadUiParametersDescription(),currentQtype=newType,$("#id_qtype_coderunner_error_div").empty();else{const errorObject=function(questionType,error){const errorObject=JSON.parse(error);return str.get_string("prototype_error","qtype_coderunner").then((function(s){str.get_string(errorObject.alert,"qtype_coderunner",questionType).then((function(str){langStringAlert("prototype_load_failure",str);let errorMessage=s+"\n";errorMessage+=str+"\n",errorMessage+="CourseId: "+courseId+", qtype: "+questionType,template.prop("value",errorMessage)}))})),errorObject}(newType,outcome.error);currentQtype!==newType&&"duplicateprototype"===errorObject.error&&(!function(currentType,errorObject,newType){str.get_string("loadprototypeerror","qtype_coderunner",{oldtype:currentType,crtype:newType,outputstring:errorObject.extras}).then((function(str){$("#id_qtype_coderunner_warning_div").append($("

"+str+"

"))}))}(currentQtype,errorObject,newType),$("#id_coderunnertype").val(currentQtype))}})).fail((function(){langStringAlert("error_loading_prototype"),template.prop("value","*** AJAX ERROR. DON'T SAVE THIS! ***"),str.get_string("ajax_error","qtype_coderunner").then((function(s){template.prop("value",s)}))})))}function updateUiParamsDescription(uiInfo){let currentuiparameters=uiparameters.val(),paramDescriptionDiv=$(".ui_parameters_descr");if(paramDescriptionDiv.empty(),null===uiInfo||0==uiInfo.uiparamstable.length&&""===currentuiparameters.trim())uiparameters.val(""),$("#fgroup_id_uiparametergroup").hide();else{paramDescriptionDiv.append(uiInfo.header);let showhidebutton=$('");if(0!=uiInfo.uiparamstable.length){paramDescriptionDiv.append(showhidebutton);let table=$(function(uiParamInfo){var param,i,html='
\n',hdrs=uiParamInfo.columnheaders;for(html+="\n",i=0;i\n";return html+"
"+hdrs[0]+""+hdrs[1]+""+hdrs[2]+"
"+(param=uiParamInfo.uiparamstable[i])[0]+""+param[1]+""+param[2]+"
\n"}(uiInfo));paramDescriptionDiv.append(table),table.hide(),showhidebutton.click((function(){showhidebutton.html()==uiInfo.showdetails?(table.show(),showhidebutton.html(uiInfo.hidedetails)):(table.hide(),showhidebutton.html(uiInfo.showdetails))}))}$("#fgroup_id_uiparametergroup").show(),useace.prop("checked")&&setUi("id_uiparameters","ace")}}function loadUiParametersDescription(){let newUi=uiplugin.children("option:selected").text();""===newUi||"none"===newUi?updateUiParamsDescription(null):$.getJSON(M.cfg.wwwroot+"/question/type/coderunner/ajax.php",{uiplugin:newUi,courseid:courseId,sesskey:M.cfg.sesskey},updateUiParamsDescription).fail((function(){langStringAlert("error_loading_ui_descr","UI: ".concat(newUi))}))}function set_testtype_visibilities(){"3"===precheck.val()?testtypedivs.show():testtypedivs.hide()}function check_ace_lang(){"ace"===uiplugin.val()&&setUis()}0!=prototypeType.prop("value")&&(testsection.css("display","none"),prototypeDisplay.removeAttr("hidden"),1==prototypeType.prop("value")&&(str.get_string("proceed_at_own_risk","qtype_coderunner").then((function(s){alert(s)})),prototypeType.prop("disabled",!0),customise.prop("disabled",!0))),function(){let messagePara=null;""!==brokenQuestion.prop("value")&&(messagePara=$("

"+brokenQuestion.prop("value")+"

"),$("#id_qtype_coderunner_error_div").append(messagePara))}(),badQuestionLoad.prop("hidden"),currentQtype=typeCombo.children("option:selected").text(),setCustomisationVisibility(isCustomised),isCustomised?(setUis(),str.get_string("info_unavailable","qtype_coderunner").then((function(s){questiontypeHelpDiv.html("

"+s+"

")}))):loadCustomisationFields(),set_testtype_visibilities(),useace.prop("checked")&&(setUi("id_templateparams","ace"),setUi("id_uiparameters","ace")),loadUiParametersDescription(),customise.on("change",(function(){customise.prop("checked")?setCustomisationVisibility(!0):str.get_string("confirm_proceed","qtype_coderunner").then((function(s){window.confirm(s)?setCustomisationVisibility(!1):customise.prop("checked",!0)}))})),acelang.on("change",check_ace_lang),language.on("change",(function(){useace.prop("checked")&&setUi("id_template","ace"),check_ace_lang()})),typeCombo.on("change",(function(){customise.prop("checked")?str.get_string("question_type_changed","qtype_coderunner").then((function(s){window.confirm(s)&&loadCustomisationFields()})):loadCustomisationFields()})),useace.on("change",(function(){useace.prop("checked")?(setUi("id_template","ace"),setUi("id_templateparams","ace"),setUi("id_uiparameters","ace")):(setUi("id_template",""),setUi("id_templateparams",""),setUi("id_uiparameters",""))})),evaluatePerStudent.on("change",(function(){evaluatePerStudent.is(":checked")&&langStringAlert("templateparamsusingsandbox")})),uiplugin.on("change",(function(){setUis(),loadUiParametersDescription()})),precheck.on("change",set_testtype_visibilities),prototypeType.on("change",(function(){"0"==prototypeType.prop("value")?(testsection.css("display","block"),prototypeDisplay.attr("hidden","1")):(testsection.css("display","none"),prototypeDisplay.removeAttr("hidden"))})),new MutationObserver((function(){setUis()})).observe(preloadHdr.get(0),{attributes:!0}),$("button.replaceexpectedwithgot").click((function(){var gotPre=$(this).prev('pre[id^="id_got_"]'),testCaseId=gotPre.attr("id").replace("id_got_","");$("#id_expected_"+testCaseId).val(gotPre.text()),$("#id_fail_expected_"+testCaseId).html(gotPre.text()),$(".failrow_"+testCaseId).addClass("fixed"),$(this).prop("disabled",!0)})),$(".btn-primary").click((function(){typeCombo.prop("disabled",!1)}))}}})); +define("qtype_coderunner/authorform",["jquery","qtype_coderunner/userinterfacewrapper","core/str"],(function($,ui,str){let currentQtype="";var JSON_TO_FORM_MAP={template:["#id_template","value",""],iscombinatortemplate:["#id_iscombinatortemplate","checked","",function(value){return"1"===value}],cputimelimitsecs:["#id_cputimelimitsecs","value",""],memlimitmb:["#id_memlimitmb","value",""],sandbox:["#id_sandbox","value","DEFAULT"],sandboxparams:["#id_sandboxparams","value",""],testsplitterre:["#id_testsplitterre","value","",function(splitter){return splitter.replace("\n","\\n")}],allowmultiplestdins:["#id_allowmultiplestdins","checked","",function(value){return"1"===value}],grader:["#id_grader","value","EqualityGrader"],resultcolumns:["#id_resultcolumns","value",""],language:["#id_language","value",""],acelang:["#id_acelang","value",""],uiplugin:["#id_uiplugin","value","ace"]};return{initEditForm:function(){var typeCombo=$("#id_coderunnertype"),prototypeDisplay=$("#id_isprototype"),template=$("#id_template"),evaluatePerStudent=$("#id_templateparamsevalpertry"),globalextra=$("#id_globalextra"),prototypeextra=$("#id_prototypeextra"),useace=$("#id_useace"),language=$("#id_language"),acelang=$("#id_acelang"),customise=$("#id_customise"),isCombinator=$("#id_iscombinatortemplate"),testSplitterRe=$("#id_testsplitterre"),allowMultipleStdins=$("#id_allowmultiplestdins"),customisationFieldSet=$("#id_customisationheader"),advancedCustomisation=$("#id_advancedcustomisationheader"),isCustomised=customise.prop("checked"),prototypeType=$("#id_prototypetype"),preloadHdr=$("#id_answerpreloadhdr"),courseId=$('input[name="courseid"]').prop("value"),questiontypeHelpDiv=$("#qtype-help"),precheck=$("select#id_precheck"),testtypedivs=$("div.testtype"),testsection=$("#id_testcasehdr"),brokenQuestion=$("#id_broken_question"),badQuestionLoad=$("#id_bad_question_load"),uiplugin=$("#id_uiplugin"),uiparameters=$("#id_uiparameters");function setUi(taId,uiname){var lang,uiWrapper,ta=$(document.getElementById(taId)),paramsJson=ta.attr("data-params"),params={};ta.attr("data-prototypeextra",prototypeextra.val()),ta.attr("data-globalextra",globalextra.val()),ta.attr("data-test0",$("#id_testcode_0").val());try{params=JSON.parse(paramsJson)}catch(err){}"none"===(uiname=uiname.toLowerCase())&&(uiname=""),"id_templateparams"==taId||"id_uiparameters"==taId?lang="":(lang=language.prop("value"),"id_template"!==taId&&acelang.prop("value")&&(lang=function(acelang){var langs,i;if(acelang.indexOf(",")<0)return acelang;for(langs=acelang.split(","),i=0;i0?langs[0]:""}(acelang.prop("value")))),uiWrapper=ta[0].current_ui_wrapper,ta.attr("data-lang",lang),uiWrapper?(params.lang=lang,uiWrapper.loadUi(uiname,params)):uiWrapper=new ui.InterfaceWrapper(uiname,taId)}function setUis(){let uiname=uiplugin.val(),answer=$("#id_answer"),enableUi=!0;if("html"===uiname&&""!==answer.attr("data-params"))try{!1===JSON.parse(answer.attr("data-params")).enable_in_editor&&(enableUi=!1)}catch(error){alert("Invalid UI parameters.")}enableUi&&(setUi("id_answer",uiname),setUi("id_answerpreload",uiname))}function setCustomisationVisibility(isVisible){var display=isVisible?"block":"none";customisationFieldSet.css("display",display),advancedCustomisation.css("display",display),isVisible&&useace.prop("checked")&&setUi("id_template","ace")}function copyFieldsFromQuestionType(newType,response){var formspecifier,attrval,isCombinatorEnabled;for(var key in function(stateOn){var uiWrapper,taIds=["id_template","id_uiparameters"];if(useace.prop("checked"))for(var i=0;i3&&(attrval=(0,formspecifier[3])(attrval)),$(formspecifier[0]).prop(formspecifier[1],attrval);customise.prop("checked",!1),str.get_string("coderunner_question_type","qtype_coderunner").then((function(s){var title,coderunner_descr,html,resultHtml;questiontypeHelpDiv.html((title=newType,coderunner_descr=s,html=response.questiontext,resultHtml='

',resultHtml+=coderunner_descr,resultHtml+=title+"

\n"+html))})),setCustomisationVisibility(!1),isCombinatorEnabled=isCombinator.prop("checked"),testSplitterRe.prop("disabled",!isCombinatorEnabled),allowMultipleStdins.prop("disabled",!isCombinatorEnabled)}function langStringAlert(key,extra){window.hasOwnProperty("behattesting")&&window.behattesting||str.get_string(key,"qtype_coderunner").then((function(s){var message=s.replace(/\n/g," ");extra&&(message+="\n"+extra),alert(message)}))}function loadCustomisationFields(){let newType=typeCombo.children("option:selected").text();""!==newType&&"Undefined"!==newType&&(typeCombo.children("option:first-child").prop("disabled","disabled"),$.getJSON(M.cfg.wwwroot+"/question/type/coderunner/ajax.php",{qtype:newType,courseid:courseId,sesskey:M.cfg.sesskey},(function(outcome){if($("#id_qtype_coderunner_warning_div").empty(),outcome.success)copyFieldsFromQuestionType(newType,outcome),setUis(),loadUiParametersDescription(),currentQtype=newType,$("#id_qtype_coderunner_error_div").empty();else{const errorObject=function(questionType,error){const errorObject=JSON.parse(error);return str.get_string("prototype_error","qtype_coderunner").then((function(s){str.get_string(errorObject.alert,"qtype_coderunner",questionType).then((function(str){langStringAlert("prototype_load_failure",str);let errorMessage=s+"\n";errorMessage+=str+"\n",errorMessage+="CourseId: "+courseId+", qtype: "+questionType,template.prop("value",errorMessage)}))})),errorObject}(newType,outcome.error);currentQtype!==newType&&"duplicateprototype"===errorObject.error&&(!function(currentType,errorObject,newType){str.get_string("loadprototypeerror","qtype_coderunner",{oldtype:currentType,crtype:newType,outputstring:errorObject.extras}).then((function(str){$("#id_qtype_coderunner_warning_div").append($("

"+str+"

"))}))}(currentQtype,errorObject,newType),$("#id_coderunnertype").val(currentQtype))}})).fail((function(){langStringAlert("error_loading_prototype"),template.prop("value","*** AJAX ERROR. DON'T SAVE THIS! ***"),str.get_string("ajax_error","qtype_coderunner").then((function(s){template.prop("value",s)}))})))}function updateUiParamsDescription(uiInfo){let currentuiparameters=uiparameters.val(),paramDescriptionDiv=$(".ui_parameters_descr");if(paramDescriptionDiv.empty(),null===uiInfo||0==uiInfo.uiparamstable.length&&""===currentuiparameters.trim())uiparameters.val(""),$("#fgroup_id_uiparametergroup").hide();else{paramDescriptionDiv.append(uiInfo.header);let showhidebutton=$('");if(0!=uiInfo.uiparamstable.length){paramDescriptionDiv.append(showhidebutton);let table=$(function(uiParamInfo){var param,i,html='
\n',hdrs=uiParamInfo.columnheaders;for(html+="\n",i=0;i\n";return html+"
"+hdrs[0]+""+hdrs[1]+""+hdrs[2]+"
"+(param=uiParamInfo.uiparamstable[i])[0]+""+param[1]+""+param[2]+"
\n"}(uiInfo));paramDescriptionDiv.append(table),table.hide(),showhidebutton.click((function(){showhidebutton.html()==uiInfo.showdetails?(table.show(),showhidebutton.html(uiInfo.hidedetails)):(table.hide(),showhidebutton.html(uiInfo.showdetails))}))}$("#fgroup_id_uiparametergroup").show(),useace.prop("checked")&&setUi("id_uiparameters","ace")}}function loadUiParametersDescription(){let newUi=uiplugin.children("option:selected").text();""===newUi||"none"===newUi?updateUiParamsDescription(null):$.getJSON(M.cfg.wwwroot+"/question/type/coderunner/ajax.php",{uiplugin:newUi,courseid:courseId,sesskey:M.cfg.sesskey},updateUiParamsDescription).fail((function(){langStringAlert("error_loading_ui_descr","UI: ".concat(newUi))}))}function set_testtype_visibilities(){"3"===precheck.val()?testtypedivs.show():testtypedivs.hide()}function check_ace_lang(){"ace"===uiplugin.val()&&setUis()}0!=prototypeType.prop("value")&&(testsection.css("display","none"),prototypeDisplay.removeAttr("hidden"),1==prototypeType.prop("value")&&(str.get_string("proceed_at_own_risk","qtype_coderunner").then((function(s){alert(s)})),prototypeType.prop("disabled",!0),customise.prop("disabled",!0))),function(){let messagePara=null;""!==brokenQuestion.prop("value")&&(messagePara=$("

"+brokenQuestion.prop("value")+"

"),$("#id_qtype_coderunner_error_div").append(messagePara))}(),badQuestionLoad.prop("hidden"),currentQtype=typeCombo.children("option:selected").text(),setCustomisationVisibility(isCustomised),isCustomised?(setUis(),str.get_string("info_unavailable","qtype_coderunner").then((function(s){questiontypeHelpDiv.html("

"+s+"

")}))):loadCustomisationFields(),set_testtype_visibilities(),useace.prop("checked")&&(setUi("id_templateparams","ace"),setUi("id_uiparameters","ace")),loadUiParametersDescription(),customise.on("change",(function(){customise.prop("checked")?setCustomisationVisibility(!0):str.get_string("confirm_proceed","qtype_coderunner").then((function(s){window.confirm(s)?setCustomisationVisibility(!1):customise.prop("checked",!0)}))})),acelang.on("change",check_ace_lang),language.on("change",(function(){useace.prop("checked")&&setUi("id_template","ace"),check_ace_lang()})),typeCombo.on("change",(function(){customise.prop("checked")?str.get_string("question_type_changed","qtype_coderunner").then((function(s){window.confirm(s)&&loadCustomisationFields()})):loadCustomisationFields()})),useace.on("change",(function(){useace.prop("checked")?(setUi("id_template","ace"),setUi("id_templateparams","ace"),setUi("id_uiparameters","ace")):(setUi("id_template",""),setUi("id_templateparams",""),setUi("id_uiparameters",""))})),evaluatePerStudent.on("change",(function(){evaluatePerStudent.is(":checked")&&langStringAlert("templateparamsusingsandbox")})),uiplugin.on("change",(function(){setUis(),loadUiParametersDescription()})),precheck.on("change",set_testtype_visibilities),prototypeType.on("change",(function(){"0"==prototypeType.prop("value")?(testsection.css("display","block"),prototypeDisplay.attr("hidden","1")):(testsection.css("display","none"),prototypeDisplay.removeAttr("hidden"))})),new MutationObserver((function(){setUis()})).observe(preloadHdr.get(0),{attributes:!0}),$("button.replaceexpectedwithgot").click((function(){var gotPre=$(this).prev('pre[id^="id_got_"]'),testCaseId=gotPre.attr("id").replace("id_got_","");$("#id_expected_"+testCaseId).val(gotPre.text()),$("#id_fail_expected_"+testCaseId).html(gotPre.text()),$(".failrow_"+testCaseId).addClass("fixed"),$(this).prop("disabled",!0)})),$(".btn-primary").click((function(){typeCombo.prop("disabled",!1)}))}}})); //# sourceMappingURL=authorform.min.js.map \ No newline at end of file diff --git a/amd/build/authorform.min.js.map b/amd/build/authorform.min.js.map index 89ee036f9..e226c73e8 100644 --- a/amd/build/authorform.min.js.map +++ b/amd/build/authorform.min.js.map @@ -1 +1 @@ -{"version":3,"file":"authorform.min.js","sources":["../src/authorform.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/* jshint esversion: 6 */\n\n/**\n * JavaScript for handling UI actions in the question authoring form.\n *\n * @module qtype_coderunner/authorform\n * @copyright Richard Lobb, 2015, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'qtype_coderunner/userinterfacewrapper', 'core/str'], function($, ui, str) {\n\n // We need this to keep track of the current question type.\n let currentQtype = \"\";\n\n // Define a mapping from the fields of the JSON object returned by an AJAX\n // 'get question type' request to the form elements. Only fields that\n // belong to the question type should appear here. Keys are JSON field\n // names, values are a 3- or 4-element array of: a jQuery form element selector;\n // the element property to be set; a default value if the JSON field is\n // empty and an optional filter function to apply to the field value before\n // setting the property with it.\n var JSON_TO_FORM_MAP = {\n template: ['#id_template', 'value', ''],\n iscombinatortemplate:['#id_iscombinatortemplate', 'checked', '',\n function (value) {\n return value === '1' ? true : false;\n }], // Need nice clean boolean for 'checked' attribute.\n cputimelimitsecs: ['#id_cputimelimitsecs', 'value', ''],\n memlimitmb: ['#id_memlimitmb', 'value', ''],\n sandbox: ['#id_sandbox', 'value', 'DEFAULT'],\n sandboxparams: ['#id_sandboxparams', 'value', ''],\n testsplitterre: ['#id_testsplitterre', 'value', '',\n function (splitter) {\n return splitter.replace('\\n', '\\\\n');\n }],\n allowmultiplestdins: ['#id_allowmultiplestdins', 'checked', '',\n function (value) {\n return value === '1' ? true : false;\n }],\n grader: ['#id_grader', 'value', 'EqualityGrader'],\n resultcolumns: ['#id_resultcolumns', 'value', ''],\n language: ['#id_language', 'value', ''],\n acelang: ['#id_acelang', 'value', ''],\n uiplugin: ['#id_uiplugin', 'value', 'ace']\n };\n\n /**\n * Set up the author edit form UI plugins and event handlers.\n * The template parameters and Ace language are passed to each\n * text area from PHP by setting its data-params and\n * data-lang attributes.\n */\n function initEditForm() {\n var typeCombo = $('#id_coderunnertype'),\n prototypeDisplay = $('#id_isprototype'),\n template = $('#id_template'),\n evaluatePerStudent = $('#id_templateparamsevalpertry'),\n globalextra = $('#id_globalextra'),\n prototypeextra = $('#id_prototypeextra'),\n useace = $('#id_useace'),\n language = $('#id_language'),\n acelang = $('#id_acelang'),\n customise = $('#id_customise'),\n isCombinator = $('#id_iscombinatortemplate'),\n testSplitterRe = $('#id_testsplitterre'),\n allowMultipleStdins = $('#id_allowmultiplestdins'),\n customisationFieldSet = $('#id_customisationheader'),\n advancedCustomisation = $('#id_advancedcustomisationheader'),\n isCustomised = customise.prop('checked'),\n prototypeType = $('#id_prototypetype'),\n preloadHdr = $('#id_answerpreloadhdr'),\n courseId = $('input[name=\"courseid\"]').prop('value'),\n questiontypeHelpDiv = $('#qtype-help'),\n precheck = $('select#id_precheck'),\n testtypedivs = $('div.testtype'),\n testsection = $('#id_testcasehdr'),\n brokenQuestion = $('#id_broken_question'),\n badQuestionLoad = $('#id_bad_question_load'),\n uiplugin = $('#id_uiplugin'),\n uiparameters = $('#id_uiparameters');\n\n /**\n * Set up the UI controller for a given textarea (one of template,\n * answer or answerpreload).\n * We don't attempt to process changes in template parameters,\n * as these need to be merged with those of the prototype. But we do handle\n * changes in the language.\n * @param {string} taId The ID of the textarea element.\n * @param {string} uiname The name of the UI controller (may be empty or none).\n */\n function setUi(taId, uiname) {\n var ta = $(document.getElementById(taId)), // The jquery text area element(s).\n lang,\n paramsJson = ta.attr('data-params'), // Ui params set by PHP.\n params = {},\n uiWrapper;\n\n // Set data attributes in the text area for UI components that need\n // global extra or testcase data (e.g. gapfiller UI).\n ta.attr('data-prototypeextra', prototypeextra.val());\n ta.attr('data-globalextra', globalextra.val());\n ta.attr('data-test0', $('#id_testcode_0').val());\n try {\n params = JSON.parse(paramsJson);\n } catch(err) {}\n uiname = uiname.toLowerCase();\n if (uiname === 'none') {\n uiname = '';\n }\n\n if (taId == 'id_templateparams' || taId == 'id_uiparameters') {\n lang = ''; // These fields may be twigged, so can't be parsed by Ace.\n } else {\n lang = language.prop('value');\n if (taId !== \"id_template\" && acelang.prop('value')) {\n lang = preferredAceLang(acelang.prop('value'));\n }\n }\n\n uiWrapper = ta.data('current-ui-wrapper'); // Currently-active UI wrapper on this ta.\n\n ta.attr('data-lang', lang);\n\n if (!uiWrapper) {\n uiWrapper = new ui.InterfaceWrapper(uiname, taId);\n } else {\n // Wrapper has already been set up - just reload the reqd UI.\n params.lang = lang;\n uiWrapper.loadUi(uiname, params);\n }\n\n }\n\n /**\n * Set the correct Ui controller on both the sample answer and the answer preload.\n * The sample answer and answer preload have the data-params attribute which contains\n * the UI params in a JSON from the current question merged with the prototype.\n * Both of them are identical and are changed simultaneously; only checking\n * answer as state is identical.\n * As a special case, we don't turn on the Ui controller in the answer\n * and answer preload fields when using Html-Ui and the ui-parameter\n * enable_in_editor is false.\n *\n */\n function setUis() {\n let uiname = uiplugin.val();\n let answer = $('#id_answer');\n let enableUi = true;\n if (uiname === 'html' && answer.attr('data-params') !== '') {\n try {\n let answerparams = JSON.parse(answer.attr('data-params'));\n if (answerparams.enable_in_editor === false) {\n enableUi = false;\n }\n } catch (error) {\n alert(\"Invalid UI parameters.\");\n }\n }\n if (enableUi) {\n setUi('id_answer', uiname);\n setUi('id_answerpreload', uiname);\n }\n }\n\n /**\n * Display or Hide all customisation parts of the form.\n * @param {bool} isVisible True to show, false to hide.\n */\n function setCustomisationVisibility(isVisible) {\n var display = isVisible ? 'block' : 'none';\n customisationFieldSet.css('display', display);\n advancedCustomisation.css('display', display);\n if (isVisible && useace.prop('checked')) {\n setUi('id_template', 'ace');\n }\n }\n\n\n /**\n * Turn on or off the Ace editor in the template and uiparameters fields\n * so we can reload the textareas with JavaScript.\n * Ignore if UseAce is unchecked.\n * @param {bool} stateOn True to stop Ace, false to restart it.\n */\n function enableAceInCustomisedFields(stateOn) {\n var taIds = ['id_template', 'id_uiparameters'];\n var uiWrapper, ta;\n if (useace.prop('checked')) {\n for(var i = 0; i < taIds.length; i++) {\n ta = $(document.getElementById(taIds[i]));\n uiWrapper = ta.data('current-ui-wrapper');\n if (uiWrapper && stateOn) {\n uiWrapper.restart();\n } else if (uiWrapper && !stateOn) {\n uiWrapper.stop();\n }\n }\n }\n }\n\n\n /**\n * After loading the form with new question type data we have to\n * enable or disable both the testsplitterre and the allow multiple\n * stdins field. These are subsequently enabled/disabled via event handlers\n * set up by code in edit_coderunner_form.php (q.v.) but those event\n * handlers do not handle the freshly downloaded state.\n */\n function enableTemplateSupportFields() {\n var isCombinatorEnabled = isCombinator.prop('checked');\n\n testSplitterRe.prop('disabled', !isCombinatorEnabled);\n allowMultipleStdins.prop('disabled', !isCombinatorEnabled);\n }\n\n /**\n * Copy fields from the AJAX \"get question type\" response into the form.\n * @param {string} newType the newly selected question type.\n * @param {object} response The AJAX resopnse.\n */\n function copyFieldsFromQuestionType(newType, response) {\n var formspecifier, attrval, filter;\n\n enableAceInCustomisedFields(false);\n for (var key in JSON_TO_FORM_MAP) {\n formspecifier = JSON_TO_FORM_MAP[key];\n attrval = response[key] ? response[key] : formspecifier[2];\n if (formspecifier.length > 3) {\n filter = formspecifier[3];\n attrval = filter(attrval);\n }\n $(formspecifier[0]).prop(formspecifier[1], attrval);\n }\n\n customise.prop('checked', false);\n str.get_string('coderunner_question_type', 'qtype_coderunner').then(function (s) {\n questiontypeHelpDiv.html(detailsHtml(newType, s, response.questiontext));\n });\n\n setCustomisationVisibility(false);\n enableTemplateSupportFields();\n }\n\n /**\n * A JSON request for a question type returned a 'failure' response - probably a\n * missing question type. Report the error with an alert, and replace\n * the template contents with an error message in case the user\n * saves the question and later wonders why it breaks.\n * Returns the JSON error object for further use.\n * @param {string} questionType The CodeRunner (sub) question type.\n * @param {string} error The error message as JSON encoded error => langstring,\n * extra => components string.\n * @return {JSON object} The JSON error object for further parsing.\n */\n function reportError(questionType, error) {\n const errorObject = JSON.parse(error);\n str.get_string('prototype_error', 'qtype_coderunner').then(function(s) {\n str.get_string(errorObject.alert, 'qtype_coderunner', questionType).then(function(str) {\n langStringAlert('prototype_load_failure', str);\n let errorMessage = s + \"\\n\";\n errorMessage += str + '\\n';\n errorMessage += \"CourseId: \" + courseId + \", qtype: \" + questionType;\n template.prop('value', errorMessage);\n });\n });\n return errorObject;\n }\n\n /**\n * Local function to return the HTML to display in the\n * question type details section of the form.\n * @param {string} title The type of the question being described.\n * @param {string} coderunner_descr The language string to introduce\n * the detail.\n * @param {html} html The HTML description of the question type, namely\n * the question text from its prototype.\n * @return {html} The composite HTML describing the question type.\n */\n function detailsHtml(title, coderunner_descr, html) {\n\n var resultHtml = '

';\n resultHtml += coderunner_descr;\n resultHtml += title + '

\\n' + html;\n return resultHtml;\n\n }\n\n /**\n * Raise an alert with the given language string and possible additional\n * extra text.\n * @param {string} key The language string to put in the Alert.\n * @param {string} extra Extra text to append.\n */\n function langStringAlert(key, extra) {\n if (window.hasOwnProperty('behattesting') && window.behattesting) {\n return;\n }\n str.get_string(key, 'qtype_coderunner').then(function(s) {\n var message = s.replace(/\\n/g, \" \");\n if (extra) {\n message += '\\n' + extra;\n }\n alert(message);\n });\n }\n\n /**\n * Get the \"preferred language\" from the AceLang string supplied.\n * @param {string} acelang The AceLang string.\n * For multilanguage questions, this is either the default (i.e.,\n * the language with a '*' suffix), or the first language. Otherwise\n * it is simply the entire AceLang string.\n * @return {string} The language to pass to Ace for syntax highlighting.\n */\n function preferredAceLang(acelang) {\n var langs, i;\n if (acelang.indexOf(',') < 0) {\n return acelang;\n } else {\n langs = acelang.split(',');\n for (i = 0; i < langs.length; i++) {\n if (langs[i].endsWith('*')) {\n return langs[i].substr(0, langs[i].length - 1);\n }\n }\n return langs.length > 0 ? langs[0] : '';\n }\n }\n\n /**\n * Load the various customisation fields into the form from the\n * CodeRunner question type currently selected by the combobox.\n * Looks at the preexisting type of the selected field.\n */\n function loadCustomisationFields() {\n let newType = typeCombo.children('option:selected').text();\n\n if (newType !== '' && newType !== 'Undefined') {\n // Prevent 'Undefined' ever being reselected.\n typeCombo.children('option:first-child').prop('disabled', 'disabled');\n\n // Load question type with ajax.\n $.getJSON(M.cfg.wwwroot + '/question/type/coderunner/ajax.php',\n {\n qtype: newType,\n courseid: courseId,\n sesskey: M.cfg.sesskey\n },\n function (outcome) {\n // Clean all warnings regardless.\n $('#id_qtype_coderunner_warning_div').empty();\n if (outcome.success) {\n copyFieldsFromQuestionType(newType, outcome);\n setUis();\n loadUiParametersDescription();\n // Success, so remove the errors and change the current Qtype.\n currentQtype = newType;\n $('#id_qtype_coderunner_error_div').empty();\n }\n else {\n const errorObject = reportError(newType, outcome.error);\n // Checks to see if there has been a change in type from last saved.\n // If so, put up a load error and keep type unchanged.\n if (currentQtype !== newType && errorObject.error === 'duplicateprototype') {\n showLoadTypeError(currentQtype, errorObject, newType);\n $(\"#id_coderunnertype\").val(currentQtype);\n }\n }\n }\n ).fail(function () {\n // AJAX failed. We're dead, Fred. The attempt to get the\n // language translation for the error message will likely\n // fail too, so use English for a start.\n langStringAlert('error_loading_prototype');\n template.prop('value', '*** AJAX ERROR. DON\\'T SAVE THIS! ***');\n str.get_string('ajax_error', 'qtype_coderunner').then(function(s) {\n template.prop('value', s); // Translates into current language (if it works).\n });\n });\n }\n }\n\n /**\n * Build an HTML table describing all the UI parameters.\n * @param {object} uiParamInfo The object describing the parameters\n * for a particular UI.\n * @return {string} An HTML table describing each UI parameter.\n */\n function UiParameterDescriptionTable(uiParamInfo) {\n var html = '
\\n',\n hdrs = uiParamInfo.columnheaders, param, i;\n html += \"\\n\";\n for (i = 0; i < uiParamInfo.uiparamstable.length; i++) {\n param = uiParamInfo.uiparamstable[i];\n html += \"\\n\";\n }\n html += \"
\" + hdrs[0] + \"\" + hdrs[1] + \"\" + hdrs[2] + \"
\" + param[0] + \"\" + param[1] + \"\" + param[2] + \"
\\n\";\n return html;\n }\n\n\n\n /**\n * Plug the UI info received by getJSON into the author form.\n * @param {object} uiInfo The response data from the getJSON call\n * @returns {undefined}\n */\n function updateUiParamsDescription(uiInfo) {\n let currentuiparameters = uiparameters.val();\n let paramDescriptionDiv = $('.ui_parameters_descr');\n let hideUiParamsDescription = function() {\n uiparameters.val(''); // Remove stray white space.\n $('#fgroup_id_uiparametergroup').hide();\n };\n paramDescriptionDiv.empty();\n if (uiInfo === null || (uiInfo.uiparamstable.length == 0 && currentuiparameters.trim() === '')) {\n hideUiParamsDescription();\n } else {\n paramDescriptionDiv.append(uiInfo.header);\n let showhidebutton = $('');\n if (uiInfo.uiparamstable.length != 0) {\n paramDescriptionDiv.append(showhidebutton);\n let table = $(UiParameterDescriptionTable(uiInfo));\n paramDescriptionDiv.append(table);\n table.hide();\n showhidebutton.click(function () {\n if (showhidebutton.html() == uiInfo.showdetails) {\n table.show();\n showhidebutton.html(uiInfo.hidedetails);\n } else {\n table.hide();\n showhidebutton.html(uiInfo.showdetails);\n }\n });\n }\n $('#fgroup_id_uiparametergroup').show();\n if (useace.prop('checked')) {\n setUi('id_uiparameters', 'ace');\n }\n }\n }\n\n /**\n * Load the UI parameter description field by Ajax initially or\n * when the UI plugin is changed.\n */\n function loadUiParametersDescription() {\n let newUi = uiplugin.children('option:selected').text();\n if (newUi === '' || newUi === 'none') {\n updateUiParamsDescription(null);\n } else {\n $.getJSON(M.cfg.wwwroot + '/question/type/coderunner/ajax.php',\n {\n uiplugin: newUi,\n courseid: courseId,\n sesskey: M.cfg.sesskey\n },\n updateUiParamsDescription\n ).fail(function () {\n // AJAX failed.\n langStringAlert('error_loading_ui_descr', `UI: ${newUi}`);\n });\n }\n }\n\n /**\n * Show/hide all testtype divs in the testcases according to the\n * 'Precheck' selector.\n */\n function set_testtype_visibilities() {\n if (precheck.val() === '3') { // Show only for case of 'Selected'.\n testtypedivs.show();\n } else {\n testtypedivs.hide();\n }\n }\n\n /**\n * Check that the Ace language is correctly set for the answer and\n * answer preload fields.\n */\n function check_ace_lang() {\n if (uiplugin.val() === 'ace'){\n setUis();\n }\n }\n\n /**\n * Check that the Ace language is correctly set for the template,\n * if template_uses_ace is checked.\n */\n function check_template_lang() {\n if (useace.prop('checked')) {\n setUi('id_template', 'ace');\n }\n }\n\n /**\n * If the brokenquestionmessage hidden element is not empty, insert the\n * given message as an error at the top of the question.\n * itself to go back to the last valid value.\n */\n function checkForBrokenQuestion() {\n let brokenQuestionMessage = brokenQuestion.prop('value'),\n messagePara = null;\n if (brokenQuestionMessage !== '') {\n messagePara = $('

' + brokenQuestion.prop('value') + '

');\n $('#id_qtype_coderunner_error_div').append(messagePara);\n }\n }\n\n /**\n * Shows the load type error of the selected type if the selected type is\n * faulty.\n * @param {string} currentType The current type with its errors.\n * @param {JSON Object} errorObject The JSON object containing a list of all the errors.\n * @param {string} newType The new type string which it failed to load.\n */\n function showLoadTypeError(currentType, errorObject, newType) {\n str.get_string('loadprototypeerror', 'qtype_coderunner',\n { oldtype : currentType, crtype : newType, outputstring : errorObject.extras })\n .then(function(str) {\n $('#id_qtype_coderunner_warning_div').append($('

' + str + '

'));\n });\n }\n\n /*************************************************************\n *\n * Body of initEditFormWhenReady starts here.\n *\n *************************************************************/\n\n if (prototypeType.prop('value') != 0) {\n // Display the prototype warning if it's a prototype and hide testboxes.\n testsection.css('display', 'none');\n prototypeDisplay.removeAttr('hidden');\n if (prototypeType.prop('value') == 1) {\n // Editing a built-in question type: Dangerous!\n str.get_string('proceed_at_own_risk', 'qtype_coderunner').then(function(s) {\n alert(s);\n });\n prototypeType.prop('disabled', true);\n customise.prop('disabled', true);\n }\n }\n\n checkForBrokenQuestion();\n badQuestionLoad.prop('hidden'); // Until we check it once.\n // Keep track of the current prototype loaded.\n currentQtype = typeCombo.children('option:selected').text();\n\n setCustomisationVisibility(isCustomised);\n if (!isCustomised) {\n // Not customised so have to load fields from prototype.\n loadCustomisationFields(); // setUis is called when this completes.\n } else {\n setUis(); // Set up UI controllers on answer and answerpreload.\n str.get_string('info_unavailable', 'qtype_coderunner').then(function(s) {\n questiontypeHelpDiv.html(\"

\" + s + \"

\");\n });\n }\n\n set_testtype_visibilities();\n\n if (useace.prop('checked')) {\n setUi('id_templateparams', 'ace');\n setUi('id_uiparameters', 'ace');\n }\n\n loadUiParametersDescription();\n\n // Set up event Handlers.\n\n customise.on('change', function() {\n let isCustomised = customise.prop('checked');\n if (isCustomised) {\n // Customisation is being turned on.\n setCustomisationVisibility(true);\n } else { // Customisation being turned off.\n str.get_string('confirm_proceed', 'qtype_coderunner').then(function(s) {\n if (window.confirm(s)) {\n setCustomisationVisibility(false);\n } else {\n customise.prop('checked', true);\n }\n });\n }\n });\n\n acelang.on('change', check_ace_lang);\n language.on('change', function() {\n check_template_lang();\n check_ace_lang();\n });\n\n typeCombo.on('change', function() {\n if (customise.prop('checked')) {\n // Author has customised the question. Ask if they want to reload inherited stuff.\n str.get_string('question_type_changed', 'qtype_coderunner').then(function (s) {\n if (window.confirm(s)) {\n loadCustomisationFields();\n }\n });\n } else {\n loadCustomisationFields();\n }\n });\n\n useace.on('change', function() {\n var isTurningOn = useace.prop('checked');\n if (isTurningOn) {\n setUi('id_template', 'ace');\n setUi('id_templateparams', 'ace');\n setUi('id_uiparameters', 'ace');\n } else {\n setUi('id_template', '');\n setUi('id_templateparams', '');\n setUi('id_uiparameters', '');\n }\n });\n\n evaluatePerStudent.on('change', function() {\n if (evaluatePerStudent.is(':checked')) {\n langStringAlert('templateparamsusingsandbox');\n }\n });\n\n uiplugin.on('change', function () {\n setUis();\n loadUiParametersDescription();\n });\n\n precheck.on('change', set_testtype_visibilities);\n\n // Displays and hides the reason for the question type to be disabled.\n // Also hides/shows the test cases section if prototype/not prototype.\n prototypeType.on('change', function () {\n if (prototypeType.prop('value') == '0') {\n testsection.css('display', 'block');\n prototypeDisplay.attr('hidden', '1');\n } else {\n testsection.css('display', 'none');\n prototypeDisplay.removeAttr('hidden');\n }\n });\n\n // In order to initialise the Ui plugin when the answer preload section is\n // expanded, we monitor attribute mutations in the Answer Preload\n // header.\n var observer = new MutationObserver( function () {\n setUis();\n });\n observer.observe(preloadHdr.get(0), {'attributes': true});\n\n // Setup click handler for the buttons that allow users to replace the\n // expected output with the output got from testing the answer program.\n $('button.replaceexpectedwithgot').click(function() {\n var gotPre = $(this).prev('pre[id^=\"id_got_\"]');\n var testCaseId = gotPre.attr('id').replace('id_got_', '');\n $('#id_expected_' + testCaseId).val(gotPre.text());\n $('#id_fail_expected_' + testCaseId).html(gotPre.text());\n $('.failrow_' + testCaseId).addClass('fixed'); // Fixed row.\n $(this).prop('disabled', true);\n });\n\n // On reloading the page, enable the typeCombo so that its value is POSTed.\n $('.btn-primary').click(function() {\n typeCombo.prop('disabled', false);\n });\n }\n\n return {initEditForm: initEditForm};\n});"],"names":["define","$","ui","str","currentQtype","JSON_TO_FORM_MAP","template","iscombinatortemplate","value","cputimelimitsecs","memlimitmb","sandbox","sandboxparams","testsplitterre","splitter","replace","allowmultiplestdins","grader","resultcolumns","language","acelang","uiplugin","initEditForm","typeCombo","prototypeDisplay","evaluatePerStudent","globalextra","prototypeextra","useace","customise","isCombinator","testSplitterRe","allowMultipleStdins","customisationFieldSet","advancedCustomisation","isCustomised","prop","prototypeType","preloadHdr","courseId","questiontypeHelpDiv","precheck","testtypedivs","testsection","brokenQuestion","badQuestionLoad","uiparameters","setUi","taId","uiname","lang","uiWrapper","ta","document","getElementById","paramsJson","attr","params","val","JSON","parse","err","toLowerCase","langs","i","indexOf","split","length","endsWith","substr","preferredAceLang","data","loadUi","InterfaceWrapper","setUis","answer","enableUi","enable_in_editor","error","alert","setCustomisationVisibility","isVisible","display","css","copyFieldsFromQuestionType","newType","response","formspecifier","attrval","isCombinatorEnabled","key","stateOn","taIds","restart","stop","enableAceInCustomisedFields","filter","get_string","then","s","title","coderunner_descr","html","resultHtml","questiontext","langStringAlert","extra","window","hasOwnProperty","behattesting","message","loadCustomisationFields","children","text","getJSON","M","cfg","wwwroot","qtype","courseid","sesskey","outcome","empty","success","loadUiParametersDescription","errorObject","questionType","errorMessage","reportError","currentType","oldtype","crtype","outputstring","extras","append","showLoadTypeError","fail","updateUiParamsDescription","uiInfo","currentuiparameters","paramDescriptionDiv","uiparamstable","trim","hide","header","showhidebutton","showdetails","table","uiParamInfo","param","hdrs","columnheaders","UiParameterDescriptionTable","click","show","hidedetails","newUi","set_testtype_visibilities","check_ace_lang","removeAttr","messagePara","checkForBrokenQuestion","on","confirm","is","MutationObserver","observe","get","gotPre","this","prev","testCaseId","addClass"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"authorform.min.js","sources":["../src/authorform.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/* jshint esversion: 6 */\n\n/**\n * JavaScript for handling UI actions in the question authoring form.\n *\n * @module qtype_coderunner/authorform\n * @copyright Richard Lobb, 2015, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'qtype_coderunner/userinterfacewrapper', 'core/str'], function($, ui, str) {\n\n // We need this to keep track of the current question type.\n let currentQtype = \"\";\n\n // Define a mapping from the fields of the JSON object returned by an AJAX\n // 'get question type' request to the form elements. Only fields that\n // belong to the question type should appear here. Keys are JSON field\n // names, values are a 3- or 4-element array of: a jQuery form element selector;\n // the element property to be set; a default value if the JSON field is\n // empty and an optional filter function to apply to the field value before\n // setting the property with it.\n var JSON_TO_FORM_MAP = {\n template: ['#id_template', 'value', ''],\n iscombinatortemplate:['#id_iscombinatortemplate', 'checked', '',\n function (value) {\n return value === '1' ? true : false;\n }], // Need nice clean boolean for 'checked' attribute.\n cputimelimitsecs: ['#id_cputimelimitsecs', 'value', ''],\n memlimitmb: ['#id_memlimitmb', 'value', ''],\n sandbox: ['#id_sandbox', 'value', 'DEFAULT'],\n sandboxparams: ['#id_sandboxparams', 'value', ''],\n testsplitterre: ['#id_testsplitterre', 'value', '',\n function (splitter) {\n return splitter.replace('\\n', '\\\\n');\n }],\n allowmultiplestdins: ['#id_allowmultiplestdins', 'checked', '',\n function (value) {\n return value === '1' ? true : false;\n }],\n grader: ['#id_grader', 'value', 'EqualityGrader'],\n resultcolumns: ['#id_resultcolumns', 'value', ''],\n language: ['#id_language', 'value', ''],\n acelang: ['#id_acelang', 'value', ''],\n uiplugin: ['#id_uiplugin', 'value', 'ace']\n };\n\n /**\n * Set up the author edit form UI plugins and event handlers.\n * The template parameters and Ace language are passed to each\n * text area from PHP by setting its data-params and\n * data-lang attributes.\n */\n function initEditForm() {\n var typeCombo = $('#id_coderunnertype'),\n prototypeDisplay = $('#id_isprototype'),\n template = $('#id_template'),\n evaluatePerStudent = $('#id_templateparamsevalpertry'),\n globalextra = $('#id_globalextra'),\n prototypeextra = $('#id_prototypeextra'),\n useace = $('#id_useace'),\n language = $('#id_language'),\n acelang = $('#id_acelang'),\n customise = $('#id_customise'),\n isCombinator = $('#id_iscombinatortemplate'),\n testSplitterRe = $('#id_testsplitterre'),\n allowMultipleStdins = $('#id_allowmultiplestdins'),\n customisationFieldSet = $('#id_customisationheader'),\n advancedCustomisation = $('#id_advancedcustomisationheader'),\n isCustomised = customise.prop('checked'),\n prototypeType = $('#id_prototypetype'),\n preloadHdr = $('#id_answerpreloadhdr'),\n courseId = $('input[name=\"courseid\"]').prop('value'),\n questiontypeHelpDiv = $('#qtype-help'),\n precheck = $('select#id_precheck'),\n testtypedivs = $('div.testtype'),\n testsection = $('#id_testcasehdr'),\n brokenQuestion = $('#id_broken_question'),\n badQuestionLoad = $('#id_bad_question_load'),\n uiplugin = $('#id_uiplugin'),\n uiparameters = $('#id_uiparameters');\n\n /**\n * Set up the UI controller for a given textarea (one of template,\n * answer or answerpreload).\n * We don't attempt to process changes in template parameters,\n * as these need to be merged with those of the prototype. But we do handle\n * changes in the language.\n * @param {string} taId The ID of the textarea element.\n * @param {string} uiname The name of the UI controller (may be empty or none).\n */\n function setUi(taId, uiname) {\n var ta = $(document.getElementById(taId)), // The jquery text area element(s).\n lang,\n paramsJson = ta.attr('data-params'), // Ui params set by PHP.\n params = {},\n uiWrapper;\n\n // Set data attributes in the text area for UI components that need\n // global extra or testcase data (e.g. gapfiller UI).\n ta.attr('data-prototypeextra', prototypeextra.val());\n ta.attr('data-globalextra', globalextra.val());\n ta.attr('data-test0', $('#id_testcode_0').val());\n try {\n params = JSON.parse(paramsJson);\n } catch(err) {}\n uiname = uiname.toLowerCase();\n if (uiname === 'none') {\n uiname = '';\n }\n\n if (taId == 'id_templateparams' || taId == 'id_uiparameters') {\n lang = ''; // These fields may be twigged, so can't be parsed by Ace.\n } else {\n lang = language.prop('value');\n if (taId !== \"id_template\" && acelang.prop('value')) {\n lang = preferredAceLang(acelang.prop('value'));\n }\n }\n\n uiWrapper = ta[0].current_ui_wrapper; // Currently-active UI wrapper on this ta.\n\n ta.attr('data-lang', lang);\n\n if (!uiWrapper) {\n uiWrapper = new ui.InterfaceWrapper(uiname, taId);\n } else {\n // Wrapper has already been set up - just reload the reqd UI.\n params.lang = lang;\n uiWrapper.loadUi(uiname, params);\n }\n\n }\n\n /**\n * Set the correct Ui controller on both the sample answer and the answer preload.\n * The sample answer and answer preload have the data-params attribute which contains\n * the UI params in a JSON from the current question merged with the prototype.\n * Both of them are identical and are changed simultaneously; only checking\n * answer as state is identical.\n * As a special case, we don't turn on the Ui controller in the answer\n * and answer preload fields when using Html-Ui and the ui-parameter\n * enable_in_editor is false.\n *\n */\n function setUis() {\n let uiname = uiplugin.val();\n let answer = $('#id_answer');\n let enableUi = true;\n if (uiname === 'html' && answer.attr('data-params') !== '') {\n try {\n let answerparams = JSON.parse(answer.attr('data-params'));\n if (answerparams.enable_in_editor === false) {\n enableUi = false;\n }\n } catch (error) {\n alert(\"Invalid UI parameters.\");\n }\n }\n if (enableUi) {\n setUi('id_answer', uiname);\n setUi('id_answerpreload', uiname);\n }\n }\n\n /**\n * Display or Hide all customisation parts of the form.\n * @param {bool} isVisible True to show, false to hide.\n */\n function setCustomisationVisibility(isVisible) {\n var display = isVisible ? 'block' : 'none';\n customisationFieldSet.css('display', display);\n advancedCustomisation.css('display', display);\n if (isVisible && useace.prop('checked')) {\n setUi('id_template', 'ace');\n }\n }\n\n\n /**\n * Turn on or off the Ace editor in the template and uiparameters fields\n * so we can reload the textareas with JavaScript.\n * Ignore if UseAce is unchecked.\n * @param {bool} stateOn True to stop Ace, false to restart it.\n */\n function enableAceInCustomisedFields(stateOn) {\n var taIds = ['id_template', 'id_uiparameters'];\n var uiWrapper, ta;\n if (useace.prop('checked')) {\n for(var i = 0; i < taIds.length; i++) {\n ta = $(document.getElementById(taIds[i]));\n uiWrapper = ta.data('current-ui-wrapper');\n if (uiWrapper && stateOn) {\n uiWrapper.restart();\n } else if (uiWrapper && !stateOn) {\n uiWrapper.stop();\n }\n }\n }\n }\n\n\n /**\n * After loading the form with new question type data we have to\n * enable or disable both the testsplitterre and the allow multiple\n * stdins field. These are subsequently enabled/disabled via event handlers\n * set up by code in edit_coderunner_form.php (q.v.) but those event\n * handlers do not handle the freshly downloaded state.\n */\n function enableTemplateSupportFields() {\n var isCombinatorEnabled = isCombinator.prop('checked');\n\n testSplitterRe.prop('disabled', !isCombinatorEnabled);\n allowMultipleStdins.prop('disabled', !isCombinatorEnabled);\n }\n\n /**\n * Copy fields from the AJAX \"get question type\" response into the form.\n * @param {string} newType the newly selected question type.\n * @param {object} response The AJAX resopnse.\n */\n function copyFieldsFromQuestionType(newType, response) {\n var formspecifier, attrval, filter;\n\n enableAceInCustomisedFields(false);\n for (var key in JSON_TO_FORM_MAP) {\n formspecifier = JSON_TO_FORM_MAP[key];\n attrval = response[key] ? response[key] : formspecifier[2];\n if (formspecifier.length > 3) {\n filter = formspecifier[3];\n attrval = filter(attrval);\n }\n $(formspecifier[0]).prop(formspecifier[1], attrval);\n }\n\n customise.prop('checked', false);\n str.get_string('coderunner_question_type', 'qtype_coderunner').then(function (s) {\n questiontypeHelpDiv.html(detailsHtml(newType, s, response.questiontext));\n });\n\n setCustomisationVisibility(false);\n enableTemplateSupportFields();\n }\n\n /**\n * A JSON request for a question type returned a 'failure' response - probably a\n * missing question type. Report the error with an alert, and replace\n * the template contents with an error message in case the user\n * saves the question and later wonders why it breaks.\n * Returns the JSON error object for further use.\n * @param {string} questionType The CodeRunner (sub) question type.\n * @param {string} error The error message as JSON encoded error => langstring,\n * extra => components string.\n * @return {JSON object} The JSON error object for further parsing.\n */\n function reportError(questionType, error) {\n const errorObject = JSON.parse(error);\n str.get_string('prototype_error', 'qtype_coderunner').then(function(s) {\n str.get_string(errorObject.alert, 'qtype_coderunner', questionType).then(function(str) {\n langStringAlert('prototype_load_failure', str);\n let errorMessage = s + \"\\n\";\n errorMessage += str + '\\n';\n errorMessage += \"CourseId: \" + courseId + \", qtype: \" + questionType;\n template.prop('value', errorMessage);\n });\n });\n return errorObject;\n }\n\n /**\n * Local function to return the HTML to display in the\n * question type details section of the form.\n * @param {string} title The type of the question being described.\n * @param {string} coderunner_descr The language string to introduce\n * the detail.\n * @param {html} html The HTML description of the question type, namely\n * the question text from its prototype.\n * @return {html} The composite HTML describing the question type.\n */\n function detailsHtml(title, coderunner_descr, html) {\n\n var resultHtml = '

';\n resultHtml += coderunner_descr;\n resultHtml += title + '

\\n' + html;\n return resultHtml;\n\n }\n\n /**\n * Raise an alert with the given language string and possible additional\n * extra text.\n * @param {string} key The language string to put in the Alert.\n * @param {string} extra Extra text to append.\n */\n function langStringAlert(key, extra) {\n if (window.hasOwnProperty('behattesting') && window.behattesting) {\n return;\n }\n str.get_string(key, 'qtype_coderunner').then(function(s) {\n var message = s.replace(/\\n/g, \" \");\n if (extra) {\n message += '\\n' + extra;\n }\n alert(message);\n });\n }\n\n /**\n * Get the \"preferred language\" from the AceLang string supplied.\n * @param {string} acelang The AceLang string.\n * For multilanguage questions, this is either the default (i.e.,\n * the language with a '*' suffix), or the first language. Otherwise\n * it is simply the entire AceLang string.\n * @return {string} The language to pass to Ace for syntax highlighting.\n */\n function preferredAceLang(acelang) {\n var langs, i;\n if (acelang.indexOf(',') < 0) {\n return acelang;\n } else {\n langs = acelang.split(',');\n for (i = 0; i < langs.length; i++) {\n if (langs[i].endsWith('*')) {\n return langs[i].substr(0, langs[i].length - 1);\n }\n }\n return langs.length > 0 ? langs[0] : '';\n }\n }\n\n /**\n * Load the various customisation fields into the form from the\n * CodeRunner question type currently selected by the combobox.\n * Looks at the preexisting type of the selected field.\n */\n function loadCustomisationFields() {\n let newType = typeCombo.children('option:selected').text();\n\n if (newType !== '' && newType !== 'Undefined') {\n // Prevent 'Undefined' ever being reselected.\n typeCombo.children('option:first-child').prop('disabled', 'disabled');\n\n // Load question type with ajax.\n $.getJSON(M.cfg.wwwroot + '/question/type/coderunner/ajax.php',\n {\n qtype: newType,\n courseid: courseId,\n sesskey: M.cfg.sesskey\n },\n function (outcome) {\n // Clean all warnings regardless.\n $('#id_qtype_coderunner_warning_div').empty();\n if (outcome.success) {\n copyFieldsFromQuestionType(newType, outcome);\n setUis();\n loadUiParametersDescription();\n // Success, so remove the errors and change the current Qtype.\n currentQtype = newType;\n $('#id_qtype_coderunner_error_div').empty();\n }\n else {\n const errorObject = reportError(newType, outcome.error);\n // Checks to see if there has been a change in type from last saved.\n // If so, put up a load error and keep type unchanged.\n if (currentQtype !== newType && errorObject.error === 'duplicateprototype') {\n showLoadTypeError(currentQtype, errorObject, newType);\n $(\"#id_coderunnertype\").val(currentQtype);\n }\n }\n }\n ).fail(function () {\n // AJAX failed. We're dead, Fred. The attempt to get the\n // language translation for the error message will likely\n // fail too, so use English for a start.\n langStringAlert('error_loading_prototype');\n template.prop('value', '*** AJAX ERROR. DON\\'T SAVE THIS! ***');\n str.get_string('ajax_error', 'qtype_coderunner').then(function(s) {\n template.prop('value', s); // Translates into current language (if it works).\n });\n });\n }\n }\n\n /**\n * Build an HTML table describing all the UI parameters.\n * @param {object} uiParamInfo The object describing the parameters\n * for a particular UI.\n * @return {string} An HTML table describing each UI parameter.\n */\n function UiParameterDescriptionTable(uiParamInfo) {\n var html = '
\\n',\n hdrs = uiParamInfo.columnheaders, param, i;\n html += \"\\n\";\n for (i = 0; i < uiParamInfo.uiparamstable.length; i++) {\n param = uiParamInfo.uiparamstable[i];\n html += \"\\n\";\n }\n html += \"
\" + hdrs[0] + \"\" + hdrs[1] + \"\" + hdrs[2] + \"
\" + param[0] + \"\" + param[1] + \"\" + param[2] + \"
\\n\";\n return html;\n }\n\n\n\n /**\n * Plug the UI info received by getJSON into the author form.\n * @param {object} uiInfo The response data from the getJSON call\n * @returns {undefined}\n */\n function updateUiParamsDescription(uiInfo) {\n let currentuiparameters = uiparameters.val();\n let paramDescriptionDiv = $('.ui_parameters_descr');\n let hideUiParamsDescription = function() {\n uiparameters.val(''); // Remove stray white space.\n $('#fgroup_id_uiparametergroup').hide();\n };\n paramDescriptionDiv.empty();\n if (uiInfo === null || (uiInfo.uiparamstable.length == 0 && currentuiparameters.trim() === '')) {\n hideUiParamsDescription();\n } else {\n paramDescriptionDiv.append(uiInfo.header);\n let showhidebutton = $('');\n if (uiInfo.uiparamstable.length != 0) {\n paramDescriptionDiv.append(showhidebutton);\n let table = $(UiParameterDescriptionTable(uiInfo));\n paramDescriptionDiv.append(table);\n table.hide();\n showhidebutton.click(function () {\n if (showhidebutton.html() == uiInfo.showdetails) {\n table.show();\n showhidebutton.html(uiInfo.hidedetails);\n } else {\n table.hide();\n showhidebutton.html(uiInfo.showdetails);\n }\n });\n }\n $('#fgroup_id_uiparametergroup').show();\n if (useace.prop('checked')) {\n setUi('id_uiparameters', 'ace');\n }\n }\n }\n\n /**\n * Load the UI parameter description field by Ajax initially or\n * when the UI plugin is changed.\n */\n function loadUiParametersDescription() {\n let newUi = uiplugin.children('option:selected').text();\n if (newUi === '' || newUi === 'none') {\n updateUiParamsDescription(null);\n } else {\n $.getJSON(M.cfg.wwwroot + '/question/type/coderunner/ajax.php',\n {\n uiplugin: newUi,\n courseid: courseId,\n sesskey: M.cfg.sesskey\n },\n updateUiParamsDescription\n ).fail(function () {\n // AJAX failed.\n langStringAlert('error_loading_ui_descr', `UI: ${newUi}`);\n });\n }\n }\n\n /**\n * Show/hide all testtype divs in the testcases according to the\n * 'Precheck' selector.\n */\n function set_testtype_visibilities() {\n if (precheck.val() === '3') { // Show only for case of 'Selected'.\n testtypedivs.show();\n } else {\n testtypedivs.hide();\n }\n }\n\n /**\n * Check that the Ace language is correctly set for the answer and\n * answer preload fields.\n */\n function check_ace_lang() {\n if (uiplugin.val() === 'ace'){\n setUis();\n }\n }\n\n /**\n * Check that the Ace language is correctly set for the template,\n * if template_uses_ace is checked.\n */\n function check_template_lang() {\n if (useace.prop('checked')) {\n setUi('id_template', 'ace');\n }\n }\n\n /**\n * If the brokenquestionmessage hidden element is not empty, insert the\n * given message as an error at the top of the question.\n * itself to go back to the last valid value.\n */\n function checkForBrokenQuestion() {\n let brokenQuestionMessage = brokenQuestion.prop('value'),\n messagePara = null;\n if (brokenQuestionMessage !== '') {\n messagePara = $('

' + brokenQuestion.prop('value') + '

');\n $('#id_qtype_coderunner_error_div').append(messagePara);\n }\n }\n\n /**\n * Shows the load type error of the selected type if the selected type is\n * faulty.\n * @param {string} currentType The current type with its errors.\n * @param {JSON Object} errorObject The JSON object containing a list of all the errors.\n * @param {string} newType The new type string which it failed to load.\n */\n function showLoadTypeError(currentType, errorObject, newType) {\n str.get_string('loadprototypeerror', 'qtype_coderunner',\n { oldtype : currentType, crtype : newType, outputstring : errorObject.extras })\n .then(function(str) {\n $('#id_qtype_coderunner_warning_div').append($('

' + str + '

'));\n });\n }\n\n /*************************************************************\n *\n * Body of initEditFormWhenReady starts here.\n *\n *************************************************************/\n\n if (prototypeType.prop('value') != 0) {\n // Display the prototype warning if it's a prototype and hide testboxes.\n testsection.css('display', 'none');\n prototypeDisplay.removeAttr('hidden');\n if (prototypeType.prop('value') == 1) {\n // Editing a built-in question type: Dangerous!\n str.get_string('proceed_at_own_risk', 'qtype_coderunner').then(function(s) {\n alert(s);\n });\n prototypeType.prop('disabled', true);\n customise.prop('disabled', true);\n }\n }\n\n checkForBrokenQuestion();\n badQuestionLoad.prop('hidden'); // Until we check it once.\n // Keep track of the current prototype loaded.\n currentQtype = typeCombo.children('option:selected').text();\n\n setCustomisationVisibility(isCustomised);\n if (!isCustomised) {\n // Not customised so have to load fields from prototype.\n loadCustomisationFields(); // setUis is called when this completes.\n } else {\n setUis(); // Set up UI controllers on answer and answerpreload.\n str.get_string('info_unavailable', 'qtype_coderunner').then(function(s) {\n questiontypeHelpDiv.html(\"

\" + s + \"

\");\n });\n }\n\n set_testtype_visibilities();\n\n if (useace.prop('checked')) {\n setUi('id_templateparams', 'ace');\n setUi('id_uiparameters', 'ace');\n }\n\n loadUiParametersDescription();\n\n // Set up event Handlers.\n\n customise.on('change', function() {\n let isCustomised = customise.prop('checked');\n if (isCustomised) {\n // Customisation is being turned on.\n setCustomisationVisibility(true);\n } else { // Customisation being turned off.\n str.get_string('confirm_proceed', 'qtype_coderunner').then(function(s) {\n if (window.confirm(s)) {\n setCustomisationVisibility(false);\n } else {\n customise.prop('checked', true);\n }\n });\n }\n });\n\n acelang.on('change', check_ace_lang);\n language.on('change', function() {\n check_template_lang();\n check_ace_lang();\n });\n\n typeCombo.on('change', function() {\n if (customise.prop('checked')) {\n // Author has customised the question. Ask if they want to reload inherited stuff.\n str.get_string('question_type_changed', 'qtype_coderunner').then(function (s) {\n if (window.confirm(s)) {\n loadCustomisationFields();\n }\n });\n } else {\n loadCustomisationFields();\n }\n });\n\n useace.on('change', function() {\n var isTurningOn = useace.prop('checked');\n if (isTurningOn) {\n setUi('id_template', 'ace');\n setUi('id_templateparams', 'ace');\n setUi('id_uiparameters', 'ace');\n } else {\n setUi('id_template', '');\n setUi('id_templateparams', '');\n setUi('id_uiparameters', '');\n }\n });\n\n evaluatePerStudent.on('change', function() {\n if (evaluatePerStudent.is(':checked')) {\n langStringAlert('templateparamsusingsandbox');\n }\n });\n\n uiplugin.on('change', function () {\n setUis();\n loadUiParametersDescription();\n });\n\n precheck.on('change', set_testtype_visibilities);\n\n // Displays and hides the reason for the question type to be disabled.\n // Also hides/shows the test cases section if prototype/not prototype.\n prototypeType.on('change', function () {\n if (prototypeType.prop('value') == '0') {\n testsection.css('display', 'block');\n prototypeDisplay.attr('hidden', '1');\n } else {\n testsection.css('display', 'none');\n prototypeDisplay.removeAttr('hidden');\n }\n });\n\n // In order to initialise the Ui plugin when the answer preload section is\n // expanded, we monitor attribute mutations in the Answer Preload\n // header.\n var observer = new MutationObserver( function () {\n setUis();\n });\n observer.observe(preloadHdr.get(0), {'attributes': true});\n\n // Setup click handler for the buttons that allow users to replace the\n // expected output with the output got from testing the answer program.\n $('button.replaceexpectedwithgot').click(function() {\n var gotPre = $(this).prev('pre[id^=\"id_got_\"]');\n var testCaseId = gotPre.attr('id').replace('id_got_', '');\n $('#id_expected_' + testCaseId).val(gotPre.text());\n $('#id_fail_expected_' + testCaseId).html(gotPre.text());\n $('.failrow_' + testCaseId).addClass('fixed'); // Fixed row.\n $(this).prop('disabled', true);\n });\n\n // On reloading the page, enable the typeCombo so that its value is POSTed.\n $('.btn-primary').click(function() {\n typeCombo.prop('disabled', false);\n });\n }\n\n return {initEditForm: initEditForm};\n});"],"names":["define","$","ui","str","currentQtype","JSON_TO_FORM_MAP","template","iscombinatortemplate","value","cputimelimitsecs","memlimitmb","sandbox","sandboxparams","testsplitterre","splitter","replace","allowmultiplestdins","grader","resultcolumns","language","acelang","uiplugin","initEditForm","typeCombo","prototypeDisplay","evaluatePerStudent","globalextra","prototypeextra","useace","customise","isCombinator","testSplitterRe","allowMultipleStdins","customisationFieldSet","advancedCustomisation","isCustomised","prop","prototypeType","preloadHdr","courseId","questiontypeHelpDiv","precheck","testtypedivs","testsection","brokenQuestion","badQuestionLoad","uiparameters","setUi","taId","uiname","lang","uiWrapper","ta","document","getElementById","paramsJson","attr","params","val","JSON","parse","err","toLowerCase","langs","i","indexOf","split","length","endsWith","substr","preferredAceLang","current_ui_wrapper","loadUi","InterfaceWrapper","setUis","answer","enableUi","enable_in_editor","error","alert","setCustomisationVisibility","isVisible","display","css","copyFieldsFromQuestionType","newType","response","formspecifier","attrval","isCombinatorEnabled","key","stateOn","taIds","data","restart","stop","enableAceInCustomisedFields","filter","get_string","then","s","title","coderunner_descr","html","resultHtml","questiontext","langStringAlert","extra","window","hasOwnProperty","behattesting","message","loadCustomisationFields","children","text","getJSON","M","cfg","wwwroot","qtype","courseid","sesskey","outcome","empty","success","loadUiParametersDescription","errorObject","questionType","errorMessage","reportError","currentType","oldtype","crtype","outputstring","extras","append","showLoadTypeError","fail","updateUiParamsDescription","uiInfo","currentuiparameters","paramDescriptionDiv","uiparamstable","trim","hide","header","showhidebutton","showdetails","table","uiParamInfo","param","hdrs","columnheaders","UiParameterDescriptionTable","click","show","hidedetails","newUi","set_testtype_visibilities","check_ace_lang","removeAttr","messagePara","checkForBrokenQuestion","on","confirm","is","MutationObserver","observe","get","gotPre","this","prev","testCaseId","addClass"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/multilanguagequestion.min.js b/amd/build/multilanguagequestion.min.js index 704d217b9..19f58719c 100644 --- a/amd/build/multilanguagequestion.min.js +++ b/amd/build/multilanguagequestion.min.js @@ -10,6 +10,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * */ -define("qtype_coderunner/multilanguagequestion",["jquery","core/str"],(function($,str){let NO_LANGUAGE_MESSAGE="";return{initLangSelector:function(taId){$().ready((function(){!function(taId){const ta=$(document.getElementById(taId)),langSelectorId=taId.replace("answer","language"),selector=document.getElementById(langSelectorId),uiWrapperId=taId+"_wrapper";function hasAncestorWithId(element,id){for(;element;){if(element.id===id)return!0;element=element.parentElement}return!1}function setAceLang(){const lang=selector.value,uiWrapper=ta.data("current-ui-wrapper");uiWrapper&&uiWrapper.uiInstance&&"function"==typeof uiWrapper.uiInstance.setLanguage&&uiWrapper.uiInstance.setLanguage(lang)}selector.onchange=setAceLang,document.body.onkeydown=function(event){event.target&&hasAncestorWithId(event.target,uiWrapperId)&&""===selector.value&&NO_LANGUAGE_MESSAGE&&(alert(NO_LANGUAGE_MESSAGE),event.preventDefault())}}(taId)}));const promise=str.get_string("nolanguage","qtype_coderunner");$.when(promise).done((function(message){NO_LANGUAGE_MESSAGE=message}))}}})); +define("qtype_coderunner/multilanguagequestion",["jquery","core/str"],(function($,str){let NO_LANGUAGE_MESSAGE="";return{initLangSelector:function(taId){$().ready((function(){!function(taId){const ta=$(document.getElementById(taId)),langSelectorId=taId.replace("answer","language"),selector=document.getElementById(langSelectorId),uiWrapperId=taId+"_wrapper";function hasAncestorWithId(element,id){for(;element;){if(element.id===id)return!0;element=element.parentElement}return!1}function setAceLang(){const lang=selector.value,uiWrapper=ta[0].current_ui_wrapper;uiWrapper&&uiWrapper.uiInstance&&"function"==typeof uiWrapper.uiInstance.setLanguage&&uiWrapper.uiInstance.setLanguage(lang)}selector.onchange=setAceLang,document.body.onkeydown=function(event){event.target&&hasAncestorWithId(event.target,uiWrapperId)&&""===selector.value&&NO_LANGUAGE_MESSAGE&&(alert(NO_LANGUAGE_MESSAGE),event.preventDefault())}}(taId)}));const promise=str.get_string("nolanguage","qtype_coderunner");$.when(promise).done((function(message){NO_LANGUAGE_MESSAGE=message}))}}})); //# sourceMappingURL=multilanguagequestion.min.js.map \ No newline at end of file diff --git a/amd/build/multilanguagequestion.min.js.map b/amd/build/multilanguagequestion.min.js.map index 0c93a1bdd..57649c839 100644 --- a/amd/build/multilanguagequestion.min.js.map +++ b/amd/build/multilanguagequestion.min.js.map @@ -1 +1 @@ -{"version":3,"file":"multilanguagequestion.min.js","sources":["../src/multilanguagequestion.js"],"sourcesContent":["/******************************************************************************\n *\n * This module simply handles changes in the Language selection dropdown for\n * multilanguage questions as seen by students. It switches the Ace language\n * accordingly.\n * It should only be loaded in conjunction with the ui_ace module.\n *\n * @module qtype_coderunner/multilanguagequestion\n * @copyright Richard Lobb, 2018, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n */\n\ndefine(['jquery', 'core/str'], function($, str) {\n\n let NO_LANGUAGE_MESSAGE = ''; // The 'no language chosen' message, to be set by AJAX.\n\n /**\n * Initialise the language selector dropdown when the document is ready.\n * @param {string} taId The ID of the student answer box text area.\n */\n function initLangSelector(taId) {\n $().ready(function() {\n initLangSelectorWhenReady(taId);\n });\n const promise = str.get_string('nolanguage', 'qtype_coderunner');\n $.when(promise).done(function(message) {\n NO_LANGUAGE_MESSAGE = message; // Get the 'no language chosen' message.\n });\n }\n\n /**\n * Initialise the language selector. Called by initLangSelector when the\n * document is ready, so the name is a bit of a misnomer.\n * @param {string} taId The ID of the student answer box text area.\n */\n function initLangSelectorWhenReady(taId) {\n const ta = $(document.getElementById(taId)); // The jquery text area element(s).\n const langSelectorId = taId.replace('answer', 'language');\n const selector = document.getElementById(langSelectorId);\n const uiWrapperId = taId + '_wrapper';\n\n /**\n * Check if an element has an ancestor with a given ID\n * @param {DOM element} element The element being tested\n * @param {string} id the ancestor id to search for\n * @returns {Boolean} True iff the specified ancestor exists\n */\n function hasAncestorWithId(element, id) {\n while (element) {\n if (element.id === id) {\n return true;\n }\n element = element.parentElement;\n }\n return false;\n }\n\n /**\n * Set the language for the Ace editor.\n */\n function setAceLang() {\n // Set the language for the Ace plugin (or any other plugin if it\n // has a setLanguage method).\n const lang = selector.value;\n const uiWrapper = ta.data('current-ui-wrapper'); // Currently-active UI wrapper on reqd ta.\n\n if (uiWrapper && uiWrapper.uiInstance && typeof uiWrapper.uiInstance.setLanguage === 'function') {\n uiWrapper.uiInstance.setLanguage(lang);\n }\n }\n\n selector.onchange = setAceLang;\n\n // Prevent processing of keydown events in the UI until the dropdown\n // has been set.\n document.body.onkeydown = function(event) {\n if (event.target && hasAncestorWithId(event.target, uiWrapperId)) {\n // Here only if keypress is within the UI wrapper for this question.\n if (selector.value === '' && NO_LANGUAGE_MESSAGE) {\n alert(NO_LANGUAGE_MESSAGE);\n event.preventDefault();\n }\n }\n };\n\n }\n\n return {'initLangSelector' : initLangSelector};\n});"],"names":["define","$","str","NO_LANGUAGE_MESSAGE","taId","ready","ta","document","getElementById","langSelectorId","replace","selector","uiWrapperId","hasAncestorWithId","element","id","parentElement","setAceLang","lang","value","uiWrapper","data","uiInstance","setLanguage","onchange","body","onkeydown","event","target","alert","preventDefault","initLangSelectorWhenReady","promise","get_string","when","done","message"],"mappings":";;;;;;;;;;;;AAaAA,gDAAO,CAAC,SAAU,aAAa,SAASC,EAAGC,SAEnCC,oBAAsB,SAyEnB,2BAnEmBC,MACtBH,IAAII,OAAM,qBAcqBD,YACzBE,GAAKL,EAAEM,SAASC,eAAeJ,OAC/BK,eAAiBL,KAAKM,QAAQ,SAAU,YACxCC,SAAWJ,SAASC,eAAeC,gBACnCG,YAAcR,KAAO,oBAQlBS,kBAAkBC,QAASC,SACzBD,SAAS,IACRA,QAAQC,KAAOA,UACR,EAEXD,QAAUA,QAAQE,qBAEf,WAMFC,mBAGCC,KAAOP,SAASQ,MAChBC,UAAYd,GAAGe,KAAK,sBAEtBD,WAAaA,UAAUE,YAA0D,mBAArCF,UAAUE,WAAWC,aACjEH,UAAUE,WAAWC,YAAYL,MAIzCP,SAASa,SAAWP,WAIpBV,SAASkB,KAAKC,UAAY,SAASC,OAC3BA,MAAMC,QAAUf,kBAAkBc,MAAMC,OAAQhB,cAEzB,KAAnBD,SAASQ,OAAgBhB,sBACzB0B,MAAM1B,qBACNwB,MAAMG,mBA1DdC,CAA0B3B,eAExB4B,QAAU9B,IAAI+B,WAAW,aAAc,oBAC7ChC,EAAEiC,KAAKF,SAASG,MAAK,SAASC,SAC1BjC,oBAAsBiC"} \ No newline at end of file +{"version":3,"file":"multilanguagequestion.min.js","sources":["../src/multilanguagequestion.js"],"sourcesContent":["/******************************************************************************\n *\n * This module simply handles changes in the Language selection dropdown for\n * multilanguage questions as seen by students. It switches the Ace language\n * accordingly.\n * It should only be loaded in conjunction with the ui_ace module.\n *\n * @module qtype_coderunner/multilanguagequestion\n * @copyright Richard Lobb, 2018, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n */\n\ndefine(['jquery', 'core/str'], function($, str) {\n\n let NO_LANGUAGE_MESSAGE = ''; // The 'no language chosen' message, to be set by AJAX.\n\n /**\n * Initialise the language selector dropdown when the document is ready.\n * @param {string} taId The ID of the student answer box text area.\n */\n function initLangSelector(taId) {\n $().ready(function() {\n initLangSelectorWhenReady(taId);\n });\n const promise = str.get_string('nolanguage', 'qtype_coderunner');\n $.when(promise).done(function(message) {\n NO_LANGUAGE_MESSAGE = message; // Get the 'no language chosen' message.\n });\n }\n\n /**\n * Initialise the language selector. Called by initLangSelector when the\n * document is ready, so the name is a bit of a misnomer.\n * @param {string} taId The ID of the student answer box text area.\n */\n function initLangSelectorWhenReady(taId) {\n const ta = $(document.getElementById(taId)); // The jquery text area element(s).\n const langSelectorId = taId.replace('answer', 'language');\n const selector = document.getElementById(langSelectorId);\n const uiWrapperId = taId + '_wrapper';\n\n /**\n * Check if an element has an ancestor with a given ID\n * @param {DOM element} element The element being tested\n * @param {string} id the ancestor id to search for\n * @returns {Boolean} True iff the specified ancestor exists\n */\n function hasAncestorWithId(element, id) {\n while (element) {\n if (element.id === id) {\n return true;\n }\n element = element.parentElement;\n }\n return false;\n }\n\n /**\n * Set the language for the Ace editor.\n */\n function setAceLang() {\n // Set the language for the Ace plugin (or any other plugin if it\n // has a setLanguage method).\n const lang = selector.value;\n const uiWrapper = ta[0].current_ui_wrapper; // Currently-active UI wrapper on reqd ta.\n\n if (uiWrapper && uiWrapper.uiInstance && typeof uiWrapper.uiInstance.setLanguage === 'function') {\n uiWrapper.uiInstance.setLanguage(lang);\n }\n }\n\n selector.onchange = setAceLang;\n\n // Prevent processing of keydown events in the UI until the dropdown\n // has been set.\n document.body.onkeydown = function(event) {\n if (event.target && hasAncestorWithId(event.target, uiWrapperId)) {\n // Here only if keypress is within the UI wrapper for this question.\n if (selector.value === '' && NO_LANGUAGE_MESSAGE) {\n alert(NO_LANGUAGE_MESSAGE);\n event.preventDefault();\n }\n }\n };\n\n }\n\n return {'initLangSelector' : initLangSelector};\n});"],"names":["define","$","str","NO_LANGUAGE_MESSAGE","taId","ready","ta","document","getElementById","langSelectorId","replace","selector","uiWrapperId","hasAncestorWithId","element","id","parentElement","setAceLang","lang","value","uiWrapper","current_ui_wrapper","uiInstance","setLanguage","onchange","body","onkeydown","event","target","alert","preventDefault","initLangSelectorWhenReady","promise","get_string","when","done","message"],"mappings":";;;;;;;;;;;;AAaAA,gDAAO,CAAC,SAAU,aAAa,SAASC,EAAGC,SAEnCC,oBAAsB,SAyEnB,2BAnEmBC,MACtBH,IAAII,OAAM,qBAcqBD,YACzBE,GAAKL,EAAEM,SAASC,eAAeJ,OAC/BK,eAAiBL,KAAKM,QAAQ,SAAU,YACxCC,SAAWJ,SAASC,eAAeC,gBACnCG,YAAcR,KAAO,oBAQlBS,kBAAkBC,QAASC,SACzBD,SAAS,IACRA,QAAQC,KAAOA,UACR,EAEXD,QAAUA,QAAQE,qBAEf,WAMFC,mBAGCC,KAAOP,SAASQ,MAChBC,UAAYd,GAAG,GAAGe,mBAEpBD,WAAaA,UAAUE,YAA0D,mBAArCF,UAAUE,WAAWC,aACjEH,UAAUE,WAAWC,YAAYL,MAIzCP,SAASa,SAAWP,WAIpBV,SAASkB,KAAKC,UAAY,SAASC,OAC3BA,MAAMC,QAAUf,kBAAkBc,MAAMC,OAAQhB,cAEzB,KAAnBD,SAASQ,OAAgBhB,sBACzB0B,MAAM1B,qBACNwB,MAAMG,mBA1DdC,CAA0B3B,eAExB4B,QAAU9B,IAAI+B,WAAW,aAAc,oBAC7ChC,EAAEiC,KAAKF,SAASG,MAAK,SAASC,SAC1BjC,oBAAsBiC"} \ No newline at end of file diff --git a/amd/build/userinterfacewrapper.min.js b/amd/build/userinterfacewrapper.min.js index a1b886bfd..7ece13374 100644 --- a/amd/build/userinterfacewrapper.min.js +++ b/amd/build/userinterfacewrapper.min.js @@ -105,6 +105,6 @@ * 'Constructor' that references the constructor (e.g. Graph, AceWrapper etc) * *****************************************************************************/ -define("qtype_coderunner/userinterfacewrapper",["jquery","core/templates","core/notification"],(function($,Templates,Notification){function InterfaceWrapper(uiname,textareaId){let t=this;this.GUTTER=16,this.DEFAULT_SYNC_INTERVAL_SECS=5,this.uniqueId=Math.random();this.isFullScreenEnable=null,this.taId=textareaId,this.loadFailId=textareaId+"_loadfailerr";const ta=document.getElementById(textareaId);this.textArea=$(ta);const params=this.textArea.attr("data-params");this.uiParams=params?JSON.parse(params):{},this.uiParams.lang=this.textArea.attr("data-lang"),this.readOnly=this.textArea.prop("readonly"),this.isLoading=!1,this.loadFailed=!1,this.retries=0;let h=parseInt(this.textArea.css("height")),content_lines=this.textArea.val().split("\n").length,rows=ta.rows;content_lines>rows&&(rows=Math.min(content_lines,50)),h=Math.max(h,19*rows,50),this.wrapperNode=$("
"),this.wrapperNode.uniqueId=this.uniqueId,this.textArea.after(this.wrapperNode),this.wrapperNode.hide(),this.wrapperNode.css({resize:"vertical",overflow:"hidden",minHeight:h,width:"100%",border:"1px solid darkgrey"}),this.textArea.data("current-ui-wrapper",this),this.uiInstance=null,this.loadUi(uiname,this.uiParams),$(document).mousemove((function(){t.checkForResize()})),$(window).resize((function(){t.checkForResize()})),this.textArea.closest("form").submit((function(){null!==t.uiInstance&&t.uiInstance.sync()})),$(document.body).on("keydown",(function keyDown(e){if(77===e.keyCode&&e.ctrlKey&&e.altKey)if(null!==t.uiInstance||t.loadFailed)t.stop();else{const wrapper=document.getElementById("".concat(t.taId,"_wrapper"));wrapper&&wrapper.uniqueId===this.uniqueId?t.restart():document.removeEventListener("keydown",keyDown)}}))}return InterfaceWrapper.prototype.setAllowFullScreen=function(enableFullScreen){this.isFullScreenEnable=enableFullScreen},InterfaceWrapper.prototype.loadUi=function(uiname,params){const t=this;function syncIntervalSecsBase(){return params.hasOwnProperty("sync_interval_secs")?parseInt(params.sync_interval_secs):t.DEFAULT_SYNC_INTERVAL_SECS}if(this.isLoading)return this.retries+=1,void(this.retries>20?(alert("Failed to load "+uiname+" UI component. If this error persists, please report it to the forum on coderunner.org.nz"),this.retries=0,this.loading=0):setTimeout((function(){t.loadUi(uiname,params)}),200));this.retries=0,this.params=params,this.stop(),this.uiname=uiname,""===this.uiname||"none"===this.uiname||sessionStorage.getItem("disableUis")?this.uiInstance=null:(this.isLoading=!0,require(["qtype_coderunner/ui_"+this.uiname],(function(ui){const h=t.wrapperNode.innerHeight()-t.GUTTER,w=t.wrapperNode.innerWidth(),uiInstance=new ui.Constructor(t.taId,w,h,params);if(uiInstance.failed()){t.loadFailed=!0,t.wrapperNode.hide(),uiInstance.destroy(),t.uiInstance=null,t.textArea.addClass("uiloadfailed");const loadFailDiv='
';let jqLoadFailDiv=$(loadFailDiv);jqLoadFailDiv.insertBefore(t.textArea),langString=uiInstance.failMessage(),errorDiv=jqLoadFailDiv,require(["core/str"],(function(str){const s=str.get_string(langString,"qtype_coderunner"),fallback=str.get_string("ui_fallback","qtype_coderunner");$.when(s,fallback).done((function(s,fallback){errorDiv.html(s+"
"+fallback)}))}))}else{var _uiInstance$allowFull;t.hLast=0,t.wLast=0,t.textArea.hide(),t.wrapperNode.show(),t.wrapperNode.append(uiInstance.getElement()),t.uiInstance=uiInstance,t.loadFailed=!1,t.checkForResize();let uiInstancePrototype=Object.getPrototypeOf(uiInstance);uiInstancePrototype.syncIntervalSecs=uiInstancePrototype.syncIntervalSecs||syncIntervalSecsBase,t.startSyncTimer(uiInstance),(null!==t.isFullScreenEnable?t.isFullScreenEnable:null===(_uiInstance$allowFull=uiInstance.allowFullScreen)||void 0===_uiInstance$allowFull?void 0:_uiInstance$allowFull.call(uiInstance))?t.initFullScreenToggle(t.taId):t.removeFullScreenButton(t.taId)}var langString,errorDiv;t.isLoading=!1})))},InterfaceWrapper.prototype.removeFullScreenButton=function(fieldId){const screenModeButton=document.getElementById("".concat(fieldId,"_wrapper")).parentNode.querySelector(".screen-mode-button");screenModeButton&&screenModeButton.remove()},InterfaceWrapper.prototype.initFullScreenToggle=function(fieldId){const wrapperEditor=document.getElementById("".concat(fieldId,"_wrapper"));function enterFullscreen(fullscreenButton,exitFullscreenButton,e){let t=this;e.preventDefault(),t.wrapperHeight=t.wrapperNode.innerHeight(),t.heightEditNode=t.hLast-t.GUTTER,t.widthEditNode=t.wLast,fullscreenButton.classList.add("d-none"),wrapperEditor.append(exitFullscreenButton),wrapperEditor.addEventListener("fullscreenchange",(()=>{null===document.fullscreenElement?(t.uiInstance.resize(t.widthEditNode,t.heightEditNode),wrapperEditor.style.height=t.wrapperHeight+"px",exitFullscreenButton.classList.add("d-none"),fullscreenButton.classList.remove("d-none")):exitFullscreenButton.classList.remove("d-none")})),wrapperEditor.requestFullscreen().catch(Notification.exception)}function exitFullscreen(e){let t=this;e.preventDefault(),document.exitFullscreen(),wrapperEditor.style.height=t.wrapperHeight+"px",t.uiInstance.resize(t.widthEditNode,t.heightEditNode)}wrapperEditor.parentNode.querySelector(".screen-mode-button")||Templates.renderForPromise("qtype_coderunner/screenmode_button",{}).then((_ref=>{let{html:html}=_ref;const screenModeButton=Templates.appendNodeContents(wrapperEditor,html,"")[0],fullscreenButton=screenModeButton.querySelector(".button-fullscreen"),exitFullscreenButton=screenModeButton.querySelector(".button-exit-fullscreen");fullscreenButton.classList.remove("d-none"),fullscreenButton.addEventListener("click",enterFullscreen.bind(this,fullscreenButton,exitFullscreenButton)),exitFullscreenButton.addEventListener("click",exitFullscreen.bind(this))}))},InterfaceWrapper.prototype.startSyncTimer=function(uiInstance){const timeout=uiInstance.syncIntervalSecs();this.uiInstance.timer=timeout?setInterval((function(){uiInstance.sync()}),1e3*timeout):null},InterfaceWrapper.prototype.stopSyncTimer=function(uiInstance){uiInstance.timer&&clearTimeout(uiInstance.timer)},InterfaceWrapper.prototype.stop=function(){null!==this.uiInstance&&(this.stopSyncTimer(this.uiInstance),this.textArea.show(),this.uiInstance.hasFocus()&&(this.textArea.focus(),this.textArea[0].selectionStart=this.textArea[0].value.length),this.uiInstance.destroy(),this.uiInstance=null,this.wrapperNode.hide()),this.loadFailed=!1,this.textArea.removeClass("uiloadfailed"),$(document.getElementById(this.loadFailId)).remove()},InterfaceWrapper.prototype.restart=function(){null===this.uiInstance&&this.loadUi(this.uiname,this.params)},InterfaceWrapper.prototype.checkForResize=function(){if(this.uiInstance){const h=this.wrapperNode.innerHeight(),w=this.wrapperNode.innerWidth();if(h!=this.hLast||w!=this.wLast){const xLeft=this.wrapperNode.offset().left,maxWidth=$(window).innerWidth()-xLeft-25,hAdjusted=h-this.GUTTER,wAdjusted=Math.min(maxWidth,w);this.uiInstance.resize(wAdjusted,hAdjusted),this.hLast=this.wrapperNode.innerHeight(),this.wLast=this.wrapperNode.innerWidth()}}},{newUiWrapper:function(uiname,textareaId){return uiname?new InterfaceWrapper(uiname,textareaId):null},InterfaceWrapper:InterfaceWrapper}})); +define("qtype_coderunner/userinterfacewrapper",["core/templates","core/notification"],(function(Templates,Notification){function InterfaceWrapper(uiname,textareaId){let t=this;this.GUTTER=16,this.DEFAULT_SYNC_INTERVAL_SECS=5,this.uniqueId=Math.random();this.isFullScreenEnable=null,this.taId=textareaId,this.loadFailId=textareaId+"_loadfailerr",this.textArea=document.getElementById(textareaId),this.textArea.current_ui_wrapper&&alert("JavaScript error: multiple UIs on ".concat(textareaId,"!"));const params=this.textArea.getAttribute("data-params");this.uiParams=params?JSON.parse(params):{},this.uiParams.lang=this.textArea.getAttribute("data-lang"),this.readOnly=this.textArea.readOnly,this.isLoading=!1,this.loadFailed=!1,this.retries=0;let h=this.textArea.clientHeight,content_lines=this.textArea.value.split("\n").length,rows=this.textArea.rows;content_lines>rows&&(rows=Math.min(content_lines,50)),h=Math.max(h,19*rows,50),this.textArea.style.height=h+"px",this.wrapperNode=document.createElement("div"),this.wrapperNode.id="".concat(this.taId,"_wrapper"),this.wrapperNode.classList.add("ui_wrapper","position-relative"),this.wrapperNode.uniqueId=this.uniqueId,this.wrapperNode.style.display="none",this.wrapperNode.style.resize="vertical",this.wrapperNode.style.overflow="hidden",this.wrapperNode.style.minHeight=h+"px",this.wrapperNode.style.width="100%",this.wrapperNode.style.border="1px solid darkgrey",this.textArea.insertAdjacentElement("afterend",this.wrapperNode),this.wLast=0,this.hLast=0,this.textArea.current_ui_wrapper=this,this.uiInstance=null,this.loadUi(uiname,this.uiParams);new ResizeObserver((function(){t.checkForResize()})).observe(this.wrapperNode),window.addEventListener("resize",(function(){t.checkForResize()}));const form=this.textArea.closest("form");form&&form.addEventListener("submit",(function(){null!==t.uiInstance&&t.uiInstance.sync()})),document.body.addEventListener("keydown",(function keyDown(e){if("m"===e.key&&e.ctrlKey&&e.altKey){const wrapper=document.getElementById("".concat(t.taId,"_wrapper"));wrapper&&wrapper.uniqueId===t.uniqueId?null!==t.uiInstance||t.loadFailed?t.stop():t.restart():document.removeEventListener("keydown",keyDown)}}))}return InterfaceWrapper.prototype.setAllowFullScreen=function(enableFullScreen){this.isFullScreenEnable=enableFullScreen},InterfaceWrapper.prototype.loadUi=function(uiname,params){const t=this;function syncIntervalSecsBase(){return params.hasOwnProperty("sync_interval_secs")?parseInt(params.sync_interval_secs):t.DEFAULT_SYNC_INTERVAL_SECS}if(this.isLoading)return this.retries+=1,void(this.retries>20?(alert("Failed to load "+uiname+" UI component. If this error persists, please report it to the forum on coderunner.org.nz"),this.retries=0,this.loading=0):setTimeout((function(){t.loadUi(uiname,params)}),200));this.retries=0,this.params=params,this.stop(),this.uiname=uiname,""===this.uiname||"none"===this.uiname||sessionStorage.getItem("disableUis")?this.uiInstance=null:(this.isLoading=!0,require(["qtype_coderunner/ui_"+this.uiname],(function(ui){const h=t.textArea.clientHeight-t.GUTTER,w=t.textArea.clientWidth,uiInstance=new ui.Constructor(t.taId,w,h,params);if(uiInstance.failed()){t.loadFailed=!0,t.wrapperNode.style.display="none",t.textArea.style.display="",uiInstance.destroy(),t.uiInstance=null,t.textArea.classList.add("uiloadfailed");const loadFailDiv=document.createElement("div");loadFailDiv.id=t.loadFailId,loadFailDiv.className="uiloadfailed",t.textArea.parentNode.insertBefore(loadFailDiv,t.textArea),langString=uiInstance.failMessage(),errorDiv=loadFailDiv,require(["core/str"],(function(str){const s=str.get_string(langString,"qtype_coderunner"),fallback=str.get_string("ui_fallback","qtype_coderunner");Promise.all([s,fallback]).then((function(results){const s=results[0],fallback=results[1];errorDiv.innerHTML=s+"
"+fallback}))}))}else{var _uiInstance$allowFull;t.textArea.style.display="none",t.wrapperNode.style.display="";let elementToAdd=uiInstance.getElement();if(elementToAdd&&elementToAdd.jquery&&(elementToAdd=elementToAdd[0]),elementToAdd){t.wrapperNode.appendChild(elementToAdd);elementToAdd.querySelectorAll("script").forEach((oldScript=>{const newScript=document.createElement("script");oldScript.src?newScript.src=oldScript.src:newScript.textContent=oldScript.textContent,document.head.appendChild(newScript),document.head.removeChild(newScript)}))}t.uiInstance=uiInstance,t.loadFailed=!1,t.checkForResize();let uiInstancePrototype=Object.getPrototypeOf(uiInstance);uiInstancePrototype.syncIntervalSecs=uiInstancePrototype.syncIntervalSecs||syncIntervalSecsBase,t.startSyncTimer(uiInstance),(null!==t.isFullScreenEnable?t.isFullScreenEnable:null===(_uiInstance$allowFull=uiInstance.allowFullScreen)||void 0===_uiInstance$allowFull?void 0:_uiInstance$allowFull.call(uiInstance))?t.initFullScreenToggle(t.taId):t.removeFullScreenButton(t.taId)}var langString,errorDiv;t.isLoading=!1})))},InterfaceWrapper.prototype.removeFullScreenButton=function(fieldId){const screenModeButton=document.getElementById("".concat(fieldId,"_wrapper")).parentNode.querySelector(".screen-mode-button");screenModeButton&&screenModeButton.remove()},InterfaceWrapper.prototype.initFullScreenToggle=function(fieldId){const wrapperEditor=document.getElementById("".concat(fieldId,"_wrapper"));function enterFullscreen(fullscreenButton,exitFullscreenButton,e){let t=this;e.preventDefault(),t.wrapperHeight=t.wrapperNode.clientHeight,t.heightEditNode=t.hLast,t.widthEditNode=t.wLast,fullscreenButton.classList.add("d-none"),wrapperEditor.append(exitFullscreenButton),wrapperEditor.addEventListener("fullscreenchange",(()=>{null===document.fullscreenElement?(t.uiInstance.resize(t.widthEditNode,t.heightEditNode),wrapperEditor.style.height=t.wrapperHeight+"px",exitFullscreenButton.classList.add("d-none"),fullscreenButton.classList.remove("d-none")):exitFullscreenButton.classList.remove("d-none")})),wrapperEditor.requestFullscreen().catch(Notification.exception)}function exitFullscreen(e){let t=this;e.preventDefault(),document.exitFullscreen(),wrapperEditor.style.height=t.wrapperHeight+"px",t.uiInstance.resize(t.widthEditNode,t.heightEditNode)}wrapperEditor.parentNode.querySelector(".screen-mode-button")||Templates.renderForPromise("qtype_coderunner/screenmode_button",{}).then((_ref=>{let{html:html}=_ref;const screenModeButton=Templates.appendNodeContents(wrapperEditor,html,"")[0],fullscreenButton=screenModeButton.querySelector(".button-fullscreen"),exitFullscreenButton=screenModeButton.querySelector(".button-exit-fullscreen");fullscreenButton.classList.remove("d-none"),fullscreenButton.addEventListener("click",enterFullscreen.bind(this,fullscreenButton,exitFullscreenButton)),exitFullscreenButton.addEventListener("click",exitFullscreen.bind(this))}))},InterfaceWrapper.prototype.startSyncTimer=function(uiInstance){const timeout=uiInstance.syncIntervalSecs();this.uiInstance.timer=timeout?setInterval((function(){uiInstance.sync()}),1e3*timeout):null},InterfaceWrapper.prototype.stopSyncTimer=function(uiInstance){uiInstance.timer&&clearTimeout(uiInstance.timer)},InterfaceWrapper.prototype.stop=function(){null!==this.uiInstance&&(this.stopSyncTimer(this.uiInstance),this.textArea.style.display="",this.uiInstance.hasFocus()&&(this.textArea.focus(),this.textArea.selectionStart=this.textArea.value.length),this.uiInstance.destroy(),this.uiInstance=null,this.wrapperNode.style.display="none"),this.loadFailed=!1,this.textArea.classList.remove("uiloadfailed");const elementToRemove=document.getElementById(this.loadFailId);elementToRemove&&elementToRemove.parentNode.removeChild(elementToRemove)},InterfaceWrapper.prototype.restart=function(){null===this.uiInstance&&this.loadUi(this.uiname,this.params)},InterfaceWrapper.prototype.checkForResize=function(){if(this.uiInstance){const h=this.wrapperNode.clientHeight,w=this.wrapperNode.clientWidth,maxWidth=this.wrapperNode.clientWidth,hAdjusted=h-this.GUTTER,wAdjusted=Math.min(maxWidth,w);hAdjusted==this.hLast&&wAdjusted==this.wLast||(this.uiInstance.resize(wAdjusted,hAdjusted),this.hLast=hAdjusted,this.wLast=wAdjusted)}},{newUiWrapper:function(uiname,textareaId){return uiname?new InterfaceWrapper(uiname,textareaId):null},InterfaceWrapper:InterfaceWrapper}})); //# sourceMappingURL=userinterfacewrapper.min.js.map \ No newline at end of file diff --git a/amd/build/userinterfacewrapper.min.js.map b/amd/build/userinterfacewrapper.min.js.map index e7b5a5b7a..57dcf3e01 100644 --- a/amd/build/userinterfacewrapper.min.js.map +++ b/amd/build/userinterfacewrapper.min.js.map @@ -1 +1 @@ -{"version":3,"file":"userinterfacewrapper.min.js","sources":["../src/userinterfacewrapper.js"],"sourcesContent":["/******************************************************************************\n *\n * This module provides a wrapper for user-interface modules, handling hiding\n * of the textArea that is being replaced by the UI element,\n * resizing of the UI component, and support of such usability functions as\n * ctrl-alt-M to disable/re-enable the entire user interface, including the\n * wrapper.\n *\n * @module coderunner/userinterfacewrapper\n * @copyright Richard Lobb, 2015, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n * The InterfaceWrapper class is constructed either by Moodle PHP calls of\n * the form\n *\n * $PAGE->requires->js_call_amd($modulename, $functionname, $params)\n *\n * (e.g. from within render.php) or by JavaScript require calls, e.g. from\n * authorform.js when the question author changes UI type.\n *\n * The InterfaceWrapper provides:\n *\n * 1. A constructor InterfaceWrapper(uiname, textareaId) which\n * hides the given text area, replaces it with a wrapper div (resizable in\n * height by the user but with width resizing managed by changes in window\n * width), created an instance of nameInstance as defined in the file\n * ui_name.js (see below).\n * params is a record containing the decoded value of\n *\n * 2. A stop() method that destroys the embedded UI and hides the wrapper.\n *\n * 3. A restart() method that shows the wrapper again and re-creates the prior\n * embedded UI component within it.\n *\n * 4. A loadUi(uiname, params) method that kills any currently running UI element\n * (if there is one) and (re)loads the specified one. The params parameter\n * is a record that allows additional parameters to be passed in, such as\n * those from the question's uiParams field and, in the case of the\n * Ace UI, the 'lang' (language) that the editor is editing. This data\n * is supplied by the PHP via the data-params attribute of the answer's\n * base textarea.\n *\n * 5. Regular checking for any resizing of the wrapper, which are passed on to\n * the embedded UI element's resize() method.\n *\n * 6. Monitoring of alt-ctrl-M key presses which toggle the visibility of the\n * wrapper plus UI element and the syncronised textArea by calls to stop()\n * and restart\n *\n * =========================================================================\n *\n * The embedded user-interface module must be defined in a JavaScript file\n * of the form ui_name.js which must define a class nameInstance with\n * the following functionality:\n *\n * 1. A constructor SomeUiName(textareaId, width, height, params) that\n * builds an HTML component of the given width and height. textareaId is the\n * ID of the textArea from which the UI element should obtain its initial\n * serialisation and to which it should write the serialisation when its save\n * or destroy methods are called. params is a JavaScript object,\n * decoded from the JSON uiParams defined by the question plus any\n * additional data required, such as the 'lang' in the case of Ace.\n *\n * 2. A getElement() method that returns the HTML element that the\n * InterfaceWrapper is to insert into the document tree.\n *\n * 3. A method failed() that should return true unless the constructor\n * failed (e.g. because it was not able to de-serialise the text area's\n * contents). The wrapper will call destroy() on the object if failed()\n * returns true and abort the use of the UI element. The text area will\n * have the uiloadfailed class added, which CSS will display in some\n * error mode (e.g. a red border).\n *\n * 4. A method failMessage() that will be called only when failed() returns\n * True. It should be a defined CodeRunner language string key.\n *\n * 5. A sync() method that copies the serialised represention of the UI plugin's\n * data to the related TextArea. This is used when submit is clicked.\n *\n * 6. A destroy() method that should sync the contents to the text area then\n * destroy any HTML elements or other created content. This method is called\n * when CTRL-ALT-M is typed by the user to turn off all UI plugins\n *\n * 7. A resize(width, height) method that should resize the entire UI element\n * to the given dimensions.\n *\n * 8. A hasFocus() method that returns true if the UI element has focus.\n *\n * 9. A syncIntervalSecs() method that returns the time interval between\n * calls to the sync() method. 0 for no sync calls. The userinterfacewrapper\n * provides all instances with a generic (base-class) version that returns\n * the value of a UI parameter sync_interval_secs if given else uses the\n * UI interface wrapper default (currently 5).\n *\n * 10. An allowFullScreen() method that returns True if the UI supports\n * use of the full-screen button in the bottom right of the UI wrapper.\n * Defaults to False if not implemented.\n *\n * 11. A setAllowFullScreen(allow) method that takes a boolean parameter that\n * allows or disallows the use of full screening. This overrides the setting\n * from the allowFullScreen() method and is provided to allow parent UIs\n * such as Scratchpad to override the default settings of a child UI.\n *\n * The return value from the module define is a record with a single field\n * 'Constructor' that references the constructor (e.g. Graph, AceWrapper etc)\n *\n *****************************************************************************/\n\n/**\n * 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 util.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\ndefine(['jquery', 'core/templates', 'core/notification'], function($, Templates, Notification) {\n /**\n * Constructor for a new user interface.\n * @param {string} uiname The name of the interface element (e.g. ace, graph, etc)\n * which should be in file ui_ace.js, ui_graph.js etc.\n * @param {string} textareaId The id of the text area that the UI is to manage.\n * The text area should have an attribute data-params, which is a\n * JSON encoded record containing whatever additional parameters might\n * be needed by the User interface. As a minimum it should contain all\n * the parameters from the uiparameters field of\n * the question so that question authors can pass in additional data\n * such as whether graph edges are bidirectional or not in the case of\n * the graph UI. Additionally the Ace editor requires a 'lang' field\n * to specify what language the editor is editing.\n * When the wrapper has been set up on a text area, the text area's\n * data attribute contains an entry for 'current-ui-wrapper' that is\n * a reference to the wrapper ('this').\n */\n function InterfaceWrapper(uiname, textareaId) {\n let t = this; // For use by embedded functions.\n\n this.GUTTER = 16; // Size of gutter at base of wrapper Node (pixels)\n this.DEFAULT_SYNC_INTERVAL_SECS = 5;\n\n this.uniqueId = Math.random();\n const PIXELS_PER_ROW = 19; // For estimating height of textareas.\n const MAX_GROWN_ROWS = 50; // Upper limit to artifically grown textarea rows.\n const MIN_WRAPPER_HEIGHT = 50;\n this.isFullScreenEnable = null;\n this.taId = textareaId;\n this.loadFailId = textareaId + '_loadfailerr';\n const ta = document.getElementById(textareaId);\n this.textArea = $(ta);\n const params = this.textArea.attr('data-params');\n if (params) {\n this.uiParams = JSON.parse(params);\n } else {\n this.uiParams = {};\n }\n this.uiParams.lang = this.textArea.attr('data-lang');\n this.readOnly = this.textArea.prop('readonly');\n this.isLoading = false; // True if we're busy loading a UI element.\n this.loadFailed = false; // True if UI failed to initialise properly.\n this.retries = 0; // Number of failed attempts to load a UI component.\n\n let h = parseInt(this.textArea.css(\"height\"));\n let content_lines = this.textArea.val().split('\\n').length;\n let rows = ta.rows;\n if (content_lines > rows) {\n // Allow reloaded text areas with lots of text to grow bigger, within limits.\n rows = Math.min(content_lines, MAX_GROWN_ROWS);\n }\n h = Math.max(h, rows * PIXELS_PER_ROW, MIN_WRAPPER_HEIGHT);\n\n /**\n * Construct an empty hidden wrapper div, inserted directly after the\n * textArea, ready to contain the actual UI.\n */\n this.wrapperNode = $(\"
\");\n this.wrapperNode.uniqueId = this.uniqueId;\n this.textArea.after(this.wrapperNode);\n this.wrapperNode.hide();\n this.wrapperNode.css({\n resize: 'vertical',\n overflow: 'hidden',\n minHeight: h,\n width: \"100%\",\n border: \"1px solid darkgrey\"\n });\n\n /**\n * Record a reference to this wrapper in the text area's data attribute\n * for use by external javascript that needs to interact with the\n * wrapper, e.g. the multilanguage.js module.\n */\n this.textArea.data('current-ui-wrapper', this);\n\n /**\n * Load the UI into the wrapper (aysnchronous).\n */\n this.uiInstance = null; // Defined by loadUi asynchronously\n this.loadUi(uiname, this.uiParams); // Load the required UI element\n\n /**\n * Add event handlers\n */\n $(document).mousemove(function() {\n t.checkForResize();\n });\n $(window).resize(function() {\n t.checkForResize();\n });\n this.textArea.closest('form').submit(function() {\n if (t.uiInstance !== null) {\n t.uiInstance.sync();\n }\n });\n $(document.body).on('keydown', function keyDown(e) {\n const KEY_M = 77;\n if (e.keyCode === KEY_M && e.ctrlKey && e.altKey) {\n if (t.uiInstance !== null || t.loadFailed) {\n t.stop();\n } else {\n // If the wrapper still exists (which won't be the case for\n // userinterfacewrappers embedded within another userinterface wrapper)\n // restart the UI within the wrapper.\n const wrapper = document.getElementById(`${t.taId}_wrapper`);\n if (wrapper && wrapper.uniqueId === this.uniqueId) {\n t.restart(); // Reactivate\n } else {\n // This wrapper has apparently been killed. Stop listening.\n // Should now be garbage collectable, too.\n document.removeEventListener('keydown', keyDown);\n }\n }\n }\n });\n }\n\n /**\n * Set the value of the allowFullScreen property.\n * If the value is true, the fullscreen mode will be shown.\n * If the value is false, the fullscreen will be hidden.\n *\n * @param {Boolean} enableFullScreen The value to set.\n */\n InterfaceWrapper.prototype.setAllowFullScreen = function(enableFullScreen) {\n this.isFullScreenEnable = enableFullScreen;\n };\n\n /**\n * Load the specified UI element (which in the case of Ace will need\n * to know the language, lang, as well - this must be supplied as\n * a 'lang' attribute of the record params.\n * When ui is up and running, this.uiInstance will reference it.\n * To avoid a potential race problem, if this method is already busy\n * with a load, try again in 200 msecs.\n * @param {string} uiname The name of the User Interface to be used.\n * @param {object} params The UI parameters object that passes parameters\n * to the actual UI object.\n */\n InterfaceWrapper.prototype.loadUi = function(uiname, params) {\n const t = this,\n errPart1 = 'Failed to load ',\n errPart2 = ' UI component. If this error persists, please report it to the forum on coderunner.org.nz';\n\n /**\n * Get the given language string and plug it into the given jQuery\n * div element as its html, plus a 'fallback' message on a separate line.\n * @param {string} langString The language string specifier for the error message,\n * to be loaded by AJAX.\n * @param {object} errorDiv The div object into which the error message\n * is to be inserted.\n */\n function setLoadFailMessage(langString, errorDiv) {\n require(['core/str'], function(str) {\n /**\n * Get langString text via AJAX\n */\n const\n s = str.get_string(langString, 'qtype_coderunner'),\n fallback = str.get_string('ui_fallback', 'qtype_coderunner');\n $.when(s, fallback).done(function(s, fallback) {\n errorDiv.html(s + '
' + fallback);\n });\n });\n }\n\n /**\n * The default method for a UIs sync_interval_secs method.\n * Returns the sync_interval_secs parameter if given, else\n * DEFAULT_SYNC_INTERVAL_SECS.\n */\n function syncIntervalSecsBase() {\n if (params.hasOwnProperty('sync_interval_secs')) {\n return parseInt(params.sync_interval_secs);\n } else {\n return t.DEFAULT_SYNC_INTERVAL_SECS;\n }\n }\n\n if (this.isLoading) { // Oops, we're loading a UI element already\n this.retries += 1;\n if (this.retries > 20) {\n alert(errPart1 + uiname + errPart2);\n this.retries = 0;\n this.loading = 0;\n } else {\n setTimeout(function() {\n t.loadUi(uiname, params);\n }, 200); // Try again in 200 msecs\n }\n return;\n }\n this.retries = 0;\n this.params = params; // Save in case need to restart\n\n this.stop(); // Kill any active UI first\n this.uiname = uiname;\n\n if (this.uiname === '' || this.uiname === 'none' || sessionStorage.getItem('disableUis')) {\n this.uiInstance = null;\n } else {\n this.isLoading = true;\n require(['qtype_coderunner/ui_' + this.uiname],\n function(ui) {\n const h = t.wrapperNode.innerHeight() - t.GUTTER;\n const w = t.wrapperNode.innerWidth();\n const uiInstance = new ui.Constructor(t.taId, w, h, params);\n if (uiInstance.failed()) {\n /*\n * Constructor failed to load serialisation.\n * Set uiloadfailed class on text area.\n */\n t.loadFailed = true;\n t.wrapperNode.hide();\n uiInstance.destroy();\n t.uiInstance = null;\n t.textArea.addClass('uiloadfailed');\n const loadFailDiv = '
';\n let jqLoadFailDiv = $(loadFailDiv);\n jqLoadFailDiv.insertBefore(t.textArea);\n setLoadFailMessage(uiInstance.failMessage(), jqLoadFailDiv); // Insert error by AJAX\n } else {\n t.hLast = 0; // Force resize (and hence redraw)\n t.wLast = 0; // ... on first call to checkForResize\n t.textArea.hide();\n t.wrapperNode.show();\n t.wrapperNode.append(uiInstance.getElement());\n t.uiInstance = uiInstance;\n t.loadFailed = false;\n t.checkForResize();\n\n /*\n * Set a default syncIntervalSecs method if uiInstance lacks one.\n */\n let uiInstancePrototype = Object.getPrototypeOf(uiInstance);\n uiInstancePrototype.syncIntervalSecs = uiInstancePrototype.syncIntervalSecs || syncIntervalSecsBase;\n t.startSyncTimer(uiInstance);\n let canDoFullScreen = t.isFullScreenEnable !== null ?\n t.isFullScreenEnable : uiInstance.allowFullScreen?.();\n if (canDoFullScreen) {\n t.initFullScreenToggle(t.taId);\n } else {\n t.removeFullScreenButton(t.taId);\n }\n }\n t.isLoading = false;\n });\n }\n };\n\n\n /**\n * Remove the fullscreen button from the wrapper editor.\n *\n * @param {String} fieldId The id of answer field.\n */\n InterfaceWrapper.prototype.removeFullScreenButton = function(fieldId) {\n const wrapperEditor = document.getElementById(`${fieldId}_wrapper`);\n const screenModeButton = wrapperEditor.parentNode.querySelector('.screen-mode-button');\n if (screenModeButton) {\n screenModeButton.remove();\n }\n };\n\n /**\n * Initialize elements and event listeners for the fullscreen mode.\n *\n * @param {String} fieldId The id of answer field.\n */\n InterfaceWrapper.prototype.initFullScreenToggle = function(fieldId) {\n const wrapperEditor = document.getElementById(`${fieldId}_wrapper`);\n const screenModeButton = wrapperEditor.parentNode.querySelector('.screen-mode-button');\n if (screenModeButton) {\n return;\n }\n\n Templates.renderForPromise('qtype_coderunner/screenmode_button', {}).then(({html}) => {\n const screenModeButton = Templates.appendNodeContents(wrapperEditor, html, '')[0];\n const fullscreenButton = screenModeButton.querySelector('.button-fullscreen');\n const exitFullscreenButton = screenModeButton.querySelector('.button-exit-fullscreen');\n\n // When load successfully, show the fullscreen button.\n fullscreenButton.classList.remove('d-none');\n\n // Add event listeners to the fullscreen/exit-fullscreen button.\n fullscreenButton.addEventListener('click', enterFullscreen.bind(this,\n fullscreenButton, exitFullscreenButton));\n exitFullscreenButton.addEventListener('click', exitFullscreen.bind(this));\n });\n\n /**\n * Make the editor fullscreen.\n *\n * @param {HTMLElement} fullscreenButton The fullscreen button.\n * @param {HTMLElement} exitFullscreenButton The exit fullscreen button.\n * @param {Event} e The click event.\n */\n function enterFullscreen(fullscreenButton, exitFullscreenButton, e) {\n let t = this;\n e.preventDefault();\n // The editor can stretch out.\n // So we need to save the original height and width of the editor before going fullscreen.\n t.wrapperHeight = t.wrapperNode.innerHeight();\n t.heightEditNode = t.hLast - t.GUTTER;\n t.widthEditNode = t.wLast;\n\n fullscreenButton.classList.add('d-none');\n // Append exit fullscreen button to the wrapper editor.\n // So that when in the fullscreen mode, the exit fullscreen button will be in the wrapper editor.\n wrapperEditor.append(exitFullscreenButton);\n\n // Handle fullscreen event.\n wrapperEditor.addEventListener('fullscreenchange', () => {\n if (document.fullscreenElement === null) {\n // When exit fullscreen using ESC key or press exit fullscreen button.\n // We need to reset the editor to the original size.\n t.uiInstance.resize(t.widthEditNode, t.heightEditNode);\n\n // We need to reset the wrapper height to the original height.\n // In fullscreen mode, the wrapper height can change by stretching it out.\n wrapperEditor.style.height = t.wrapperHeight + 'px';\n\n // Add and remove the d-none class to show and hide the buttons.\n exitFullscreenButton.classList.add('d-none');\n fullscreenButton.classList.remove('d-none');\n } else {\n exitFullscreenButton.classList.remove('d-none');\n }\n });\n wrapperEditor.requestFullscreen().catch(Notification.exception);\n }\n\n /**\n * Exit the fullscreen mode.\n *\n * @param {Event} e the click event.\n */\n function exitFullscreen(e) {\n let t = this;\n e.preventDefault();\n document.exitFullscreen();\n\n // Reset the editor to the original size before going fullscreen.\n wrapperEditor.style.height = t.wrapperHeight + 'px';\n t.uiInstance.resize(t.widthEditNode, t.heightEditNode);\n }\n };\n\n /**\n * Start a sync timer on the given uiInstance, unless its time interval is 0.\n * @param {object} uiInstance The instance of the user interface object whose\n * timer is to be set up.\n */\n InterfaceWrapper.prototype.startSyncTimer = function(uiInstance) {\n const timeout = uiInstance.syncIntervalSecs();\n if (timeout) {\n this.uiInstance.timer = setInterval(function () {\n uiInstance.sync();\n }, timeout * 1000);\n } else {\n this.uiInstance.timer = null;\n }\n };\n\n\n /**\n * Stop the sync timer on the given uiInstance, if running.\n * @param {object} uiInstance The instance of the user interface object whose\n * timer is to be set up.\n */\n InterfaceWrapper.prototype.stopSyncTimer = function(uiInstance) {\n if (uiInstance.timer) {\n clearTimeout(uiInstance.timer);\n }\n };\n\n\n InterfaceWrapper.prototype.stop = function() {\n /*\n * Disable (shutdown) the embedded ui component.\n * The wrapper remains active for ctrl-alt-M events, but is hidden.\n */\n if (this.uiInstance !== null) {\n this.stopSyncTimer(this.uiInstance);\n this.textArea.show();\n if (this.uiInstance.hasFocus()) {\n this.textArea.focus();\n this.textArea[0].selectionStart = this.textArea[0].value.length;\n }\n this.uiInstance.destroy();\n this.uiInstance = null;\n this.wrapperNode.hide();\n }\n this.loadFailed = false;\n this.textArea.removeClass('uiloadfailed'); // Just in case it failed before\n $(document.getElementById(this.loadFailId)).remove();\n };\n\n /*\n * Re-enable the ui element (e.g. after alt-cntrl-M). This is\n * a full re-initialisation of the ui element.\n */\n InterfaceWrapper.prototype.restart = function() {\n if (this.uiInstance === null) {\n /**\n * Restart the UI component in the textarea\n */\n this.loadUi(this.uiname, this.params);\n }\n };\n\n\n /**\n * Check for wrapper resize - propagate to ui element.\n */\n InterfaceWrapper.prototype.checkForResize = function() {\n const SIZE_HACK = 25; // Horrible but best I can do. TODO: FIXME\n\n if (this.uiInstance) {\n const h = this.wrapperNode.innerHeight();\n const w = this.wrapperNode.innerWidth();\n if (h != this.hLast || w != this.wLast) {\n const xLeft = this.wrapperNode.offset().left;\n const maxWidth = $(window).innerWidth() - xLeft - SIZE_HACK;\n const hAdjusted = h - this.GUTTER;\n const wAdjusted = Math.min(maxWidth, w);\n this.uiInstance.resize(wAdjusted, hAdjusted);\n this.hLast = this.wrapperNode.innerHeight();\n this.wLast = this.wrapperNode.innerWidth();\n }\n }\n };\n\n /**\n * The external entry point from the PHP.\n * @param {string} uiname The name of the User Interface to use e.g. 'ace'\n * @param {string} textareaId The ID of the textarea to be wrapped.\n */\n function newUiWrapper(uiname, textareaId) {\n if (uiname) {\n return new InterfaceWrapper(uiname, textareaId);\n } else {\n return null;\n }\n }\n\n\n return {\n newUiWrapper: newUiWrapper,\n InterfaceWrapper: InterfaceWrapper\n };\n});\n"],"names":["define","$","Templates","Notification","InterfaceWrapper","uiname","textareaId","t","this","GUTTER","DEFAULT_SYNC_INTERVAL_SECS","uniqueId","Math","random","isFullScreenEnable","taId","loadFailId","ta","document","getElementById","textArea","params","attr","uiParams","JSON","parse","lang","readOnly","prop","isLoading","loadFailed","retries","h","parseInt","css","content_lines","val","split","length","rows","min","max","wrapperNode","after","hide","resize","overflow","minHeight","width","border","data","uiInstance","loadUi","mousemove","checkForResize","window","closest","submit","sync","body","on","keyDown","e","keyCode","ctrlKey","altKey","stop","wrapper","restart","removeEventListener","prototype","setAllowFullScreen","enableFullScreen","syncIntervalSecsBase","hasOwnProperty","sync_interval_secs","alert","loading","setTimeout","sessionStorage","getItem","require","ui","innerHeight","w","innerWidth","Constructor","failed","destroy","addClass","loadFailDiv","jqLoadFailDiv","insertBefore","langString","failMessage","errorDiv","str","s","get_string","fallback","when","done","html","hLast","wLast","show","append","getElement","uiInstancePrototype","Object","getPrototypeOf","syncIntervalSecs","startSyncTimer","allowFullScreen","_uiInstance$allowFull","initFullScreenToggle","removeFullScreenButton","fieldId","screenModeButton","parentNode","querySelector","remove","wrapperEditor","enterFullscreen","fullscreenButton","exitFullscreenButton","preventDefault","wrapperHeight","heightEditNode","widthEditNode","classList","add","addEventListener","fullscreenElement","style","height","requestFullscreen","catch","exception","exitFullscreen","renderForPromise","then","_ref","appendNodeContents","bind","timeout","timer","setInterval","stopSyncTimer","clearTimeout","hasFocus","focus","selectionStart","value","removeClass","xLeft","offset","left","maxWidth","hAdjusted","wAdjusted","newUiWrapper"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"userinterfacewrapper.min.js","sources":["../src/userinterfacewrapper.js"],"sourcesContent":["/******************************************************************************\n *\n * This module provides a wrapper for user-interface modules, handling hiding\n * of the textArea that is being replaced by the UI element,\n * resizing of the UI component, and support of such usability functions as\n * ctrl-alt-M to disable/re-enable the entire user interface, including the\n * wrapper.\n *\n * @module coderunner/userinterfacewrapper\n * @copyright Richard Lobb, 2015, The University of Canterbury\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n *\n * The InterfaceWrapper class is constructed either by Moodle PHP calls of\n * the form\n *\n * $PAGE->requires->js_call_amd($modulename, $functionname, $params)\n *\n * (e.g. from within render.php) or by JavaScript require calls, e.g. from\n * authorform.js when the question author changes UI type.\n *\n * The InterfaceWrapper provides:\n *\n * 1. A constructor InterfaceWrapper(uiname, textareaId) which\n * hides the given text area, replaces it with a wrapper div (resizable in\n * height by the user but with width resizing managed by changes in window\n * width), created an instance of nameInstance as defined in the file\n * ui_name.js (see below).\n * params is a record containing the decoded value of\n *\n * 2. A stop() method that destroys the embedded UI and hides the wrapper.\n *\n * 3. A restart() method that shows the wrapper again and re-creates the prior\n * embedded UI component within it.\n *\n * 4. A loadUi(uiname, params) method that kills any currently running UI element\n * (if there is one) and (re)loads the specified one. The params parameter\n * is a record that allows additional parameters to be passed in, such as\n * those from the question's uiParams field and, in the case of the\n * Ace UI, the 'lang' (language) that the editor is editing. This data\n * is supplied by the PHP via the data-params attribute of the answer's\n * base textarea.\n *\n * 5. Regular checking for any resizing of the wrapper, which are passed on to\n * the embedded UI element's resize() method.\n *\n * 6. Monitoring of alt-ctrl-M key presses which toggle the visibility of the\n * wrapper plus UI element and the syncronised textArea by calls to stop()\n * and restart\n *\n * =========================================================================\n *\n * The embedded user-interface module must be defined in a JavaScript file\n * of the form ui_name.js which must define a class nameInstance with\n * the following functionality:\n *\n * 1. A constructor SomeUiName(textareaId, width, height, params) that\n * builds an HTML component of the given width and height. textareaId is the\n * ID of the textArea from which the UI element should obtain its initial\n * serialisation and to which it should write the serialisation when its save\n * or destroy methods are called. params is a JavaScript object,\n * decoded from the JSON uiParams defined by the question plus any\n * additional data required, such as the 'lang' in the case of Ace.\n *\n * 2. A getElement() method that returns the HTML element that the\n * InterfaceWrapper is to insert into the document tree.\n *\n * 3. A method failed() that should return true unless the constructor\n * failed (e.g. because it was not able to de-serialise the text area's\n * contents). The wrapper will call destroy() on the object if failed()\n * returns true and abort the use of the UI element. The text area will\n * have the uiloadfailed class added, which CSS will display in some\n * error mode (e.g. a red border).\n *\n * 4. A method failMessage() that will be called only when failed() returns\n * True. It should be a defined CodeRunner language string key.\n *\n * 5. A sync() method that copies the serialised represention of the UI plugin's\n * data to the related TextArea. This is used when submit is clicked.\n *\n * 6. A destroy() method that should sync the contents to the text area then\n * destroy any HTML elements or other created content. This method is called\n * when CTRL-ALT-M is typed by the user to turn off all UI plugins\n *\n * 7. A resize(width, height) method that should resize the entire UI element\n * to the given dimensions.\n *\n * 8. A hasFocus() method that returns true if the UI element has focus.\n *\n * 9. A syncIntervalSecs() method that returns the time interval between\n * calls to the sync() method. 0 for no sync calls. The userinterfacewrapper\n * provides all instances with a generic (base-class) version that returns\n * the value of a UI parameter sync_interval_secs if given else uses the\n * UI interface wrapper default (currently 5).\n *\n * 10. An allowFullScreen() method that returns True if the UI supports\n * use of the full-screen button in the bottom right of the UI wrapper.\n * Defaults to False if not implemented.\n *\n * 11. A setAllowFullScreen(allow) method that takes a boolean parameter that\n * allows or disallows the use of full screening. This overrides the setting\n * from the allowFullScreen() method and is provided to allow parent UIs\n * such as Scratchpad to override the default settings of a child UI.\n *\n * The return value from the module define is a record with a single field\n * 'Constructor' that references the constructor (e.g. Graph, AceWrapper etc)\n *\n *****************************************************************************/\n\n/**\n * 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 util.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\ndefine(['core/templates', 'core/notification'], function(Templates, Notification) {\n /**\n * Constructor for a new user interface.\n * @param {string} uiname The name of the interface element (e.g. ace, graph, etc)\n * which should be in file ui_ace.js, ui_graph.js etc.\n * @param {string} textareaId The id of the text area that the UI is to manage.\n * The text area should have an attribute data-params, which is a\n * JSON encoded record containing whatever additional parameters might\n * be needed by the User interface. As a minimum it should contain all\n * the parameters from the uiparameters field of\n * the question so that question authors can pass in additional data\n * such as whether graph edges are bidirectional or not in the case of\n * the graph UI. Additionally the Ace editor requires a 'lang' field\n * to specify what language the editor is editing.\n * When the wrapper has been set up on a text area, the text area\n * element has a reference, current_ui_wrapper, to the UI wrapper.\n */\n function InterfaceWrapper(uiname, textareaId) {\n let t = this; // For use by embedded functions.\n\n this.GUTTER = 16; // Size of gutter at base of wrapper Node (pixels)\n this.DEFAULT_SYNC_INTERVAL_SECS = 5;\n\n this.uniqueId = Math.random();\n const PIXELS_PER_ROW = 19; // For estimating height of textareas.\n const MAX_GROWN_ROWS = 50; // Upper limit to artifically grown textarea rows.\n const MIN_WRAPPER_HEIGHT = 50;\n this.isFullScreenEnable = null;\n this.taId = textareaId;\n this.loadFailId = textareaId + '_loadfailerr';\n this.textArea = document.getElementById(textareaId);\n if (this.textArea.current_ui_wrapper) {\n alert(`JavaScript error: multiple UIs on ${textareaId}!`);\n }\n const params = this.textArea.getAttribute('data-params');\n if (params) {\n this.uiParams = JSON.parse(params);\n } else {\n this.uiParams = {};\n }\n this.uiParams.lang = this.textArea.getAttribute('data-lang');\n this.readOnly = this.textArea.readOnly;\n this.isLoading = false; // True if we're busy loading a UI element.\n this.loadFailed = false; // True if UI failed to initialise properly.\n this.retries = 0; // Number of failed attempts to load a UI component.\n\n let h = this.textArea.clientHeight; // Just a first guess. Will be fine tuned in resize.\n\n // Grow height if textarea contents warrant.\n let content_lines = this.textArea.value.split('\\n').length;\n let rows = this.textArea.rows;\n if (content_lines > rows) {\n // Allow reloaded text areas with lots of text to grow bigger, within limits.\n rows = Math.min(content_lines, MAX_GROWN_ROWS);\n }\n h = Math.max(h, rows * PIXELS_PER_ROW, MIN_WRAPPER_HEIGHT);\n this.textArea.style.height = h + 'px';\n /**\n * Construct a hidden empty wrapper div, inserted directly after the\n * textArea, ready to contain the actual UI.\n */\n this.wrapperNode = document.createElement('div');\n this.wrapperNode.id = `${this.taId}_wrapper`;\n this.wrapperNode.classList.add('ui_wrapper', 'position-relative');\n this.wrapperNode.uniqueId = this.uniqueId;\n this.wrapperNode.style.display = 'none';\n this.wrapperNode.style.resize = 'vertical';\n this.wrapperNode.style.overflow = 'hidden';\n this.wrapperNode.style.minHeight = h + \"px\";\n this.wrapperNode.style.width = '100%';\n this.wrapperNode.style.border = '1px solid darkgrey';\n this.textArea.insertAdjacentElement('afterend', this.wrapperNode);\n\n this.wLast = 0; // Record last known width and height. See checkForResize().\n this.hLast = 0;\n\n\n /**\n * Record a reference to this wrapper in the text area\n * for use by external javascript that needs to interact with the\n * wrapper, e.g. the multilanguage.js module.\n */\n this.textArea.current_ui_wrapper = this;\n\n /**\n * Load the UI into the wrapper (aysnchronous).\n */\n this.uiInstance = null; // Defined by loadUi asynchronously\n this.loadUi(uiname, this.uiParams); // Load the required UI element\n\n /**\n * Add event handlers\n */\n const resizeObserver = new ResizeObserver(function () {\n t.checkForResize();\n });\n resizeObserver.observe(this.wrapperNode);\n\n\n window.addEventListener('resize', function() {\n t.checkForResize();\n });\n\n const form = this.textArea.closest('form');\n if (form) {\n form.addEventListener('submit', function() {\n if (t.uiInstance !== null) {\n t.uiInstance.sync();\n }\n });\n }\n\n document.body.addEventListener('keydown', function keyDown(e) {\n if (e.key === 'm' && e.ctrlKey && e.altKey) {\n // Before trying to handle ctrl-alt-m keypresses, make sure the\n // current instance of the wrapper in the DOM is the same as\n // when this event handler was created. This might not be\n // the case when userinterface wrappers are nested.\n const wrapper = document.getElementById(`${t.taId}_wrapper`);\n if (!wrapper || wrapper.uniqueId !== t.uniqueId) {\n // This wrapper has apparently been killed. Stop listening.\n // Should now be garbage collectable, too.\n document.removeEventListener('keydown', keyDown);\n } else if (t.uiInstance !== null || t.loadFailed) {\n t.stop();\n } else {\n t.restart(); // Reactivate\n }\n }\n });\n }\n\n /**\n * Set the value of the allowFullScreen property.\n * If the value is true, the fullscreen mode will be shown.\n * If the value is false, the fullscreen will be hidden.\n *\n * @param {Boolean} enableFullScreen The value to set.\n */\n InterfaceWrapper.prototype.setAllowFullScreen = function(enableFullScreen) {\n this.isFullScreenEnable = enableFullScreen;\n };\n\n /**\n * Load the specified UI element (which in the case of Ace will need\n * to know the language, lang, as well - this must be supplied as\n * a 'lang' attribute of the record params.\n * When ui is up and running, this.uiInstance will reference it.\n * To avoid a potential race problem, if this method is already busy\n * with a load, try again in 200 msecs.\n * @param {string} uiname The name of the User Interface to be used.\n * @param {object} params The UI parameters object that passes parameters\n * to the actual UI object.\n */\n InterfaceWrapper.prototype.loadUi = function(uiname, params) {\n const MAX_RETRIES = 20; // Maximum number of attempts to load the UI.\n const t = this;\n const errPart1 = 'Failed to load ';\n const errPart2 = ' UI component. If this error persists, please report it to the forum on coderunner.org.nz';\n\n /**\n * Get the given language string and plug it into the given\n * div element as its html, plus a 'fallback' message on a separate line.\n * @param {string} langString The language string specifier for the error message,\n * to be loaded by AJAX.\n * @param {object} errorDiv The div object into which the error message\n * is to be inserted.\n */\n function setLoadFailMessage(langString, errorDiv) {\n require(['core/str'], function(str) {\n /**\n * Get langString text via AJAX\n */\n const s = str.get_string(langString, 'qtype_coderunner');\n const fallback = str.get_string('ui_fallback', 'qtype_coderunner');\n Promise.all([s, fallback]).then(function(results) {\n const s = results[0];\n const fallback = results[1];\n errorDiv.innerHTML = s + '
' + fallback;\n });\n });\n }\n\n /**\n * The default method for a UIs sync_interval_secs method.\n * Returns the sync_interval_secs parameter if given, else\n * DEFAULT_SYNC_INTERVAL_SECS.\n */\n function syncIntervalSecsBase() {\n if (params.hasOwnProperty('sync_interval_secs')) {\n return parseInt(params.sync_interval_secs);\n } else {\n return t.DEFAULT_SYNC_INTERVAL_SECS;\n }\n }\n\n if (this.isLoading) { // Oops, we're loading a UI element already\n this.retries += 1;\n if (this.retries > MAX_RETRIES) {\n alert(errPart1 + uiname + errPart2);\n this.retries = 0;\n this.loading = 0;\n } else {\n setTimeout(function() {\n t.loadUi(uiname, params);\n }, 200); // Try again in 200 msecs\n }\n return;\n }\n this.retries = 0;\n this.params = params; // Save in case need to restart\n\n this.stop(); // Kill any active UI first\n this.uiname = uiname;\n\n if (this.uiname === '' || this.uiname === 'none' || sessionStorage.getItem('disableUis')) {\n this.uiInstance = null;\n } else {\n this.isLoading = true;\n require(['qtype_coderunner/ui_' + this.uiname],\n function(ui) {\n const h = t.textArea.clientHeight - t.GUTTER;\n const w = t.textArea.clientWidth;\n const uiInstance = new ui.Constructor(t.taId, w, h, params);\n if (uiInstance.failed()) {\n /*\n * Constructor failed to load serialisation.\n * Set uiloadfailed class on text area.\n */\n t.loadFailed = true;\n t.wrapperNode.style.display = 'none';\n t.textArea.style.display = '';\n uiInstance.destroy();\n t.uiInstance = null;\n t.textArea.classList.add('uiloadfailed');\n const loadFailDiv = document.createElement('div');\n loadFailDiv.id = t.loadFailId;\n loadFailDiv.className = 'uiloadfailed';\n t.textArea.parentNode.insertBefore(loadFailDiv, t.textArea);\n setLoadFailMessage(uiInstance.failMessage(), loadFailDiv); // Insert error by AJAX\n } else {\n t.textArea.style.display = 'none';\n t.wrapperNode.style.display = '';\n let elementToAdd = uiInstance.getElement();\n if (elementToAdd && elementToAdd.jquery) { // Check if the UI instance returned a jQuery object.\n elementToAdd = elementToAdd[0];\n }\n\n if (elementToAdd) {\n // Some naughty (?) UIs, such as scratchpad UI, return null, and then\n // plug themselves into the wrapper asynchronously. [Necessary when using mustache templates].\n // So fingers crossed they know what they're doing.\n\n t.wrapperNode.appendChild(elementToAdd);\n\n // With jQuery, any embedded