Updated mform helpers. some work on full page support
This commit is contained in:
		
							parent
							
								
									93e174967f
								
							
						
					
					
						commit
						702435566d
					
				
					 31 changed files with 807 additions and 364 deletions
				
			
		
							
								
								
									
										2
									
								
								amd/build/page-edit-plan.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								amd/build/page-edit-plan.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								amd/build/studyplan-editor-components.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								amd/build/studyplan-editor-components.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								amd/build/studyplan-processor.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								amd/build/studyplan-processor.min.js
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,3 +1,3 @@
 | 
				
			||||||
define("local_treestudyplan/studyplan-processor",["exports"],(function(_exports){function ProcessStudyplan(studyplan){var connections={};for(var ip in studyplan.pages){var page=studyplan.pages[ip];for(var il in page.studylines){var line=page.studylines[il];for(var is in line.slots){var slot=line.slots[is];if(void 0!==slot.courses)for(var ic in slot.courses){var itm=slot.courses[ic];for(var idx in itm.connections.in){var conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(var _idx in itm.connections.out){var _conn=itm.connections.out[_idx];_conn.id in connections?itm.connections[_idx]=connections[_conn.id]:connections[_conn.id]=_conn}}if(void 0!==slot.filters)for(var ix in slot.filters){var _itm=slot.filters[ix];for(var _idx2 in _itm.connections.in){var _conn2=_itm.connections.in[_idx2];_conn2.id in connections?_itm.connections[_idx2]=connections[_conn2.id]:connections[_conn2.id]=_conn2}for(var _idx3 in _itm.connections.out){var _conn3=_itm.connections.out[_idx3];_conn3.id in connections?_itm.connections[_idx3]=connections[_conn3.id]:connections[_conn3.id]=_conn3}}}}}return studyplan}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ProcessStudyplan=ProcessStudyplan,_exports.ProcessStudyplans=function(studyplans){for(var isx in studyplans){ProcessStudyplan(studyplans[isx])}return studyplans},_exports.objCopy=function(target,source,fields){for(var ix in fields){var field=fields[ix];target[field]=source[field]}},_exports.transportItem=function(target,source,identifier,param){param||(param="value");var item,itemindex;for(var ix in source)if(source[ix][param]==identifier){item=source[ix],itemindex=ix;break}item&&(target.push(item),source.splice(itemindex,1))}}));
 | 
					define("local_treestudyplan/studyplan-processor",["exports"],(function(_exports){function ProcessStudyplan(studyplan){for(var ip in studyplan.pages){ProcessStudyplanPage(studyplan.pages[ip])}return studyplan}function ProcessStudyplanPage(page){var connections={};for(var il in page.studylines){var line=page.studylines[il];for(var is in line.slots){var slot=line.slots[is];if(void 0!==slot.courses)for(var ic in slot.courses){var itm=slot.courses[ic];for(var idx in itm.connections.in){var conn=itm.connections.in[idx];conn.id in connections?itm.connections[idx]=connections[conn.id]:connections[conn.id]=conn}for(var _idx in itm.connections.out){var _conn=itm.connections.out[_idx];_conn.id in connections?itm.connections[_idx]=connections[_conn.id]:connections[_conn.id]=_conn}}if(void 0!==slot.filters)for(var ix in slot.filters){var _itm=slot.filters[ix];for(var _idx2 in _itm.connections.in){var _conn2=_itm.connections.in[_idx2];_conn2.id in connections?_itm.connections[_idx2]=connections[_conn2.id]:connections[_conn2.id]=_conn2}for(var _idx3 in _itm.connections.out){var _conn3=_itm.connections.out[_idx3];_conn3.id in connections?_itm.connections[_idx3]=connections[_conn3.id]:connections[_conn3.id]=_conn3}}}}return page}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.ProcessStudyplan=ProcessStudyplan,_exports.ProcessStudyplanPage=ProcessStudyplanPage,_exports.ProcessStudyplans=function(studyplans){for(var isx in studyplans){ProcessStudyplan(studyplans[isx])}return studyplans},_exports.objCopy=function(target,source,fields){for(var ix in fields){var field=fields[ix];target[field]=source[field]}},_exports.transportItem=function(target,source,identifier,param){param||(param="value");var item,itemindex;for(var ix in source)if(source[ix][param]==identifier){item=source[ix],itemindex=ix;break}item&&(target.push(item),source.splice(itemindex,1))}}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//# sourceMappingURL=studyplan-processor.min.js.map
 | 
					//# sourceMappingURL=studyplan-processor.min.js.map
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								amd/build/treestudyplan-components.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								amd/build/treestudyplan-components.min.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								amd/build/util/formfields.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								amd/build/util/formfields.min.js
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,3 +1,3 @@
 | 
				
			||||||
define("local_treestudyplan/util/formfields",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.text_integer=function(id){var unsigned=arguments.length>1&&void 0!==arguments[1]&&arguments[1],element=document.getElementById(id);element&&element.addEventListener("input",(function(){var val=element.value;if(""!=val)return!(isNaN(val)&&(unsigned||"-"!=val)||unsigned&&val<0)||(element.value=unsigned?val.replace(/[^0-9]/g,""):val.replace(/[^0-9-]/g,"").replace(/(.{1})(-)/g,"$1"),!1)}))}}));
 | 
					define("local_treestudyplan/util/formfields",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.text_integer=function(id){let unsigned=arguments.length>1&&void 0!==arguments[1]&&arguments[1],nonzero=arguments.length>2&&void 0!==arguments[2]&&arguments[2];const element=document.getElementById(id);element&&element.addEventListener("input",(()=>{var val=element.value;if(""!=val)return!(isNaN(val)&&(unsigned||"-"!=val)||unsigned&&val<0||nonzero&&0==val)||(unsigned?(element.value=val.replace(/[^0-9]/g,""),nonzero&&0==val&&(element.value="")):element.value=val.replace(/[^0-9-]/g,"").replace(/(.{1})(-)/g,"$1"),!1)}))}}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//# sourceMappingURL=formfields.min.js.map
 | 
					//# sourceMappingURL=formfields.min.js.map
 | 
				
			||||||
| 
						 | 
					@ -1 +1 @@
 | 
				
			||||||
{"version":3,"file":"formfields.min.js","sources":["../../src/util/formfields.js"],"sourcesContent":["/**\n * convert a text field into an integer only text field\n * @param {string} id The Id of the form field\n * @param {bool} unsigned Allow only unsigned values\n */\nexport function text_integer(id,unsigned=false){\n    const element = document.getElementById(id);\n\n    if (element) {\n        element.addEventListener(\"input\",() => {\n            var val = element.value;\n            if (val != '') {\n                if ((isNaN(val) && !(!unsigned && val == '-')) || (unsigned && val < 0)) {\n                    // Set input value empty\n                    if (unsigned) {\n                        element.value = val.replace(/[^0-9]/g,'');\n                    } else {\n                        element.value = val.replace(/[^0-9-]/g,'').replace(/(.{1})(-)/g,'$1');\n                    }\n                    return false;\n                } else {\n                    return true;\n                }\n            }\n        });\n    }\n}"],"names":["id","unsigned","element","document","getElementById","addEventListener","val","value","isNaN","replace"],"mappings":"oKAK6BA,QAAGC,iEACtBC,QAAUC,SAASC,eAAeJ,IAEpCE,SACAA,QAAQG,iBAAiB,SAAQ,eACzBC,IAAMJ,QAAQK,SACP,IAAPD,YACKE,MAAMF,OAAWL,UAAmB,KAAPK,MAAiBL,UAAYK,IAAM,KAG7DJ,QAAQK,MADRN,SACgBK,IAAIG,QAAQ,UAAU,IAEtBH,IAAIG,QAAQ,WAAW,IAAIA,QAAQ,aAAa,OAE7D"}
 | 
					{"version":3,"file":"formfields.min.js","sources":["../../src/util/formfields.js"],"sourcesContent":["/**\n * convert a text field into an integer only text field\n * @param {string} id The Id of the form field\n * @param {bool} unsigned Allow only unsigned values\n * @param {bool} nonzero Do not allow zero values\n */\nexport function text_integer(id,unsigned=false,nonzero=false){\n    const element = document.getElementById(id);\n\n    if (element) {\n        element.addEventListener(\"input\",() => {\n            var val = element.value;\n            if (val != '') {\n                if ((isNaN(val) && !(!unsigned && val == '-')) || (unsigned && val < 0) || (nonzero && val == 0)) {\n                    // Set input value empty\n                    if (unsigned) {\n                        element.value = val.replace(/[^0-9]/g,'');\n                        if (nonzero && val == 0) {\n                            element.value = '';\n                        }\n                    } else {\n                        element.value = val.replace(/[^0-9-]/g,'').replace(/(.{1})(-)/g,'$1');\n                    }\n                    return false;\n                } else {\n                    return true;\n                }\n            }\n        });\n    }\n}"],"names":["id","unsigned","nonzero","element","document","getElementById","addEventListener","val","value","isNaN","replace"],"mappings":"oKAM6BA,QAAGC,iEAAeC,sEACrCC,QAAUC,SAASC,eAAeL,IAEpCG,SACAA,QAAQG,iBAAiB,SAAQ,SACzBC,IAAMJ,QAAQK,SACP,IAAPD,YACKE,MAAMF,OAAWN,UAAmB,KAAPM,MAAiBN,UAAYM,IAAM,GAAOL,SAAkB,GAAPK,OAE/EN,UACAE,QAAQK,MAAQD,IAAIG,QAAQ,UAAU,IAClCR,SAAkB,GAAPK,MACXJ,QAAQK,MAAQ,KAGpBL,QAAQK,MAAQD,IAAIG,QAAQ,WAAW,IAAIA,QAAQ,aAAa,OAE7D"}
 | 
				
			||||||
							
								
								
									
										2
									
								
								amd/build/util/mform-helper.min.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								amd/build/util/mform-helper.min.js
									
									
									
									
										vendored
									
									
								
							| 
						 | 
					@ -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: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},title:{type:String,default:""},variant:{type:String,default:"primary"},type:{type:String,default:"link"}},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:{formname:self.name,params:JSON.stringify(self.params)}}])[0].then((function(data){var html=data.html;self.loading=!1;var js=(0,_fragment.processCollectedJavascript)(data.javascript);(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_mform",args:{formname:self.name,params:JSON.stringify(self.params),formdata:data}}])[0].then((function(response){var updatedplan=JSON.parse(response.data);self.$emit("saved",updatedplan,formdata)})).catch(_notification.default.exception)}},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                    :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.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}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//# sourceMappingURL=mform-helper.min.js.map
 | 
					//# sourceMappingURL=mform-helper.min.js.map
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
					@ -228,7 +228,7 @@ export function init(contextid,categoryid,options) {
 | 
				
			||||||
                    methodname: 'local_treestudyplan_get_studyplan_map',
 | 
					                    methodname: 'local_treestudyplan_get_studyplan_map',
 | 
				
			||||||
                    args: { id: studyplan.id}
 | 
					                    args: { id: studyplan.id}
 | 
				
			||||||
                }])[0].done(function(response){
 | 
					                }])[0].done(function(response){
 | 
				
			||||||
                    app.activestudyplan = ProcessStudyplan(response,true);
 | 
					                    app.activestudyplan = ProcessStudyplan(response);
 | 
				
			||||||
                    debug.info('studyplan processed');
 | 
					                    debug.info('studyplan processed');
 | 
				
			||||||
                    app.loadingstudyplan = false;
 | 
					                    app.loadingstudyplan = false;
 | 
				
			||||||
                    window.location.hash = app.activestudyplan.id;
 | 
					                    window.location.hash = app.activestudyplan.id;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ import {format_date, add_days, datespaninfo } from './util/date-helper';
 | 
				
			||||||
import {objCopy,transportItem} from './studyplan-processor';
 | 
					import {objCopy,transportItem} from './studyplan-processor';
 | 
				
			||||||
import Debugger from './util/debugger';
 | 
					import Debugger from './util/debugger';
 | 
				
			||||||
import {download,upload} from './downloader';
 | 
					import {download,upload} from './downloader';
 | 
				
			||||||
import {ProcessStudyplan} from './studyplan-processor';
 | 
					import {ProcessStudyplan, ProcessStudyplanPage} from './studyplan-processor';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import TSComponents from './treestudyplan-components';
 | 
					import TSComponents from './treestudyplan-components';
 | 
				
			||||||
import mFormComponents from "./util/mform-helper";
 | 
					import mFormComponents from "./util/mform-helper";
 | 
				
			||||||
| 
						 | 
					@ -570,6 +570,71 @@ export default {
 | 
				
			||||||
            `
 | 
					            `
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					         * T-STUDYPLAN-EDIT
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        Vue.component('t-studyplan-page-edit', {
 | 
				
			||||||
 | 
					            props: {
 | 
				
			||||||
 | 
					                'value' :{
 | 
				
			||||||
 | 
					                    type: Object,
 | 
				
			||||||
 | 
					                    default(){ return null;},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                'mode' :{
 | 
				
			||||||
 | 
					                    type: String,
 | 
				
			||||||
 | 
					                    default() { return "edit";},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                'type' :{
 | 
				
			||||||
 | 
					                    type: String,
 | 
				
			||||||
 | 
					                    default() { return "link";},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                'variant' : {
 | 
				
			||||||
 | 
					                    type: String,
 | 
				
			||||||
 | 
					                    default() { return "";},
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                'studyplan': {
 | 
				
			||||||
 | 
					                    type: Object,
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            data() {
 | 
				
			||||||
 | 
					                return {
 | 
				
			||||||
 | 
					                    text: strings.studyplan_edit,
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            computed: {
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            methods: {
 | 
				
			||||||
 | 
					                planSaved(updatedpage){
 | 
				
			||||||
 | 
					                    const self = this;
 | 
				
			||||||
 | 
					                    debug.info("Got new page data",updatedpage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if(self.mode == 'create'){
 | 
				
			||||||
 | 
					                        // Inform parent of the details of the newly created plan
 | 
				
			||||||
 | 
					                        self.$emit("created",updatedpage);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else {
 | 
				
			||||||
 | 
					                        const page = ProcessStudyplanPage(updatedpage);
 | 
				
			||||||
 | 
					                        debug.info('studyplan page processed');
 | 
				
			||||||
 | 
					                        self.$emit('input',page);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            ,
 | 
				
			||||||
 | 
					            template:
 | 
				
			||||||
 | 
					            `
 | 
				
			||||||
 | 
					            <span class='s-studyplan-page-edit'>
 | 
				
			||||||
 | 
					                <mform 
 | 
				
			||||||
 | 
					                    name="studyplanpage_editform" 
 | 
				
			||||||
 | 
					                    :params="{page_id: value.id, studyplan_id: studyplan.id, mode: mode }" 
 | 
				
			||||||
 | 
					                    @saved="planSaved"
 | 
				
			||||||
 | 
					                    :variant="variant"
 | 
				
			||||||
 | 
					                    :type="type"
 | 
				
			||||||
 | 
					                    :title="(mode == 'create')?text.studyplan_add:text.studyplan_edit"
 | 
				
			||||||
 | 
					                    ><slot><i class='fa fa-gear'></i></slot></mform>
 | 
				
			||||||
 | 
					            </span>
 | 
				
			||||||
 | 
					            `
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
        * T-STUDYPLAN-ASSOCIATE
 | 
					        * T-STUDYPLAN-ASSOCIATE
 | 
				
			||||||
        */
 | 
					        */
 | 
				
			||||||
| 
						 | 
					@ -1032,6 +1097,7 @@ export default {
 | 
				
			||||||
            `
 | 
					            `
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TAG: Start studyplan component
 | 
				
			||||||
        /*
 | 
					        /*
 | 
				
			||||||
        * T-STUDYPLAN
 | 
					        * T-STUDYPLAN
 | 
				
			||||||
        */
 | 
					        */
 | 
				
			||||||
| 
						 | 
					@ -1057,6 +1123,11 @@ export default {
 | 
				
			||||||
                            'shortname': '',
 | 
					                            'shortname': '',
 | 
				
			||||||
                            'color': '#DDDDDD',
 | 
					                            'color': '#DDDDDD',
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
 | 
					                        page: {
 | 
				
			||||||
 | 
					                            'id': -1,
 | 
				
			||||||
 | 
					                            'name' : '',
 | 
				
			||||||
 | 
					                            'shortname' : ''
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    edit: {
 | 
					                    edit: {
 | 
				
			||||||
                        toolbox_shown: false,
 | 
					                        toolbox_shown: false,
 | 
				
			||||||
| 
						 | 
					@ -1091,14 +1162,14 @@ export default {
 | 
				
			||||||
                    text: strings.studyplan_text,
 | 
					                    text: strings.studyplan_text,
 | 
				
			||||||
                    cache: {
 | 
					                    cache: {
 | 
				
			||||||
                        linelayers: {},
 | 
					                        linelayers: {},
 | 
				
			||||||
                    }
 | 
					                    },
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            created() {
 | 
					            created() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            mounted() {
 | 
					            mounted() {
 | 
				
			||||||
                if(this.page.studylines.length == 0){
 | 
					                if(this.value.pages[0].studylines.length == 0){
 | 
				
			||||||
                    // start in editmode if studylines are empty
 | 
					                    // start in editmode if studylines are empty
 | 
				
			||||||
                    this.edit.studyline.editmode = true;
 | 
					                    this.edit.studyline.editmode = true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -1109,23 +1180,20 @@ export default {
 | 
				
			||||||
                ItemEventBus.$emit('redrawLines');
 | 
					                ItemEventBus.$emit('redrawLines');
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            computed: {
 | 
					            computed: {
 | 
				
			||||||
                columns() {
 | 
					                
 | 
				
			||||||
                    return 1+ (this.page.periods * 2);
 | 
					            },
 | 
				
			||||||
 | 
					            methods: {
 | 
				
			||||||
 | 
					                columns(page) {
 | 
				
			||||||
 | 
					                    return 1+ (page.periods * 2);
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                columns_stylerule() {
 | 
					                columns_stylerule(page) {
 | 
				
			||||||
                    // Uses css variables, so width for slots and filters can be configured in css
 | 
					                    // Uses css variables, so width for slots and filters can be configured in css
 | 
				
			||||||
                    let s = "grid-template-columns: var(--studyplan-filter-width)"; // use css variable here
 | 
					                    let s = "grid-template-columns: var(--studyplan-filter-width)"; // use css variable here
 | 
				
			||||||
                    for(let i=0; i<this.page.periods;i++){
 | 
					                    for(let i=0; i<page.periods;i++){
 | 
				
			||||||
                        s+= " var(--studyplan-course-width) var(--studyplan-filter-width)";
 | 
					                        s+= " var(--studyplan-course-width) var(--studyplan-filter-width)";
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    return s+";";
 | 
					                    return s+";";
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                page(){
 | 
					 | 
				
			||||||
                    // FIXME: Temporary hack until real page management is implemented
 | 
					 | 
				
			||||||
                    return this.value.pages[0];
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            methods: {
 | 
					 | 
				
			||||||
                trashbin_accepts(type){
 | 
					                trashbin_accepts(type){
 | 
				
			||||||
                    if(type.item){
 | 
					                    if(type.item){
 | 
				
			||||||
                        return true;
 | 
					                        return true;
 | 
				
			||||||
| 
						 | 
					@ -1133,7 +1201,7 @@ export default {
 | 
				
			||||||
                        return false;
 | 
					                        return false;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                countLineLayers(line){
 | 
					                countLineLayers(line,page){
 | 
				
			||||||
                    // For some optimization, we cache the value of this calculation for about a second
 | 
					                    // For some optimization, we cache the value of this calculation for about a second
 | 
				
			||||||
                    // Would be a lot nicer if we could use a computed property for this.....
 | 
					                    // Would be a lot nicer if we could use a computed property for this.....
 | 
				
			||||||
                    if( this.cache.linelayers[line.id]
 | 
					                    if( this.cache.linelayers[line.id]
 | 
				
			||||||
| 
						 | 
					@ -1144,7 +1212,7 @@ export default {
 | 
				
			||||||
                    else
 | 
					                    else
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        let maxLayer = -1;
 | 
					                        let maxLayer = -1;
 | 
				
			||||||
                        for(let i = 0; i <= this.page.periods; i++){
 | 
					                        for(let i = 0; i <= page.periods; i++){
 | 
				
			||||||
                            if(line.slots[i]){
 | 
					                            if(line.slots[i]){
 | 
				
			||||||
                                const slot = line.slots[i];
 | 
					                                const slot = line.slots[i];
 | 
				
			||||||
                                // Determine the amount of used layers in a studyline slit
 | 
					                                // Determine the amount of used layers in a studyline slit
 | 
				
			||||||
| 
						 | 
					@ -1303,11 +1371,11 @@ export default {
 | 
				
			||||||
                    }).fail(notification.exception);
 | 
					                    }).fail(notification.exception);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                showslot(line,index, layeridx, type){
 | 
					                showslot(page,line,index, layeridx, type){
 | 
				
			||||||
                    // check if the slot should be hidden because a previous slot has an item with a span
 | 
					                    // check if the slot should be hidden because a previous slot has an item with a span
 | 
				
			||||||
                    // so big that it hides this slot
 | 
					                    // so big that it hides this slot
 | 
				
			||||||
                    const forGradable = (type == 'gradable')?true:false;
 | 
					                    const forGradable = (type == 'gradable')?true:false;
 | 
				
			||||||
                    const periods = this.page.periods;
 | 
					                    const periods = page.periods;
 | 
				
			||||||
                    let show = true;
 | 
					                    let show = true;
 | 
				
			||||||
                    for(let i = 0; i < periods; i++){
 | 
					                    for(let i = 0; i < periods; i++){
 | 
				
			||||||
                        if(line.slots[index-i] && line.slots[index-i].courses){
 | 
					                        if(line.slots[index-i] && line.slots[index-i].courses){
 | 
				
			||||||
| 
						 | 
					@ -1355,6 +1423,9 @@ export default {
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                toolbox_switched(event){
 | 
					                toolbox_switched(event){
 | 
				
			||||||
                    this.$emit('toggletoolbox',event);
 | 
					                    this.$emit('toggletoolbox',event);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                pagecreated(page) {
 | 
				
			||||||
 | 
					                    this.value.pages.push(page);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            ,
 | 
					            ,
 | 
				
			||||||
| 
						 | 
					@ -1390,193 +1461,219 @@ export default {
 | 
				
			||||||
                                ><i class='fa fa-gear'></i> {{text.edit$core}}</t-studyplan-edit>
 | 
					                                ><i class='fa fa-gear'></i> {{text.edit$core}}</t-studyplan-edit>
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
                        <span class='control deletable'>
 | 
					                        <span class='control deletable'>
 | 
				
			||||||
                            <a v-if='page.studylines.length == 0' href='#' @click='deletePlan(value)'
 | 
					                            <a v-if='value.pages.length == 0' href='#' @click='deletePlan(value)'
 | 
				
			||||||
                                ><i class='text-danger fa fa-trash'></i></a>
 | 
					                                ><i class='text-danger fa fa-trash'></i></a>
 | 
				
			||||||
                        </span>
 | 
					                        </span>
 | 
				
			||||||
                    </div>
 | 
					                    </div>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
                <div class='t-studyplan-content-edit' v-if="edit.studyline.editmode">
 | 
					                <b-tabs content-class="mt-1">
 | 
				
			||||||
                    <drop-list
 | 
					                    <!-- New Tab Button (Using tabs-end slot) -->
 | 
				
			||||||
                        :items="page.studylines"
 | 
					                    <template #tabs-end>
 | 
				
			||||||
                        class="t-slot-droplist"
 | 
					                        <t-studyplan-page-edit 
 | 
				
			||||||
                        :accepts-type="'studyline-'+page.id"
 | 
					                            :studyplan="value"
 | 
				
			||||||
                        xreorder="$event.apply(page.studylines)"
 | 
					                            v-model="create.page"
 | 
				
			||||||
                        @reorder="reorderLines($event,page.studylines)"
 | 
					                            type="link"
 | 
				
			||||||
                        mode="copy"
 | 
					                            mode="create"
 | 
				
			||||||
                        row
 | 
					                            @created="pagecreated"
 | 
				
			||||||
                    >
 | 
					                        ><i class='fa fa-plus'></i></t-studyplan-page-edit>
 | 
				
			||||||
                        <template v-slot:item="{item}">
 | 
					                    </template>
 | 
				
			||||||
                            <drag
 | 
					                    <b-tab
 | 
				
			||||||
                                :key="item.id"
 | 
					                        v-for="(page,pageindex) in value.pages"
 | 
				
			||||||
                                class='t-studyline-drag'
 | 
					                        :key="page.id"
 | 
				
			||||||
                                :data="item"
 | 
					                        >
 | 
				
			||||||
                                :type="'studyline-'+page.id"
 | 
					                        <template #title>
 | 
				
			||||||
                                >
 | 
					                            {{page.shortname}}
 | 
				
			||||||
                                <template v-slot:drag-image>
 | 
					                            <t-studyplan-page-edit 
 | 
				
			||||||
                                    <i class="fa fa-arrows text-primary"></i>
 | 
					                                v-model="value.pages[pageindex]"
 | 
				
			||||||
                                </template>
 | 
					                                :studyplan="value"
 | 
				
			||||||
                                <t-studyline-edit
 | 
					                                type="link"
 | 
				
			||||||
                                    v-model="item"
 | 
					                            ></t-studyplan-page-edit>
 | 
				
			||||||
                                    @edit='editLineStart(item)'
 | 
					 | 
				
			||||||
                                    @delete='deleteLine(page,item)'
 | 
					 | 
				
			||||||
                                    >
 | 
					 | 
				
			||||||
                                    <div v-if="!slotsempty(item.slots)"> {{ text.editmode_modules_hidden}} </div>
 | 
					 | 
				
			||||||
                                </t-studyline-edit>
 | 
					 | 
				
			||||||
                            </drag>
 | 
					 | 
				
			||||||
                        </template>
 | 
					                        </template>
 | 
				
			||||||
                    </drop-list>
 | 
					                        <div class='t-studyplan-content-edit' v-if="edit.studyline.editmode">
 | 
				
			||||||
                </div>
 | 
					                            <drop-list
 | 
				
			||||||
                <div class='t-studyplan-content' v-else>
 | 
					                                :items="page.studylines"
 | 
				
			||||||
 | 
					                                class="t-slot-droplist"
 | 
				
			||||||
                    <!-- Now paint the headings column -->
 | 
					                                :accepts-type="'studyline-'+page.id"
 | 
				
			||||||
                    <div class='t-studyplan-headings'>
 | 
					                                xreorder="$event.apply(page.studylines)"
 | 
				
			||||||
                        <s-studyline-header-heading></s-studyline-header-heading>
 | 
					                                @reorder="reorderLines($event,page.studylines)"
 | 
				
			||||||
                        <t-studyline-heading  v-for="(line,lineindex) in page.studylines"
 | 
					                                mode="copy"
 | 
				
			||||||
                            :key="line.id"
 | 
					                                row
 | 
				
			||||||
                            @resize="headingresized(lineindex,$event)"
 | 
					                            >
 | 
				
			||||||
                            v-model="page.studylines[lineindex]"
 | 
					                                <template v-slot:item="{item}">
 | 
				
			||||||
                            :layers='countLineLayers(line)+1'
 | 
					                                    <drag
 | 
				
			||||||
                            :class=" 't-studyline' + ((lineindex%2==0)?' odd ' :' even ' )
 | 
					                                        :key="item.id"
 | 
				
			||||||
                                    + ((lineindex==0)?' first ':' ')
 | 
					                                        class='t-studyline-drag'
 | 
				
			||||||
                                    + ((lineindex==page.studylines.length-1)?' last ':' ')"
 | 
					                                        :data="item"
 | 
				
			||||||
                        ></t-studyline-heading>
 | 
					                                        :type="'studyline-'+page.id"
 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <!-- Next, paint all the cells in the scrollable -->
 | 
					 | 
				
			||||||
                    <div class="t-studyplan-scrollable" >
 | 
					 | 
				
			||||||
                        <div class="t-studyplan-timeline" :style="columns_stylerule">
 | 
					 | 
				
			||||||
                        <!-- add period information -->
 | 
					 | 
				
			||||||
                        <template v-for="(n,index) in (page.periods+1)">
 | 
					 | 
				
			||||||
                            <s-studyline-header-period
 | 
					 | 
				
			||||||
                                v-if="index > 0"
 | 
					 | 
				
			||||||
                                v-model="page.perioddesc[index-1]"
 | 
					 | 
				
			||||||
                                ><t-period-edit
 | 
					 | 
				
			||||||
                                    :ref="'periodeditor-'+index"
 | 
					 | 
				
			||||||
                                    @edited="periodEdited"
 | 
					 | 
				
			||||||
                                    v-model="page.perioddesc[index-1]"
 | 
					 | 
				
			||||||
                                    :minstart="(index > 1) ? add_day(page.perioddesc[index-2].startdate,2) : null"
 | 
					 | 
				
			||||||
                                    :maxend="(index < page.periods) ? sub_day(page.perioddesc[index].enddate,2) : null"
 | 
					 | 
				
			||||||
                                ></t-period-edit
 | 
					 | 
				
			||||||
                            ></s-studyline-header-period>
 | 
					 | 
				
			||||||
                            <div class="s-studyline-header-filter"></div>
 | 
					 | 
				
			||||||
                        </template>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        <!-- Line by line add the items -->
 | 
					 | 
				
			||||||
                        <!-- The grid layout handles putting it in rows and columns -->
 | 
					 | 
				
			||||||
                        <template v-for="(line,lineindex) in page.studylines"
 | 
					 | 
				
			||||||
                            ><template v-for="(layernr,layeridx) in countLineLayers(line)+1"
 | 
					 | 
				
			||||||
                                ><template v-for="(n,index) in (page.periods+1)"
 | 
					 | 
				
			||||||
                                    >
 | 
					 | 
				
			||||||
                                    <t-studyline-slot
 | 
					 | 
				
			||||||
                                        v-if="index > 0 && showslot(line, index, layeridx, 'gradable')"
 | 
					 | 
				
			||||||
                                        type='gradable'
 | 
					 | 
				
			||||||
                                        v-model="line.slots[index].courses"
 | 
					 | 
				
			||||||
                                        :key="'c-'+lineindex+'-'+index+'-'+layernr"
 | 
					 | 
				
			||||||
                                        :slotindex="index"
 | 
					 | 
				
			||||||
                                        :line="line"
 | 
					 | 
				
			||||||
                                        :plan="value"
 | 
					 | 
				
			||||||
                                        :page="page"
 | 
					 | 
				
			||||||
                                        :period="page.perioddesc[index-1]"
 | 
					 | 
				
			||||||
                                        :layer="layeridx"
 | 
					 | 
				
			||||||
                                        :class="'t-studyline ' + ((lineindex%2==0)?' odd ':' even ')
 | 
					 | 
				
			||||||
                                                + ((lineindex==0 && layernr==1)?' first ':' ')
 | 
					 | 
				
			||||||
                                                + ((lineindex==page.studylines.length-1)?' last ':' ')
 | 
					 | 
				
			||||||
                                                + ((layernr == countLineLayers(line))?' lastlyr ':' ')
 | 
					 | 
				
			||||||
                                                + ((layernr == countLineLayers(line)+1)?' newlyr ':' ')"
 | 
					 | 
				
			||||||
                                    ></t-studyline-slot
 | 
					 | 
				
			||||||
                                    ><t-studyline-slot
 | 
					 | 
				
			||||||
                                        type='filter'
 | 
					 | 
				
			||||||
                                        v-if="showslot(line, index, layeridx, 'filter')"
 | 
					 | 
				
			||||||
                                        v-model="line.slots[index].filters"
 | 
					 | 
				
			||||||
                                        :key="'f-'+lineindex+'-'+index+'-'+layernr"
 | 
					 | 
				
			||||||
                                        :slotindex="index"
 | 
					 | 
				
			||||||
                                        :line="line"
 | 
					 | 
				
			||||||
                                        :plan="value"
 | 
					 | 
				
			||||||
                                        :page="page"
 | 
					 | 
				
			||||||
                                        :layer="layeridx"
 | 
					 | 
				
			||||||
                                        :class="'t-studyline ' + ((lineindex%2==0)?' odd ':' even ')
 | 
					 | 
				
			||||||
                                                + ((lineindex==0 && layernr==1)?' first ':'')
 | 
					 | 
				
			||||||
                                                + ((lineindex==page.studylines.length-1)?' last ':' ')
 | 
					 | 
				
			||||||
                                                + ((index==page.periods)?' rightmost':'')
 | 
					 | 
				
			||||||
                                                + ((layernr == countLineLayers(line))?' lastlyr ':' ')
 | 
					 | 
				
			||||||
                                                + ((layernr == countLineLayers(line)+1)?' newlyr ':' ')"
 | 
					 | 
				
			||||||
                                        >
 | 
					                                        >
 | 
				
			||||||
                                    </t-studyline-slot
 | 
					                                        <template v-slot:drag-image>
 | 
				
			||||||
 | 
					                                            <i class="fa fa-arrows text-primary"></i>
 | 
				
			||||||
 | 
					                                        </template>
 | 
				
			||||||
 | 
					                                        <t-studyline-edit
 | 
				
			||||||
 | 
					                                            v-model="item"
 | 
				
			||||||
 | 
					                                            @edit='editLineStart(item)'
 | 
				
			||||||
 | 
					                                            @delete='deleteLine(page,item)'
 | 
				
			||||||
 | 
					                                            >
 | 
				
			||||||
 | 
					                                            <div v-if="!slotsempty(item.slots)"> {{ text.editmode_modules_hidden}} </div>
 | 
				
			||||||
 | 
					                                        </t-studyline-edit>
 | 
				
			||||||
 | 
					                                    </drag>
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					                            </drop-list>
 | 
				
			||||||
 | 
					                        </div>
 | 
				
			||||||
 | 
					                        <div class='t-studyplan-content' v-else>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            <!-- Now paint the headings column -->
 | 
				
			||||||
 | 
					                            <div class='t-studyplan-headings'>
 | 
				
			||||||
 | 
					                                <s-studyline-header-heading :identifier='Number(page.id)'></s-studyline-header-heading>
 | 
				
			||||||
 | 
					                                <t-studyline-heading  v-for="(line,lineindex) in page.studylines"
 | 
				
			||||||
 | 
					                                    :key="line.id"
 | 
				
			||||||
 | 
					                                    @resize="headingresized(lineindex,$event)"
 | 
				
			||||||
 | 
					                                    v-model="page.studylines[lineindex]"
 | 
				
			||||||
 | 
					                                    :layers='countLineLayers(line,page)+1'
 | 
				
			||||||
 | 
					                                    :class=" 't-studyline' + ((lineindex%2==0)?' odd ' :' even ' )
 | 
				
			||||||
 | 
					                                            + ((lineindex==0)?' first ':' ')
 | 
				
			||||||
 | 
					                                            + ((lineindex==page.studylines.length-1)?' last ':' ')"
 | 
				
			||||||
 | 
					                                ></t-studyline-heading>
 | 
				
			||||||
 | 
					                            </div>
 | 
				
			||||||
 | 
					                            <!-- Next, paint all the cells in the scrollable -->
 | 
				
			||||||
 | 
					                            <div class="t-studyplan-scrollable" >
 | 
				
			||||||
 | 
					                                <div class="t-studyplan-timeline" :style="columns_stylerule(page)">
 | 
				
			||||||
 | 
					                                <!-- add period information --> 
 | 
				
			||||||
 | 
					                                <template v-for="(n,index) in (page.periods+1)">
 | 
				
			||||||
 | 
					                                    <s-studyline-header-period
 | 
				
			||||||
 | 
					                                        :identifier='Number(page.id)'
 | 
				
			||||||
 | 
					                                        v-if="index > 0"
 | 
				
			||||||
 | 
					                                        v-model="page.perioddesc[index-1]"
 | 
				
			||||||
 | 
					                                        ><t-period-edit
 | 
				
			||||||
 | 
					                                            :ref="'periodeditor-'+index"
 | 
				
			||||||
 | 
					                                            @edited="periodEdited"
 | 
				
			||||||
 | 
					                                            v-model="page.perioddesc[index-1]"
 | 
				
			||||||
 | 
					                                            :minstart="(index > 1) ? add_day(page.perioddesc[index-2].startdate,2) : null"
 | 
				
			||||||
 | 
					                                            :maxend="(index < page.periods) ? sub_day(page.perioddesc[index].enddate,2) : null"
 | 
				
			||||||
 | 
					                                        ></t-period-edit
 | 
				
			||||||
 | 
					                                    ></s-studyline-header-period>
 | 
				
			||||||
 | 
					                                    <div class="s-studyline-header-filter"></div>
 | 
				
			||||||
 | 
					                                </template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                <!-- Line by line add the items -->
 | 
				
			||||||
 | 
					                                <!-- The grid layout handles putting it in rows and columns -->
 | 
				
			||||||
 | 
					                                <template v-for="(line,lineindex) in page.studylines"
 | 
				
			||||||
 | 
					                                    ><template v-for="(layernr,layeridx) in countLineLayers(line,page)+1"
 | 
				
			||||||
 | 
					                                        ><template v-for="(n,index) in (page.periods+1)"
 | 
				
			||||||
 | 
					                                            >
 | 
				
			||||||
 | 
					                                            <t-studyline-slot
 | 
				
			||||||
 | 
					                                                v-if="index > 0 && showslot(page,line, index, layeridx, 'gradable')"
 | 
				
			||||||
 | 
					                                                type='gradable'
 | 
				
			||||||
 | 
					                                                v-model="line.slots[index].courses"
 | 
				
			||||||
 | 
					                                                :key="'c-'+lineindex+'-'+index+'-'+layernr"
 | 
				
			||||||
 | 
					                                                :slotindex="index"
 | 
				
			||||||
 | 
					                                                :line="line"
 | 
				
			||||||
 | 
					                                                :plan="value"
 | 
				
			||||||
 | 
					                                                :page="page"
 | 
				
			||||||
 | 
					                                                :period="page.perioddesc[index-1]"
 | 
				
			||||||
 | 
					                                                :layer="layeridx"
 | 
				
			||||||
 | 
					                                                :class="'t-studyline ' + ((lineindex%2==0)?' odd ':' even ')
 | 
				
			||||||
 | 
					                                                        + ((lineindex==0 && layernr==1)?' first ':' ')
 | 
				
			||||||
 | 
					                                                        + ((lineindex==page.studylines.length-1)?' last ':' ')
 | 
				
			||||||
 | 
					                                                        + ((layernr == countLineLayers(line,page))?' lastlyr ':' ')
 | 
				
			||||||
 | 
					                                                        + ((layernr == countLineLayers(line,page)+1)?' newlyr ':' ')"
 | 
				
			||||||
 | 
					                                            ></t-studyline-slot
 | 
				
			||||||
 | 
					                                            ><t-studyline-slot
 | 
				
			||||||
 | 
					                                                type='filter'
 | 
				
			||||||
 | 
					                                                v-if="showslot(page,line, index, layeridx, 'filter')"
 | 
				
			||||||
 | 
					                                                v-model="line.slots[index].filters"
 | 
				
			||||||
 | 
					                                                :key="'f-'+lineindex+'-'+index+'-'+layernr"
 | 
				
			||||||
 | 
					                                                :slotindex="index"
 | 
				
			||||||
 | 
					                                                :line="line"
 | 
				
			||||||
 | 
					                                                :plan="value"
 | 
				
			||||||
 | 
					                                                :page="page"
 | 
				
			||||||
 | 
					                                                :layer="layeridx"
 | 
				
			||||||
 | 
					                                                :class="'t-studyline ' + ((lineindex%2==0)?' odd ':' even ')
 | 
				
			||||||
 | 
					                                                        + ((lineindex==0 && layernr==1)?' first ':'')
 | 
				
			||||||
 | 
					                                                        + ((lineindex==page.studylines.length-1)?' last ':' ')
 | 
				
			||||||
 | 
					                                                        + ((index==page.periods)?' rightmost':'')
 | 
				
			||||||
 | 
					                                                        + ((layernr == countLineLayers(line,page))?' lastlyr ':' ')
 | 
				
			||||||
 | 
					                                                        + ((layernr == countLineLayers(line,page)+1)?' newlyr ':' ')"
 | 
				
			||||||
 | 
					                                                >
 | 
				
			||||||
 | 
					                                            </t-studyline-slot
 | 
				
			||||||
 | 
					                                        ></template
 | 
				
			||||||
 | 
					                                    ></template
 | 
				
			||||||
                                ></template
 | 
					                                ></template
 | 
				
			||||||
                            ></template
 | 
					                                ></div>
 | 
				
			||||||
                        ></template
 | 
					                            </div>
 | 
				
			||||||
                        ></div>
 | 
					                        </div>
 | 
				
			||||||
                    </div>
 | 
					                        <div v-if="edit.studyline.editmode" class='t-studyline-add'>
 | 
				
			||||||
                </div>
 | 
					                            <a href="#" v-b-modal="'modal-add-studyline-'+page.id" @click="false;"
 | 
				
			||||||
                <div v-if="edit.studyline.editmode" class='t-studyline-add'>
 | 
					                                ><i class='fa fa-plus'></i>{{ text.studyline_add }}</a>
 | 
				
			||||||
                    <a href="#" v-b-modal="'modal-add-studyline-'+page.id" @click="false;"
 | 
					                        </div>
 | 
				
			||||||
                        ><i class='fa fa-plus'></i>{{ text.studyline_add }}</a>
 | 
					                        <b-modal
 | 
				
			||||||
                </div>
 | 
					                            :id="'modal-add-studyline-'+page.id"
 | 
				
			||||||
                <b-modal
 | 
					                            size="lg"
 | 
				
			||||||
                    :id="'modal-add-studyline-'+page.id"
 | 
					                            :ok-title="text.add$core"
 | 
				
			||||||
                    size="lg"
 | 
					                            ok-variant="primary"
 | 
				
			||||||
                    :ok-title="text.add$core"
 | 
					                            :title="text.studyline_add"
 | 
				
			||||||
                    ok-variant="primary"
 | 
					                            @ok="addStudyLine(page,create.studyline)"
 | 
				
			||||||
                    :title="text.studyline_add"
 | 
					                            :ok-disabled="Math.min(create.studyline.name.length,create.studyline.shortname.length) == 0"
 | 
				
			||||||
                    @ok="addStudyLine(page,create.studyline)"
 | 
					                            >
 | 
				
			||||||
                    :ok-disabled="Math.min(create.studyline.name.length,create.studyline.shortname.length) == 0"
 | 
					                            <b-container>
 | 
				
			||||||
                    >
 | 
					                                <b-row>
 | 
				
			||||||
                    <b-container>
 | 
					                                    <b-col cols="3">{{text.studyline_name}}</b-col>
 | 
				
			||||||
                        <b-row>
 | 
					                                    <b-col>
 | 
				
			||||||
                            <b-col cols="3">{{text.studyline_name}}</b-col>
 | 
					                                        <b-form-input v-model="create.studyline.name" :placeholder="text.studyline_name_ph"></b-form-input>
 | 
				
			||||||
                            <b-col>
 | 
					                                    </b-col>
 | 
				
			||||||
                                <b-form-input v-model="create.studyline.name" :placeholder="text.studyline_name_ph"></b-form-input>
 | 
					                                </b-row>
 | 
				
			||||||
                            </b-col>
 | 
					                                <b-row>
 | 
				
			||||||
                        </b-row>
 | 
					                                    <b-col cols="3">{{text.studyline_shortname}}</b-col>
 | 
				
			||||||
                        <b-row>
 | 
					                                    <b-col>
 | 
				
			||||||
                            <b-col cols="3">{{text.studyline_shortname}}</b-col>
 | 
					                                    <b-form-input
 | 
				
			||||||
                            <b-col>
 | 
					                                        v-model="create.studyline.shortname"
 | 
				
			||||||
                            <b-form-input
 | 
					                                        :placeholder="text.studyline_shortname_ph"></b-form-input>
 | 
				
			||||||
                                v-model="create.studyline.shortname"
 | 
					                                    </b-col>
 | 
				
			||||||
                                :placeholder="text.studyline_shortname_ph"></b-form-input>
 | 
					                                </b-row>
 | 
				
			||||||
                            </b-col>
 | 
					                                <b-row>
 | 
				
			||||||
                        </b-row>
 | 
					                                    <b-col cols="3">{{text.studyline_color}}</b-col>
 | 
				
			||||||
                        <b-row>
 | 
					                                    <b-col>
 | 
				
			||||||
                            <b-col cols="3">{{text.studyline_color}}</b-col>
 | 
					                                        <input type="color" v-model="create.studyline.color" />
 | 
				
			||||||
                            <b-col>
 | 
					                                        <!-- hsluv-picker v-model="create.studyline.color" horizontal displaysize="175" ></hsluv-picker -->
 | 
				
			||||||
                                <input type="color" v-model="create.studyline.color" />
 | 
					                                    </b-col>
 | 
				
			||||||
                                <!-- hsluv-picker v-model="create.studyline.color" horizontal displaysize="175" ></hsluv-picker -->
 | 
					                                </b-row>
 | 
				
			||||||
                            </b-col>
 | 
					                            </b-container>
 | 
				
			||||||
                        </b-row>
 | 
					                        </b-modal>
 | 
				
			||||||
                    </b-container>
 | 
					                        <b-modal
 | 
				
			||||||
                </b-modal>
 | 
					                            :id="'modal-edit-studyline-'+page.id"
 | 
				
			||||||
                <b-modal
 | 
					                            size="lg"
 | 
				
			||||||
                    :id="'modal-edit-studyline-'+value.id"
 | 
					                            ok-variant="primary"
 | 
				
			||||||
                    size="lg"
 | 
					                            :title="text.studyline_edit"
 | 
				
			||||||
                    ok-variant="primary"
 | 
					                            @ok="editLineFinish()"
 | 
				
			||||||
                    :title="text.studyline_edit"
 | 
					                            :ok-disabled="Math.min(edit.studyline.data.name.length,edit.studyline.data.shortname.length) == 0"
 | 
				
			||||||
                    @ok="editLineFinish()"
 | 
					                            >
 | 
				
			||||||
                    :ok-disabled="Math.min(edit.studyline.data.name.length,edit.studyline.data.shortname.length) == 0"
 | 
					                            <b-container>
 | 
				
			||||||
                    >
 | 
					                                <b-row>
 | 
				
			||||||
                    <b-container>
 | 
					                                    <b-col cols="3">{{ text.studyline_name}}</b-col>
 | 
				
			||||||
                        <b-row>
 | 
					                                    <b-col>
 | 
				
			||||||
                            <b-col cols="3">{{ text.studyline_name}}</b-col>
 | 
					                                        <b-form-input
 | 
				
			||||||
                            <b-col>
 | 
					                                            v-model="edit.studyline.data.name"
 | 
				
			||||||
                                <b-form-input
 | 
					                                            :placeholder="text.studyline_name_ph"></b-form-input>
 | 
				
			||||||
                                    v-model="edit.studyline.data.name"
 | 
					                                    </b-col>
 | 
				
			||||||
                                    :placeholder="text.studyline_name_ph"></b-form-input>
 | 
					                                </b-row>
 | 
				
			||||||
                            </b-col>
 | 
					                                <b-row>
 | 
				
			||||||
                        </b-row>
 | 
					                                    <b-col cols="3">{{ text.studyline_shortname}}</b-col>
 | 
				
			||||||
                        <b-row>
 | 
					                                    <b-col>
 | 
				
			||||||
                            <b-col cols="3">{{ text.studyline_shortname}}</b-col>
 | 
					                                    <b-form-input
 | 
				
			||||||
                            <b-col>
 | 
					                                        v-model="edit.studyline.data.shortname"
 | 
				
			||||||
                            <b-form-input
 | 
					                                        :placeholder="text.studyline_shortname_ph"></b-form-input>
 | 
				
			||||||
                                v-model="edit.studyline.data.shortname"
 | 
					                                    </b-col>
 | 
				
			||||||
                                :placeholder="text.studyline_shortname_ph"></b-form-input>
 | 
					                                </b-row>
 | 
				
			||||||
                            </b-col>
 | 
					                                <b-row>
 | 
				
			||||||
                        </b-row>
 | 
					                                    <b-col cols="3">{{ text.studyline_color}}</b-col>
 | 
				
			||||||
                        <b-row>
 | 
					                                    <b-col>
 | 
				
			||||||
                            <b-col cols="3">{{ text.studyline_color}}</b-col>
 | 
					                                        <input type="color" v-model="edit.studyline.data.color" />
 | 
				
			||||||
                            <b-col>
 | 
					                                    </b-col>
 | 
				
			||||||
                                <input type="color" v-model="edit.studyline.data.color" />
 | 
					                                </b-row>
 | 
				
			||||||
                            </b-col>
 | 
					                            </b-container>
 | 
				
			||||||
                        </b-row>
 | 
					                        </b-modal>
 | 
				
			||||||
                    </b-container>
 | 
					                    </b-tab>
 | 
				
			||||||
                </b-modal>
 | 
					                </b-tabs>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            `
 | 
					            `
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -63,66 +63,77 @@ export function ProcessStudyplans(studyplans){
 | 
				
			||||||
 * @returns Processed studyplan
 | 
					 * @returns Processed studyplan
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function ProcessStudyplan(studyplan){
 | 
					export function ProcessStudyplan(studyplan){
 | 
				
			||||||
    let connections = {};
 | 
					 | 
				
			||||||
    for(const ip in studyplan.pages){
 | 
					    for(const ip in studyplan.pages){
 | 
				
			||||||
        const page = studyplan.pages[ip];
 | 
					        const page = studyplan.pages[ip];
 | 
				
			||||||
        for(const il in page.studylines) {
 | 
					        ProcessStudyplanPage(page);
 | 
				
			||||||
            const line = page.studylines[il];
 | 
					    }
 | 
				
			||||||
 | 
					    return studyplan;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for(const is in line.slots ) {
 | 
					/**
 | 
				
			||||||
                const slot = line.slots[is];
 | 
					 * Perform initial processing on a downloaded studyplan'page
 | 
				
			||||||
 | 
					 * Mainly used to create the proper references between items
 | 
				
			||||||
 | 
					 * @param {Object} page The studyplan page to process
 | 
				
			||||||
 | 
					 * @returns Processed studyplan
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function ProcessStudyplanPage(page){
 | 
				
			||||||
 | 
					    let connections = {};
 | 
				
			||||||
 | 
					    for(const il in page.studylines) {
 | 
				
			||||||
 | 
					        const line = page.studylines[il];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if(slot.courses !== undefined){
 | 
					        for(const is in line.slots ) {
 | 
				
			||||||
                    for(const ic in slot.courses){
 | 
					            const slot = line.slots[is];
 | 
				
			||||||
                        const itm = slot.courses[ic];
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        for(const idx in itm.connections.in) {
 | 
					            if(slot.courses !== undefined){
 | 
				
			||||||
                            const conn = itm.connections.in[idx];
 | 
					                for(const ic in slot.courses){
 | 
				
			||||||
 | 
					                    const itm = slot.courses[ic];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if(conn.id in connections){
 | 
					                    for(const idx in itm.connections.in) {
 | 
				
			||||||
                                itm.connections[idx] = connections[conn.id];
 | 
					                        const conn = itm.connections.in[idx];
 | 
				
			||||||
                            } else {
 | 
					
 | 
				
			||||||
                                connections[conn.id] = conn;
 | 
					                        if(conn.id in connections){
 | 
				
			||||||
                            }
 | 
					                            itm.connections[idx] = connections[conn.id];
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            connections[conn.id] = conn;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        for(const idx in itm.connections.out) {
 | 
					                    }
 | 
				
			||||||
                            const conn = itm.connections.out[idx];
 | 
					                    for(const idx in itm.connections.out) {
 | 
				
			||||||
 | 
					                        const conn = itm.connections.out[idx];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if(conn.id in connections){
 | 
					                        if(conn.id in connections){
 | 
				
			||||||
                                itm.connections[idx] = connections[conn.id];
 | 
					                            itm.connections[idx] = connections[conn.id];
 | 
				
			||||||
                            } else {
 | 
					                        } else {
 | 
				
			||||||
                                connections[conn.id] = conn;
 | 
					                            connections[conn.id] = conn;
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if(slot.filters !== undefined){
 | 
					            if(slot.filters !== undefined){
 | 
				
			||||||
                    for(const ix in slot.filters){
 | 
					                for(const ix in slot.filters){
 | 
				
			||||||
                        const itm = slot.filters[ix];
 | 
					                    const itm = slot.filters[ix];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        for(const idx in itm.connections.in) {
 | 
					                    for(const idx in itm.connections.in) {
 | 
				
			||||||
                            const conn = itm.connections.in[idx];
 | 
					                        const conn = itm.connections.in[idx];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if(conn.id in connections){
 | 
					                        if(conn.id in connections){
 | 
				
			||||||
                                itm.connections[idx] = connections[conn.id];
 | 
					                            itm.connections[idx] = connections[conn.id];
 | 
				
			||||||
                            } else {
 | 
					                        } else {
 | 
				
			||||||
                                connections[conn.id] = conn;
 | 
					                            connections[conn.id] = conn;
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        for(const idx in itm.connections.out) {
 | 
					                    }
 | 
				
			||||||
                            const conn = itm.connections.out[idx];
 | 
					                    for(const idx in itm.connections.out) {
 | 
				
			||||||
 | 
					                        const conn = itm.connections.out[idx];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            if(conn.id in connections){
 | 
					                        if(conn.id in connections){
 | 
				
			||||||
                                itm.connections[idx] = connections[conn.id];
 | 
					                            itm.connections[idx] = connections[conn.id];
 | 
				
			||||||
                            } else {
 | 
					                        } else {
 | 
				
			||||||
                                connections[conn.id] = conn;
 | 
					                            connections[conn.id] = conn;
 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return studyplan;
 | 
					    return page;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -170,7 +170,10 @@ export default {
 | 
				
			||||||
        */
 | 
					        */
 | 
				
			||||||
        Vue.component('s-studyline-header-heading', {
 | 
					        Vue.component('s-studyline-header-heading', {
 | 
				
			||||||
            props: {
 | 
					            props: {
 | 
				
			||||||
 | 
					                identifier: {
 | 
				
			||||||
 | 
					                    type: Number, // Page reference.
 | 
				
			||||||
 | 
					                    default() { return 0;}
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            data() {
 | 
					            data() {
 | 
				
			||||||
                return {
 | 
					                return {
 | 
				
			||||||
| 
						 | 
					@ -186,14 +189,16 @@ export default {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            methods: {
 | 
					            methods: {
 | 
				
			||||||
                onHeaderHeightChange(newheight){
 | 
					                onHeaderHeightChange(newheight,identifier){
 | 
				
			||||||
                    if(this.$refs.main){
 | 
					                    if (this.identifier == identifier) {
 | 
				
			||||||
                        this.$refs.main.style.height = `${newheight}px`;
 | 
					                        if(this.$refs.main){
 | 
				
			||||||
 | 
					                            this.$refs.main.style.height = `${newheight}px`;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            template: `
 | 
					            template: `
 | 
				
			||||||
            <div class="s-studyline-header-heading" ref="main"></div>
 | 
					            <div class="s-studyline-header-heading" ref="main" :data-id="identifier"></div>
 | 
				
			||||||
            `,
 | 
					            `,
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -202,6 +207,10 @@ export default {
 | 
				
			||||||
                value: {
 | 
					                value: {
 | 
				
			||||||
                    type: Object, // dict with layer as index
 | 
					                    type: Object, // dict with layer as index
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                identifier: {
 | 
				
			||||||
 | 
					                    type: Number, // Page reference.
 | 
				
			||||||
 | 
					                    default() { return 0;}
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            mounted() {
 | 
					            mounted() {
 | 
				
			||||||
                const self=this;
 | 
					                const self=this;
 | 
				
			||||||
| 
						 | 
					@ -209,7 +218,7 @@ export default {
 | 
				
			||||||
                    self.resizeListener = new ResizeObserver(() => {
 | 
					                    self.resizeListener = new ResizeObserver(() => {
 | 
				
			||||||
                        if(self.$refs.main){
 | 
					                        if(self.$refs.main){
 | 
				
			||||||
                            const size = self.$refs.main.getBoundingClientRect();
 | 
					                            const size = self.$refs.main.getBoundingClientRect();
 | 
				
			||||||
                            ItemEventBus.$emit('headerHeightChange', size.height);
 | 
					                            ItemEventBus.$emit('headerHeightChange', size.height, self.identifier);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }).observe(self.$refs.main);
 | 
					                    }).observe(self.$refs.main);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
| 
						 | 
					@ -244,7 +253,7 @@ export default {
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            template: `
 | 
					            template: `
 | 
				
			||||||
            <div :class="'s-studyline-header-period ' + (current?'current ':' ')" ref="main"
 | 
					            <div :class="'s-studyline-header-period ' + (current?'current ':' ')" ref="main" :data-id="identifier"
 | 
				
			||||||
                ><p><abbr :id="'s-period-'+value.id" :title="value.fullname">{{ value.shortname }}</abbr>
 | 
					                ><p><abbr :id="'s-period-'+value.id" :title="value.fullname">{{ value.shortname }}</abbr>
 | 
				
			||||||
                <b-tooltip
 | 
					                <b-tooltip
 | 
				
			||||||
                    :target="'s-period-'+value.id" triggers="hover"
 | 
					                    :target="'s-period-'+value.id" triggers="hover"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,18 +2,22 @@
 | 
				
			||||||
 * convert a text field into an integer only text field
 | 
					 * convert a text field into an integer only text field
 | 
				
			||||||
 * @param {string} id The Id of the form field
 | 
					 * @param {string} id The Id of the form field
 | 
				
			||||||
 * @param {bool} unsigned Allow only unsigned values
 | 
					 * @param {bool} unsigned Allow only unsigned values
 | 
				
			||||||
 | 
					 * @param {bool} nonzero Do not allow zero values
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export function text_integer(id,unsigned=false){
 | 
					export function text_integer(id,unsigned=false,nonzero=false){
 | 
				
			||||||
    const element = document.getElementById(id);
 | 
					    const element = document.getElementById(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (element) {
 | 
					    if (element) {
 | 
				
			||||||
        element.addEventListener("input",() => {
 | 
					        element.addEventListener("input",() => {
 | 
				
			||||||
            var val = element.value;
 | 
					            var val = element.value;
 | 
				
			||||||
            if (val != '') {
 | 
					            if (val != '') {
 | 
				
			||||||
                if ((isNaN(val) && !(!unsigned && val == '-')) || (unsigned && val < 0)) {
 | 
					                if ((isNaN(val) && !(!unsigned && val == '-')) || (unsigned && val < 0) || (nonzero && val == 0)) {
 | 
				
			||||||
                    // Set input value empty
 | 
					                    // Set input value empty
 | 
				
			||||||
                    if (unsigned) {
 | 
					                    if (unsigned) {
 | 
				
			||||||
                        element.value = val.replace(/[^0-9]/g,'');
 | 
					                        element.value = val.replace(/[^0-9]/g,'');
 | 
				
			||||||
 | 
					                        if (nonzero && val == 0) {
 | 
				
			||||||
 | 
					                            element.value = '';
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        element.value = val.replace(/[^0-9-]/g,'').replace(/(.{1})(-)/g,'$1');
 | 
					                        element.value = val.replace(/[^0-9-]/g,'').replace(/(.{1})(-)/g,'$1');
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,16 +65,19 @@ export default {
 | 
				
			||||||
                    loading: true,
 | 
					                    loading: true,
 | 
				
			||||||
                    uuid: create_uuid(),
 | 
					                    uuid: create_uuid(),
 | 
				
			||||||
                    text: strings,
 | 
					                    text: strings,
 | 
				
			||||||
 | 
					                    submitok: false,
 | 
				
			||||||
 | 
					                    observer: null,
 | 
				
			||||||
 | 
					                    inputs: [],
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            computed: {
 | 
					            computed: {
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            methods: {
 | 
					            methods: {
 | 
				
			||||||
                openForm(){
 | 
					                openForm() {
 | 
				
			||||||
                    const self = this;
 | 
					                    const self = this;
 | 
				
			||||||
                    self.$refs["editormodal"].show();
 | 
					                    self.$refs["editormodal"].show();
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                onShown(){
 | 
					                onShown() {
 | 
				
			||||||
                    const self = this;
 | 
					                    const self = this;
 | 
				
			||||||
                    debug.info(`Loading form "${self.name}" with params`,self.params);
 | 
					                    debug.info(`Loading form "${self.name}" with params`,self.params);
 | 
				
			||||||
                    self.loading = false;
 | 
					                    self.loading = false;
 | 
				
			||||||
| 
						 | 
					@ -87,11 +90,11 @@ export default {
 | 
				
			||||||
                        // Process the collected javascript;
 | 
					                        // Process the collected javascript;
 | 
				
			||||||
                        const js = processCollectedJavascript(data.javascript);
 | 
					                        const js = processCollectedJavascript(data.javascript);
 | 
				
			||||||
                        replaceNodeContents(self.$refs["content"], html, js);
 | 
					                        replaceNodeContents(self.$refs["content"], html, js);
 | 
				
			||||||
                        
 | 
					                        self.initListenChanges();
 | 
				
			||||||
                    }).catch(notification.exception);
 | 
					                    }).catch(notification.exception);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                onSave(){
 | 
					                onSave() {
 | 
				
			||||||
                    const self = this;
 | 
					                    const self = this;
 | 
				
			||||||
                    let form = this.$refs["content"].getElementsByTagName("form")[0];
 | 
					                    let form = this.$refs["content"].getElementsByTagName("form")[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -104,13 +107,59 @@ export default {
 | 
				
			||||||
                    const formdata = new FormData(form);
 | 
					                    const formdata = new FormData(form);
 | 
				
			||||||
                    const data = new URLSearchParams(formdata).toString();
 | 
					                    const data = new URLSearchParams(formdata).toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    call([{
 | 
					                    if(this.checkSave()){
 | 
				
			||||||
                        methodname: 'local_treestudyplan_submit_mform',
 | 
					                        call([{
 | 
				
			||||||
                        args: {formname: self.name, params: JSON.stringify(self.params), formdata: data}
 | 
					                            methodname: 'local_treestudyplan_submit_mform',
 | 
				
			||||||
                    }])[0].then((response)=>{
 | 
					                            args: {formname: self.name, params: JSON.stringify(self.params), formdata: data}
 | 
				
			||||||
                        const updatedplan = JSON.parse(response.data);
 | 
					                        }])[0].then((response)=>{
 | 
				
			||||||
                        self.$emit("saved",updatedplan,formdata);
 | 
					                            const updatedplan = JSON.parse(response.data);
 | 
				
			||||||
                    }).catch(notification.exception);
 | 
					                            self.$emit("saved",updatedplan,formdata);
 | 
				
			||||||
 | 
					                        }).catch(notification.exception);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    /* No error if we cannot save, since it would just be to handle the edge case 
 | 
				
			||||||
 | 
					                       where someone clicks on the save button before
 | 
				
			||||||
 | 
					                       an invalid input got a chance to update. */
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                checkSave() {
 | 
				
			||||||
 | 
					                    let canSave = true;
 | 
				
			||||||
 | 
					                    this.inputs.forEach(el => {
 | 
				
			||||||
 | 
					                        if (el.classList.contains("is-invalid")) {
 | 
				
			||||||
 | 
					                            canSave = false;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    },this);
 | 
				
			||||||
 | 
					                    this.submitok = canSave;
 | 
				
			||||||
 | 
					                    return canSave;
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                initListenChanges() {
 | 
				
			||||||
 | 
					                    const content = this.$refs["content"];
 | 
				
			||||||
 | 
					                    this.inputs = content.querySelectorAll("input.form-control");
 | 
				
			||||||
 | 
					                    // Check if save needs to be blocked immediately.
 | 
				
			||||||
 | 
					                    this.checkSave();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Disconnect any existing observer.
 | 
				
			||||||
 | 
					                    if(this.observer) {
 | 
				
			||||||
 | 
					                        this.observer.disconnect();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    // Initialize new observer and callback.
 | 
				
			||||||
 | 
					                    this.observer = new MutationObserver((mutationList) => {
 | 
				
			||||||
 | 
					                        for(const mix in mutationList){
 | 
				
			||||||
 | 
					                            const mutation = mutationList[mix];
 | 
				
			||||||
 | 
					                            if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
 | 
				
			||||||
 | 
					                                this.checkSave();
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // Connect the observer to the form inputs.
 | 
				
			||||||
 | 
					                    this.inputs.forEach(el => {
 | 
				
			||||||
 | 
					                        this.observer.observe(el,{ attributes: true });
 | 
				
			||||||
 | 
					                    },this);
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            unmount() {
 | 
				
			||||||
 | 
					                if(this.observer) {
 | 
				
			||||||
 | 
					                    this.observer.disconnect();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            template: `
 | 
					            template: `
 | 
				
			||||||
| 
						 | 
					@ -127,6 +176,7 @@ export default {
 | 
				
			||||||
                    id="'modal-'+uuid"
 | 
					                    id="'modal-'+uuid"
 | 
				
			||||||
                    @shown="onShown"
 | 
					                    @shown="onShown"
 | 
				
			||||||
                    @ok="onSave"
 | 
					                    @ok="onSave"
 | 
				
			||||||
 | 
					                    :ok-disabled="!submitok"
 | 
				
			||||||
                    :title="title"
 | 
					                    :title="title"
 | 
				
			||||||
                    :ok-title="text.save$core"
 | 
					                    :ok-title="text.save$core"
 | 
				
			||||||
                    ><div :class="'s-mform-content'" ref="content"
 | 
					                    ><div :class="'s-mform-content'" ref="content"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,10 @@ class contextinfo {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function __construct(context $context) {
 | 
					    public function __construct(context $context) {
 | 
				
			||||||
        $this->context = $context;
 | 
					        $this->context = $context;
 | 
				
			||||||
 | 
					        $this->ctxpath = array_reverse($this->context->get_parent_context_ids(true));
 | 
				
			||||||
 | 
					        if (count($this->ctxpath) > 1 && $this->ctxpath[0] == 1) {
 | 
				
			||||||
 | 
					            array_shift($this->ctxpath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -57,22 +61,42 @@ class contextinfo {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function model() {
 | 
					    public function model() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $ctxpath = array_reverse($this->context->get_parent_context_ids(true));
 | 
					
 | 
				
			||||||
        if (count($ctxpath) > 1 && $ctxpath[0] == 1) {
 | 
					 | 
				
			||||||
            array_shift($ctxpath);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return  [
 | 
					        return  [
 | 
				
			||||||
            "name" => $this->context->get_context_name(false, false),
 | 
					            "name" => $this->context->get_context_name(false, false),
 | 
				
			||||||
            "shortname" => $this->context->get_context_name(false, true),
 | 
					            "shortname" => $this->context->get_context_name(false, true),
 | 
				
			||||||
            "path" => array_map(function($c) {
 | 
					            "path" => $this->path(false),
 | 
				
			||||||
                return \context::instance_by_id($c)->get_context_name(false, false);
 | 
					            "shortpath" => $this->path(true),
 | 
				
			||||||
            }, $ctxpath),
 | 
					 | 
				
			||||||
            "shortpath" => array_map(function($c) {
 | 
					 | 
				
			||||||
                return \context::instance_by_id($c)->get_context_name(false, true);
 | 
					 | 
				
			||||||
            }, $ctxpath),
 | 
					 | 
				
			||||||
        ];
 | 
					        ];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return context path names
 | 
				
			||||||
 | 
					     * @param $short Use short names of contexts in path
 | 
				
			||||||
 | 
					     * @return array of context path names
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function path($short=false){
 | 
				
			||||||
 | 
					        if ($short) {
 | 
				
			||||||
 | 
					            return array_map(function($c) {
 | 
				
			||||||
 | 
					                return \context::instance_by_id($c)->get_context_name(false, true);
 | 
				
			||||||
 | 
					            }, $this->ctxpath);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            return array_map(function($c) {
 | 
				
			||||||
 | 
					                return \context::instance_by_id($c)->get_context_name(false, false);
 | 
				
			||||||
 | 
					            }, $this->ctxpath);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Return full context path name
 | 
				
			||||||
 | 
					     * @param $short Use short names of contexts in path
 | 
				
			||||||
 | 
					     * @return string Concatenated string of paths
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function pathstr($short=false){
 | 
				
			||||||
 | 
					        return implode(" / ", $this->path($short));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Make new Contextinfo for context id
 | 
					     * Make new Contextinfo for context id
 | 
				
			||||||
     * @param int $contextid Context id
 | 
					     * @param int $contextid Context id
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ namespace local_treestudyplan\form;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use local_treestudyplan\aggregator;
 | 
					use local_treestudyplan\aggregator;
 | 
				
			||||||
use local_treestudyplan\studyplan;
 | 
					use local_treestudyplan\studyplan;
 | 
				
			||||||
 | 
					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 local_treestudyplan\local\helpers\webservicehelper;
 | 
				
			||||||
| 
						 | 
					@ -189,24 +190,28 @@ class studyplan_editform extends formbase {
 | 
				
			||||||
            $customdata->fileoptions
 | 
					            $customdata->fileoptions
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $field = 'startdate';
 | 
					        if ($customdata->create) {
 | 
				
			||||||
        $mform->addElement('date_selector',$field,
 | 
					            // Only add these fields if a new studyplan is created, to easily initialize the first page
 | 
				
			||||||
            get_string('studyplan_startdate','local_treestudyplan'),
 | 
					            $field = 'startdate';
 | 
				
			||||||
            []);
 | 
					            $mform->addElement('date_selector',$field,
 | 
				
			||||||
        $mform->addRule($field, null, 'required', null, 'client');
 | 
					                get_string('studyplan_startdate','local_treestudyplan'),
 | 
				
			||||||
 | 
					                []);
 | 
				
			||||||
 | 
					            $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $field = 'enddate';
 | 
					            $field = 'enddate';
 | 
				
			||||||
        $mform->addElement('date_selector',$field,
 | 
					            $mform->addElement('date_selector',$field,
 | 
				
			||||||
            get_string('studyplan_startdate','local_treestudyplan'),
 | 
					                get_string('studyplan_startdate','local_treestudyplan'),
 | 
				
			||||||
            []);
 | 
					                []);
 | 
				
			||||||
        $mform->addRule($field, null, 'required', null, 'client');
 | 
					            $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $field = 'periods';
 | 
					            $field = 'periods';
 | 
				
			||||||
        $mform->addElement('text_integer',$field,
 | 
					            $mform->addElement('text_integer',$field,
 | 
				
			||||||
            get_string('studyplan_slots','local_treestudyplan'),
 | 
					                get_string('studyplan_slots','local_treestudyplan'),
 | 
				
			||||||
            ["unsigned" => true]);
 | 
					                ["unsigned" => true]);
 | 
				
			||||||
        $mform->setType($field, PARAM_INT);
 | 
					            $mform->setType($field, PARAM_INT);
 | 
				
			||||||
        $mform->addRule($field, null, 'required', null, 'client');
 | 
					            $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $aggregators = [];
 | 
					        $aggregators = [];
 | 
				
			||||||
        foreach(aggregator::list_model() as $a){
 | 
					        foreach(aggregator::list_model() as $a){
 | 
				
			||||||
| 
						 | 
					@ -330,9 +335,6 @@ class studyplan_editform extends formbase {
 | 
				
			||||||
                        'descriptionformat' => $entry->descriptionformat,
 | 
					                        'descriptionformat' => $entry->descriptionformat,
 | 
				
			||||||
                        'aggregation' => $entry->aggregation,
 | 
					                        'aggregation' => $entry->aggregation,
 | 
				
			||||||
                        'aggregation_config' => $aggregation_config,
 | 
					                        'aggregation_config' => $aggregation_config,
 | 
				
			||||||
                        'startdate' => date("Y-m-d",$entry->startdate),
 | 
					 | 
				
			||||||
                        'enddate' => date("Y-m-d",$entry->enddate),
 | 
					 | 
				
			||||||
                        'periods' => $entry->periods,
 | 
					 | 
				
			||||||
                        ]);
 | 
					                        ]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -349,7 +351,11 @@ class studyplan_editform extends formbase {
 | 
				
			||||||
            $customdata->fileoptions
 | 
					            $customdata->fileoptions
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $plan->simple_model(); // Return the simple model of the plan to make sure we can update stuff
 | 
					        /* Return the simple model of the plan to make sure we can update stuff.
 | 
				
			||||||
 | 
					           Parse it through the clean_returnvalue function of exernal api (of which studyplanservice is a subclass)
 | 
				
			||||||
 | 
					           so we return it in a consistent way 
 | 
				
			||||||
 | 
					        */
 | 
				
			||||||
 | 
					        return studyplanservice::clean_returnvalue(studyplan::simple_structure(),$plan->simple_model());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										251
									
								
								classes/form/studyplanpage_editform.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										251
									
								
								classes/form/studyplanpage_editform.php
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,251 @@
 | 
				
			||||||
 | 
					<?php
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace local_treestudyplan\form;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use local_treestudyplan\aggregator;
 | 
				
			||||||
 | 
					use local_treestudyplan\studyplan;
 | 
				
			||||||
 | 
					use local_treestudyplan\studyplanpage;
 | 
				
			||||||
 | 
					use local_treestudyplan\courseservice;
 | 
				
			||||||
 | 
					use local_treestudyplan\form\text_integer;
 | 
				
			||||||
 | 
					use local_treestudyplan\local\helpers\webservicehelper;
 | 
				
			||||||
 | 
					use local_treestudyplan\studyplanservice;
 | 
				
			||||||
 | 
					use moodle_exception;
 | 
				
			||||||
 | 
					use stdClass;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Moodleform class for the studyplan editor. A Moodleform is used here to facilitate a rich editor 
 | 
				
			||||||
 | 
					 * in the studyplan description 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class studyplanpage_editform extends formbase {
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Capability required to edit study plans
 | 
				
			||||||
 | 
					     * @var string
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    const CAP_EDIT = "local/treestudyplan:editstudyplan";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Translate parameters into customdata. 
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param object $params The parameters for form initialization
 | 
				
			||||||
 | 
					     * @return array Form data based on parameters
 | 
				
			||||||
 | 
					     */    
 | 
				
			||||||
 | 
					    public static function init_customdata(object $params) {
 | 
				
			||||||
 | 
					        $customdata = new stdClass;
 | 
				
			||||||
 | 
					        $customdata->create = $params->mode=='create'?true:false;
 | 
				
			||||||
 | 
					        if($customdata->create){
 | 
				
			||||||
 | 
					            $customdata->plan = studyplan::find_by_id($params->studyplan_id);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $customdata->page = studyplanpage::find_by_id($params->page_id);
 | 
				
			||||||
 | 
					            $customdata->plan = $customdata->page->studyplan();
 | 
				
			||||||
 | 
					            $customdata->simplemodel = $customdata->page->simple_model();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        $customdata->context = $customdata->plan->context();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $customdata->editoroptions = [
 | 
				
			||||||
 | 
					            'trusttext' => true,
 | 
				
			||||||
 | 
					            'subdirs' => true,
 | 
				
			||||||
 | 
					            'maxfiles' => 20,
 | 
				
			||||||
 | 
					            'maxbytes' => 20*1024*1024,
 | 
				
			||||||
 | 
					            'context' => \context_system::instance(), // Keep the files in system context
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        $customdata->fileoptions = [
 | 
				
			||||||
 | 
					            'subdirs' => 0,
 | 
				
			||||||
 | 
					            'maxbytes' => 10*1024*1024, // Max 10MiB should be sufficient for a picture.
 | 
				
			||||||
 | 
					            'areamaxbytes' => 10485760,
 | 
				
			||||||
 | 
					            'maxfiles' => 1, // Just one file
 | 
				
			||||||
 | 
					            'accepted_types' => ['.jpg', '.png'],
 | 
				
			||||||
 | 
					            'return_types' => FILE_INTERNAL | FILE_EXTERNAL,
 | 
				
			||||||
 | 
					        ];
 | 
				
			||||||
 | 
					        return $customdata; 
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Validate security access for this form based on the customdata generated by init_customdata
 | 
				
			||||||
 | 
					     * Return true if validation passes, false or throw an exception if it does not.
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param object $customdata The customdata for this form
 | 
				
			||||||
 | 
					     * @return bool True if security validation passes. 
 | 
				
			||||||
 | 
					     * @throws \moodle_exception if access denied for a specific reason.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static function check_security(object $customdata) {
 | 
				
			||||||
 | 
					        /*webservicehelper::require_capabilities(self::CAP_EDIT,$customdata->context); */
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Generate form data from parameters
 | 
				
			||||||
 | 
					     * Also validate parameters and access permissions here
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param object $customdata The parameters for form initialization
 | 
				
			||||||
 | 
					     * @return array Form data based on parameters
 | 
				
			||||||
 | 
					     */    
 | 
				
			||||||
 | 
					    public function init_formdata(object $customdata) {
 | 
				
			||||||
 | 
					        global $DB;
 | 
				
			||||||
 | 
					        /*  Use direct database retrieval to avoid our abstractions causing trouble
 | 
				
			||||||
 | 
					            with existing moodle code assumptions. 
 | 
				
			||||||
 | 
					            The form API does seem needlessly convoluted in it's use, but we need the editor...
 | 
				
			||||||
 | 
					        */
 | 
				
			||||||
 | 
					        if($customdata->create) {
 | 
				
			||||||
 | 
					            $plan = $customdata->plan;
 | 
				
			||||||
 | 
					            $entry = new stdClass;
 | 
				
			||||||
 | 
					            $entry->studyplan_id = $plan->id();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // By default, make the start date of a new page, continue 1 day after the last page's start date;
 | 
				
			||||||
 | 
					            $otherpages = $plan->pages();
 | 
				
			||||||
 | 
					            if(count($otherpages) > 0){
 | 
				
			||||||
 | 
					                $lastpage = $otherpages[count($otherpages) -1];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Propose 1 year after the last page's start date, if no end date is set.
 | 
				
			||||||
 | 
					                if ($lastpage->enddate(false) == null) {
 | 
				
			||||||
 | 
					                    $entry->startdate = $lastpage->startdate()->add(new \DateInterval("P1Y"))->format("U");
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // Otherwise, propose 1 day after the last page's end date
 | 
				
			||||||
 | 
					                    $entry->startdate = $lastpage->enddate()->add(new \DateInterval("P1D"))->format("U");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // Determine the next august 1st for default value purposes. Only if no other page is available
 | 
				
			||||||
 | 
					                $august = strtotime("first day of august this year");
 | 
				
			||||||
 | 
					                if($august < time()) {
 | 
				
			||||||
 | 
					                    $august = strtotime("first day of august next year");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					                $entry->startdate = $august;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            $entry->enddate = $entry->startdate + (364*24*60*60); // Not bothering about leap years here.
 | 
				
			||||||
 | 
					            $entry->periods = 4;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $entry = $DB->get_record(studyplanpage::TABLE, ['id' => $customdata->page->id()]);
 | 
				
			||||||
 | 
					            $entry->startdate = strtotime($entry->startdate);
 | 
				
			||||||
 | 
					            $entry->enddate = strtotime($entry->enddate);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Prepare the editor
 | 
				
			||||||
 | 
					        $entry = file_prepare_standard_editor(  $entry,
 | 
				
			||||||
 | 
					            'description',
 | 
				
			||||||
 | 
					            $customdata->editoroptions,
 | 
				
			||||||
 | 
					            \context_system::instance(),
 | 
				
			||||||
 | 
					            'local_treestudyplan',
 | 
				
			||||||
 | 
					            'studyplanpage',
 | 
				
			||||||
 | 
					            ($customdata->create)?null:$customdata->page->id()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return $entry;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Set up the form definition
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public function definition() {
 | 
				
			||||||
 | 
					        $mform = $this->_form;
 | 
				
			||||||
 | 
					        $customdata = (object)$this->_customdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Register integer type
 | 
				
			||||||
 | 
					        text_integer::Register();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Define the form
 | 
				
			||||||
 | 
					        $field = 'fullname';
 | 
				
			||||||
 | 
					        $mform->addElement('text',$field,
 | 
				
			||||||
 | 
					            get_string('studyplan_name','local_treestudyplan'),
 | 
				
			||||||
 | 
					            []);
 | 
				
			||||||
 | 
					        $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $field = 'shortname';
 | 
				
			||||||
 | 
					        $mform->addElement('text',$field,
 | 
				
			||||||
 | 
					            get_string('studyplan_shortname','local_treestudyplan'),
 | 
				
			||||||
 | 
					            []);
 | 
				
			||||||
 | 
					        $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $field = 'startdate';
 | 
				
			||||||
 | 
					        $mform->addElement('date_selector',$field,
 | 
				
			||||||
 | 
					            get_string('studyplan_startdate','local_treestudyplan'),
 | 
				
			||||||
 | 
					            []);
 | 
				
			||||||
 | 
					        $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $field = 'enddate';
 | 
				
			||||||
 | 
					        $mform->addElement('date_selector',$field,
 | 
				
			||||||
 | 
					            get_string('studyplan_startdate','local_treestudyplan'),
 | 
				
			||||||
 | 
					            []);
 | 
				
			||||||
 | 
					        $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $field = 'periods';
 | 
				
			||||||
 | 
					        $mform->addElement('text_integer',$field,
 | 
				
			||||||
 | 
					            get_string('studyplan_slots','local_treestudyplan'),
 | 
				
			||||||
 | 
					            ["unsigned" => true, "nonzero" => true]);
 | 
				
			||||||
 | 
					        $mform->setType($field, PARAM_INT);
 | 
				
			||||||
 | 
					        $mform->addRule($field, null, 'required', null, 'client');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $field = 'description_editor';
 | 
				
			||||||
 | 
					        $mform->addElement('editor', $field,
 | 
				
			||||||
 | 
					             get_string('studyplan_description', 'local_treestudyplan'), 
 | 
				
			||||||
 | 
					             null, 
 | 
				
			||||||
 | 
					             $customdata->editoroptions);
 | 
				
			||||||
 | 
					        $mform->setType($field, PARAM_RAW);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** 
 | 
				
			||||||
 | 
					     * Process the submitted data and perform necessary actions
 | 
				
			||||||
 | 
					     * @param object $entry The processed form data;
 | 
				
			||||||
 | 
					     * @return bool false if submission not successful
 | 
				
			||||||
 | 
					     * @throws \moodle_exception if an error must be given for a specific reason.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    protected function process_submitted_data($entry) {
 | 
				
			||||||
 | 
					        $customdata = (object)$this->_customdata;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if($customdata->create) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Use our own abstraction to update the record, so caches are maintained
 | 
				
			||||||
 | 
					            $page = studyplanpage::add(['fullname' => $entry->fullname,
 | 
				
			||||||
 | 
					                                    'shortname' => $entry->shortname,
 | 
				
			||||||
 | 
					                                    'startdate' => date("Y-m-d",$entry->startdate),
 | 
				
			||||||
 | 
					                                    'enddate' => date("Y-m-d",$entry->enddate),
 | 
				
			||||||
 | 
					                                    'periods' => $entry->periods,
 | 
				
			||||||
 | 
					                                    'studyplan_id' => $customdata->plan->id(),
 | 
				
			||||||
 | 
					                                    ]);
 | 
				
			||||||
 | 
					            // Process the provided files in the description editor
 | 
				
			||||||
 | 
					            $entry = file_postupdate_standard_editor($entry,
 | 
				
			||||||
 | 
					                                                    'description',
 | 
				
			||||||
 | 
					                                                    $customdata->editoroptions,
 | 
				
			||||||
 | 
					                                                    \context_system::instance(),
 | 
				
			||||||
 | 
					                                                    'local_treestudyplan',
 | 
				
			||||||
 | 
					                                                    'studyplan',
 | 
				
			||||||
 | 
					                                                    $page->id());   
 | 
				
			||||||
 | 
					            // Update the description
 | 
				
			||||||
 | 
					            $page->edit([
 | 
				
			||||||
 | 
					                'description' => $entry->description,
 | 
				
			||||||
 | 
					                'descriptionformat' => $entry->descriptionformat,
 | 
				
			||||||
 | 
					            ]);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $page = $customdata->page;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // Process the provided files in the description editor
 | 
				
			||||||
 | 
					            $entry = file_postupdate_standard_editor($entry,
 | 
				
			||||||
 | 
					                                                    'description',
 | 
				
			||||||
 | 
					                                                    $customdata->editoroptions,
 | 
				
			||||||
 | 
					                                                    \context_system::instance(),
 | 
				
			||||||
 | 
					                                                    'local_treestudyplan',
 | 
				
			||||||
 | 
					                                                    'studyplanpage',
 | 
				
			||||||
 | 
					                                                    $page->id());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Use our own abstraction to update the record, so caches are maintained
 | 
				
			||||||
 | 
					            $page->edit(['fullname' => $entry->fullname,
 | 
				
			||||||
 | 
					                        'shortname' => $entry->shortname,
 | 
				
			||||||
 | 
					                        'description' => $entry->description,
 | 
				
			||||||
 | 
					                        'descriptionformat' => $entry->descriptionformat,
 | 
				
			||||||
 | 
					                        'startdate' => date("Y-m-d",$entry->startdate),
 | 
				
			||||||
 | 
					                        'enddate' => date("Y-m-d",$entry->enddate),
 | 
				
			||||||
 | 
					                        'periods' => $entry->periods,
 | 
				
			||||||
 | 
					                        ]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Return the simple model of the page  to make sure we can update stuff.
 | 
				
			||||||
 | 
					           Parse it through the clean_returnvalue function of exernal api (of which studyplanservice is a subclass)
 | 
				
			||||||
 | 
					           so we return it in a consistent way 
 | 
				
			||||||
 | 
					        */
 | 
				
			||||||
 | 
					        return studyplanservice::clean_returnvalue(studyplanpage::editor_structure(),$page->editor_model());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -32,23 +32,14 @@ class text_integer extends MoodleQuickForm_text {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        $label = $this->getLabel();
 | 
					        $label = $this->getLabel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /*
 | 
					 | 
				
			||||||
        $text = '';
 | 
					 | 
				
			||||||
        if (method_exists($this, 'getText')) {
 | 
					 | 
				
			||||||
            // There currently exists code that adds a form element with an empty label.
 | 
					 | 
				
			||||||
            // If this is the case then set the label to the description.
 | 
					 | 
				
			||||||
            if (empty($label)) {
 | 
					 | 
				
			||||||
                $label = $this->getText();
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                $text = $this->getText();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }*/
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $unsigned = (isset($this->_attributes['unsigned']) && $this->_attributes['unsigned']);
 | 
					        $unsigned = (isset($this->_attributes['unsigned']) && $this->_attributes['unsigned']);
 | 
				
			||||||
 | 
					        $nonzero = (isset($this->_attributes['nonzero']) && $this->_attributes['nonzero']);
 | 
				
			||||||
        $context = array(
 | 
					        $context = array(
 | 
				
			||||||
                'element' => $elementcontext,
 | 
					                'element' => $elementcontext,
 | 
				
			||||||
                'label' => $label,
 | 
					                'label' => $label,
 | 
				
			||||||
                'unsigned' => ($unsigned)?true:false ,
 | 
					                'unsigned' => ($unsigned)?true:false ,
 | 
				
			||||||
 | 
					                'nonzero' => ($nonzero)?true:false ,
 | 
				
			||||||
                'required' => $required,
 | 
					                'required' => $required,
 | 
				
			||||||
                'advanced' => $advanced,
 | 
					                'advanced' => $advanced,
 | 
				
			||||||
                'helpbutton' => $helpbutton,
 | 
					                'helpbutton' => $helpbutton,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -137,16 +137,13 @@ class period {
 | 
				
			||||||
     * @return period[]
 | 
					     * @return period[]
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static function find_for_page(studyplanpage $page): array {
 | 
					    public static function find_for_page(studyplanpage $page): array {
 | 
				
			||||||
        if (!array_key_exists($page->id(), self::$pagecache)) {
 | 
					        $periods = [];
 | 
				
			||||||
            $periods = [];
 | 
					        // Find and add the periods to an array with the period sequence as a key.
 | 
				
			||||||
            // Find and add the periods to an array with the period sequence as a key.
 | 
					        for ($i = 1; $i <= $page->periods(); $i++) {
 | 
				
			||||||
            for ($i = 1; $i <= $page->periods(); $i++) {
 | 
					            $period = self::find($page, $i);
 | 
				
			||||||
                $period = self::find($page, $i);
 | 
					            $periods[$i] = $period;
 | 
				
			||||||
                $periods[$i] = $period;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            self::$pagecache[$page->id()] = $periods;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return self::$pagecache[$page->id()];
 | 
					        return $periods;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
| 
						 | 
					@ -286,7 +283,6 @@ class period {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        $id = $DB->insert_record(self::TABLE, $info);
 | 
					        $id = $DB->insert_record(self::TABLE, $info);
 | 
				
			||||||
        unset(self::$pagecache[$fields['page_id']]); // Invalidate the cache for this page.
 | 
					 | 
				
			||||||
        return self::find_by_id($id); // Make sure the new page is immediately cached.
 | 
					        return self::find_by_id($id); // Make sure the new page is immediately cached.
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -330,7 +326,6 @@ class period {
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unset(self::$pagecache[$this->r->page_id]); // Invalidate the cache for this page.
 | 
					 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -340,7 +335,6 @@ class period {
 | 
				
			||||||
    public function delete() : success {
 | 
					    public function delete() : success {
 | 
				
			||||||
        global $DB;
 | 
					        global $DB;
 | 
				
			||||||
        $DB->delete_records(self::TABLE, ['id' => $this->id]);
 | 
					        $DB->delete_records(self::TABLE, ['id' => $this->id]);
 | 
				
			||||||
        unset(self::$pagecache[$this->r->page_id]); // Invalidate the cache for this page.
 | 
					 | 
				
			||||||
        return success::success();
 | 
					        return success::success();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -359,7 +353,7 @@ class period {
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static function page_model(studyplanpage $page) : array {
 | 
					    public static function page_model(studyplanpage $page) : array {
 | 
				
			||||||
        $model = [];
 | 
					        $model = [];
 | 
				
			||||||
        foreach (self::find_for_page($page) as $p) {
 | 
					        foreach (self::find_for_page($page,true) as $p) {
 | 
				
			||||||
            $model[] = $p->model();
 | 
					            $model[] = $p->model();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return $model;
 | 
					        return $model;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,7 +89,7 @@ class studyplan {
 | 
				
			||||||
    private function __construct($id) {
 | 
					    private function __construct($id) {
 | 
				
			||||||
        global $DB;
 | 
					        global $DB;
 | 
				
			||||||
        $this->id = $id;
 | 
					        $this->id = $id;
 | 
				
			||||||
        $this->r = $DB->get_record(self::TABLE, ['id' => $id]);
 | 
					        $this->r = $DB->get_record(self::TABLE, ['id' => $id],"*",MUST_EXIST);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $this->aggregator = aggregator::create_or_default($this->r->aggregation, $this->r->aggregation_config);
 | 
					        $this->aggregator = aggregator::create_or_default($this->r->aggregation, $this->r->aggregation_config);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -176,11 +176,11 @@ class studyplan {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Return the studyplan pages associated with this plan
 | 
					     * Return the studyplan pages associated with this plan
 | 
				
			||||||
 | 
					     * @param bool $refresh Set to true to force a refresh of the pages
 | 
				
			||||||
     * @return studyplanpage[]
 | 
					     * @return studyplanpage[]
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function pages() : array {
 | 
					    public function pages($refresh=false) : array {
 | 
				
			||||||
 | 
					        if ( ((bool)$refresh) || empty($this->pagecache)) {
 | 
				
			||||||
        if (empty($this->pagecache)) {
 | 
					 | 
				
			||||||
            $this->pagecache = studyplanpage::find_studyplan_children($this);
 | 
					            $this->pagecache = studyplanpage::find_studyplan_children($this);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return $this->pagecache;
 | 
					        return $this->pagecache;
 | 
				
			||||||
| 
						 | 
					@ -347,11 +347,9 @@ class studyplan {
 | 
				
			||||||
        $id = $DB->insert_record(self::TABLE, $info);
 | 
					        $id = $DB->insert_record(self::TABLE, $info);
 | 
				
			||||||
        $plan = self::find_by_id($id); // Make sure the new studyplan is immediately cached.
 | 
					        $plan = self::find_by_id($id); // Make sure the new studyplan is immediately cached.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Start temporary skräpp code.
 | 
					        // Add a single page and initialize it with placeholder data 
 | 
				
			||||||
        // Add a single page and copy the names.This keeps the data sane until the upgrade to .
 | 
					        // This makes it easier to create a new study plan
 | 
				
			||||||
        // Real page management is done.
 | 
					        // On import, adding an empty page messes things up , so we have an option to skip this....
 | 
				
			||||||
        // On import, adding an empty page messes things up for now, so we have an option to skip this....
 | 
					 | 
				
			||||||
        // TODO: Remove this when proper page management is implemented.
 | 
					 | 
				
			||||||
        if (!$bare) {
 | 
					        if (!$bare) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            $pageaddable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
 | 
					            $pageaddable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
 | 
				
			||||||
| 
						 | 
					@ -398,27 +396,6 @@ class studyplan {
 | 
				
			||||||
        // Reload aggregator.
 | 
					        // Reload aggregator.
 | 
				
			||||||
        $this->aggregator = aggregator::create_or_default($this->r->aggregation, $this->r->aggregation_config);
 | 
					        $this->aggregator = aggregator::create_or_default($this->r->aggregation, $this->r->aggregation_config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Start temporary skräpp code.
 | 
					 | 
				
			||||||
        // TODO: Until proper page editing is implemented, copy data from studyplan to it's first page.
 | 
					 | 
				
			||||||
        // This keeps the data sane until the upgrade is done.
 | 
					 | 
				
			||||||
        if (count($this->pages()) == 1) {
 | 
					 | 
				
			||||||
            // Update the info to the page as well.
 | 
					 | 
				
			||||||
            $page = $this->pages()[0];
 | 
					 | 
				
			||||||
            $pageeditable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
 | 
					 | 
				
			||||||
            $pageinfo = [];
 | 
					 | 
				
			||||||
            foreach ($pageeditable as $f) {
 | 
					 | 
				
			||||||
                if (array_key_exists($f, $fields)) {
 | 
					 | 
				
			||||||
                    if ($f == "name") {
 | 
					 | 
				
			||||||
                        $pageinfo["fullname"] = $fields[$f];
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        $pageinfo[$f] = $fields[$f];
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            $page->edit($pageinfo);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // End temporary skräpp code.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return $this;
 | 
					        return $this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,14 +126,18 @@ class studyplanpage {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * End date
 | 
					     * End date
 | 
				
			||||||
     * @return \DateTime
 | 
					     * @return \DateTime|null
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public function enddate() {
 | 
					    public function enddate($farahead = true) {
 | 
				
			||||||
        if ($this->r->enddate && strlen($this->r->enddate) > 0) {
 | 
					        if ($this->r->enddate && strlen($this->r->enddate) > 0) {
 | 
				
			||||||
            return new \DateTime($this->r->enddate);
 | 
					            return new \DateTime($this->r->enddate);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Return a date 100 years into the future.
 | 
					            // Return a date 100 years into the future.
 | 
				
			||||||
            return (new \DateTime($this->r->startdate))->add(new \DateInterval("P100Y"));
 | 
					            if ($farahead) {
 | 
				
			||||||
 | 
					                return (new \DateTime($this->r->startdate))->add(new \DateInterval("P100Y"));
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -221,10 +225,13 @@ class studyplanpage {
 | 
				
			||||||
     * @param mixed $fields Parameter for new study plan page
 | 
					     * @param mixed $fields Parameter for new study plan page
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static function add($fields) : self {
 | 
					    public static function add($fields) : self {
 | 
				
			||||||
        global $CFG, $DB;
 | 
					        global $DB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!isset($fields['studyplan_id'])) {
 | 
					        if (!isset($fields['studyplan_id'])) {
 | 
				
			||||||
            throw new \InvalidArgumentException("parameter 'studyplan_id' missing");
 | 
					            throw new \InvalidArgumentException("parameter 'studyplan_id' missing");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            $plan = studyplan::find_by_id($fields['studyplan_id']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $addable = ['studyplan_id', 'fullname', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
 | 
					        $addable = ['studyplan_id', 'fullname', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
 | 
				
			||||||
| 
						 | 
					@ -241,6 +248,8 @@ class studyplanpage {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $id = $DB->insert_record(self::TABLE, $info);
 | 
					        $id = $DB->insert_record(self::TABLE, $info);
 | 
				
			||||||
 | 
					        // Refresh the page cache for the studyplan
 | 
				
			||||||
 | 
					        $plan->pages(true);
 | 
				
			||||||
        return self::find_by_id($id); // Make sure the new page is immediately cached.
 | 
					        return self::find_by_id($id); // Make sure the new page is immediately cached.
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1298,6 +1298,12 @@
 | 
				
			||||||
.features-treestudyplan .border-grey {
 | 
					.features-treestudyplan .border-grey {
 | 
				
			||||||
    border-color: #aaa;
 | 
					    border-color: #aaa;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.path-local-treestudyplan .s-studyplan-page-edit,
 | 
				
			||||||
 | 
					.features-treestudyplan .s-studyplan-page-edit {
 | 
				
			||||||
 | 
					    margin-top: auto;
 | 
				
			||||||
 | 
					    margin-bottom: auto;
 | 
				
			||||||
 | 
					    margin-left: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.path-local-treestudyplan .card.s-studyplan-card,
 | 
					.path-local-treestudyplan .card.s-studyplan-card,
 | 
				
			||||||
.features-treestudyplan .card.s-studyplan-card {
 | 
					.features-treestudyplan .card.s-studyplan-card {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ require_once("../../config.php");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require_once($CFG->libdir.'/weblib.php');
 | 
					require_once($CFG->libdir.'/weblib.php');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use local_treestudyplan\contextinfo;
 | 
				
			||||||
use \local_treestudyplan\courseservice;
 | 
					use \local_treestudyplan\courseservice;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$systemcontext = context_system::instance();
 | 
					$systemcontext = context_system::instance();
 | 
				
			||||||
| 
						 | 
					@ -61,10 +62,11 @@ if ($categoryid > 0) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require_capability('local/treestudyplan:editstudyplan', $studyplancontext);
 | 
					require_capability('local/treestudyplan:editstudyplan', $studyplancontext);
 | 
				
			||||||
$contextname = $studyplancontext->get_context_name(false, false);
 | 
					$ci = new contextinfo($studyplancontext);
 | 
				
			||||||
 | 
					$contextname = $ci->pathstr();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$PAGE->set_pagelayout('coursecategory');
 | 
					$PAGE->set_pagelayout('base');
 | 
				
			||||||
$PAGE->set_context($studyplancontext);
 | 
					//$PAGE->set_context($studyplancontext);
 | 
				
			||||||
$PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$contextname);
 | 
					$PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$contextname);
 | 
				
			||||||
$PAGE->set_heading($contextname);
 | 
					$PAGE->set_heading($contextname);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1112,4 +1112,10 @@
 | 
				
			||||||
        border-color: #aaa;
 | 
					        border-color: #aaa;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .s-studyplan-page-edit {
 | 
				
			||||||
 | 
					        margin-top: auto;
 | 
				
			||||||
 | 
					        margin-bottom: auto;
 | 
				
			||||||
 | 
					        margin-left: 0.5em;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1298,6 +1298,12 @@
 | 
				
			||||||
.features-treestudyplan .border-grey {
 | 
					.features-treestudyplan .border-grey {
 | 
				
			||||||
    border-color: #aaa;
 | 
					    border-color: #aaa;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.path-local-treestudyplan .s-studyplan-page-edit,
 | 
				
			||||||
 | 
					.features-treestudyplan .s-studyplan-page-edit {
 | 
				
			||||||
 | 
					    margin-top: auto;
 | 
				
			||||||
 | 
					    margin-bottom: auto;
 | 
				
			||||||
 | 
					    margin-left: 0.5em;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.path-local-treestudyplan .card.s-studyplan-card,
 | 
					.path-local-treestudyplan .card.s-studyplan-card,
 | 
				
			||||||
.features-treestudyplan .card.s-studyplan-card {
 | 
					.features-treestudyplan .card.s-studyplan-card {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,6 @@
 | 
				
			||||||
{{/ core_form/element-template }}
 | 
					{{/ core_form/element-template }}
 | 
				
			||||||
{{#js}}
 | 
					{{#js}}
 | 
				
			||||||
require(['local_treestudyplan/util/formfields'], function(formfields) {
 | 
					require(['local_treestudyplan/util/formfields'], function(formfields) {
 | 
				
			||||||
    formfields.text_integer({{#quote}}{{element.id}}{{/quote}},{{unsigned}});
 | 
					    formfields.text_integer({{#quote}}{{element.id}}{{/quote}},{{unsigned}},{{nonzero}});
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
{{/js}}
 | 
					{{/js}}
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,7 @@ require_capability('local/treestudyplan:viewuserreports', $studyplancontext);
 | 
				
			||||||
$contextname = $studyplancontext->get_context_name(false, false);
 | 
					$contextname = $studyplancontext->get_context_name(false, false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$PAGE->set_pagelayout('base');
 | 
					$PAGE->set_pagelayout('base');
 | 
				
			||||||
$PAGE->set_context($studyplancontext);
 | 
					//$PAGE->set_context($studyplancontext);
 | 
				
			||||||
$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
 | 
					$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
 | 
				
			||||||
$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
 | 
					$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Reference in a new issue