{"version":3,"file":"studyplan-editor-components.min.js","sources":["../src/studyplan-editor-components.js"],"sourcesContent":["/*eslint no-var: \"error\"*/\n/*eslint no-console: \"off\"*/\n/*eslint no-unused-vars: warn */\n/*eslint max-len: [\"error\", { \"code\": 160 }] */\n/*eslint-disable no-trailing-spaces */\n/*eslint-env es6*/\n// Put this file in path/to/plugin/amd/src\n\nimport {SimpleLine} from \"./simpleline/simpleline\";\nimport {call} from 'core/ajax';\nimport notification from 'core/notification';\nimport {get_strings} from 'core/str';\nimport {load_stringkeys, load_strings, format_date, datespaninfo} from './util/string-helper';\nimport {objCopy,transportItem} from './studyplan-processor';\nimport Debugger from './util/debugger';\nimport {download,upload} from './downloader';\nimport {ProcessStudyplan} from './studyplan-processor';\n\n\nconst STUDYPLAN_EDITOR_FIELDS = \n['name','shortname','description','idnumber','context_id', 'aggregation','aggregation_config'];\nconst STUDYPLAN_EDITOR_PAGE_FIELDS = //TODO: Add 'fullname', 'shortname' and 'description' when implementing proper page management\n['context_id', 'periods','startdate','enddate'];\nconst PERIOD_EDITOR_FIELDS = \n['fullname','shortname','startdate','enddate'];\n\nconst LINE_GRAVITY = 1.3;\n\nconst datechanger_globals = {\n default: false,\n defaultchoice: false,\n hidewarn: false,\n};\n\nexport default {\n STUDYPLAN_EDITOR_FIELDS: STUDYPLAN_EDITOR_FIELDS, // make copy available in plugin\n install(Vue/*,options*/){\n\n let debug = new Debugger(\"treestudyplan-editor\");\n \n /************************************\n * *\n * Treestudyplan Editor components *\n * *\n ************************************/\n\n /**\n * Check if element is visible\n * @param {Object} elem The element to check\n * @returns {boolean} True if visible\n */\n function isVisible(elem){\n return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );\n }\n\n\n // Create new eventbus for interaction between item components\n const ItemEventBus = new Vue();\n\n let string_keys = load_stringkeys({\n conditions: [\n { value: null, textkey: 'condition_default'},\n { value: 'ALL', textkey: 'condition_all'},\n { value: '67', textkey: 'condition_67'},\n { value: '50', textkey: 'condition_50'},\n { value: 'ANY', textkey: 'condition_any'},\n ],\n });\n\n let strings = load_strings({\n studyplan_text: {\n studyline_editmode: 'studyline_editmode',\n editmode_modules_hidden:'editmode_modules_hidden',\n studyline_add: 'studyline_add',\n add$core: 'add$core',\n edit$core: 'edit$core',\n studyline_name: 'studyline_name',\n studyline_name_ph: 'studyline_name_ph',\n studyline_shortname: 'studyline_shortname',\n studyline_shortname_ph: 'studyline_shortname_ph',\n studyline_color: 'studyline_color',\n associations: 'associations',\n associated_cohorts: 'associated_cohorts',\n associated_users: 'associated_users',\n studyline_edit: 'studyline_edit',\n studyplan_name: 'studyplan_name',\n studyplan_name_ph: 'studyplan_name_ph',\n studyplan_shortname: 'studyplan_shortname',\n studyplan_shortname_ph: 'studyplan_shortname_ph',\n studyplan_description: 'studyplan_description',\n studyplan_description_ph: 'studyplan_description_ph',\n studyplan_idnumber: 'studyplan_idnumber',\n studyplan_idnumber_ph: 'studyplan_idnumber_ph',\n studyplan_slots: 'studyplan_slots',\n studyplan_startdate: 'studyplan_startdate',\n studyplan_enddate: 'studyplan_enddate',\n },\n studyplan_advanced: {\n advanced_tools: 'advanced_tools',\n confirm_cancel: 'confirm_cancel',\n confirm_ok: 'confirm_ok',\n success$core: 'success$core',\n error$core: 'failed$core',\n advanced_converted: 'advanced_converted',\n advanced_skipped: 'advanced_skipped',\n advanced_failed: 'advanced_failed',\n advanced_locked: 'advanced_locked',\n advanced_multiple: 'advanced_multiple',\n advanced_error: 'advanced_error',\n advanced_tools_heading: 'advanced_tools_heading',\n advanced_warning_title: 'advanced_warning_title',\n advanced_warning: 'advanced_warning',\n advanced_pick_scale: 'advanced_pick_scale',\n advanced_course_manipulation_title: 'advanced_course_manipulation_title',\n advanced_force_scale_title: 'advanced_force_scale_title',\n advanced_force_scale_desc: 'advanced_force_scale_desc',\n advanced_force_scale_button: 'advanced_force_scale_button',\n advanced_disable_autoenddate_title: 'advanced_disable_autoenddate_title',\n advanced_disable_autoenddate_desc: 'advanced_disable_autoenddate_desc',\n advanced_disable_autoenddate_button: 'advanced_disable_autoenddate_button',\n advanced_confirm_header: 'advanced_confirm_header',\n advanced_force_scale_confirm: 'advanced_force_scale_confirm',\n advanced_import: 'advanced_import',\n advanced_export: 'advanced_export',\n advanced_export_csv: 'advanced_export_csv',\n advanced_import_from_file: 'advanced_import_from_file',\n advanced_purge: \"advanced_purge\",\n advanced_purge_expl: \"advanced_purge_expl\",\n advanced_cascade_cohortsync_title: \"advanced_cascade_cohortsync_title\",\n advanced_cascade_cohortsync_desc: \"advanced_cascade_cohortsync_desc\",\n advanced_cascade_cohortsync: \"advanced_cascade_cohortsync\",\n },\n studyplan_edit: {\n studyplan_edit: 'studyplan_edit',\n studyplan_name: 'studyplan_name',\n studyplan_name_ph: 'studyplan_name_ph',\n studyplan_shortname: 'studyplan_shortname',\n studyplan_shortname_ph: 'studyplan_shortname_ph',\n studyplan_description: 'studyplan_description',\n studyplan_description_ph: 'studyplan_description_ph',\n studyplan_idnumber: 'studyplan_idnumber',\n studyplan_idnumber_ph: 'studyplan_idnumber_ph',\n studyplan_context: 'studyplan_context',\n studyplan_slots: 'studyplan_slots',\n studyplan_startdate: 'studyplan_startdate',\n studyplan_enddate: 'studyplan_enddate',\n choose_aggregation_style: 'choose_aggregation_style',\n setting_bistate_thresh_excellent: 'setting_bistate_thresh_excellent',\n settingdesc_bistate_thresh_excellent: 'settingdesc_bistate_thresh_excellent',\n setting_bistate_thresh_good: 'setting_bistate_thresh_good',\n settingdesc_bistate_thresh_good: 'settingdesc_bistate_thresh_good',\n setting_bistate_thresh_completed: 'setting_bistate_thresh_completed',\n settingdesc_bistate_thresh_completed: 'settingdesc_bistate_thresh_completed',\n setting_bistate_support_failed: 'setting_bistate_support_failed',\n settingdesc_bistate_support_failed: 'settingdesc_bistate_support_failed',\n setting_bistate_thresh_progress: 'setting_bistate_thresh_progress',\n settingdesc_bistate_thresh_progress: 'settingdesc_bistate_thresh_progress',\n setting_bistate_accept_pending_submitted: 'setting_bistate_accept_pending_submitted',\n settingdesc_bistate_accept_pending_submitted: 'settingdesc_bistate_accept_pending_submitted',\n },\n period_edit: {\n edit: 'period_edit',\n fullname: 'studyplan_name',\n shortname: 'studyplan_shortname',\n startdate: 'studyplan_startdate',\n enddate: 'studyplan_enddate',\n },\n course_timing: {\n title: 'course_timing_title',\n desc: 'course_timing_desc',\n question: 'course_timing_question',\n warning: 'course_timing_warning',\n timing_ok: 'course_timing_ok',\n timing_off: 'course_timing_off',\n course: 'course$core',\n period: 'period',\n yes: 'yes$core',\n no: 'no$core',\n duration: 'duration',\n years: 'years$core',\n year: 'year$core',\n weeks: 'weeks$core',\n week: 'week$core',\n days: 'days$core',\n day: 'day$core',\n rememberchoice: 'course_timing_rememberchoice',\n hidewarning: 'course_timing_hidewarning',\n periodspan: 'course_period_span',\n periods: 'periods',\n periodspan_desc: 'course_period_span_desc',\n },\n studyplan_associate: {\n associations: 'associations',\n associated_cohorts: 'associated_cohorts',\n associated_users: 'associated_users',\n associate_cohorts: 'associate_cohorts',\n associate_users: 'associate_users',\n add_association: 'add_association',\n delete_association: 'delete_association',\n associations_empty: 'associations_empty',\n associations_search: 'associations_search',\n cohorts: 'cohorts',\n users: 'users',\n selected: 'selected',\n name: 'name',\n context: 'context',\n },\n item_text: {\n select_conditions: \"select_conditions\",\n item_configuration: \"item_configuration\",\n },\n item_course_text: {\n select_conditions: \"select_conditions\",\n select_grades: \"select_grades\",\n coursetiming_past: \"coursetiming_past\",\n coursetiming_present: \"coursetiming_present\",\n coursetiming_future: \"coursetiming_future\", \n grade_include: \"grade_include\", \n grade_require: \"grade_require\",\n ok: \"ok$core\", \n },\n invalid: {\n error: 'error',\n },\n completion: {\n completion_completed: \"completion_completed\",\n completion_incomplete: \"completion_incomplete\",\n aggregation_all: \"aggregation_all\",\n aggregation_any: \"aggregation_any\",\n aggregation_overall_all: \"aggregation_overall_all\",\n aggregation_overall_any: \"aggregation_overall_any\",\n completion_not_configured: \"completion_not_configured\",\n configure_completion: \"configure_completion\",\n },\n badge: {\n share_badge: \"share_badge\",\n dateissued: \"dateissued\",\n dateexpire: \"dateexpire\",\n badgeinfo: \"badgeinfo\",\n },\n\n });\n\n\n /*\n * T-STUDYPLAN-ADVANCED\n */\n Vue.component('t-studyplan-advanced', {\n props: {\n value: {\n type: Object,\n default(){ return null;},\n },\n },\n data() {\n return {\n force_scales: {\n selected_scale: null,\n result: [],\n },\n text: strings.studyplan_advanced,\n };\n },\n created() {\n },\n mounted() {\n },\n updated() {\n\n },\n computed: {\n scales(){\n return [{\n id: null,\n disabled: true,\n name: this.text.advanced_pick_scale,\n }].concat(this.value.advanced.force_scales.scales);\n },\n },\n methods: {\n disable_autoenddate(){\n const self=this;\n call([{\n methodname: 'local_treestudyplan_disable_autoenddate',\n args: {\n studyplan_id: this.value.id,\n }\n }])[0].done(function(response){\n self.$bvModal.msgBoxConfirm((response.success?self.text.success$core:self.text.error$core)\n + \"\\n\" + response.msg);\n }).fail(notification.exception);\n },\n force_scales_start(){\n // set confirmation box\n const self=this;\n this.$bvModal.msgBoxConfirm(this.text.advanced_force_scale_confirm,{\n title: this.text.advanced_force_scale_confirm,\n okVariant: 'danger',\n okTitle: this.text.confirm_ok,\n cancelTitle: this.text.confirm_cancel,\n }).then( value => {\n if(value == true){\n call([{\n methodname: 'local_treestudyplan_force_studyplan_scale',\n args: {\n studyplan_id: this.value.id,\n scale_id: this.force_scales.selected_scale,\n }\n }])[0].done(function(response){\n self.force_scales.result = response;\n }).fail(notification.exception);\n }\n });\n },\n export_plan(format){\n const self = this;\n if(format == undefined || ![\"json\",\"csv\"].includes(format)){\n format = \"json\";\n }\n call([{\n methodname: 'local_treestudyplan_export_plan',\n args: {\n studyplan_id: this.value.id,\n format: format,\n },\n }])[0].done(function(response){\n \n download(self.value.shortname+\".\"+format,response.content,response.format);\n }).fail(notification.exception); \n },\n import_studylines(){\n //const self = this;\n upload((filename,content)=>{\n call([{\n methodname: 'local_treestudyplan_import_studylines',\n args: {\n studyplan_id: this.value.id,\n content: content,\n format: \"application/json\",\n },\n }])[0].done(function(response){\n if(response.success){\n location.reload();\n } else {\n debug.error(\"Import failed: \",response.msg);\n }\n \n }).fail(notification.exception); \n }, \"application/json\");\n },\n purge_studyline(){\n call([{\n methodname: 'local_treestudyplan_delete_studyplan',\n args: {\n id: this.value.id,\n force: true,\n },\n }])[0].done(function(response){\n if(response.success){\n location.reload();\n } else {\n debug.error(\"Could not delete plan: \",response.msg);\n }\n \n }).fail(notification.exception); \n },\n cascade_cohortsync(){\n const self = this;\n call([{\n methodname: 'local_treestudyplan_cascade_cohortsync',\n args: {\n studyplan_id: this.value.id,\n },\n }])[0].done(function(response){\n self.$bvModal.msgBoxOk(response.success?self.text.success$core:self.text.error$core,\n { title: self.text.advanced_cascade_cohortsync});\n }).fail(notification.exception); \n },\n modal_close(){\n this.force_scales.result = [];\n }\n },\n template: \n `\n \n {{text.advanced_tools}}\n \n \n \n \n {{ text.advanced_warning}}\n \n \n \n \n

{{ text.advanced_cascade_cohortsync_title}}

\n {{ text.advanced_cascade_cohortsync_desc}}\n
\n \n {{ text.advanced_cascade_cohortsync}}\n \n \n

{{ text.advanced_force_scale_title}}

\n {{ text.advanced_force_scale_desc}}\n
\n \n \n \n \n \n {{ text.advanced_force_scale_button}}\n \n\n \n \n
    0\">\n
  • \n {{c.course.fullname}}\n
      0\">\n
    • \n {{g.name}}\n {{text.advanced_converted}}{{text.advanced_skipped}}\n {{text.advanced_error}}\n
    • \n
    \n
  • \n
\n
\n \n

{{ text.advanced_disable_autoenddate_title}}

\n {{ text.advanced_disable_autoenddate_desc}}\n
\n \n {{ text.advanced_disable_autoenddate_button}}\n \n
\n
\n \n {{ text.advanced_export}}\n {{ text.advanced_import}}\n {{ text.advanced_export_csv}}\n \n \n

{{text.advanced_purge_expl}}

\n

{{ text.advanced_purge}}

\n
\n
\n
\n
\n `\n });\n\n\n /*\n * T-STUDYPLAN-EDIT\n */\n Vue.component('t-studyplan-edit', {\n props: {\n 'value' :{\n type: Object,\n default(){ return null;},\n },\n 'mode' :{\n type: String,\n default() { return \"edit\";},\n },\n 'type' :{\n type: String,\n default() { return \"link\";},\n },\n 'variant' : {\n type: String,\n default() { return \"\";},\n },\n 'contextid': {\n type: Number,\n default: 1\n }\n },\n data() {\n return {\n show: false,\n config: {\n userfields: [ \n { key: \"selected\",},\n { key: \"firstname\", \"sortable\": true,},\n { key: \"lastname\", \"sortable\": true,},\n ],\n cohortfields:[ \n { key: \"selected\",},\n { key: \"name\", \"sortable\": true,},\n { key: \"context\", \"sortable\": true,},\n ]\n },\n editdata: { \n name: '',\n shortname: '',\n description: '',\n idnumber: '',\n context_id: this.contextid,\n periods : 4,\n startdate: (new Date()).getFullYear() + '-08-01',\n enddate: ((new Date()).getFullYear()+1) + '-08-01',\n aggregation: 'bistate',\n aggregation_config: '',\n },\n aggregation_parsed: {\n\n },\n aggregators: [],\n categories: [ { context_id: 1, category: { path: \"System\"}}], // overwritten during load...\n text: strings.studyplan_edit,\n };\n },\n created() {\n // retrieve aggregator info\n const self = this;\n call([{\n methodname: 'local_treestudyplan_list_aggregators',\n args: [],\n }])[0].done(function(response){\n self.aggregators = response;\n for(const ix in self.aggregators){\n const ag = self.aggregators[ix];\n \n try{\n if(ag.defaultconfig && ag.defaultconfig.length > 0){\n self.aggregation_parsed[ag.id] = JSON.parse(ag.defaultconfig);\n }\n }\n catch(e){\n debug.warn(e);\n }\n }\n }).fail(notification.exception);\n call([{\n methodname: 'local_treestudyplan_list_accessible_categories',\n args: {operation: \"edit\",}\n }])[0].done(function(response){\n for(const ix in response){\n const cat = response[ix];\n cat.category.pathname = cat.category.path.join(\" / \");\n }\n self.categories = response;\n }).fail(notification.exception);\n },\n mounted() {\n },\n updated() {\n\n },\n computed: {\n },\n methods: {\n editPlanStart(){\n if(this.mode != 'create'){\n objCopy(this.editdata,this.value.pages[0],STUDYPLAN_EDITOR_PAGE_FIELDS);\n objCopy(this.editdata,this.value,STUDYPLAN_EDITOR_FIELDS);\n\n }\n // decode the aggregation config data that is stored\n if(this.editdata.aggregation_config && this.editdata.aggregation_config.length > 0){\n try{\n this.aggregation_parsed[this.editdata.aggregation] = JSON.parse(this.editdata.aggregation_config);\n }\n catch(e){\n debug.warn(e);\n }\n }\n this.show = true;\n },\n editPlanFinish(){\n const self = this;\n let args = { };\n let method = 'local_treestudyplan_edit_studyplan';\n if(this.mode == 'create'){\n method = 'local_treestudyplan_add_studyplan';\n } else {\n args['id'] = this.value.id;\n }\n\n // store the configuration for this aggregation type if it is relevant\n if(this.aggregation_parsed[this.editdata.aggregation]){\n this.editdata.aggregation_config = JSON.stringify(this.aggregation_parsed[this.editdata.aggregation]);\n }\n objCopy(args,this.editdata,STUDYPLAN_EDITOR_FIELDS);\n objCopy(args,this.editdata,STUDYPLAN_EDITOR_PAGE_FIELDS);\n\n call([{\n methodname: method,\n args: args\n }])[0].done(function(response){\n if(self.mode == 'create'){\n self.$emit(\"created\", response);\n // And reset the edit fields to default\n self.editdata = { \n name: '',\n shortname: '',\n description: '',\n context_id: 1,\n periods : 4,\n startdate: (new Date()).getFullYear() + '-08-01',\n enddate: ((new Date()).getFullYear()+1) + '-08-01',\n aggregation: 'bistate',\n aggregation_config: '',\n };\n }\n else {\n // determine if the plan moved context...\n const moved_from = self.value.context_id;\n const moved_to = response.context_id;\n const moved = (moved_from != moved_to);\n\n if(response.pages[0].periods != self.value.pages[0].periods){\n // reload the entire model\n call([{\n methodname: 'local_treestudyplan_get_studyplan_map',\n args: { id: self.value.id}\n }])[0].done(function(response){\n self.value = ProcessStudyplan(response,true);\n debug.info('studyplan processed');\n self.$emit('input',self.value);\n }).fail(function(error){\n notification.exception(error);\n });\n } else {\n objCopy(self.value,response,STUDYPLAN_EDITOR_FIELDS);\n self.$emit('input',self.value);\n if(moved){\n self.$emit('moved',self.value,moved_from, moved_to);\n }\n }\n }\n }).fail(notification.exception);\n },\n numberFilter(value){\n return value;\n }\n }\n ,\n template: \n `\n \n \n \n \n \n \n {{ text.studyplan_name}}\n \n \n \n \n \n {{ text.studyplan_shortname}}\n \n \n \n \n \n {{ text.studyplan_idnumber}}\n \n \n \n \n {{ text.studyplan_description}}\n \n \n \n \n \n {{ text.studyplan_context}}\n \n \n \n \n \n {{ text.studyplan_slots}}\n \n \n \n \n \n {{ text.studyplan_startdate}}\n \n \n \n \n \n {{ text.studyplan_enddate}}\n \n \n \n \n \n {{ text.choose_aggregation_style}}\n \n \n \n \n \n \n \n \n `\n });\n\n /*\n * T-STUDYPLAN-ASSOCIATE\n */\n Vue.component('t-studyplan-associate', {\n props: ['value',],\n data() {\n return {\n show: false,\n config: {\n userfields: [ \n { key: \"selected\",},\n { key: \"firstname\", \"sortable\": true,},\n { key: \"lastname\", \"sortable\": true,},\n ],\n cohortfields:[ \n { key: \"selected\",},\n { key: \"name\", \"sortable\": true,},\n { key: \"context\", \"sortable\": true,},\n ]\n },\n association: {\n cohorts: [],\n users: [],\n },\n loading: {\n cohorts: false,\n users: false,\n },\n search: {users: [], cohorts:[]},\n selected: {\n search: {users: [] , cohorts:[]}, \n associated: {users: [] , cohorts:[]}\n }, \n text: strings.studyplan_associate,\n };\n },\n created() {\n \n },\n mounted() {\n },\n updated() {\n\n },\n methods: {\n showModal(){\n this.show = true;\n this.loadAssociations();\n },\n cohortOptionModel(c){\n return {\n value: c.id,\n text: c.name + ' (' + c.context.path.join(' / ') + ')',\n };\n },\n userOptionModel(u){\n return {\n value: u.id,\n text: u.firstname + ' ' + u.lastname,\n };\n },\n loadAssociations(){\n const self = this;\n self.loading.cohorts = true;\n self.loading.users = true;\n call([{\n methodname: 'local_treestudyplan_associated_users',\n args: { studyplan_id: self.value.id,}\n }])[0].done(function(response){\n self.association.users = response.map(self.userOptionModel);\n self.loading.users = false;\n }).fail(notification.exception); \n\n call([{\n methodname: 'local_treestudyplan_associated_cohorts',\n args: { studyplan_id: self.value.id,}\n }])[0].done(function(response){\n self.association.cohorts = response.map(self.cohortOptionModel);\n self.loading.cohorts = false;\n }).fail(notification.exception); \n }, \n searchCohorts(searchtext){\n const self = this;\n\n if(searchtext.length > 0)\n {\n call([{\n methodname: 'local_treestudyplan_list_cohort',\n args: { like: searchtext, exclude_id: self.value.id}\n }])[0].done(function(response){\n self.search.cohorts = response.map(self.cohortOptionModel);\n }).fail(notification.exception); \n }\n else {\n self.search.cohorts = [];\n }\n },\n cohortAssociate(){\n const self = this;\n let requests = [];\n const associated = self.association.cohorts;\n const search = self.search.cohorts;\n const searchselected = self.selected.search.cohorts;\n for(const i in searchselected){\n const r = searchselected[i]; \n requests.push({\n methodname: 'local_treestudyplan_connect_cohort',\n args: {studyplan_id: self.value.id, cohort_id: r},\n fail: notification.exception,\n done: function(response){\n if(response.success){\n transportItem(associated,search,r);\n }\n }\n });\n }\n call(requests);\n },\n cohortDisassociate(){\n const self = this;\n let requests = [];\n const associatedselected = self.selected.associated.cohorts;\n const associated = self.association.cohorts;\n const search = self.search.cohorts;\n for(const i in associatedselected){\n const r = associatedselected[i]; \n requests.push({\n methodname: 'local_treestudyplan_disconnect_cohort',\n args: {studyplan_id: self.value.id, cohort_id: r},\n fail: notification.exception,\n done: function(response){\n if(response.success){\n transportItem(search,associated,r);\n }\n }\n });\n }\n call(requests);\n }, \n searchUsers(searchtext){\n const self = this;\n if(searchtext.length > 0)\n {\n call([{\n methodname: 'local_treestudyplan_find_user',\n args: { like: searchtext, exclude_id: self.value.id}\n }])[0].done(function(response){\n self.search.users = response.map(self.userOptionModel);\n }).fail(notification.exception); \n }\n else {\n self.search.users = [];\n }\n },\n userAssociate(){\n const self = this;\n let requests = [];\n const associated = self.association.users;\n const search = self.search.users;\n const searchselected = self.selected.search.users;\n for(const i in searchselected){\n const r = searchselected[i]; \n \n requests.push({\n methodname: 'local_treestudyplan_connect_user',\n args: {studyplan_id: self.value.id, user_id: r},\n fail: notification.exception,\n done: function(response){\n if(response.success){\n transportItem(associated,search,r);\n }\n }\n });\n }\n call(requests);\n },\n userDisassociate(){\n const self = this;\n let requests = [];\n const associated = self.association.users;\n const associatedselected = self.selected.associated.users;\n const search = self.search.users;\n for(const i in associatedselected){\n const r = associatedselected[i]; \n \n requests.push({\n methodname: 'local_treestudyplan_disconnect_user',\n args: {studyplan_id: self.value.id, user_id: r},\n fail: notification.exception,\n done: function(response){\n if(response.success){\n transportItem(search,associated,r);\n }\n }\n });\n }\n call(requests);\n },\n }\n ,\n template: \n`\n\n \n \n \n \n \n {{text.associated_cohorts}}\n {{text.associate_cohorts}}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n  {{text.delete_association}}\n \n \n  {{text.add_association}} \n \n \n \n \n \n \n \n {{text.associated_users}}\n {{text.associate_users}}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n  {{text.delete_association}}\n \n \n  {{text.add_association}} \n \n \n \n \n \n \n \n`\n });\n\n /*******************\n * \n * Period editor\n * \n *************/\n\n Vue.component('t-period-edit', {\n props: {\n 'value' :{\n type: Object,\n default(){ return null;},\n },\n 'type' :{\n type: String,\n default() { return \"link\";},\n },\n 'variant' : {\n type: String,\n default() { return \"\";},\n }\n },\n data() {\n return {\n show: false,\n editdata: { \n fullname: '',\n shortname: '',\n startdate: (new Date()).getFullYear() + '-08-01',\n enddate: ((new Date()).getFullYear()+1) + '-08-01',\n },\n text: strings.period_edit,\n };\n },\n created() {\n },\n mounted() {\n },\n updated() {\n\n },\n computed: {\n },\n methods: {\n editStart(){\n objCopy(this.editdata,this.value,PERIOD_EDITOR_FIELDS);\n this.show = true;\n },\n editFinish(){\n const self = this;\n let args = { 'id': this.value.id };\n let method = 'local_treestudyplan_edit_period';\n\n objCopy(args,this.editdata,PERIOD_EDITOR_FIELDS);\n\n call([{\n methodname: method,\n args: args\n }])[0].done(function(response){\n objCopy(self.value,response,PERIOD_EDITOR_FIELDS);\n self.$emit('input',self.value);\n }).fail(notification.exception);\n },\n }\n ,\n template: \n `\n \n \n \n \n \n \n {{ text.fullname}}\n \n \n \n \n \n {{ text.shortname}}\n \n \n \n \n \n {{ text.studyplan_startdate}}\n \n \n \n \n \n {{ text.studyplan_enddate}}\n \n \n \n \n \n \n \n `\n });\n\n /*\n * T-STUDYPLAN\n */\n Vue.component('t-studyplan', {\n props: [ 'value', 'index', ],\n data() {\n return {\n config: {\n userfields: [ \n { key: \"selected\",},\n { key: \"firstname\", \"sortable\": true,},\n { key: \"lastname\", \"sortable\": true,},\n ],\n cohortfields:[ \n { key: \"selected\",},\n { key: \"name\", \"sortable\": true,},\n { key: \"context\", \"sortable\": true,},\n ]\n },\n create: {\n studyline: {\n 'name': '',\n 'shortname': '',\n 'color': '#DDDDDD',\n },\n },\n edit: {\n studyline: {\n editmode: false,\n data: { \n name: '',\n shortname: '',\n color: '#DDDDDD',\n },\n original: {},\n },\n studyplan: {\n data: { \n name: '',\n shortname: '',\n description: '',\n slots : 4,\n startdate: '2020-08-01',\n enddate: '',\n aggregation: '',\n aggregation_config: '',\n aggregation_info: {\n useRequiredGrades: true,\n useItemCondition: false,\n },\n\n },\n original: {},\n }\n },\n text: strings.studyplan_text,\n cache: {\n linelayers: {},\n }\n };\n },\n created() {\n \n },\n mounted() {\n if(this.page.studylines.length == 0){\n // start in editmode if studylines are empty\n this.edit.studyline.editmode = true;\n } \n this.$root.$emit('redrawLines');\n },\n updated() {\n this.$root.$emit('redrawLines');\n ItemEventBus.$emit('redrawLines');\n },\n computed: {\n columns() {\n return 1+ (this.page.periods * 2);\n },\n columns_stylerule() {\n // Uses css variables, so width for slots and filters can be configured in css\n let s = \"grid-template-columns: var(--studyplan-filter-width)\"; // use css variable here\n for(let i=0; i maxLayer){\n maxLayer = item.layer;\n }\n }\n for(const ix in line.slots[i].filters){\n const item = line.slots[i].filters[ix];\n if(item.layer > maxLayer){\n maxLayer = item.layer;\n }\n }\n\n }\n }\n this.cache.linelayers[line.id] = {\n value: (maxLayer + 1),\n timestamp: (new Date()), \n };\n return maxLayer+1;\n }\n },\n slotsempty(slots) {\n if(Array.isArray(slots)){\n let count = 0;\n for(let i = 0; i < slots.length; i++) {\n if(Array.isArray(slots[i].competencies)){\n count += slots[i].competencies.length;\n }\n if(Array.isArray(slots[i].filters)){\n count += slots[i].filters.length;\n }\n }\n return (count == 0);\n } else {\n return false;\n }\n },\n movedStudyplan(plan,from,to) {\n this.$emit('moved',plan,from,to); // Throw the event up....\n },\n addStudyLine(page,newlineinfo) {\n call([{\n methodname: 'local_treestudyplan_add_studyline',\n args: {\n 'page_id': page.id,\n 'name': newlineinfo.name,\n 'shortname': newlineinfo.shortname,\n 'color': newlineinfo.color,\n 'sequence': page.studylines.length,\n }\n }])[0].done(function(response){\n page.studylines.push(response);\n newlineinfo.name = '';\n newlineinfo.shortname = '';\n newlineinfo.color = \"#dddddd\";\n }).fail(notification.exception);\n },\n editLineStart(line) {\n Object.assign(this.edit.studyline.data,line);\n this.edit.studyline.original = line;\n this.$bvModal.show('modal-edit-studyline-'+this.value.id);\n },\n editLineFinish() {\n let editedline = this.edit.studyline.data;\n let originalline = this.edit.studyline.original;\n call([{\n methodname: 'local_treestudyplan_edit_studyline',\n args: { 'id': editedline.id,\n 'name': editedline.name,\n 'shortname': editedline.shortname,\n 'color': editedline.color,}\n }])[0].done(function(response){\n originalline['name'] = response['name'];\n originalline['shortname'] = response['shortname'];\n originalline['color'] = response['color'];\n }).fail(notification.exception);\n },\n deleteLine(page,line) {\n const self=this;\n get_strings([\n {key: 'studyline_confirm_remove', param: line.name, component: 'local_treestudyplan' },\n {key: 'delete', component: 'core' },\n ]).then(function(s){\n self.$bvModal.msgBoxConfirm(s[0], {\n okTitle: s[1],\n okVariant: 'danger',\n }).then(function(modalresponse){\n if(modalresponse){\n call([{\n methodname: 'local_treestudyplan_delete_studyline',\n args: { 'id': line.id, }\n }])[0].done(function(response){\n if(response.success == true){\n let index = page.studylines.indexOf(line);\n page.studylines.splice(index, 1);\n }\n }).fail(notification.exception);\n }\n });\n });\n },\n reorderLines(event,lines){\n\n // apply reordering\n event.apply(lines);\n // send the new sequence to the server\n let sequence = [];\n for(let idx in lines)\n {\n sequence.push({'id': lines[idx].id,'sequence': idx});\n }\n call([{\n methodname: 'local_treestudyplan_reorder_studylines',\n args: { 'sequence': sequence }\n }])[0].done(function(response){\n }).fail(notification.exception);\n },\n deletePlan(studyplan){\n const self=this;\n get_strings([\n {key: 'studyplan_confirm_remove', param: studyplan.name, component: 'local_treestudyplan' },\n {key: 'delete', component: 'core' },\n ]).then(function(s){\n self.$bvModal.msgBoxConfirm(s[0], {\n okTitle: s[1],\n okVariant: 'danger',\n }).then(function(modalresponse){\n if(modalresponse){\n call([{\n methodname: 'local_treestudyplan_delete_studyplan',\n args: { 'id': studyplan.id, force: true}\n }])[0].done(function(response){\n if(response.success == true){\n self.$root.$emit(\"studyplanRemoved\",studyplan);\n }\n }).fail(notification.exception);\n }\n });\n });\n },\n deleteStudyItem(event){\n //const self = this;\n let item = event.data;\n\n call([{\n methodname: 'local_treestudyplan_delete_studyitem',\n args: { 'id': item.id, }\n }])[0].done(function(response){\n if(response.success == true){\n event.source.$emit('cut',event);\n }\n }).fail(notification.exception);\n\n },\n showslot(line,index, layeridx, type){\n // check if the slot should be hidden because a previous slot has an item with a span\n // so big that it hides this slot\n const forGradable = (type == 'gradable')?true:false;\n const periods = this.page.periods;\n let show = true;\n for(let i = 0; i < periods; i++){\n if(line.slots[index-i] && line.slots[index-i].competencies){\n const list = line.slots[index-i].competencies;\n for(const ix in list){ // Really wish that 'for of' would work with the minifier moodle uses\n const item = list[ix];\n if(item.layer == layeridx){\n if(forGradable){\n if(i > 0 && (item.span - i) > 0){\n show = false;\n }\n } else {\n if((item.span - i) > 1){\n show = false;\n }\n }\n }\n }\n }\n }\n \n return show;\n }\n }\n ,\n template: \n `\n
\n
\n {{ text.studyline_editmode }}\n \n \n \n \n \n \n {{text.edit$core}} \n \n \n {{text.associations}}\n \n \n \n \n
\n
\n \n \n \n
\n
\n\n \n
\n \n \n
\n \n
\n
\n \n \n \n \n \n