This commit is contained in:
PMKuipers 2024-01-26 12:26:01 +01:00
parent 88321ec703
commit 414c900b34
21 changed files with 44 additions and 26 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/primary-nav-tools",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.hide_primary=function(hrefs){let element=document.createElement("style");document.head.appendChild(element);let sheet=element.sheet;("string"==typeof hrefs||hrefs instanceof String)&&(hrefs=[hrefs]);if("object"==typeof hrefs&&Array.isArray(hrefs))for(const ix in hrefs){const href=hrefs[ix];let style=`\n .primary-navigation li.nav-item > a[href*="${href}"] {\n display: none;\n }\n `;sheet.insertRule(style,sheet.cssRules.length),style=`\n #usernavigation a[href*="${href}"] {\n display: none;\n }\n `,sheet.insertRule(style,sheet.cssRules.length)}}})); define("local_treestudyplan/primary-nav-tools",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.hide_primary=function(hrefs){let element=document.createElement("style");document.head.appendChild(element);let sheet=element.sheet;("string"==typeof hrefs||hrefs instanceof String)&&(hrefs=[hrefs]);if("object"==typeof hrefs&&Array.isArray(hrefs))for(const ix in hrefs){const href=hrefs[ix];let style=`\n li > a[href*="${href}"] {\n display: none;\n }\n `;sheet.insertRule(style,sheet.cssRules.length),style=`\n #usernavigation a[href*="${href}"] {\n display: none;\n }\n `,sheet.insertRule(style,sheet.cssRules.length)}}}));
//# sourceMappingURL=primary-nav-tools.min.js.map //# sourceMappingURL=primary-nav-tools.min.js.map

View File

@ -1 +1 @@
{"version":3,"file":"primary-nav-tools.min.js","sources":["../src/primary-nav-tools.js"],"sourcesContent":["/*eslint-env es6*/\n\n/**\n * Hide a primary navigation item by href\n * @param {string|Array} hrefs The link that should be hidden\n */\nexport function hide_primary(hrefs) {\n let element = document.createElement('style');\n document.head.appendChild(element);\n let sheet = element.sheet;\n\n if(typeof hrefs === 'string' || hrefs instanceof String){\n hrefs = [hrefs];\n }\n\n if(typeof hrefs === 'object' && Array.isArray(hrefs)){\n for(const ix in hrefs){\n const href = hrefs[ix];\n let style = `\n .primary-navigation li.nav-item > a[href*=\"${href}\"] {\n display: none;\n }\n `;\n sheet.insertRule(style, sheet.cssRules.length);\n style = `\n #usernavigation a[href*=\"${href}\"] {\n display: none;\n }\n `;\n sheet.insertRule(style, sheet.cssRules.length);\n }\n }\n}\n"],"names":["hrefs","element","document","createElement","head","appendChild","sheet","String","Array","isArray","ix","href","style","insertRule","cssRules","length"],"mappings":"sKAM6BA,WACrBC,QAAUC,SAASC,cAAc,SACrCD,SAASE,KAAKC,YAAYJ,aACtBK,MAAQL,QAAQK,OAEA,iBAAVN,OAAsBA,iBAAiBO,UAC7CP,MAAQ,CAACA,WAGO,iBAAVA,OAAsBQ,MAAMC,QAAQT,WACtC,MAAMU,MAAMV,MAAM,OACZW,KAAOX,MAAMU,QACfE,MAAS,gEACoCD,gFAIjDL,MAAMO,WAAWD,MAAON,MAAMQ,SAASC,QACvCH,MAAS,8CACsBD,gFAI/BL,MAAMO,WAAWD,MAAON,MAAMQ,SAASC"} {"version":3,"file":"primary-nav-tools.min.js","sources":["../src/primary-nav-tools.js"],"sourcesContent":["/*eslint-env es6*/\n\n/**\n * Hide a primary navigation item by href\n * @param {string|Array} hrefs The link that should be hidden\n */\nexport function hide_primary(hrefs) {\n let element = document.createElement('style');\n document.head.appendChild(element);\n let sheet = element.sheet;\n\n if(typeof hrefs === 'string' || hrefs instanceof String){\n hrefs = [hrefs];\n }\n\n if(typeof hrefs === 'object' && Array.isArray(hrefs)){\n for(const ix in hrefs){\n const href = hrefs[ix];\n let style = `\n li > a[href*=\"${href}\"] {\n display: none;\n }\n `;\n sheet.insertRule(style, sheet.cssRules.length);\n style = `\n #usernavigation a[href*=\"${href}\"] {\n display: none;\n }\n `;\n sheet.insertRule(style, sheet.cssRules.length);\n }\n }\n}\n"],"names":["hrefs","element","document","createElement","head","appendChild","sheet","String","Array","isArray","ix","href","style","insertRule","cssRules","length"],"mappings":"sKAM6BA,WACrBC,QAAUC,SAASC,cAAc,SACrCD,SAASE,KAAKC,YAAYJ,aACtBK,MAAQL,QAAQK,OAEA,iBAAVN,OAAsBA,iBAAiBO,UAC7CP,MAAQ,CAACA,WAGO,iBAAVA,OAAsBQ,MAAMC,QAAQT,WACtC,MAAMU,MAAMV,MAAM,OACZW,KAAOX,MAAMU,QACfE,MAAS,mCACOD,gFAIpBL,MAAMO,WAAWD,MAAON,MAAMQ,SAASC,QACvCH,MAAS,8CACsBD,gFAI/BL,MAAMO,WAAWD,MAAON,MAAMQ,SAASC"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("local_treestudyplan/util/mform-helper",["exports","core/ajax","core/fragment","core/templates","core/notification","./string-helper","./debugger"],(function(_exports,_ajax,_fragment,_templates,_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(Vue){let 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},title:{type:String,default:""},variant:{type:String,default:"primary"},type:{type:String,default:"link"}},data:()=>({content:"",loading:!0,uuid:void 0!==crypto.randomUUID?crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,(c=>(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16))),text:strings,submitok:!1,observer:null,inputs:[]}),computed:{},methods:{openForm(){this.$refs.editormodal.show()},onShown(){const self=this;debug.info(`Loading form "${self.name}" with params`,self.params),self.loading=!1,(0,_ajax.call)([{methodname:"local_treestudyplan_get_mform",args:{formname:self.name,params:JSON.stringify(self.params)}}])[0].then((data=>{const html=data.html;self.loading=!1;const js=(0,_fragment.processCollectedJavascript)(data.javascript);(0,_templates.replaceNodeContents)(self.$refs.content,html,js),self.initListenChanges()})).catch(_notification.default.exception)},onSave(){const self=this;let form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));const formdata=new FormData(form),data=new URLSearchParams(formdata).toString();this.checkSave()&&(0,_ajax.call)([{methodname:"local_treestudyplan_submit_mform",args:{formname:self.name,params:JSON.stringify(self.params),formdata:data}}])[0].then((response=>{const updatedplan=JSON.parse(response.data);self.$emit("saved",updatedplan,formdata)})).catch(_notification.default.exception)},checkSave(){let canSave=!0;return this.inputs.forEach((el=>{el.classList.contains("is-invalid")&&(canSave=!1)}),this),this.submitok=canSave,canSave},initListenChanges(){const content=this.$refs.content;this.inputs=content.querySelectorAll("input.form-control"),this.checkSave(),this.observer&&this.observer.disconnect(),this.observer=new MutationObserver((mutationList=>{for(const mix in mutationList){const mutation=mutationList[mix];"attributes"===mutation.type&&"class"===mutation.attributeName&&this.checkSave()}})),this.inputs.forEach((el=>{this.observer.observe(el,{attributes:!0})}),this)}},unmount(){this.observer&&this.observer.disconnect()},template:'\n <span class=\'mform-container\'>\n <b-button :variant="variant" v-if=\'type == "button"\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></b-button>\n <a variant="variant" v-else href=\'#\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></a>\n <b-modal\n ref="editormodal"\n scrollable\n centered\n size="xl"\n id="\'modal-\'+uuid"\n @shown="onShown"\n @ok="onSave"\n :ok-disabled="!submitok"\n :title="title"\n :ok-title="text.save$core"\n ><div :class="\'s-mform-content\'" ref="content"\n ><div class="d-flex justify-content-center mb-3"\n ><b-spinner variant="primary"></b-spinner\n ></div\n ></div\n ></b-modal>\n </span>\n '})}};return _exports.default=_default,_exports.default})); define("local_treestudyplan/util/mform-helper",["exports","core/ajax","core/fragment","core/templates","core/notification","./string-helper","./debugger"],(function(_exports,_ajax,_fragment,_templates,_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(Vue){let 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},title:{type:String,default:""},variant:{type:String,default:"primary"},type:{type:String,default:"link"}},data:()=>({content:"",loading:!0,uuid:void 0!==crypto.randomUUID?crypto.randomUUID():"10000000-1000-4000-8000-100000000000".replace(/[018]/g,(c=>(c^crypto.getRandomValues(new Uint8Array(1))[0]&15>>c/4).toString(16))),text:strings,submitok:!1,observer:null,inputs:[]}),computed:{},methods:{openForm(){this.$refs.editormodal.show()},onShown(){const self=this;debug.info(`Loading form "${self.name}" with params`,self.params),self.loading=!1,(0,_ajax.call)([{methodname:"local_treestudyplan_get_mform",args:{formname:self.name,params:JSON.stringify(self.params)}}])[0].then((data=>{const html=data.html;self.loading=!1;const js=(0,_fragment.processCollectedJavascript)(data.javascript);(0,_templates.replaceNodeContents)(self.$refs.content,html,js),self.initListenChanges()})).catch(_notification.default.exception)},onSave(){const self=this;let form=this.$refs.content.getElementsByTagName("form")[0];form.dispatchEvent(new Event("save-form-state"));const formdata=new FormData(form),data=new URLSearchParams(formdata).toString();this.checkSave()&&(0,_ajax.call)([{methodname:"local_treestudyplan_submit_mform",args:{formname:self.name,params:JSON.stringify(self.params),formdata:data}}])[0].then((response=>{const updatedplan=JSON.parse(response.data);self.$emit("saved",updatedplan,formdata)})).catch(_notification.default.exception)},checkSave(){let canSave=!0;return this.inputs.forEach((el=>{el.focus(),el.blur(),el.classList.contains("is-invalid")&&(canSave=!1)}),this),this.submitok=canSave,canSave},initListenChanges(){const content=this.$refs.content;this.inputs=content.querySelectorAll("input.form-control"),setTimeout(this.checkSave,100),this.observer&&this.observer.disconnect(),this.observer=new MutationObserver((mutationList=>{for(const mix in mutationList){const mutation=mutationList[mix];"attributes"===mutation.type&&"class"===mutation.attributeName&&this.checkSave()}})),this.inputs.forEach((el=>{this.observer.observe(el,{attributes:!0})}),this)}},unmount(){this.observer&&this.observer.disconnect()},template:'\n <span class=\'mform-container\'>\n <b-button :variant="variant" v-if=\'type == "button"\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></b-button>\n <a variant="variant" v-else href=\'#\' @click.prevent=\'openForm\'\n ><slot><i class=\'fa fa-gear\'></i></slot></a>\n <b-modal\n ref="editormodal"\n scrollable\n centered\n size="xl"\n id="\'modal-\'+uuid"\n @shown="onShown"\n @ok="onSave"\n :ok-disabled="!submitok"\n :title="title"\n :ok-title="text.save$core"\n ><div :class="\'s-mform-content\'" ref="content"\n ><div class="d-flex justify-content-center mb-3"\n ><b-spinner variant="primary"></b-spinner\n ></div\n ></div\n ></b-modal>\n </span>\n '})}};return _exports.default=_default,_exports.default}));
//# sourceMappingURL=mform-helper.min.js.map //# sourceMappingURL=mform-helper.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ export function hide_primary(hrefs) {
for(const ix in hrefs){ for(const ix in hrefs){
const href = hrefs[ix]; const href = hrefs[ix];
let style = ` let style = `
.primary-navigation li.nav-item > a[href*="${href}"] { li > a[href*="${href}"] {
display: none; display: none;
} }
`; `;

View File

@ -3503,8 +3503,8 @@ export default {
template: ` template: `
<table class="t-item-course-competency-list"> <table class="t-item-course-competency-list">
<tr v-if="value.competencies.length == 0"> <tr v-if="value.competencies.length == 0">
<td colspan='2'>{{text.competencies_not_configured}}! <td colspan='2'>{{text.competency_not_configured}}
<br><a :href="'/admin/tool/lp/coursecompetencies.php?courseid='+item.course.id" target='_blank'>{{text.configure_competencies}}</a> <br><a :href="'/admin/tool/lp/coursecompetencies.php?courseid='+item.course.id" target='_blank'>{{text.configure_competency}}</a>
</td> </td>
</tr> </tr>
<template v-else> <template v-else>

View File

@ -123,6 +123,8 @@ export default {
checkSave() { checkSave() {
let canSave = true; let canSave = true;
this.inputs.forEach(el => { this.inputs.forEach(el => {
el.focus();
el.blur();
if (el.classList.contains("is-invalid")) { if (el.classList.contains("is-invalid")) {
canSave = false; canSave = false;
} }
@ -133,8 +135,9 @@ export default {
initListenChanges() { initListenChanges() {
const content = this.$refs["content"]; const content = this.$refs["content"];
this.inputs = content.querySelectorAll("input.form-control"); this.inputs = content.querySelectorAll("input.form-control");
// Check if save needs to be blocked immediately.
this.checkSave(); // Check if save needs to be blocked immediately. (delay call by a few ms)
setTimeout(this.checkSave, 100);
// Disconnect any existing observer. // Disconnect any existing observer.
if(this.observer) { if(this.observer) {
@ -154,6 +157,8 @@ export default {
this.inputs.forEach(el => { this.inputs.forEach(el => {
this.observer.observe(el,{ attributes: true }); this.observer.observe(el,{ attributes: true });
},this); },this);
}, },
}, },

View File

@ -230,7 +230,7 @@ class badgeinfo {
} }
protected function badge_completion_structure($value) { protected static function badge_completion_structure($value) {
return new \external_single_structure([ return new \external_single_structure([
"types" => new \external_multiple_structure(new \external_single_structure([ "types" => new \external_multiple_structure(new \external_single_structure([
'criteria' => new \external_multiple_structure(new \external_single_structure([ 'criteria' => new \external_multiple_structure(new \external_single_structure([

View File

@ -225,6 +225,7 @@ class coursecompetencyinfo {
$count = 0; $count = 0;
$nproficient = 0; $nproficient = 0;
$cis = [];
foreach($coursecompetencies as $c) { foreach($coursecompetencies as $c) {
$ci = $this->competencyinfo_model($c); $ci = $this->competencyinfo_model($c);
if(!empty($studentlist)){ if(!empty($studentlist)){

View File

@ -1,16 +1,18 @@
<?php <?php
namespace local_treestudyplan\form; namespace local_treestudyplan\form;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/repository/lib.php');
use local_treestudyplan\aggregator; use local_treestudyplan\aggregator;
use local_treestudyplan\studyplan; use local_treestudyplan\studyplan;
use local_treestudyplan\studyplanservice; use local_treestudyplan\studyplanservice;
use local_treestudyplan\courseservice; use local_treestudyplan\courseservice;
use local_treestudyplan\form\text_integer; use local_treestudyplan\form\text_integer;
use local_treestudyplan\local\helpers\webservicehelper;
use moodle_exception; use moodle_exception;
use stdClass; use stdClass;
/** /**
* Moodleform class for the studyplan editor. A Moodleform is used here to facilitate a rich editor * Moodleform class for the studyplan editor. A Moodleform is used here to facilitate a rich editor
* in the studyplan description * in the studyplan description
@ -53,7 +55,7 @@ class studyplan_editform extends formbase {
'areamaxbytes' => 10485760, 'areamaxbytes' => 10485760,
'maxfiles' => 1, // Just one file 'maxfiles' => 1, // Just one file
'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'], 'accepted_types' => ['.jpg', '.png', '.jpeg', '.svg', '.svgz'],
'return_types' => FILE_INTERNAL | FILE_EXTERNAL, 'return_types' => \FILE_INTERNAL | \FILE_EXTERNAL,
]; ];
return $customdata; return $customdata;
} }

View File

@ -120,7 +120,9 @@ class studyplanservice extends \external_api {
if (isset($id) && $id > 0) { if (isset($id) && $id > 0) {
$studyplan = studyplan::find_by_id($id); $studyplan = studyplan::find_by_id($id);
webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context()); webservicehelper::require_capabilities([self::CAP_EDIT, self::CAP_VIEW], $studyplan->context());
return $studyplan->editor_model(); $model = $studyplan->editor_model();
debug::dump($model);
return $model;
} else { } else {
return null; return null;
} }

View File

@ -46,7 +46,7 @@ class utilityservice extends \external_api {
const CAP_VIEW = "local/treestudyplan:viewuserreports"; const CAP_VIEW = "local/treestudyplan:viewuserreports";
protected function load_mform($formname, $params, $ajaxformdata = null) { protected static function load_mform($formname, $params, $ajaxformdata = null) {
global $CFG; global $CFG;
/* We don't need to load the form php file (class autoloading will handle that) /* We don't need to load the form php file (class autoloading will handle that)
but we do need to check it's existence to give a nice developer warning but we do need to check it's existence to give a nice developer warning

View File

@ -107,7 +107,11 @@ print $OUTPUT->header();
</div> </div>
<div v-cloak> <div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'> <div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'>
<b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext'> <b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext'
:class="(!(usedcontexts.length))?'text-primary':''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
:class="'text-primary'">
<span><?php t("loading",null,"core"); ?>...</span></b-form-select-option>
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" <b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''" :class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path" ><span v-for="(p, i) in ctx.category.path"

View File

@ -22,14 +22,14 @@
defined('MOODLE_INTERNAL') || die(); defined('MOODLE_INTERNAL') || die();
$plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494). $plugin->component = 'local_treestudyplan'; // Recommended since 2.0.2 (MDL-26035). Required since 3.0 (MDL-48494).
$plugin->version = 2023121306; // YYYYMMDDHH (year, month, day, iteration). $plugin->version = 2024011900; // YYYYMMDDHH (year, month, day, iteration).
$plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11). $plugin->requires = 2021051700; // YYYYMMDDHH (This is the release version for Moodle 3.11).
$plugin->release = "1.1.0"; $plugin->release = "1.1.0";
$plugin->maturity = MATURITY_BETA; /*MATURITY_STABLE;*/ $plugin->maturity = MATURITY_BETA; /*MATURITY_STABLE;*/
// Supported from Moodle 3.11 to 4.1 (4.2 not yet tested). // Supported from Moodle 3.11 to 4.3.
$plugin->supported = [ 311, 401 ]; $plugin->supported = [ 311, 403];
$plugin->dependencies = [ $plugin->dependencies = [
'theme_boost' => 2019052000, 'theme_boost' => 2019052000,

View File

@ -104,7 +104,11 @@ print $OUTPUT->header();
</div> </div>
<div v-cloak> <div v-cloak>
<div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'> <div v-if='!activestudyplan && usedcontexts' class='ml-3 mb-3 s-context-selector'>
<b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext'> <b-form-select text='<?php print($contextname);?>' :value="contextid" @change='switchContext'
:class="(!(usedcontexts.length))?'text-primary':''">
<b-form-select-option v-if='!(usedcontexts.length)' :value="contextid"
:class="'text-primary'">
<span><?php t("loading",null,"core"); ?>...</span></b-form-select-option>
<b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id" <b-form-select-option v-for='ctx in usedcontexts' :key='ctx.id' :value="ctx.context_id"
:class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''" :class="(ctx.studyplancount > 0) ? 'font-weight-bold' : ''"
><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span> ><span v-for="(p, i) in ctx.category.path"><span v-if="i>0"> / </span>{{ p }}</span>