diff --git a/amd/build/util/mform-helper.min.js b/amd/build/util/mform-helper.min.js
new file mode 100644
index 0000000..a33841d
--- /dev/null
+++ b/amd/build/util/mform-helper.min.js
@@ -0,0 +1,3 @@
+define("local_treestudyplan/util/mform-helper",["exports","core/ajax","core/fragment","core/notification","./string-helper","./debugger"],(function(_exports,_ajax,_fragment,_notification,_stringHelper,_debugger){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=_interopRequireDefault(_notification),_debugger=_interopRequireDefault(_debugger);var _default={install:function(Vue){var debug=new _debugger.default("treestudyplan-mform-helper"),strings=(0,_stringHelper.load_strings)({editmod:{save$core:"save$core",cancel$core:"cancel$core"}});Vue.component("mform",{props:{name:{type:String},params:{type:Object}},data:function(){return{content:"",loading:!0,uuid:void 0!==crypto.randomUUID?crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,(function(c){return(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16)})),text:strings}},computed:{},methods:{openForm:function(){this.$refs.editormodal.show()},onShown:function(){var self=this;debug.info('Loading form "'.concat(self.name,'" with params'),self.params),self.loading=!1,(0,_ajax.call)([{methodname:"local_treestudyplan_get_mform",args:{name:self.name,params:self.params}}])[0].then((function(data){self.content=data.html,self.loading=!1,(0,_fragment.processCollectedJavascript)(data.js)})).catch(_notification.default.exception)},onSave:function(){var self=this,form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));var formdata=new FormData(form),data=new URLSearchParams(formdata).toString();(0,_ajax.call)([{methodname:"local_treestudyplan_submit_mform",args:{name:self.name,params:self.params,data:data}}])[0].then((function(){self.$emit("saved",formdata)})).catch(_notification.default.exception)}},template:'\n \n \n \n '})}};return _exports.default=_default,_exports.default}));
+
+//# sourceMappingURL=mform-helper.min.js.map
\ No newline at end of file
diff --git a/amd/build/util/mform-helper.min.js.map b/amd/build/util/mform-helper.min.js.map
new file mode 100644
index 0000000..8bbc41d
--- /dev/null
+++ b/amd/build/util/mform-helper.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"mform-helper.min.js","sources":["../../src/util/mform-helper.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\n/*eslint no-console: \"off\"*/\n/*eslint no-bitwise: \"off\"*/\n/*eslint-disable no-trailing-spaces*/\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {call} from 'core/ajax';\nimport {processCollectedJavascript} from 'core/fragment';\n//import {replaceNodeContents} from 'core/templates';\nimport notification from 'core/notification';\nimport {load_strings} from './string-helper';\nimport Debugger from './debugger';\n//import {markFormSubmitted} from 'core_form/changechecker'; // Moodle 4.00+ only\n//import {notifyFormSubmittedByJavascript} from 'core_form/events'; // Moodle 4.00+ only\n\n/**\n * Create a random UUID in both secure and insecure contexts\n * @returns UUID\n */\nfunction create_uuid() {\n if (crypto.randomUUID !== undefined ) {\n return crypto.randomUUID();\n } else {\n return \"10000000-1000-4000-8000-100000000000\".replace(/[018]/g, c =>\n (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)\n );\n }\n}\n\nexport default {\n install(Vue/*,options*/){\n let debug = new Debugger(\"treestudyplan-mform-helper\");\n let strings = load_strings({\n editmod: {\n save$core: \"save$core\",\n cancel$core: \"cancel$core\",\n }\n });\n\n Vue.component('mform', {\n props: {\n name: {\n type: String,\n },\n params: {\n type: Object,\n }\n },\n data() {\n return {\n content: \"\",\n loading: true,\n uuid: create_uuid(),\n text: strings,\n };\n },\n computed: {\n },\n methods: {\n openForm(){\n const self = this;\n self.$refs[\"editormodal\"].show();\n },\n onShown(){\n const self = this;\n debug.info(`Loading form \"${self.name}\" with params`,self.params);\n self.loading = false;\n call([{\n methodname: 'local_treestudyplan_get_mform',\n args: {name: self.name, params: self.params}\n }])[0].then((data)=>{\n self.content = data.html;\n self.loading = false;\n // Process the collected javascript;\n processCollectedJavascript(data.js);\n }).catch(notification.exception);\n\n //loadFragment('local_treestudyplan', 'mod_edit_form', this.coursectxid, params).then((html,js) =>{\n // replaceNodeContents(self.$refs[\"content\"], html, js);\n //}).catch(notification.exception);\n\n },\n onSave(){\n const self = this;\n let form = this.$refs[\"content\"].getElementsByTagName(\"form\")[0];\n\n // markFormSubmitted(form); // Moodle 4.00+ only\n // We call this, so other modules can update the form with the latest state.\n form.dispatchEvent(new Event(\"save-form-state\"));\n // Tell all form fields we are about to submit the form.\n // notifyFormSubmittedByJavascript(form); // Moodle 4.00+ only\n\n const formdata = new FormData(form);\n const data =new URLSearchParams(formdata).toString();\n //const formdata = new FormData(form);\n //const data = {};\n //formdata.forEach((value, key) => (data[key] = value));\n\n call([{\n methodname: 'local_treestudyplan_submit_mform',\n args: {name: self.name, params: self.params, data: data}\n }])[0].then(()=>{\n self.$emit(\"saved\",formdata);\n }).catch(notification.exception);\n\n }\n },\n template: `\n \n \n \n `,\n });\n }\n};\n\n"],"names":["install","Vue","debug","Debugger","strings","editmod","save$core","cancel$core","component","props","name","type","String","params","Object","data","content","loading","uuid","undefined","crypto","randomUUID","replace","c","getRandomValues","Uint8Array","toString","text","computed","methods","openForm","this","$refs","show","onShown","self","info","methodname","args","then","html","js","catch","notification","exception","onSave","form","getElementsByTagName","dispatchEvent","Event","formdata","FormData","URLSearchParams","$emit","template"],"mappings":"meA8Be,CACXA,iBAAQC,SACAC,MAAQ,IAAIC,kBAAS,8BACrBC,SAAU,8BAAa,CACvBC,QAAS,CACLC,UAAW,YACXC,YAAa,iBAIrBN,IAAIO,UAAU,QAAS,CACnBC,MAAO,CACHC,KAAM,CACFC,KAAMC,QAEVC,OAAQ,CACJF,KAAMG,SAGdC,sBACW,CACHC,QAAS,GACTC,SAAS,EACTC,UAhCUC,IAAtBC,OAAOC,WACAD,OAAOC,aAEP,uCAAuCC,QAAQ,UAAU,SAAAC,UAC3DA,EAAIH,OAAOI,gBAAgB,IAAIC,WAAW,IAAI,GAAK,IAAMF,EAAI,GAAGG,SAAS,OA6BlEC,KAAMvB,UAGdwB,SAAU,GAEVC,QAAS,CACLC,oBACiBC,KACRC,MAAL,YAA0BC,QAE9BC,uBACUC,KAAOJ,KACb7B,MAAMkC,6BAAsBD,KAAKzB,sBAAoByB,KAAKtB,QAC1DsB,KAAKlB,SAAU,iBACV,CAAC,CACFoB,WAAY,gCACZC,KAAM,CAAC5B,KAAMyB,KAAKzB,KAAMG,OAAQsB,KAAKtB,WACrC,GAAG0B,MAAK,SAACxB,MACToB,KAAKnB,QAAUD,KAAKyB,KACpBL,KAAKlB,SAAU,2CAEYF,KAAK0B,OACjCC,MAAMC,sBAAaC,YAO1BC,sBACUV,KAAOJ,KACTe,KAAOf,KAAKC,MAAL,QAAsBe,qBAAqB,QAAQ,GAI9DD,KAAKE,cAAc,IAAIC,MAAM,wBAIvBC,SAAW,IAAIC,SAASL,MACxB/B,KAAM,IAAIqC,gBAAgBF,UAAUxB,0BAKrC,CAAC,CACFW,WAAY,mCACZC,KAAM,CAAC5B,KAAMyB,KAAKzB,KAAMG,OAAQsB,KAAKtB,OAAQE,KAAMA,SACnD,GAAGwB,MAAK,WACRJ,KAAKkB,MAAM,QAAQH,aACpBR,MAAMC,sBAAaC,aAI9BU"}
\ No newline at end of file
diff --git a/amd/build/util/modedit-modal.min.js b/amd/build/util/modedit-modal.min.js
new file mode 100644
index 0000000..cdc9408
--- /dev/null
+++ b/amd/build/util/modedit-modal.min.js
@@ -0,0 +1,3 @@
+define("local_treestudyplan/util/modedit-modal",["exports","core/fragment","./util/string-helper","core/ajax","core/notification","core/templates"],(function(_exports,_fragment,_stringHelper,_ajax,_notification,_templates){var obj;Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_notification=(obj=_notification)&&obj.__esModule?obj:{default:obj};var _default={install:function(Vue){var strings=(0,_stringHelper.load_strings)({editmod:{save$core:"save$core",cancel$core:"cancel$core"}});Vue.component("s-edit-mod",{props:{cmid:{type:Number},coursectxid:{type:Number},title:{type:String,default:""},genericonly:{type:Boolean,default:!1}},data:function(){return{content:"",text:strings.editmod}},computed:{},methods:{openForm:function(){this.$refs.editormodal.show()},onShown:function(){var self=this,params={cmid:this.cmid};console.info("Loading form"),(0,_fragment.loadFragment)("local_treestudyplan","mod_edit_form",this.coursectxid,params).then((function(html,js){(0,_templates.replaceNodeContents)(self.$refs.content,html,js)})).catch(_notification.default.exception)},onSave:function(){var self=this,form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));var formdata=new FormData(form),data=new URLSearchParams(formdata).toString();(0,_ajax.call)([{methodname:"local_treestudyplan_submit_cm_editform",args:{cmid:this.cmid,formdata:data}}])[0].done((function(){self.$emit("saved",formdata)})).fail(_notification.default.exception)}},template:'\n \n \n \n '})}};return _exports.default=_default,_exports.default}));
+
+//# sourceMappingURL=modedit-modal.min.js.map
\ No newline at end of file
diff --git a/amd/build/util/modedit-modal.min.js.map b/amd/build/util/modedit-modal.min.js.map
new file mode 100644
index 0000000..be68d17
--- /dev/null
+++ b/amd/build/util/modedit-modal.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"modedit-modal.min.js","sources":["../../src/util/modedit-modal.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\n/*eslint no-console: \"off\"*/\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {loadFragment} from 'core/fragment';\nimport {load_strings} from './util/string-helper';\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\nimport {replaceNodeContents} from 'core/templates';\n//import {markFormSubmitted} from 'core_form/changechecker'; // Moodle 4.00+ only\n//import {notifyFormSubmittedByJavascript} from 'core_form/events'; // Moodle 4.00+ only\n\nexport default {\n install(Vue/*,options*/){\n\n let strings = load_strings({\n editmod: {\n save$core: \"save$core\",\n cancel$core: \"cancel$core\",\n }\n });\n\n Vue.component('s-edit-mod', {\n props: {\n cmid: {\n type: Number,\n },\n coursectxid:{\n type: Number,\n },\n title: {\n type: String,\n default: \"\",\n },\n genericonly: {\n type: Boolean,\n default: false,\n }\n },\n data() {\n return {\n content: \"\",\n text: strings.editmod,\n };\n },\n computed: {\n },\n methods: {\n openForm(){\n const self = this;\n self.$refs[\"editormodal\"].show();\n },\n onShown(){\n const self = this;\n let params = {cmid: this.cmid};\n console.info(\"Loading form\");\n loadFragment('local_treestudyplan', 'mod_edit_form', this.coursectxid, params).then((html,js) =>{\n replaceNodeContents(self.$refs[\"content\"], html, js);\n }).catch(notification.exception);\n\n },\n onSave(){\n const self = this;\n let form = this.$refs[\"content\"].getElementsByTagName(\"form\")[0];\n\n // markFormSubmitted(form); // Moodle 4.00+ only\n // We call this, so other modules can update the form with the latest state.\n form.dispatchEvent(new Event(\"save-form-state\"));\n // Tell all form fields we are about to submit the form.\n // notifyFormSubmittedByJavascript(form); // Moodle 4.00+ only\n\n const formdata = new FormData(form);\n const data =new URLSearchParams(formdata).toString();\n //const formdata = new FormData(form);\n //const data = {};\n //formdata.forEach((value, key) => (data[key] = value));\n\n call([{\n methodname: 'local_treestudyplan_submit_cm_editform',\n args: {cmid: this.cmid, formdata: data}\n }])[0].done(()=>{\n self.$emit(\"saved\",formdata);\n }).fail(notification.exception);\n\n }\n },\n template: `\n \n \n \n `,\n });\n }\n};\n\n"],"names":["install","Vue","strings","editmod","save$core","cancel$core","component","props","cmid","type","Number","coursectxid","title","String","default","genericonly","Boolean","data","content","text","computed","methods","openForm","this","$refs","show","onShown","self","params","console","info","then","html","js","catch","notification","exception","onSave","form","getElementsByTagName","dispatchEvent","Event","formdata","FormData","URLSearchParams","toString","methodname","args","done","$emit","fail","template"],"mappings":"wYAce,CACXA,iBAAQC,SAEAC,SAAU,8BAAa,CACvBC,QAAS,CACLC,UAAW,YACXC,YAAa,iBAIrBJ,IAAIK,UAAU,aAAc,CACxBC,MAAO,CACHC,KAAM,CACFC,KAAMC,QAEVC,YAAY,CACRF,KAAMC,QAEVE,MAAO,CACHH,KAAMI,OACNC,QAAS,IAEbC,YAAa,CACTN,KAAMO,QACNF,SAAS,IAGjBG,sBACW,CACHC,QAAS,GACTC,KAAMjB,QAAQC,UAGtBiB,SAAU,GAEVC,QAAS,CACLC,oBACiBC,KACRC,MAAL,YAA0BC,QAE9BC,uBACUC,KAAOJ,KACTK,OAAS,CAACpB,KAAMe,KAAKf,MACzBqB,QAAQC,KAAK,2CACA,sBAAuB,gBAAiBP,KAAKZ,YAAaiB,QAAQG,MAAK,SAACC,KAAKC,uCAClEN,KAAKH,MAAL,QAAuBQ,KAAMC,OAClDC,MAAMC,sBAAaC,YAG1BC,sBACUV,KAAOJ,KACTe,KAAOf,KAAKC,MAAL,QAAsBe,qBAAqB,QAAQ,GAI9DD,KAAKE,cAAc,IAAIC,MAAM,wBAIvBC,SAAW,IAAIC,SAASL,MACxBrB,KAAM,IAAI2B,gBAAgBF,UAAUG,0BAKrC,CAAC,CACFC,WAAY,yCACZC,KAAM,CAACvC,KAAMe,KAAKf,KAAMkC,SAAUzB,SAClC,GAAG+B,MAAK,WACRrB,KAAKsB,MAAM,QAAQP,aACpBQ,KAAKf,sBAAaC,aAI7Be"}
\ No newline at end of file
diff --git a/amd/build/util/string-helper.min.js b/amd/build/util/string-helper.min.js
index 7b7d267..4b44105 100644
--- a/amd/build/util/string-helper.min.js
+++ b/amd/build/util/string-helper.min.js
@@ -1,3 +1,3 @@
-define("local_treestudyplan/util/string-helper",["exports","core/str"],(function(_exports,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.load_stringkeys=function(string_keys){var _loop2=function(idx){var stringkeys=[];for(var i in string_keys[idx]){var parts=string_keys[idx][i].textkey.split("$"),identifier=parts[0],component=parts.length>1?parts[1]:"local_treestudyplan";stringkeys.push({key:identifier,component:component})}(0,_str.get_strings)(stringkeys).then((function(strings){for(var _i in strings){var s=strings[_i];string_keys[idx][_i].text=s}}))};for(var idx in string_keys)_loop2(idx);return string_keys},_exports.load_strings=function(strings){var _loop=function(idx){var stringkeys=[];for(var handle in strings[idx]){var parts=strings[idx][handle].split(/[$@]/),identifier=parts[0],component=parts.length>1?parts[1]:"local_treestudyplan";stringkeys.push({key:identifier,component:component})}(0,_str.get_strings)(stringkeys).then((function(str){var i=0;for(var _key in strings[idx])strings[idx][_key]=str[i],i++}))};for(var idx in strings)_loop(idx);return strings},_exports.strformat=function(str,values){return str.replace(/\{(\w+)\}/g,(function(m,m1){return m1&&values.hasOwnProperty(m1)?values[m1]:m}))}}));
+define("local_treestudyplan/util/string-helper",["exports","core/str"],(function(_exports,_str){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.load_stringkeys=function(string_keys){var _loop2=function(idx){var stringkeys=[];for(var i in string_keys[idx]){var parts=string_keys[idx][i].textkey.split("$"),identifier=parts[0],component=parts.length>1?parts[1]:"local_treestudyplan";stringkeys.push({key:identifier,component:component})}getstr_func(stringkeys).then((function(strings){for(var _i in strings){var s=strings[_i];string_keys[idx][_i].text=s}}))};for(var idx in string_keys)_loop2(idx);return string_keys},_exports.load_strings=function(strings){var _loop=function(idx){var stringkeys=[];for(var handle in strings[idx]){var parts=strings[idx][handle].split(/[$@]/),identifier=parts[0],component=parts.length>1?parts[1]:"local_treestudyplan";stringkeys.push({key:identifier,component:component})}getstr_func(stringkeys).then((function(str){var i=0;for(var _key in strings[idx])strings[idx][_key]=str[i],i++}))};for(var idx in strings)_loop(idx);return strings},_exports.strformat=function(str,values){return str.replace(/\{(\w+)\}/g,(function(m,m1){return m1&&values.hasOwnProperty(m1)?values[m1]:m}))};var getstr_func=void 0!==_str.getStrings?_str.getStrings:_str.get_strings}));
//# sourceMappingURL=string-helper.min.js.map
\ No newline at end of file
diff --git a/amd/build/util/string-helper.min.js.map b/amd/build/util/string-helper.min.js.map
index 33dd722..814f510 100644
--- a/amd/build/util/string-helper.min.js.map
+++ b/amd/build/util/string-helper.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"string-helper.min.js","sources":["../../src/util/string-helper.js"],"sourcesContent":["import {get_strings} from 'core/str';\n\n/**\n * Load the translation of strings from a strings object\n * @param {Object} strings The map of strings\n * @returns {Object} The map with strings loaded in\n */\nexport function load_strings(strings){\n for(let idx in strings){\n let stringkeys = [];\n for(const handle in strings[idx]){\n const key = strings[idx][handle];\n let parts = key.split(/[$@]/);\n let identifier = parts[0];\n let component = (parts.length > 1)?parts[1]:'local_treestudyplan';\n stringkeys.push({ key: identifier, component: component});\n }\n get_strings(stringkeys).then(function(str){\n let i = 0;\n for(const key in strings[idx]){\n strings[idx][key] = str[i];\n i++;\n }\n });\n }\n\n return strings;\n}\n\n/**\n * Load the translation of strings from a strings object based on keys\n * Used for loading values for a drop down menu or the like\n * @param {Object} string_keys The map of stringkeys\n * @returns {Object} The map with strings loaded in\n */\nexport function load_stringkeys(string_keys){\n for(let idx in string_keys){\n // Get text strings for condition settings\n let stringkeys = [];\n for(const i in string_keys[idx]){\n const key = string_keys[idx][i].textkey;\n let parts = key.split(\"$\");\n let identifier = parts[0];\n let component = (parts.length > 1)?parts[1]:'local_treestudyplan';\n stringkeys.push({ key: identifier, component: component});\n }\n get_strings(stringkeys).then(function(strings){\n for(const i in strings) {\n const s = strings[i];\n const l = string_keys[idx][i];\n l.text = s;\n }\n });\n }\n return string_keys;\n}\n\n/**\n * String formatting function - replaces {name} in string by value of same key in values parameter\n * @param {string} str String t\n * @param {object} values Object containing keys to replace {key} strings with\n * @returns Formatted string\n */\nexport function strformat(str,values) {\n return str.replace(/\\{(\\w+)\\}/g, (m, m1) => {\n if (m1 && values.hasOwnProperty(m1)) {\n return values[m1];\n } else {\n return m;\n }\n });\n}"],"names":["string_keys","idx","stringkeys","i","parts","textkey","split","identifier","component","length","push","key","then","strings","s","text","handle","str","values","replace","m","m1","hasOwnProperty"],"mappings":"0LAmCgCA,iCACpBC,SAEAC,WAAa,OACb,IAAMC,KAAMH,YAAYC,KAAK,KAEzBG,MADQJ,YAAYC,KAAKE,GAAGE,QAChBC,MAAM,KAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAGL,MAAM,GAAG,sBAC5CF,WAAWQ,KAAK,CAAEC,IAAKJ,WAAYC,UAAWA,iCAEtCN,YAAYU,MAAK,SAASC,aAC9B,IAAMV,MAAKU,QAAS,KACdC,EAAID,QAAQV,IACRH,YAAYC,KAAKE,IACzBY,KAAOD,WAdjB,IAAIb,OAAOD,mBAAPC,YAkBDD,4CA/CkBa,4BACjBZ,SACAC,WAAa,OACb,IAAMc,UAAWH,QAAQZ,KAAK,KAE1BG,MADQS,QAAQZ,KAAKe,QACTV,MAAM,QAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAGL,MAAM,GAAG,sBAC5CF,WAAWQ,KAAK,CAAEC,IAAKJ,WAAYC,UAAWA,iCAEtCN,YAAYU,MAAK,SAASK,SAC9Bd,EAAI,MACJ,IAAMQ,QAAOE,QAAQZ,KACrBY,QAAQZ,KAAKU,MAAOM,IAAId,GACxBA,YAbR,IAAIF,OAAOY,cAAPZ,YAkBDY,qCAqCeI,IAAIC,eACnBD,IAAIE,QAAQ,cAAc,SAACC,EAAGC,WAC7BA,IAAMH,OAAOI,eAAeD,IACrBH,OAAOG,IAEPD"}
\ No newline at end of file
+{"version":3,"file":"string-helper.min.js","sources":["../../src/util/string-helper.js"],"sourcesContent":["import {get_strings} from 'core/str';\nimport {getStrings} from 'core/str';\n\n/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */\nconst getstr_func = (getStrings !== undefined)?getStrings:get_strings;\n\n/**\n * Load the translation of strings from a strings object\n * @param {Object} strings The map of strings\n * @returns {Object} The map with strings loaded in\n */\nexport function load_strings(strings){\n for(let idx in strings){\n let stringkeys = [];\n for(const handle in strings[idx]){\n const key = strings[idx][handle];\n let parts = key.split(/[$@]/);\n let identifier = parts[0];\n let component = (parts.length > 1)?parts[1]:'local_treestudyplan';\n stringkeys.push({ key: identifier, component: component});\n }\n getstr_func(stringkeys).then(function(str){\n let i = 0;\n for(const key in strings[idx]){\n strings[idx][key] = str[i];\n i++;\n }\n });\n }\n\n return strings;\n}\n\n/**\n * Load the translation of strings from a strings object based on keys\n * Used for loading values for a drop down menu or the like\n * @param {Object} string_keys The map of stringkeys\n * @returns {Object} The map with strings loaded in\n */\nexport function load_stringkeys(string_keys){\n for(let idx in string_keys){\n // Get text strings for condition settings\n let stringkeys = [];\n for(const i in string_keys[idx]){\n const key = string_keys[idx][i].textkey;\n let parts = key.split(\"$\");\n let identifier = parts[0];\n let component = (parts.length > 1)?parts[1]:'local_treestudyplan';\n stringkeys.push({ key: identifier, component: component});\n }\n getstr_func(stringkeys).then(function(strings){\n for(const i in strings) {\n const s = strings[i];\n const l = string_keys[idx][i];\n l.text = s;\n }\n });\n }\n return string_keys;\n}\n\n/**\n * String formatting function - replaces {name} in string by value of same key in values parameter\n * @param {string} str String t\n * @param {object} values Object containing keys to replace {key} strings with\n * @returns Formatted string\n */\nexport function strformat(str,values) {\n return str.replace(/\\{(\\w+)\\}/g, (m, m1) => {\n if (m1 && values.hasOwnProperty(m1)) {\n return values[m1];\n } else {\n return m;\n }\n });\n}"],"names":["string_keys","idx","stringkeys","i","parts","textkey","split","identifier","component","length","push","key","getstr_func","then","strings","s","text","handle","str","values","replace","m","m1","hasOwnProperty","undefined","getStrings","get_strings"],"mappings":"0LAuCgCA,iCACpBC,SAEAC,WAAa,OACb,IAAMC,KAAMH,YAAYC,KAAK,KAEzBG,MADQJ,YAAYC,KAAKE,GAAGE,QAChBC,MAAM,KAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAGL,MAAM,GAAG,sBAC5CF,WAAWQ,KAAK,CAAEC,IAAKJ,WAAYC,UAAWA,YAElDI,YAAYV,YAAYW,MAAK,SAASC,aAC9B,IAAMX,MAAKW,QAAS,KACdC,EAAID,QAAQX,IACRH,YAAYC,KAAKE,IACzBa,KAAOD,WAdjB,IAAId,OAAOD,mBAAPC,YAkBDD,4CA/CkBc,4BACjBb,SACAC,WAAa,OACb,IAAMe,UAAWH,QAAQb,KAAK,KAE1BG,MADQU,QAAQb,KAAKgB,QACTX,MAAM,QAClBC,WAAaH,MAAM,GACnBI,UAAaJ,MAAMK,OAAS,EAAGL,MAAM,GAAG,sBAC5CF,WAAWQ,KAAK,CAAEC,IAAKJ,WAAYC,UAAWA,YAElDI,YAAYV,YAAYW,MAAK,SAASK,SAC9Bf,EAAI,MACJ,IAAMQ,QAAOG,QAAQb,KACrBa,QAAQb,KAAKU,MAAOO,IAAIf,GACxBA,YAbR,IAAIF,OAAOa,cAAPb,YAkBDa,qCAqCeI,IAAIC,eACnBD,IAAIE,QAAQ,cAAc,SAACC,EAAGC,WAC7BA,IAAMH,OAAOI,eAAeD,IACrBH,OAAOG,IAEPD,UApEbT,iBAA8BY,IAAfC,gBAA0BA,gBAAWC"}
\ No newline at end of file
diff --git a/amd/src/util/mform-helper.js b/amd/src/util/mform-helper.js
new file mode 100644
index 0000000..cf7abe3
--- /dev/null
+++ b/amd/src/util/mform-helper.js
@@ -0,0 +1,133 @@
+/*eslint no-var: "error"*/
+/*eslint no-console: "off"*/
+/*eslint no-bitwise: "off"*/
+/*eslint-disable no-trailing-spaces*/
+/*eslint-env es6*/
+// Put this file in path/to/plugin/amd/src
+
+import {call} from 'core/ajax';
+import {processCollectedJavascript} from 'core/fragment';
+//import {replaceNodeContents} from 'core/templates';
+import notification from 'core/notification';
+import {load_strings} from './string-helper';
+import Debugger from './debugger';
+//import {markFormSubmitted} from 'core_form/changechecker'; // Moodle 4.00+ only
+//import {notifyFormSubmittedByJavascript} from 'core_form/events'; // Moodle 4.00+ only
+
+/**
+ * Create a random UUID in both secure and insecure contexts
+ * @returns UUID
+ */
+function create_uuid() {
+ if (crypto.randomUUID !== undefined ) {
+ return crypto.randomUUID();
+ } else {
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
+ (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
+ );
+ }
+}
+
+export default {
+ install(Vue/*,options*/){
+ let debug = new Debugger("treestudyplan-mform-helper");
+ let strings = load_strings({
+ editmod: {
+ save$core: "save$core",
+ cancel$core: "cancel$core",
+ }
+ });
+
+ Vue.component('mform', {
+ props: {
+ name: {
+ type: String,
+ },
+ params: {
+ type: Object,
+ }
+ },
+ data() {
+ return {
+ content: "",
+ loading: true,
+ uuid: create_uuid(),
+ text: strings,
+ };
+ },
+ computed: {
+ },
+ methods: {
+ openForm(){
+ const self = this;
+ self.$refs["editormodal"].show();
+ },
+ onShown(){
+ const self = this;
+ debug.info(`Loading form "${self.name}" with params`,self.params);
+ self.loading = false;
+ call([{
+ methodname: 'local_treestudyplan_get_mform',
+ args: {name: self.name, params: self.params}
+ }])[0].then((data)=>{
+ self.content = data.html;
+ self.loading = false;
+ // Process the collected javascript;
+ processCollectedJavascript(data.js);
+ }).catch(notification.exception);
+
+ //loadFragment('local_treestudyplan', 'mod_edit_form', this.coursectxid, params).then((html,js) =>{
+ // replaceNodeContents(self.$refs["content"], html, js);
+ //}).catch(notification.exception);
+
+ },
+ onSave(){
+ const self = this;
+ let form = this.$refs["content"].getElementsByTagName("form")[0];
+
+ // markFormSubmitted(form); // Moodle 4.00+ only
+ // We call this, so other modules can update the form with the latest state.
+ form.dispatchEvent(new Event("save-form-state"));
+ // Tell all form fields we are about to submit the form.
+ // notifyFormSubmittedByJavascript(form); // Moodle 4.00+ only
+
+ const formdata = new FormData(form);
+ const data =new URLSearchParams(formdata).toString();
+ //const formdata = new FormData(form);
+ //const data = {};
+ //formdata.forEach((value, key) => (data[key] = value));
+
+ call([{
+ methodname: 'local_treestudyplan_submit_mform',
+ args: {name: self.name, params: self.params, data: data}
+ }])[0].then(()=>{
+ self.$emit("saved",formdata);
+ }).catch(notification.exception);
+
+ }
+ },
+ template: `
+
+
+
+ `,
+ });
+ }
+};
+
diff --git a/amd/src/util/string-helper.js b/amd/src/util/string-helper.js
index 71f99e1..5fce6ad 100644
--- a/amd/src/util/string-helper.js
+++ b/amd/src/util/string-helper.js
@@ -1,4 +1,8 @@
import {get_strings} from 'core/str';
+import {getStrings} from 'core/str';
+
+/* Determine the proper getstrings function to use (MDL4.3+ recommends use of getStrings, which is jquery independent) */
+const getstr_func = (getStrings !== undefined)?getStrings:get_strings;
/**
* Load the translation of strings from a strings object
@@ -15,7 +19,7 @@ export function load_strings(strings){
let component = (parts.length > 1)?parts[1]:'local_treestudyplan';
stringkeys.push({ key: identifier, component: component});
}
- get_strings(stringkeys).then(function(str){
+ getstr_func(stringkeys).then(function(str){
let i = 0;
for(const key in strings[idx]){
strings[idx][key] = str[i];
@@ -44,7 +48,7 @@ export function load_stringkeys(string_keys){
let component = (parts.length > 1)?parts[1]:'local_treestudyplan';
stringkeys.push({ key: identifier, component: component});
}
- get_strings(stringkeys).then(function(strings){
+ getstr_func(stringkeys).then(function(strings){
for(const i in strings) {
const s = strings[i];
const l = string_keys[idx][i];
diff --git a/classes/local/forms/studyplan_editform.php b/classes/local/forms/studyplan_editform.php
new file mode 100644
index 0000000..31f50ea
--- /dev/null
+++ b/classes/local/forms/studyplan_editform.php
@@ -0,0 +1,20 @@
+_form;
+
+ // Activity.
+ $mform->addElement('editor', 'activityeditor',
+ get_string('activityeditor', 'assign'), array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES,
+ 'noclean' => true, 'context' => $this->context, 'subdirs' => true));
+ $mform->addHelpButton('activityeditor', 'activityeditor', 'assign');
+ $mform->setType('activityeditor', PARAM_RAW);
+ }
+}
\ No newline at end of file
diff --git a/version.php b/version.php
index 6efa2c8..062c370 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@ $plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-260
$plugin->version = 2023090701; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
-$plugin->release = "1.0.0";
+$plugin->release = "1.1.0-RC1";
$plugin->maturity = MATURITY_STABLE;
// Supported from Moodle 3.11 to 4.1 (4.2 not yet tested).