Updated mform helpers. some work on full page support
This commit is contained in:
parent
93e174967f
commit
702435566d
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
|
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
|
|
@ -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
|
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',
|
||||
args: { id: studyplan.id}
|
||||
}])[0].done(function(response){
|
||||
app.activestudyplan = ProcessStudyplan(response,true);
|
||||
app.activestudyplan = ProcessStudyplan(response);
|
||||
debug.info('studyplan processed');
|
||||
app.loadingstudyplan = false;
|
||||
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 Debugger from './util/debugger';
|
||||
import {download,upload} from './downloader';
|
||||
import {ProcessStudyplan} from './studyplan-processor';
|
||||
import {ProcessStudyplan, ProcessStudyplanPage} from './studyplan-processor';
|
||||
|
||||
import TSComponents from './treestudyplan-components';
|
||||
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
|
||||
*/
|
||||
|
@ -1032,6 +1097,7 @@ export default {
|
|||
`
|
||||
});
|
||||
|
||||
// TAG: Start studyplan component
|
||||
/*
|
||||
* T-STUDYPLAN
|
||||
*/
|
||||
|
@ -1057,6 +1123,11 @@ export default {
|
|||
'shortname': '',
|
||||
'color': '#DDDDDD',
|
||||
},
|
||||
page: {
|
||||
'id': -1,
|
||||
'name' : '',
|
||||
'shortname' : ''
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
toolbox_shown: false,
|
||||
|
@ -1091,14 +1162,14 @@ export default {
|
|||
text: strings.studyplan_text,
|
||||
cache: {
|
||||
linelayers: {},
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
if(this.page.studylines.length == 0){
|
||||
if(this.value.pages[0].studylines.length == 0){
|
||||
// start in editmode if studylines are empty
|
||||
this.edit.studyline.editmode = true;
|
||||
}
|
||||
|
@ -1109,23 +1180,20 @@ export default {
|
|||
ItemEventBus.$emit('redrawLines');
|
||||
},
|
||||
computed: {
|
||||
columns() {
|
||||
return 1+ (this.page.periods * 2);
|
||||
|
||||
},
|
||||
columns_stylerule() {
|
||||
methods: {
|
||||
columns(page) {
|
||||
return 1+ (page.periods * 2);
|
||||
},
|
||||
columns_stylerule(page) {
|
||||
// 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
|
||||
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)";
|
||||
}
|
||||
return s+";";
|
||||
},
|
||||
page(){
|
||||
// FIXME: Temporary hack until real page management is implemented
|
||||
return this.value.pages[0];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
trashbin_accepts(type){
|
||||
if(type.item){
|
||||
return true;
|
||||
|
@ -1133,7 +1201,7 @@ export default {
|
|||
return false;
|
||||
}
|
||||
},
|
||||
countLineLayers(line){
|
||||
countLineLayers(line,page){
|
||||
// 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.....
|
||||
if( this.cache.linelayers[line.id]
|
||||
|
@ -1144,7 +1212,7 @@ export default {
|
|||
else
|
||||
{
|
||||
let maxLayer = -1;
|
||||
for(let i = 0; i <= this.page.periods; i++){
|
||||
for(let i = 0; i <= page.periods; i++){
|
||||
if(line.slots[i]){
|
||||
const slot = line.slots[i];
|
||||
// Determine the amount of used layers in a studyline slit
|
||||
|
@ -1303,11 +1371,11 @@ export default {
|
|||
}).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
|
||||
// so big that it hides this slot
|
||||
const forGradable = (type == 'gradable')?true:false;
|
||||
const periods = this.page.periods;
|
||||
const periods = page.periods;
|
||||
let show = true;
|
||||
for(let i = 0; i < periods; i++){
|
||||
if(line.slots[index-i] && line.slots[index-i].courses){
|
||||
|
@ -1355,6 +1423,9 @@ export default {
|
|||
},
|
||||
toolbox_switched(event){
|
||||
this.$emit('toggletoolbox',event);
|
||||
},
|
||||
pagecreated(page) {
|
||||
this.value.pages.push(page);
|
||||
}
|
||||
}
|
||||
,
|
||||
|
@ -1390,11 +1461,34 @@ export default {
|
|||
><i class='fa fa-gear'></i> {{text.edit$core}}</t-studyplan-edit>
|
||||
</span>
|
||||
<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>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<b-tabs content-class="mt-1">
|
||||
<!-- New Tab Button (Using tabs-end slot) -->
|
||||
<template #tabs-end>
|
||||
<t-studyplan-page-edit
|
||||
:studyplan="value"
|
||||
v-model="create.page"
|
||||
type="link"
|
||||
mode="create"
|
||||
@created="pagecreated"
|
||||
><i class='fa fa-plus'></i></t-studyplan-page-edit>
|
||||
</template>
|
||||
<b-tab
|
||||
v-for="(page,pageindex) in value.pages"
|
||||
:key="page.id"
|
||||
>
|
||||
<template #title>
|
||||
{{page.shortname}}
|
||||
<t-studyplan-page-edit
|
||||
v-model="value.pages[pageindex]"
|
||||
:studyplan="value"
|
||||
type="link"
|
||||
></t-studyplan-page-edit>
|
||||
</template>
|
||||
<div class='t-studyplan-content-edit' v-if="edit.studyline.editmode">
|
||||
<drop-list
|
||||
:items="page.studylines"
|
||||
|
@ -1430,12 +1524,12 @@ export default {
|
|||
|
||||
<!-- Now paint the headings column -->
|
||||
<div class='t-studyplan-headings'>
|
||||
<s-studyline-header-heading></s-studyline-header-heading>
|
||||
<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)+1'
|
||||
:layers='countLineLayers(line,page)+1'
|
||||
:class=" 't-studyline' + ((lineindex%2==0)?' odd ' :' even ' )
|
||||
+ ((lineindex==0)?' first ':' ')
|
||||
+ ((lineindex==page.studylines.length-1)?' last ':' ')"
|
||||
|
@ -1443,10 +1537,11 @@ export default {
|
|||
</div>
|
||||
<!-- Next, paint all the cells in the scrollable -->
|
||||
<div class="t-studyplan-scrollable" >
|
||||
<div class="t-studyplan-timeline" :style="columns_stylerule">
|
||||
<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
|
||||
|
@ -1463,11 +1558,11 @@ export default {
|
|||
<!-- 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="(layernr,layeridx) in countLineLayers(line,page)+1"
|
||||
><template v-for="(n,index) in (page.periods+1)"
|
||||
>
|
||||
<t-studyline-slot
|
||||
v-if="index > 0 && showslot(line, index, layeridx, 'gradable')"
|
||||
v-if="index > 0 && showslot(page,line, index, layeridx, 'gradable')"
|
||||
type='gradable'
|
||||
v-model="line.slots[index].courses"
|
||||
:key="'c-'+lineindex+'-'+index+'-'+layernr"
|
||||
|
@ -1480,12 +1575,12 @@ export default {
|
|||
: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 ':' ')"
|
||||
+ ((layernr == countLineLayers(line,page))?' lastlyr ':' ')
|
||||
+ ((layernr == countLineLayers(line,page)+1)?' newlyr ':' ')"
|
||||
></t-studyline-slot
|
||||
><t-studyline-slot
|
||||
type='filter'
|
||||
v-if="showslot(line, index, layeridx, 'filter')"
|
||||
v-if="showslot(page,line, index, layeridx, 'filter')"
|
||||
v-model="line.slots[index].filters"
|
||||
:key="'f-'+lineindex+'-'+index+'-'+layernr"
|
||||
:slotindex="index"
|
||||
|
@ -1497,8 +1592,8 @@ export default {
|
|||
+ ((lineindex==0 && layernr==1)?' first ':'')
|
||||
+ ((lineindex==page.studylines.length-1)?' last ':' ')
|
||||
+ ((index==page.periods)?' rightmost':'')
|
||||
+ ((layernr == countLineLayers(line))?' lastlyr ':' ')
|
||||
+ ((layernr == countLineLayers(line)+1)?' newlyr ':' ')"
|
||||
+ ((layernr == countLineLayers(line,page))?' lastlyr ':' ')
|
||||
+ ((layernr == countLineLayers(line,page)+1)?' newlyr ':' ')"
|
||||
>
|
||||
</t-studyline-slot
|
||||
></template
|
||||
|
@ -1545,7 +1640,7 @@ export default {
|
|||
</b-container>
|
||||
</b-modal>
|
||||
<b-modal
|
||||
:id="'modal-edit-studyline-'+value.id"
|
||||
:id="'modal-edit-studyline-'+page.id"
|
||||
size="lg"
|
||||
ok-variant="primary"
|
||||
:title="text.studyline_edit"
|
||||
|
@ -1577,6 +1672,8 @@ export default {
|
|||
</b-row>
|
||||
</b-container>
|
||||
</b-modal>
|
||||
</b-tab>
|
||||
</b-tabs>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
|
|
|
@ -63,9 +63,21 @@ export function ProcessStudyplans(studyplans){
|
|||
* @returns Processed studyplan
|
||||
*/
|
||||
export function ProcessStudyplan(studyplan){
|
||||
let connections = {};
|
||||
for(const ip in studyplan.pages){
|
||||
const page = studyplan.pages[ip];
|
||||
ProcessStudyplanPage(page);
|
||||
}
|
||||
return studyplan;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
|
||||
|
@ -123,6 +135,5 @@ export function ProcessStudyplan(studyplan){
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return studyplan;
|
||||
return page;
|
||||
}
|
||||
|
|
|
@ -170,7 +170,10 @@ export default {
|
|||
*/
|
||||
Vue.component('s-studyline-header-heading', {
|
||||
props: {
|
||||
|
||||
identifier: {
|
||||
type: Number, // Page reference.
|
||||
default() { return 0;}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -186,14 +189,16 @@ export default {
|
|||
|
||||
},
|
||||
methods: {
|
||||
onHeaderHeightChange(newheight){
|
||||
onHeaderHeightChange(newheight,identifier){
|
||||
if (this.identifier == identifier) {
|
||||
if(this.$refs.main){
|
||||
this.$refs.main.style.height = `${newheight}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
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: {
|
||||
type: Object, // dict with layer as index
|
||||
},
|
||||
identifier: {
|
||||
type: Number, // Page reference.
|
||||
default() { return 0;}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
const self=this;
|
||||
|
@ -209,7 +218,7 @@ export default {
|
|||
self.resizeListener = new ResizeObserver(() => {
|
||||
if(self.$refs.main){
|
||||
const size = self.$refs.main.getBoundingClientRect();
|
||||
ItemEventBus.$emit('headerHeightChange', size.height);
|
||||
ItemEventBus.$emit('headerHeightChange', size.height, self.identifier);
|
||||
}
|
||||
}).observe(self.$refs.main);
|
||||
}
|
||||
|
@ -244,7 +253,7 @@ export default {
|
|||
};
|
||||
},
|
||||
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>
|
||||
<b-tooltip
|
||||
:target="'s-period-'+value.id" triggers="hover"
|
||||
|
|
|
@ -2,18 +2,22 @@
|
|||
* convert a text field into an integer only text field
|
||||
* @param {string} id The Id of the form field
|
||||
* @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);
|
||||
|
||||
if (element) {
|
||||
element.addEventListener("input",() => {
|
||||
var val = element.value;
|
||||
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
|
||||
if (unsigned) {
|
||||
element.value = val.replace(/[^0-9]/g,'');
|
||||
if (nonzero && val == 0) {
|
||||
element.value = '';
|
||||
}
|
||||
} else {
|
||||
element.value = val.replace(/[^0-9-]/g,'').replace(/(.{1})(-)/g,'$1');
|
||||
}
|
||||
|
|
|
@ -65,6 +65,9 @@ export default {
|
|||
loading: true,
|
||||
uuid: create_uuid(),
|
||||
text: strings,
|
||||
submitok: false,
|
||||
observer: null,
|
||||
inputs: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -87,7 +90,7 @@ export default {
|
|||
// Process the collected javascript;
|
||||
const js = processCollectedJavascript(data.javascript);
|
||||
replaceNodeContents(self.$refs["content"], html, js);
|
||||
|
||||
self.initListenChanges();
|
||||
}).catch(notification.exception);
|
||||
|
||||
},
|
||||
|
@ -104,6 +107,7 @@ export default {
|
|||
const formdata = new FormData(form);
|
||||
const data = new URLSearchParams(formdata).toString();
|
||||
|
||||
if(this.checkSave()){
|
||||
call([{
|
||||
methodname: 'local_treestudyplan_submit_mform',
|
||||
args: {formname: self.name, params: JSON.stringify(self.params), formdata: data}
|
||||
|
@ -112,6 +116,51 @@ export default {
|
|||
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: `
|
||||
<span class='mform-container'>
|
||||
|
@ -127,6 +176,7 @@ export default {
|
|||
id="'modal-'+uuid"
|
||||
@shown="onShown"
|
||||
@ok="onSave"
|
||||
:ok-disabled="!submitok"
|
||||
:title="title"
|
||||
:ok-title="text.save$core"
|
||||
><div :class="'s-mform-content'" ref="content"
|
||||
|
|
|
@ -36,6 +36,10 @@ class contextinfo {
|
|||
*/
|
||||
public function __construct(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() {
|
||||
|
||||
$ctxpath = array_reverse($this->context->get_parent_context_ids(true));
|
||||
if (count($ctxpath) > 1 && $ctxpath[0] == 1) {
|
||||
array_shift($ctxpath);
|
||||
}
|
||||
|
||||
return [
|
||||
"name" => $this->context->get_context_name(false, false),
|
||||
"shortname" => $this->context->get_context_name(false, true),
|
||||
"path" => array_map(function($c) {
|
||||
return \context::instance_by_id($c)->get_context_name(false, false);
|
||||
}, $ctxpath),
|
||||
"shortpath" => array_map(function($c) {
|
||||
return \context::instance_by_id($c)->get_context_name(false, true);
|
||||
}, $ctxpath),
|
||||
"path" => $this->path(false),
|
||||
"shortpath" => $this->path(true),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param int $contextid Context id
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace local_treestudyplan\form;
|
|||
|
||||
use local_treestudyplan\aggregator;
|
||||
use local_treestudyplan\studyplan;
|
||||
use local_treestudyplan\studyplanservice;
|
||||
use local_treestudyplan\courseservice;
|
||||
use local_treestudyplan\form\text_integer;
|
||||
use local_treestudyplan\local\helpers\webservicehelper;
|
||||
|
@ -189,6 +190,8 @@ class studyplan_editform extends formbase {
|
|||
$customdata->fileoptions
|
||||
);
|
||||
|
||||
if ($customdata->create) {
|
||||
// Only add these fields if a new studyplan is created, to easily initialize the first page
|
||||
$field = 'startdate';
|
||||
$mform->addElement('date_selector',$field,
|
||||
get_string('studyplan_startdate','local_treestudyplan'),
|
||||
|
@ -208,6 +211,8 @@ class studyplan_editform extends formbase {
|
|||
$mform->setType($field, PARAM_INT);
|
||||
$mform->addRule($field, null, 'required', null, 'client');
|
||||
|
||||
}
|
||||
|
||||
$aggregators = [];
|
||||
foreach(aggregator::list_model() as $a){
|
||||
// Add method only if not deprecated or currently used
|
||||
|
@ -330,9 +335,6 @@ class studyplan_editform extends formbase {
|
|||
'descriptionformat' => $entry->descriptionformat,
|
||||
'aggregation' => $entry->aggregation,
|
||||
'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
|
||||
);
|
||||
|
||||
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();
|
||||
|
||||
/*
|
||||
$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']);
|
||||
$nonzero = (isset($this->_attributes['nonzero']) && $this->_attributes['nonzero']);
|
||||
$context = array(
|
||||
'element' => $elementcontext,
|
||||
'label' => $label,
|
||||
'unsigned' => ($unsigned)?true:false ,
|
||||
'nonzero' => ($nonzero)?true:false ,
|
||||
'required' => $required,
|
||||
'advanced' => $advanced,
|
||||
'helpbutton' => $helpbutton,
|
||||
|
|
|
@ -137,16 +137,13 @@ class period {
|
|||
* @return period[]
|
||||
*/
|
||||
public static function find_for_page(studyplanpage $page): array {
|
||||
if (!array_key_exists($page->id(), self::$pagecache)) {
|
||||
$periods = [];
|
||||
// Find and add the periods to an array with the period sequence as a key.
|
||||
for ($i = 1; $i <= $page->periods(); $i++) {
|
||||
$period = self::find($page, $i);
|
||||
$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);
|
||||
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.
|
||||
}
|
||||
|
||||
|
@ -330,7 +326,6 @@ class period {
|
|||
}
|
||||
}
|
||||
|
||||
unset(self::$pagecache[$this->r->page_id]); // Invalidate the cache for this page.
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -340,7 +335,6 @@ class period {
|
|||
public function delete() : success {
|
||||
global $DB;
|
||||
$DB->delete_records(self::TABLE, ['id' => $this->id]);
|
||||
unset(self::$pagecache[$this->r->page_id]); // Invalidate the cache for this page.
|
||||
return success::success();
|
||||
}
|
||||
|
||||
|
@ -359,7 +353,7 @@ class period {
|
|||
*/
|
||||
public static function page_model(studyplanpage $page) : array {
|
||||
$model = [];
|
||||
foreach (self::find_for_page($page) as $p) {
|
||||
foreach (self::find_for_page($page,true) as $p) {
|
||||
$model[] = $p->model();
|
||||
}
|
||||
return $model;
|
||||
|
|
|
@ -89,7 +89,7 @@ class studyplan {
|
|||
private function __construct($id) {
|
||||
global $DB;
|
||||
$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);
|
||||
}
|
||||
|
@ -176,11 +176,11 @@ class studyplan {
|
|||
}
|
||||
/**
|
||||
* Return the studyplan pages associated with this plan
|
||||
* @param bool $refresh Set to true to force a refresh of the pages
|
||||
* @return studyplanpage[]
|
||||
*/
|
||||
public function pages() : array {
|
||||
|
||||
if (empty($this->pagecache)) {
|
||||
public function pages($refresh=false) : array {
|
||||
if ( ((bool)$refresh) || empty($this->pagecache)) {
|
||||
$this->pagecache = studyplanpage::find_studyplan_children($this);
|
||||
}
|
||||
return $this->pagecache;
|
||||
|
@ -347,11 +347,9 @@ class studyplan {
|
|||
$id = $DB->insert_record(self::TABLE, $info);
|
||||
$plan = self::find_by_id($id); // Make sure the new studyplan is immediately cached.
|
||||
|
||||
// Start temporary skräpp code.
|
||||
// Add a single page and copy the names.This keeps the data sane until the upgrade to .
|
||||
// Real page management is done.
|
||||
// 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.
|
||||
// Add a single page and initialize it with placeholder data
|
||||
// This makes it easier to create a new study plan
|
||||
// On import, adding an empty page messes things up , so we have an option to skip this....
|
||||
if (!$bare) {
|
||||
|
||||
$pageaddable = ['name', 'shortname', 'description', 'periods', 'startdate', 'enddate'];
|
||||
|
@ -398,27 +396,6 @@ class studyplan {
|
|||
// Reload aggregator.
|
||||
$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;
|
||||
}
|
||||
|
||||
|
|
|
@ -126,14 +126,18 @@ class studyplanpage {
|
|||
|
||||
/**
|
||||
* End date
|
||||
* @return \DateTime
|
||||
* @return \DateTime|null
|
||||
*/
|
||||
public function enddate() {
|
||||
public function enddate($farahead = true) {
|
||||
if ($this->r->enddate && strlen($this->r->enddate) > 0) {
|
||||
return new \DateTime($this->r->enddate);
|
||||
} else {
|
||||
// Return a date 100 years into the future.
|
||||
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
|
||||
*/
|
||||
public static function add($fields) : self {
|
||||
global $CFG, $DB;
|
||||
global $DB;
|
||||
|
||||
if (!isset($fields['studyplan_id'])) {
|
||||
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'];
|
||||
|
@ -241,6 +248,8 @@ class studyplanpage {
|
|||
}
|
||||
|
||||
$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.
|
||||
}
|
||||
|
||||
|
|
|
@ -1298,6 +1298,12 @@
|
|||
.features-treestudyplan .border-grey {
|
||||
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,
|
||||
.features-treestudyplan .card.s-studyplan-card {
|
||||
|
|
|
@ -24,6 +24,7 @@ require_once("../../config.php");
|
|||
|
||||
require_once($CFG->libdir.'/weblib.php');
|
||||
|
||||
use local_treestudyplan\contextinfo;
|
||||
use \local_treestudyplan\courseservice;
|
||||
|
||||
$systemcontext = context_system::instance();
|
||||
|
@ -61,10 +62,11 @@ if ($categoryid > 0) {
|
|||
}
|
||||
|
||||
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_context($studyplancontext);
|
||||
$PAGE->set_pagelayout('base');
|
||||
//$PAGE->set_context($studyplancontext);
|
||||
$PAGE->set_title(get_string('cfg_plans', 'local_treestudyplan')." - ".$contextname);
|
||||
$PAGE->set_heading($contextname);
|
||||
|
||||
|
|
|
@ -1112,4 +1112,10 @@
|
|||
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 {
|
||||
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,
|
||||
.features-treestudyplan .card.s-studyplan-card {
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
{{/ core_form/element-template }}
|
||||
{{#js}}
|
||||
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}}
|
|
@ -64,7 +64,7 @@ require_capability('local/treestudyplan:viewuserreports', $studyplancontext);
|
|||
$contextname = $studyplancontext->get_context_name(false, false);
|
||||
|
||||
$PAGE->set_pagelayout('base');
|
||||
$PAGE->set_context($studyplancontext);
|
||||
//$PAGE->set_context($studyplancontext);
|
||||
$PAGE->set_title(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
|
||||
$PAGE->set_heading(get_string('view_plan', 'local_treestudyplan')." - ".$contextname);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user