/* eslint no-var: "error"*/
/* eslint no-console: "off"*/
/* eslint no-unused-vars: warn */
/* eslint max-len: ["error", { "code": 160 }] */
/* eslint promise/no-nesting: "off" */
/* eslint no-trailing-spaces: warn */
/* eslint max-depth: ["error", 6] */
/* eslint-env es6*/

import {SimpleLine} from "./simpleline/simpleline";
import {call} from 'core/ajax';
import notification from 'core/notification';
import {loadStringKeys, loadStrings, strformat} from './util/string-helper';
import {formatDate, addDays, datespaninfo} from './util/date-helper';
import {objCopy, transportItem} from './studyplan-processor';
import Debugger from './util/debugger';
import Config from 'core/config';
import {download, upload} from './downloader';
import {processStudyplan, processStudyplanPage} from './studyplan-processor';
import FitTextVue from './util/fittext-vue';
import {settings} from "./util/settings";
import TSComponents from './treestudyplan-components';
import mFormComponents from "./util/mform-helper";
import pSideBarComponents from "./util/psidebar-vue";
import {debounce} from "./util/debounce";

import {Drag, Drop, DropList} from './vue-easy-dnd/vue-easy-dnd.esm';

const STUDYPLAN_EDITOR_FIELDS =
['name', 'shortname', 'description', 'idnumber', 'context_id', 'aggregation', 'aggregation_config'];
const PERIOD_EDITOR_FIELDS =
['fullname', 'shortname', 'startdate', 'enddate'];

const LINE_GRAVITY = 1.3;

export default {
    install(Vue/* ,options */) {
        Vue.component('drag', Drag);
        Vue.component('drop', Drop);
        Vue.component('drop-list', DropList);
        Vue.use(TSComponents);
        Vue.use(mFormComponents);
        Vue.use(pSideBarComponents);
        Vue.use(FitTextVue);
        let debug = new Debugger("treestudyplan-editor");
        /* **********************************
         *                                  *
         * Treestudyplan Editor components  *
         *                                  *
         * **********************************/

        /**
         * Check if element is visible
         * @param {Object} elem The element to check
         * @returns {boolean} True if visible
         */
        function isVisible(elem) {
            return !!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
        }

        // Create new eventbus for interaction between item components
        const ItemEventBus = new Vue();

        /*
        // Add event listener for the edit mode event so we can react to it, or at the very least ignore it
        document.addEventListener(editSwEventTypes.editModeSet,(e) => {
            e.preventDefault();
            ItemEventBus.$emit('editModeSet', e.detail.editMode);
        });
        */

        let stringKeys = loadStringKeys({
            conditions: [
                {value: 'ALL', textkey: 'condition_all'},
                {value: 'ANY', textkey: 'condition_any'},
            ],
        });

        let strings = loadStrings({
            studyplanText: {
                'studyline_editmode': 'studyline_editmode',
                'toolbox_toggle': 'toolbox_toggle',
                'editmode_modules_hidden': 'editmode_modules_hidden',
                'studyline_add': 'studyline_add',
                add: 'add@core',
                edit: 'edit@core',
                save: 'save@core',
                'delete': "delete@core",
                'studyline_name': 'studyline_name',
                'studyline_name_ph': 'studyline_name_ph',
                'studyline_shortname': 'studyline_shortname',
                'studyline_shortname_ph': 'studyline_shortname_ph',
                'studyline_enrollable': 'studyline_enrollable',
                'studyline_enrolroles': 'studyline_enrolroles',
                'studyline_color': 'studyline_color',
                associations: 'associations',
                'associated_cohorts': 'associated_cohorts',
                'associated_users': 'associated_users',
                'studyline_edit': 'studyline_edit',
                'studyplan_name': 'studyplan_name',
                'studyplan_name_ph': 'studyplan_name_ph',
                'studyplan_shortname': 'studyplan_shortname',
                'studyplan_shortname_ph': 'studyplan_shortname_ph',
                'studyplan_description': 'studyplan_description',
                'studyplan_description_ph': 'studyplan_description_ph',
                'studyplan_idnumber': 'studyplan_idnumber',
                'studyplan_idnumber_ph': 'studyplan_idnumber_ph',
                'studyplan_slots': 'studyplan_slots',
                'studyplan_startdate': 'studyplan_startdate',
                'studyplan_enddate': 'studyplan_enddate',
                'line_enrollable_0': 'line_enrollable:0',
                'line_enrollable_1': 'line_enrollable:1',
                'line_enrollable_2': 'line_enrollable:2',
                'line_enrollable_3': 'line_enrollable:3',
                drophere: 'drophere',
                studylineConfirmRemove: 'studyline_confirm_remove',
                studyplanConfirmRemove: 'studyplan_confirm_remove',

            },
            studyplanAdvanced: {
                'advanced_tools': 'advanced_tools',
                'confirm_cancel': 'confirm_cancel',
                'confirm_ok': 'confirm_ok',
                success: 'success@core',
                error: 'failed@completion',
                'advanced_converted': 'advanced_converted',
                'advanced_skipped': 'advanced_skipped',
                'advanced_failed': 'advanced_failed',
                'advanced_locked': 'advanced_locked',
                'advanced_multiple': 'advanced_multiple',
                'advanced_error': 'advanced_error',
                'advanced_tools_heading': 'advanced_tools_heading',
                'advanced_warning_title': 'advanced_warning_title',
                'advanced_warning': 'advanced_warning',
                'advanced_pick_scale': 'advanced_pick_scale',
                'advanced_course_manipulation_title': 'advanced_course_manipulation_title',
                'advanced_bulk_course_timing': 'advanced_bulk_course_timing',
                'advanced_bulk_course_timing_desc': 'advanced_bulk_course_timing_desc',
                'advanced_force_scale_title': 'advanced_force_scale_title',
                'advanced_force_scale_desc': 'advanced_force_scale_desc',
                'advanced_force_scale_button': 'advanced_force_scale_button',
                'advanced_confirm_header': 'advanced_confirm_header',
                'advanced_force_scale_confirm': 'advanced_force_scale_confirm',
                'advanced_backup_restore': 'advanced_backup_restore',
                'advanced_restore': 'advanced_restore',
                'advanced_backup': 'advanced_backup',
                'advanced_restore_pages': 'advanced_restore_pages',
                'advanced_restore_lines': 'advanced_restore_lines',
                'advanced_backup_plan': 'advanced_backup_plan',
                'advanced_backup_page': 'advanced_backup_page',
                'advanced_export': 'advanced_export',
                'advanced_export_csv_plan': 'advanced_export_csv_plan',
                'advanced_export_csv_page': 'advanced_export_csv_page',
                'advanced_import_from_file': 'advanced_import_from_file',
                'advanced_purge': "advanced_purge",
                'advanced_purge_plan': "advanced_purge_plan",
                'advanced_purge_plan_expl': "advanced_purge_plan_expl",
                'advanced_purge_page': "advanced_purge_page",
                'advanced_purge_page_expl': "advanced_purge_page_expl",
                'advanced_cascade_cohortsync_title': "advanced_cascade_cohortsync_title",
                'advanced_cascade_cohortsync_desc': "advanced_cascade_cohortsync_desc",
                'advanced_cascade_cohortsync': "advanced_cascade_cohortsync",
                currentpage: "currentpage",
            },
            studyplanEdit: {
                studyplanEdit: 'studyplan_edit',
                'studyplan_add': 'studyplan_add',
                'studyplanpage_add': 'studyplanpage_add',
                'studyplanpage_edit': 'studyplanpage_edit',
                'info_periodsextended': 'studyplanpage_info_periodsextended',
                warning: 'warning@core',
            },
            periodEdit: {
                edit: 'period_edit',
                fullname: 'studyplan_name',
                shortname: 'studyplan_shortname',
                startdate: 'studyplan_startdate',
                enddate: 'studyplan_enddate',
            },
            courseTiming: {
                title: 'course_timing_title',
                desc: 'course_timing_desc',
                question: 'course_timing_question',
                warning: 'course_timing_warning',
                'timing_ok': 'course_timing_ok',
                'timing_off': 'course_timing_off',
                course: 'course@core',
                period: 'period',
                yes: 'yes$core',
                no: 'no$core',
                duration: 'duration',
                years: 'years$core',
                year: 'year$core',
                weeks: 'weeks$core',
                week: 'week$core',
                days: 'days$core',
                day: 'day$core',
                rememberchoice: 'course_timing_rememberchoice',
                hidewarning: 'course_timing_hidewarning',
                periodspan: 'course_period_span',
                periods: 'periods',
                'periodspan_desc': 'course_period_span_desc',
            },
            studyplanAssociate: {
                'associations': 'associations',
                'associated_cohorts': 'associated_cohorts',
                'associated_users': 'associated_users',
                'associated_coaches': 'associated_coaches',
                'associate_cohorts': 'associate_cohorts',
                'associate_users': 'associate_users',
                'associate_coached': 'associate_coaches',
                'add_association': 'add_association',
                'delete_association': 'delete_association',
                'associations_empty': 'associations_empty',
                'associations_search': 'associations_search',
                cohorts: 'cohorts',
                users: 'users',
                coaches: 'coaches',
                selected: 'selected',
                name: 'name',
                context: 'context',
                search: 'search',
            },
            itemText: {
                'select_conditions': "select_conditions",
                'item_configuration': "item_configuration",
                ok: "ok@core",
                'delete': "delete@core",
                'item_delete_message': "item_delete_message",
                'type_course': "course@core",
                'type_junction': "tool-junction",
                'type_start': "tool-start",
                'type_finish': "tool-finish",
                'type_badge': "tool-badge",
                'type_invalid': "course-invalid",
            },
            itemCourseText: {
                'select_conditions': "select_conditions",
                'select_grades': "select_grades",
                'coursetiming_past': "coursetiming_past",
                'coursetiming_present': "coursetiming_present",
                'coursetiming_future': "coursetiming_future",
                'grade_include': "grade_include",
                'grade_require': "grade_require",
                ok: "ok@core",
                cancel: "cancel@core",
                'delete': "delete@core",
                noenddate: "noenddate",
            },
            invalid: {
                error: 'error',
            },
            completion: {
                'completion_completed': "completion_completed",
                'completion_incomplete': "completion_incomplete",
                'aggregation_all': "aggregation_all",
                'aggregation_any': "aggregation_any",
                'aggregation_overall_all': "aggregation_overall_all",
                'aggregation_overall_any': "aggregation_overall_any",
                'completion_not_configured': "completion_not_configured",
                'configure_completion': "configure_completion",
            },
            competency: {
                'competency_not_configured': "competency_not_configured",
                'configure_competency': "configure_competency",
                when: "when",
                required: "required",
                points: "points@core_grades",
                heading: "competency_heading",
                details: "competency_details",
            },
            badge: {
                'share_badge': "share_badge",
                dateissued: "dateissued",
                dateexpire: "dateexpire",
                badgeinfo: "badgeinfo",
            },
            toolbox: {
                toolbox: 'toolbox',
                toolbarRight: 'toolbar-right',
                courses: 'courses',
                flow: 'flow',
                toolJunction: 'tool-junction',
                toolFinish: 'tool-finish',
                toolStart: 'tool-start',
                badges: 'badges',
                relatedbadges: 'relatedbages@badges', /* [sic] as in badges translation file */
                search: 'search@core',
                sitebadges: 'sitebadges@badges',
                badgesearchinstruction: 'badgesearchinstruction',
            }
        });

        /*
        * T-STUDYPLAN-ADVANCED
        */
        Vue.component('t-studyplan-advanced', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                selectedpage: {
                    type: Object,
                    default() {
                        return null;
                    },
                }

            },
            data() {
                return {
                    forceScales: {
                        selectedScale: null,
                        result: [],
                    },
                    text: strings.studyplanAdvanced,
                };
            },
            computed: {
                scales() {
                    return [{
                        id: null,
                        disabled: true,
                        name: this.text.advanced_pick_scale,
                    }].concat(this.value.advanced.force_scales.scales);
                },
            },
            methods: {
                forceScalesStart() {
                    // Set confirmation box
                    const self = this;
                    this.$bvModal.msgBoxConfirm(this.text.advanced_force_scale_confirm, {
                        title: this.text.advanced_force_scale_confirm,
                        okVariant: 'danger',
                        okTitle: this.text.confirm_ok,
                        cancelTitle: this.text.confirm_cancel,
                    }).then(value => {
                        if (value == true) {
                            call([{
                                methodname: 'local_treestudyplan_force_studyplan_scale',
                                args: {
                                    'studyplan_id': this.value.id,
                                    'scale_id': this.forceScales.selectedScale,
                                }
                            }])[0].then((response) => {
                                self.forceScales.result = response;
                                return;
                            }).catch(notification.exception);
                        }
                        return;
                    }).catch(notification.exception);
                },
                exportPage(format) {
                    const self = this;
                    if (format == undefined || !["json", "csv"].includes(format)) {
                        format = "json";
                    }
                    call([{
                            methodname: 'local_treestudyplan_export_page',
                            args: {
                                'page_id': this.selectedpage.id,
                                format: format,
                            },
                        }])[0].then((response) => {
                            download(self.value.shortname + ".page." + format, response.content, response.format);
                            return;
                        }).catch(notification.exception);
                },
                exportPlan() {
                    const self = this;
                    call([{
                            methodname: 'local_treestudyplan_export_plan',
                            args: {
                                'studyplan_id': this.value.id,
                            },
                        }])[0].then((response) => {
                            download(self.value.shortname + ".plan.json", response.content, response.format);
                            return;
                        }).catch(notification.exception);
                },
                bulkCourseTiming() {
                    const self = this;
                    call([{
                            methodname: 'local_treestudyplan_bulk_course_timing',
                            args: {
                                'page_id': this.selectedpage.id,
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                // Reloading the webpage saves trouble reloading the specific page updated.
                                location.reload();
                            } else {
                                self.$bvModal.msgBoxOk(response.msg, {title: "Could not set bulk course timing"});
                                debug.error("Could not set bulk course timing: ", response.msg);
                            }
                            return;
                        }).catch(notification.exception);
                },
                importStudylines() {
                    const self = this;
                    upload((filename, content)=>{
                        call([{
                            methodname: 'local_treestudyplan_import_studylines',
                            args: {
                                'page_id':  this.selectedpage.id,
                                content: content,
                                format: "application/json",
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                location.reload();
                            } else {
                                self.$bvModal.msgBoxOk(response.msg, {title: "Import failed"});
                                debug.error("Import failed: ", response.msg);
                            }
                            return;
                        }).catch(notification.exception);
                    }, "application/json");
                },
                importPages() {
                    const self = this;
                    upload((filename, content)=>{
                        call([{
                            methodname: 'local_treestudyplan_import_pages',
                            args: {
                                'studyplan_id':  this.value.id,
                                content: content,
                                format: "application/json",
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                location.reload();
                            } else {
                                self.$bvModal.msgBoxOk(response.msg, {title: "Import failed"});
                                debug.error("Import failed: ", response.msg);
                            }
                            return;
                        }).catch(notification.exception);
                    }, "application/json");
                },
                purgeStudyplan() {
                    const self = this;
                    call([{
                        methodname: 'local_treestudyplan_delete_studyplan',
                        args: {
                            id:  this.value.id,
                            force: true,
                        },
                    }])[0].then((response) => {
                        if (response.success) {
                            location.reload();
                        } else {
                            self.$bvModal.msgBoxOk(response.msg, {title: "Could not delete plan "});
                            debug.error("Could not delete plan: ", response.msg);
                        }
                        return;
                    }).catch(notification.exception);
                },
                purgeStudyplanpage() {
                    const self = this;
                    if (this.selectedpage) {
                        call([{
                            methodname: 'local_treestudyplan_delete_studyplanpage',
                            args: {
                                id:  this.selectedpage.id,
                                force: true,
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                location.reload();
                            } else {
                                self.$bvModal.msgBoxOk(response.msg, {title: "Could not delete page"});
                                debug.error("Could not delete page: ", response.msg);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                },
                cascadeCohortsync() {
                    const self = this;
                    call([{
                        methodname: 'local_treestudyplan_cascade_cohortsync',
                        args: {
                            'studyplan_id':  this.value.id,
                        },
                    }])[0].then((response) => {
                        self.$bvModal.msgBoxOk(response.success ? self.text.success : self.text.error,
                            {title: self.text.advanced_cascade_cohortsync});
                        return;
                    }).catch(notification.exception);
                },
                modalClose() {
                    this.forceScales.result = [];
                }
            },
            template:
            `
            <span>
                <a  href='#'
                    @click.prevent=''
                    class='text-danger'
                    v-b-modal="'t-studyplan-'+value.id+'-advanced'"
                    ><i class='fa fa-wrench'></i> {{text.advanced_tools}}</a>
                <b-modal v-if="value.advanced"
                    :id="'t-studyplan-'+value.id+'-advanced'"
                    size="lg"
                    :title="text.advanced_tools_heading"
                    ok-only
                    @hide="modalClose"
                    body-class="p-0"
                    >
                    <b-tabs card>
                        <b-tab :title="text.advanced_warning_title" active>
                            {{ text.advanced_warning}}
                        </b-tab>
                        <b-tab :title="text.advanced_course_manipulation_title" >
                            <h3>{{ text.advanced_cascade_cohortsync_title}}</h3>
                            <p>{{ text.advanced_cascade_cohortsync_desc}}</p>
                            <p class="mt-2"><b-button
                                    variant="info"
                                    @click.prevent="cascadeCohortsync"
                                >{{ text.advanced_cascade_cohortsync}}</b-button></p>
                            <h3>{{ text.advanced_bulk_course_timing}}</h3>
                            <p>{{ text.advanced_bulk_course_timing_desc}}</p>
                            <p>{{text.currentpage}} <i><span v-html="selectedpage.display.fullname"></span></i></p>
                            <p class="mt-2"><b-button
                                    variant="info"
                                    @click.prevent="bulkCourseTiming"
                                >{{ text.advanced_bulk_course_timing}}</b-button></p>
                            <template v-if="value.advanced && value.advanced.force_scales && ['bistate','tristate'].includes(value.aggregation)">
                            <h3>{{ text.advanced_force_scale_title}}</h3>
                                {{ text.advanced_force_scale_desc}}
                            <p class="mt-2"><b-form-select v-model="forceScales.selectedScale"
                                    :options="scales" text-field="name" value-field="id"
                                ></b-form-select>
                                <b-button
                                    variant="danger"
                                    :disabled="forceScales.selectedScale == null"
                                    @click.prevent="forceScalesStart"
                                >{{ text.advanced_force_scale_button}}</b-button>
                            </p>
                            <p class="mt-2">
                                <ul class='t-advanced-scrollable' v-if="forceScales.result.length > 0">
                                    <li v-for="c in forceScales.result">
                                        <span class='t-advanced-coursename'><span v-html="c.course.fullname"></span></span>
                                        <ul v-if="c.grades.length > 0">
                                            <li v-for='g in c.grades'
                                                ><span class='t-advanced-gradename'><span v-html="g.name"></span></span>
                                                <span v-if="g.changed == 'converted'" class='t-advanced-status changed'
                                                    >{{text.advanced_converted}}</span
                                                ><span v-else-if="g.changed == 'skipped'" class='t-advanced-status skipped'
                                                    >{{text.advanced_skipped}}</span
                                                ><span v-else class='t-advanced-status skipped'
                                                    >{{text.advanced_error}}</span
                                            ></li>
                                        </ul>
                                    </li>
                                </ul>
                            </p>
                            </template>
                        </b-tab>
                        <b-tab :title='text.advanced_backup_restore'>
                            <h3>{{ text.advanced_backup }}</h3>
                            <p><b-button
                                variant="primary"
                                @click.prevent="exportPage('json')"
                            >{{ text.advanced_backup_page }}</b-button>
                            {{text.currentpage}} <i><span v-html="selectedpage.display.fullname"></span></i></p>
                            <p><b-button
                                    variant="primary"
                                    @click.prevent="exportPlan('json')"
                                >{{ text.advanced_backup_plan }}</b-button></p>
                            <h3>{{ text.advanced_restore }}</h3>
                            <p><b-button
                                variant="danger"
                                @click.prevent="importStudylines"
                            >{{ text.advanced_restore_lines}}</b-button></p>
                            <p><b-button
                                variant="danger"
                                @click.prevent="importPages"
                            >{{ text.advanced_restore_pages }}</b-button></p>
                            <h3>{{ text.advanced_export }}</h3>
                            <p><b-button
                                variant="primary"
                                @click.prevent="exportPage('csv')"
                            >{{ text.advanced_export_csv_page }}</b-button>
                            {{text.currentpage}} <i><span v-html="selectedpage.display.fullname"></span></i></p>
                        </b-tab>
                        <b-tab :title='text.advanced_purge'>
                            <p>{{text.advanced_purge_page_expl}}</p>
                            <p>{{text.currentpage}} <i><span v-html="selectedpage.display.fullname"></span></i></p>
                            <p><b-button
                                variant="danger"
                                @click.prevent="purgeStudyplanpage"
                            >{{ text.advanced_purge_page}}</b-button></p>
                            <p>{{text.advanced_purge_plan_expl}}</p>
                            <p><b-button
                                variant="danger"
                                @click.prevent="purgeStudyplan"
                            >{{ text.advanced_purge_plan}}</b-button></p>
                        </b-tab>
                    </b-tabs>
                </b-modal>
            </span>
            `
        });


        /*
        * T-STUDYPLAN-EDIT
        */
        Vue.component('t-studyplan-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 "";
                    },
                },
                'contextid': {
                    type: Number,
                    'default': 1
                },
            },
            data() {
                return {
                    text: strings.studyplanEdit,
                };
            },
            computed: {
            },
            methods: {
                planSaved(updatedplan) {
                    const self = this;
                    debug.info("Got new plan data", updatedplan);

                    if (self.mode == 'create') {
                        // Inform parent of the details of the newly created plan
                        self.$emit("created", updatedplan);
                    } else {
                        // Determine if the plan moved context...
                        const movedFrom = self.value.context_id;
                        const movedTo = updatedplan.context_id;
                        const moved = (movedFrom != movedTo);

                        if (updatedplan.pages[0].periods != self.value.pages[0].periods) {
                            // If the pages changed, just reload the entire model for the plan
                            call([{
                                methodname: 'local_treestudyplan_get_studyplan_map',
                                args: {id: self.value.id}
                            }])[0].then((response) => {
                                self.value = processStudyplan(response, true);
                                debug.info('studyplan processed');
                                self.$emit('input', self.value);
                                return;
                            }).catch(function(error) {
                                notification.exception(error);
                            });
                        } else {
                            // Copy updated fields and trigger update
                            objCopy(self.value, updatedplan, STUDYPLAN_EDITOR_FIELDS);
                            self.$emit('input', self.value);
                            if (moved) {
                                self.$emit('moved', self.value, movedFrom, movedTo);
                            }
                        }
                    }
                },
            },
            template:
            `
            <span class='s-studyplan-edit'>
                <mform
                    name="studyplan_editform"
                    :params="{studyplan_id: value.id, mode: mode, contextid: contextid }"
                    @saved="planSaved"
                    :variant="variant"
                    :type="type"
                    :title="(mode == 'create')?text.studyplan_add:text.studyplanEdit"
                    ><slot><i class='fa fa-gear'></i></slot></mform>
            </span>
            `
        });

        /*
         * 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.studyplanEdit,
                };
            },
            computed: {
            },
            methods: {
                planSaved(updatedpage) {
                    const self = this;

                    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');

                        if (self.value.periods < page.periods) {
                            this.$bvModal.msgBoxOk(this.text.info_periodsextended, {
                                title: this.text.warning,
                                okVariant: 'success',
                                centered: true
                            });
                        }
                        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.studyplanpage_add:text.studyplanpage_edit"
                    ><slot><i class='fa fa-gear'></i></slot></mform>
            </span>
            `
        });


        /*
        * T-STUDYPLAN-ASSOCIATE
        */
        Vue.component('t-studyplan-associate', {
            props: ['value'],
            data() {
                return {
                    show: false,
                    config: {
                        userfields: [
                            {key: "selected"},
                            {key: "firstname", "sortable": true},
                            {key: "lastname", "sortable": true},
                        ],
                        cohortfields: [
                            {key: "selected"},
                            {key: "name", "sortable": true},
                            {key: "context", "sortable": true},
                        ]
                    },
                    association: {
                        cohorts: [],
                        users: [],
                        coaches: []
                    },
                    loading: {
                        cohorts: false,
                        users: false,
                        coaches: false,
                    },
                    search: {users: [], cohorts: [], coaches: []},
                    selected: {
                         search: {users: [], cohorts: [], coaches: []},
                         associated: {users: [], cohorts: [], coaches: []}
                    },
                    text: strings.studyplanAssociate,
                };
            },
            methods: {
                showModal() {
                    this.show = true;
                    this.loadAssociations();
                },
                cohortOptionModel(c) {
                    return {
                        value: c.id,
                        text: c.name + ' (' + c.context.path.join(' / ') + ')',
                    };
                },
                userOptionModel(u) {
                    return {
                        value: u.id,
                        text: u.firstname + ' ' + u.lastname,
                    };
                },
                loadAssociations() {
                    const self = this;
                    self.loading.cohorts = true;
                    self.loading.users = true;
                    call([{
                        methodname: 'local_treestudyplan_associated_users',
                        args: {
                            'studyplan_id': self.value.id,
                        }
                    }])[0].then((response) => {
                        self.association.users = response.map(self.userOptionModel);
                        self.loading.users = false;
                        return;
                    }).catch(notification.exception);

                    call([{
                        methodname: 'local_treestudyplan_associated_cohorts',
                        args: {
                            'studyplan_id': self.value.id,
                        }
                    }])[0].then((response) => {
                        self.association.cohorts = response.map(self.cohortOptionModel);
                        self.loading.cohorts = false;
                        return;
                    }).catch(notification.exception);

                    self.loading.coaches = true;
                    call([{
                        methodname: 'local_treestudyplan_associated_coaches',
                        args: {
                            'studyplan_id': self.value.id,
                        }
                    }])[0].then((response) => {
                        self.association.coaches = response.map(self.userOptionModel);
                        self.loading.coaches = false;
                        return;
                    }).catch(notification.exception);
                },
                searchCohorts(searchtext) {
                    const self = this;

                    if (searchtext.length > 0) {
                        call([{
                            methodname: 'local_treestudyplan_list_cohort',
                            args: {
                                like: searchtext,
                                'studyplan_id': self.value.id
                            }
                        }])[0].then((response) => {
                            self.search.cohorts = response.map(self.cohortOptionModel);
                            return;
                        }).catch(notification.exception);
                    } else {
                        self.search.cohorts = [];
                    }
                },
                cohortAssociate() {
                    const self = this;
                    let requests = [];
                    const associated = self.association.cohorts;
                    const search = self.search.cohorts;
                    const searchselected = self.selected.search.cohorts;
                    for (const i in searchselected) {
                        const r = searchselected[i];
                        call([{
                            methodname: 'local_treestudyplan_connect_cohort',
                            args: {
                                'studyplan_id': self.value.id,
                                'cohort_id': r,
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                transportItem(associated, search, r);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                    call(requests);
                },
                cohortDisassociate() {
                    const self = this;
                    const associatedselected = self.selected.associated.cohorts;
                    const associated = self.association.cohorts;
                    const search = self.search.cohorts;
                    for (const i in associatedselected) {
                        const r = associatedselected[i];
                        call([{
                            methodname: 'local_treestudyplan_disconnect_cohort',
                            args: {
                                'studyplan_id': self.value.id,
                                'cohort_id': r,
                            }
                        }])[0].then((response) => {
                            if (response.success) {
                                transportItem(search, associated, r);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                },
                searchUsers(searchtext) {
                    const self = this;
                    if (searchtext.length > 0) {
                        call([{
                            methodname: 'local_treestudyplan_find_user',
                            args: {
                                like: searchtext,
                                'studyplan_id': self.value.id
                            }
                        }])[0].then((response) => {
                            self.search.users = response.map(self.userOptionModel);
                            return;
                        }).catch(notification.exception);
                    } else {
                        self.search.users = [];
                    }
                },
                userAssociate() {
                    const self = this;
                    const associated = self.association.users;
                    const search = self.search.users;
                    const searchselected = self.selected.search.users;
                    for (const i in searchselected) {
                        const r = searchselected[i];
                        call([{
                            methodname: 'local_treestudyplan_connect_user',
                            args: {
                                'studyplan_id': self.value.id,
                                'user_id': r,
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                transportItem(associated, search, r);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                },
                userDisassociate() {
                    const self = this;
                    const associated = self.association.users;
                    const associatedselected = self.selected.associated.users;
                    const search = self.search.users;
                    for (const i in associatedselected) {
                        const r = associatedselected[i];
                        call([{
                            methodname: 'local_treestudyplan_disconnect_user',
                            args: {
                                'studyplan_id': self.value.id,
                                'user_id': r,
                            }
                        }])[0].then((response) => {
                            if (response.success) {
                                transportItem(search, associated, r);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                },
                searchCoaches(searchtext) {
                    const self = this;
                    if (searchtext.length > 0) {
                        call([{
                            methodname: 'local_treestudyplan_find_coach',
                            args: {
                                like: searchtext,
                                'studyplan_id': self.value.id,
                            }
                        }])[0].then((response) => {
                            self.search.coaches = response.map(self.userOptionModel);
                            return;
                        }).catch(notification.exception);
                    } else {
                        self.search.coaches = [];
                    }
                },
                coachAssociate() {
                    const self = this;
                    const associated = self.association.coaches;
                    const search = self.search.coaches;
                    const searchselected = self.selected.search.coaches;
                    for (const i in searchselected) {
                        const r = searchselected[i];

                        call([{
                            methodname: 'local_treestudyplan_connect_coach',
                            args: {
                                'studyplan_id': self.value.id,
                                'user_id': r,
                            },
                        }])[0].then((response) => {
                            if (response.success) {
                                transportItem(associated, search, r);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                },
                coachDisassociate() {
                    const self = this;
                    const associated = self.association.coaches;
                    const associatedselected = self.selected.associated.coaches;
                    const search = self.search.coaches;
                    for (const i in associatedselected) {
                        const r = associatedselected[i];

                        call([{
                            methodname: 'local_treestudyplan_disconnect_coach',
                            args: {
                                'studyplan_id': self.value.id,
                                'user_id': r,
                            }
                        }])[0].then((response) => {
                            if (response.success) {
                                transportItem(search, associated, r);
                            }
                            return;
                        }).catch(notification.exception);
                    }
                },
            },
            template:
`
<span class='s-studyplan-associate'
    ><a href='#' @click.prevent="showModal" ><slot><i class='fa fa-users'></i></slot></a>
    <b-modal
        v-model="show"
        size="lg"
        ok-variant="primary"
        :title="text.associations + ' - ' + value.name"
        ok-only>
        <b-tabs class='s-studyplan-associate-window'>
            <b-tab :title="text.cohorts">
                <b-container>
                    <b-row class='mb-2 mt-2'>
                        <b-col>{{text.associated_cohorts}}</b-col>
                        <b-col>{{text.associate_cohorts}}</b-col>
                    </b-row>
                    <b-row class='mb-2'>
                        <b-col>
                        </b-col>
                        <b-col>
                            <b-form-input
                                type="text" @input="searchCohorts($event)"
                                :placeholder="text.search"></b-form-input>
                        </b-col>
                    </b-row>
                    <b-row>
                        <b-col>
                            <b-form-select
                                multiple
                                v-model="selected.associated.cohorts"
                                :options="association.cohorts"
                                :select-size="10"
                                ></b-form-select>
                        </b-col>
                        <b-col>
                            <b-form-select
                                multiple
                                v-model="selected.search.cohorts"
                                :options="search.cohorts"
                                :select-size="10"
                                ></b-form-select>
                        </b-col>
                    </b-row>
                    <b-row class='mt-2'>
                        <b-col>
                            <b-button variant='danger' @click.prevent="cohortDisassociate()"
                                ><i class='fa fa-chain-broken'></i>&nbsp;{{text.delete_association}}</b-button>
                        </b-col>
                        <b-col>
                            <b-button variant='success' @click.prevent="cohortAssociate()"
                                ><i class='fa fa-link'></i>&nbsp;{{text.add_association}}</b-button>
                        </b-col>
                    </b-row>
                </b-container>
            </b-tab>
            <b-tab :title="text.users">
                <b-container>
                    <b-row class='mb-2 mt-2'>
                        <b-col>{{text.associated_users}}</b-col>
                        <b-col>{{text.associate_users}}</b-col>
                    </b-row>
                    <b-row class='mb-2'>
                        <b-col>
                        </b-col>
                        <b-col>
                            <b-form-input
                                type="text"
                                @input="searchUsers($event)"
                                :placeholder="text.search + ' ' + text.users"></b-form-input>
                        </b-col>
                    </b-row>
                    <b-row>
                        <b-col>
                            <b-form-select
                                multiple
                                v-model="selected.associated.users"
                                :options="association.users"
                                :select-size="10"
                                ></b-form-select>
                        </b-col>
                        <b-col>
                            <b-form-select
                                multiple
                                v-model="selected.search.users"
                                :options="search.users"
                                :select-size="10"
                                ></b-form-select>
                        </b-col>
                    </b-row>
                    <b-row class='mt-2'>
                        <b-col>
                                <b-button variant='danger' @click.prevent="userDisassociate()"
                                ><i class='fa fa-chain-broken'></i>&nbsp;{{text.delete_association}}</b-button>
                        </b-col>
                        <b-col>
                            <b-button variant='success' @click.prevent="userAssociate()"
                                ><i class='fa fa-link'></i>&nbsp;{{text.add_association}}</b-button>
                        </b-col>
                    </b-row>
                </b-container>
            </b-tab>
            <b-tab :title="text.coaches">
                <b-container>
                    <b-row class='mb-2 mt-2'>
                        <b-col>{{text.associated_coaches}}</b-col>
                        <b-col>{{text.associate_coaches}}</b-col>
                    </b-row>
                    <b-row class='mb-2'>
                        <b-col>
                        </b-col>
                        <b-col>
                            <b-form-input
                                type="text"
                                @input="searchCoaches($event)"
                                :placeholder="text.search + ' ' + text.coaches"></b-form-input>
                        </b-col>
                    </b-row>
                    <b-row>
                        <b-col>
                            <b-form-select
                                multiple
                                v-model="selected.associated.coaches"
                                :options="association.coaches"
                                :select-size="10"
                                ></b-form-select>
                        </b-col>
                        <b-col>
                            <b-form-select
                                multiple
                                v-model="selected.search.coaches"
                                :options="search.coaches"
                                :select-size="10"
                                ></b-form-select>
                        </b-col>
                    </b-row>
                    <b-row class='mt-2'>
                        <b-col>
                            <b-button variant='danger' @click.prevent="coachDisassociate()"
                                ><i class='fa fa-chain-broken'></i>&nbsp;{{text.delete_association}}</b-button>
                        </b-col>
                        <b-col>
                            <b-button variant='success' @click.prevent="coachAssociate()"
                                ><i class='fa fa-link'></i>&nbsp;{{text.add_association}}</b-button>
                        </b-col>
                    </b-row>
                </b-container>
            </b-tab>
        </b-tabs>
    </b-modal>
</span>
`
        });

        /* * ****************
         *
         * Period editor
         *
         *************/

        Vue.component('t-period-edit', {
            props: {
                'value': {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                'type': {
                    type: String,
                    default() {
                        return "link";
                    },
                },
                'variant': {
                    type: String,
                    default() {
                        return "";
                    },
                },
                'minstart': {
                    type: String,
                    default() {
                        return null;
                    },
                },
                'maxend': {
                    type: String,
                    default() {
                        return null;
                    },
                }
            },
            data() {
                return {
                    show: false,
                    editdata: {
                        fullname: '',
                        shortname: '',
                        startdate: (new Date()).getFullYear() + '-08-01',
                        enddate:  ((new Date()).getFullYear() + 1) + '-08-01',
                    },
                    text: strings.periodEdit,
                };
            },
            methods: {
                editStart() {
                    objCopy(this.editdata, this.value, PERIOD_EDITOR_FIELDS);
                    this.show = true;
                },
                editFinish() {
                    const self = this;
                    let args = {'id': this.value.id};

                    objCopy(args, this.editdata, PERIOD_EDITOR_FIELDS);

                    call([{
                        methodname: 'local_treestudyplan_edit_period',
                        args: args
                    }])[0].then((response) => {
                        objCopy(self.value, response, PERIOD_EDITOR_FIELDS);
                        self.$emit('input', self.value);
                        self.$emit('edited', self.value);
                        return;
                    }).catch(notification.exception);
                },
                refresh() {
                    const self = this;
                    call([{
                        methodname: 'local_treestudyplan_get_period',
                        args: {'id': this.value.id},
                    }])[0].then((response) => {
                        objCopy(self.value, response, PERIOD_EDITOR_FIELDS);
                        self.$emit('input', self.value);
                        return;
                    }).catch(notification.exception);
                },
                addDay(date, days) {
                    if (days === undefined) {
                        days = 1;
                    }
                    return addDays(date, days);
                },
                subDay(date, days) {
                    if (days === undefined) {
                        days = 1;
                    }
                    return addDays(date, 0 - days);
                },
            },
            template:
            `
            <span class='t-period-edit'>
                <b-button :variant="variant" v-if='type == "button"' @click.prevent='editStart()'
                    ><slot><i class='fa fa-gear'></i></slot></b-button>
                <a variant="variant" v-else href='#' @click.prevent='editStart()'
                    ><slot><i class='fa fa-gear'></i></slot></a>
                <b-modal
                    v-model="show"
                    size="lg"
                    ok-variant="primary"
                    :title="text.edit"
                    @ok="editFinish()"
                    :ok-disabled="Math.min(editdata.fullname.length,editdata.shortname.length) == 0"
                    >
                    <b-container>
                        <b-row>
                            <b-col cols="4">{{ text.fullname}}</b-col>
                            <b-col cols="8">
                                <b-form-input v-model="editdata.fullname"
                                :state='editdata.fullname.length>0'
                                ></b-form-input>
                            </b-col>
                        </b-row>
                        <b-row>
                            <b-col cols="4">{{ text.shortname}}</b-col>
                            <b-col cols="8">
                                <b-form-input v-model="editdata.shortname"
                                :state='editdata.shortname.length>0'
                                ></b-form-input>
                            </b-col>
                        </b-row>
                        <b-row v-if="!value.timeless">
                            <b-col cols="4">{{ text.studyplan_startdate}}</b-col>
                            <b-col cols="8">
                                <b-form-datepicker
                                    start-weekday="1"
                                    v-model="editdata.startdate"
                                    :min="(minstart ? minstart : '')"
                                    :max="subDay(value.enddate)"
                                ></b-form-datepicker>
                            </b-col>
                        </b-row>
                        <b-row v-if="!value.timeless">
                            <b-col cols="4">{{ text.studyplan_enddate}}</b-col>
                            <b-col cols="8">
                                <b-form-datepicker
                                    start-weekday="1"
                                    v-model="editdata.enddate"
                                    :min="addDay(value.startdate)"
                                    :max="(maxend ? maxend : '')"
                                ></b-form-datepicker>
                            </b-col>
                        </b-row>
                    </b-container>
                </b-modal>
            </span>
            `
        });

        // TAG: Start studyplan component
        /*
        * T-STUDYPLAN
        */
        Vue.component('t-studyplan', {
            props: {
                'value': {
                    type: Object,
                },
                'coaching': {
                    type: Boolean,
                    'default': false,
                },
            },
            data() {
                return {
                    settings: settings,
                    config: {
                        userfields: [
                            {key: "selected"},
                            {key: "firstname", "sortable": true},
                            {key: "lastname", "sortable": true},
                        ],
                        cohortfields: [
                            {key: "selected"},
                            {key: "name", "sortable": true},
                            {key: "context", "sortable": true},
                        ]
                    },
                    create: {
                        studyline: {
                            name: '',
                            shortname: '',
                            color: '#DDDDDD',
                            enrol: {
                                enrollable: 0,
                                enrolroles: [],
                            }
                        },
                        page: {
                            id: -1,
                            name: '',
                            shortname: ''
                        }
                    },
                    edit: {
                        toolboxShown: false,
                        studyline: {
                            saving: false,
                            editmode: false,
                            data: {
                                name: '',
                                shortname: '',
                                color: '#DDDDDD',
                                enrol: {
                                    enrollable: 0,
                                    enrolroles: [],
                                }
                            },
                            original: {},
                            availableroles: [],
                        },
                        studyplan: {
                            data: {
                                name: '',
                                shortname: '',
                                description: '',
                                slots: 4,
                                startdate: '2020-08-01',
                                enddate: '',
                                aggregation: '',
                                'aggregation_config': '',
                                'aggregation_info': {
                                    useRequiredGrades: true,
                                    useItemCondition: false,
                                },

                            },
                            original: {},
                        }
                    },
                    text: strings.studyplanText,
                    cache: {
                        linelayers: {},
                    },
                    selectedpageindex: 0,
                    emptyline: {
                        id: -1,
                        name: '<No study lines defined>',
                        shortname: '<No study lines>',
                        color: '#FF0000',
                        filterslots: [{}],
                        courseslots: [{}]
                    },
                    availableroles: [],
                };
            },
            created() {
                    const self = this;
                    // Listener for the signal that a new connection was made and needs to be drawn
                    // Sent by the incoming item  - By convention, outgoing items are responsible for drawing the lines
                    ItemEventBus.$on('coursechange', () => {
                        self.$emit('pagechanged', this.selectedpage);
                    });
            },
            mounted() {
                const self = this;
                if (this.value.pages[0].studylines.length == 0 && !this.coaching) {
                    // Start in editmode if studylines on first page are empty
                    this.edit.studyline.editmode = true;
                }

                if (!self.coaching) {
                    // Retrieve available roles (only needed as manager)
                    call([{
                        methodname: 'local_treestudyplan_list_roles',
                        args: {
                            'studyplan_id': this.value.id,
                        }
                    }])[0].then((response) => {
                        self.availableroles = response;
                        return;
                    }).catch(notification.exception);
                } else {
                    self.edit.toolboxShown = true; // Defaults to on in coching view.
                }
                this.$root.$emit('redrawLines');
                this.$emit('pagechanged', this.selectedpage);
            },
            beforeUnmount() {
                this.edit.toolboxShown = false;
                debug.info("Hiding toolbar because of destroy");
            },
            deactivated() {
                this.edit.toolboxShown = false;
                debug.info("Hiding toolbar because of deactivation");
            },
            activated() {
                if (this.coaching) {
                    self.edit.toolboxShown = true; // Defaults to on in coching view.
                }

            },
            updated() {
                this.$root.$emit('redrawLines');
                ItemEventBus.$emit('redrawLines');
            },
            computed: {
                selectedpage() {
                    return this.value.pages[this.selectedpageindex];
                },
            },
            methods: {
                columns(page) {
                    return 1 + (page.periods * 2);
                },
                columnsStylerule(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 < page.periods; i++) {
                        s += " var(--studyplan-course-width) var(--studyplan-filter-width)";
                    }
                    return s + ";";
                },
                trashbinAccepts(type) {
                    if (type.item) {
                        return true;
                    } else {
                        return false;
                    }
                },
                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]
                        && ((new Date()) - this.cache.linelayers[line.id].timestamp < 1000)
                    ) {
                        return this.cache.linelayers[line.id].value;
                    } else {
                        let maxLayer = -1;
                        for (let i = 0; i <= page.periods; i++) {
                            if (line.slots[i]) {
                                // Determine the amount of used layers in a studyline slot
                                for (const ix in line.slots[i].courses) {
                                    const item = line.slots[i].courses[ix];
                                    if (item.layer > maxLayer) {
                                        maxLayer = item.layer;
                                    }
                                }
                                for (const ix in line.slots[i].filters) {
                                    const item = line.slots[i].filters[ix];
                                    if (item.layer > maxLayer) {
                                        maxLayer = item.layer;
                                    }
                                }

                            }
                        }
                        this.cache.linelayers[line.id] = {
                            value: (maxLayer + 1),
                            timestamp: (new Date()),
                        };
                        return maxLayer + 1;
                    }
                },
                slotsempty(slots) {
                    if (Array.isArray(slots)) {
                        let count = 0;
                        for (let i = 0; i < slots.length; i++) {
                            if (Array.isArray(slots[i].courses)) {
                                count += slots[i].courses.length;
                            }
                            if (Array.isArray(slots[i].filters)) {
                                count += slots[i].filters.length;
                            }
                        }
                        return (count == 0);
                    } else {
                        return false;
                    }
                },
                movedStudyplan(plan, from, to) {
                    this.$emit('moved', plan, from, to); // Throw the event up....
                },
                addStudyLine(page, newlineinfo, bvModalEvent) {
                    const self = this;
                    bvModalEvent.preventDefault(); // Do not hide the modal.
                    this.edit.studyline.saving = true; // Set spinner.
                    call([{
                        methodname: 'local_treestudyplan_add_studyline',
                        args: {
                            'page_id': page.id,
                            'name': newlineinfo.name,
                            'shortname': newlineinfo.shortname,
                            'color': newlineinfo.color,
                            'sequence': page.studylines.length,
                            'enrollable': newlineinfo.enrol.enrollable,
                            'enrolroles': newlineinfo.enrol.enrolroles
                        }
                    }])[0].then((response) => {
                        page.studylines.push(response);
                        newlineinfo.name = '';
                        newlineinfo.shortname = '';
                        newlineinfo.color = "#dddddd";
                        newlineinfo.enrol.enrollable = 0;
                        newlineinfo.enrol.enrolroles = [];
                        // Hide the modal manually after reponse is received
                        self.$nextTick(() => {
                            this.edit.studyline.saving = false; // Remove spinner.
                            debug.info("Attempting to hide add line screen");
                            self.$refs['addmodal-' + page.id][0].hide();
                        });
                        return;
                    }).catch(notification.exception);
                },
                editLineStart(line) {
                    const page = this.value.pages[this.selectedpageindex];
                    debug.info("Starting line edit", line);
                    Object.assign(this.edit.studyline.data, line);
                    this.edit.studyline.original = line;
                    this.$bvModal.show('modal-edit-studyline-' + page.id);
                },
                editLineFinish(page, bvModalEvent) {
                    const self = this;
                    let editedline = this.edit.studyline.data;
                    let originalline = this.edit.studyline.original;

                    bvModalEvent.preventDefault(); // Do not hide the modal.
                    this.edit.studyline.saving = true; // Set spinner.

                    call([{
                        methodname: 'local_treestudyplan_edit_studyline',
                        args: {'id': editedline.id,
                                'name': editedline.name,
                                'shortname': editedline.shortname,
                                'color': editedline.color,
                                'enrollable': editedline.enrol.enrollable,
                                'enrolroles': editedline.enrol.enrolroles // ?? [],
                            }
                    }])[0].then((response) => {
                        originalline.name = response.name;
                        originalline.shortname = response.shortname;
                        originalline.color = response.color;
                        originalline.enrol.enrollable = response.enrol.enrollable;
                        originalline.enrol.enrolroles = response.enrol.enrolroles;
                        // Hide the modal manually after reponse is received
                        self.$nextTick(() => {
                            this.edit.studyline.saving = false; // Remove spinner.
                            debug.info("Attempting to hide edit line screen");
                            self.$refs['editormodal-' + page.id][0].hide();
                        });
                        return;
                    }).catch(notification.exception);
                },
                deleteLine(page, line) {
                    const self = this;

                    self.$bvModal.msgBoxConfirm(this.text.studylineConfirmRemove.replace('{$a}', line.name), {
                        okTitle: this.text.delete,
                        okVariant: 'danger',
                    }).then((modalresponse) => {
                        if (modalresponse) {
                            call([{
                                methodname: 'local_treestudyplan_delete_studyline',
                                args: {'id': line.id}
                            }])[0].then((response) => {
                                if (response.success == true) {
                                    let index = page.studylines.indexOf(line);
                                    page.studylines.splice(index, 1);
                                }
                                return;
                            }).catch(notification.exception);
                        }
                        return;
                    }).catch(notification.exception);
                },
                reorderLines(event, lines) {

                    // Apply reordering
                    event.apply(lines);
                    // Send the new sequence to the server
                    let sequence = [];
                    for (let idx in lines) {
                        sequence.push({'id': lines[idx].id, 'sequence': idx});
                    }
                    call([{
                        methodname: 'local_treestudyplan_reorder_studylines',
                        args: {'sequence': sequence}
                    }])[0].then(() => {
                        return;
                    }).catch(notification.exception);
                },
                deletePlan(studyplan) {
                    const self = this;
                    self.$bvModal.msgBoxConfirm(this.text.studyplabConfirmRemove.replace('{$a}', studyplan.name), {
                        okTitle: this.text.delete,
                        okVariant: 'danger',
                    }).then(function(modalresponse) {
                        if (modalresponse) {
                            call([{
                                methodname: 'local_treestudyplan_delete_studyplan',
                                args: {'id': studyplan.id, force: true}
                            }])[0].then((response) => {
                                if (response.success == true) {
                                    self.$root.$emit("studyplanRemoved", studyplan);
                                }
                                return;
                            }).catch(notification.exception);
                        }
                        return;
                    }).catch(notification.exception);
                },
                deleteStudyItem(event) {
                    let item = event.data;
                    call([{
                        methodname: 'local_treestudyplan_delete_studyitem',
                        args: {'id': item.id}
                    }])[0].then((response) => {
                        if (response.success == true) {
                            event.source.$emit('cut', event);
                        }
                        return;
                    }).catch(notification.exception);

                },
                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 = page.periods;
                    let show = true;
                    for (let i = 0; i < periods; i++) {
                        if (line.slots[index - i] && line.slots[index - i].courses) {
                            const list = line.slots[index - i].courses;
                            for (const ix in list) {
                                const item = list[ix];
                                if (item.layer == layeridx) {
                                    if (forGradable) {
                                        if (i > 0 && (item.span - i) > 0) {
                                            show = false;
                                        }
                                    } else {
                                        if ((item.span - i) > 1) {
                                            show = false;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    return show;
                },
                periodEdited(pi) {
                    const prev = this.$refs["periodeditor-" + (pi.period - 1)];
                    if (prev && prev[0]) {
                        prev[0].refresh();
                    }
                    const next = this.$refs["periodeditor-" + (pi.period + 1)];
                    if (next && next[0]) {
                        next[0].refresh();
                    }
                },
                addDay(date, days) {
                    if (days === undefined) {
                        days = 1;
                    }
                    return addDays(date, days);
                },
                subDay(date, days) {
                    if (days === undefined) {
                        days = 1;
                    }
                    return addDays(date, 0 - days);
                },
                toolboxSwitched(event) {
                    this.$emit('toggletoolbox', event);
                },
                pagecreated(page) {
                    this.value.pages.push(page);
                },
                selectedpageChanged(newTabIndex /* , prevTabIndex*/) {
                    const page = this.value.pages[newTabIndex];
                    this.$emit('pagechanged', page);
                },
                sumLineLayers(idx, page) {
                    if (idx < 0 || page.studylines.count == 0) {
                        return 0;
                    } else {
                        let sum = 0;
                        for (let i = 0; i < idx; i++) {
                            sum += this.countLineLayers(page.studylines[i], page) + 1;
                        }
                        return sum;
                    }
                },
                span(line, slot, layer) {
                    let span = 1;
                    for (const course of line.slots[slot].courses) {
                        if (course.slot == slot && course.layer == layer) {
                            span = course.span;
                        }
                    }
                    return span;
                },
                onDrop(event, line, slot) {
                    /*
                     * For HivizDropslots an overlay is placed on the slots. That requires some (copied) dropping
                     * code here I would much rather keep in t-studyline-slot only
                     */
                    debug.info("dropping (t-studyplan)", event, line, slot);
                    const self = this;
                    if (event.type.component) { // Double check in case filter fails
                        debug.info("Adding new component");
                        if (event.type.type == "gradable") {
                            // Determine first available layer;
                            const lineslot = line.slots[slot].courses;
                            let nextlayer = 0;
                            for (const itm of lineslot) {
                                if (itm.layer >= nextlayer) {
                                    nextlayer = itm.layer + 1;
                                }
                            }

                            // Setup placeholder.
                            const placeholder = {
                                type: "spinner",
                                layer: nextlayer,
                            };
                            lineslot.push(placeholder);

                            call([{
                                methodname: 'local_treestudyplan_add_studyitem',
                                args: {
                                    "line_id": line.id,
                                    "slot": slot,
                                    "layer": nextlayer,
                                    "type": 'course',
                                    "details": {
                                        "competency_id": null,
                                        'conditions': '',
                                        'course_id': event.data.id,
                                        'badge_id': null,
                                        'continuation_id': null,
                                    }
                                }
                            }])[0].then((response) => {
                                // Remove placeholder.
                                const index = lineslot.indexOf(placeholder);
                                if (index > -1) {
                                    lineslot.splice(index, 1);
                                }

                                let item = response;
                                lineslot.push(item);
                                self.$emit("input", self.value);

                                // Call the validate period function on next tick,
                                // since it paints the item in the slot first
                                this.$nextTick(() => {
                                    // Find the slot by reference.
                                    const refcode = "slot-" + line.id + "-" + slot + "-" + nextlayer;
                                    const slotcomponent = self.$refs[refcode][0]; // Since the ref is in an array.
                                    // Trigger the validation code.
                                    if (slotcomponent.$refs.timingChecker) {
                                        slotcomponent.$refs.timingChecker.validateCoursePeriod();
                                    }
                                });
                                ItemEventBus.$emit('coursechange');
                                return;
                            }).catch(notification.exception);
                        } else if (event.type.type == "filter") {
                            debug.info("Adding new filter compenent");
                            // Determine first available layer;
                            const lineslot = line.slots[slot].filters;
                            let nextlayer = 0;
                            for (const itm of lineslot) {
                                if (itm.layer >= nextlayer) {
                                    nextlayer = itm.layer + 1;
                                }
                            }
                            // Setup placeholder.
                            const placeholder = {
                                type: "spinner",
                                layer: nextlayer,
                            };
                            lineslot.push(placeholder);

                            call([{
                                methodname: 'local_treestudyplan_add_studyitem',
                                args: {
                                    "line_id": line.id,
                                    "slot": slot,
                                    "type": event.data.type,
                                    "layer": nextlayer,
                                    "details": {
                                        "badge_id": event.data.badge ? event.data.badge.id : undefined,
                                    }
                                }
                            }])[0].then((response) => {
                                // Remove placeholder.
                                const index = lineslot.indexOf(placeholder);
                                if (index > -1) {
                                    lineslot.splice(index, 1);
                                }

                                let item = response;
                                lineslot.push(item);
                                self.$emit("input", self.value);
                                return;
                            }).catch(notification.exception);
                        }
                    }
                },
                checkTypeCourse(type) {
                    if (type.type == "gradable") {
                        if (settings.hivizdropslots && !type.item) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                },
                checkTypeFilter(type) {
                    if (type.type == "filter") {
                        if (settings.hivizdropslots && !type.item) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }
            },
            template:
            `
            <div>
                <t-toolbox v-model="edit.toolboxShown"
                 :activepage="selectedpage"
                 :coaching="coaching"
                 :studyplanid="value.id"></t-toolbox>
                <div class='controlbox t-studyplan-controlbox'>
                    <div class="controlbox-group">
                        <b-form-checkbox v-if="!coaching"
                            v-model="edit.studyline.editmode" class="sw-studyplan-toolbar" switch
                            @change="toolboxSwitched(edit.toolboxShown && !edit.studyline.editmode); "
                            >{{ text.studyline_editmode }}</b-form-checkbox>
                        <b-form-checkbox
                            v-if="!edit.studyline.editmode" v-model="edit.toolboxShown" class="sw-studyplan-toolbar" switch
                            @change="toolboxSwitched"
                            >{{ text.toolbox_toggle}}</b-form-checkbox>
                        <drop
                            mode='copy'
                            class='t-item-deletebox text-danger border-danger'
                            @drop='deleteStudyItem'
                            :accepts-type="trashbinAccepts"
                            ><i class='fa fa-trash'></i>
                        </drop>
                    </div>
                    <div class="controlbox-group" v-if="!coaching">
                        <span class='control editable'>
                            <t-studyplan-advanced v-model="value" :selectedpage="selectedpage"></t-studyplan-advanced>
                        </span>
                        <span class='control editable'>
                            <t-studyplan-associate
                                v-model="value"><i class='fa fa-users'></i>&nbsp;{{text.associations}}</t-studyplan-associate>
                        </span>
                        <span class='control editable'>
                            <t-studyplan-edit v-model="value" @moved="movedStudyplan"
                                ><i class='fa fa-gear'></i>&nbsp;{{text.edit}}</t-studyplan-edit>
                        </span>
                        <span class='control deletable'>
                            <a v-if='value.pages.length == 0' href='#' @click.prevent='deletePlan(value)'
                                ><i class='text-danger fa fa-trash'></i></a>
                        </span>
                    </div>
                </div>
                <b-card no-body>
                    <b-tabs
                        v-model='selectedpageindex'
                        @activate-tab='selectedpageChanged'
                        content-class="mt-1">
                        <!-- New Tab Button (Using tabs-end slot) -->
                        <template #tabs-end>
                            <t-studyplan-page-edit
                                v-if="!coaching"
                                :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>
                                <span v-html="page.display.shortname"></span>
                                <t-studyplan-page-edit
                                    v-if="!coaching && (pageindex == selectedpageindex)"
                                    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"
                                    class="t-slot-droplist"
                                    :accepts-type="'studyline-'+page.id"
                                    @reorder="reorderLines($event,page.studylines)"
                                    mode="copy"
                                    row
                                >
                                    <template v-slot:item="{item}">
                                        <drag
                                            :key="item.id"
                                            class='t-studyline-drag'
                                            :data="item"
                                            :type="'studyline-'+page.id"
                                            >
                                            <template v-slot:drag-image>
                                                <i class="fa fa-arrows text-primary"></i>
                                            </template>
                                            <t-studyline-edit
                                                v-if="!coaching"
                                                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>
                                    <template v-if="page.studylines.length > 0">
                                        <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>
                                    </template>
                                    <t-studyline-heading v-else
                                        @resize="headingresized(0,$event)"

                                        :layers="1"
                                        :class="'odd first last'"
                                    ></t-studyline-heading>
                                </div>
                                <!-- Next, paint all the cells in the scrollable -->
                                <div class="t-studyplan-scrollable" >
                                    <div class="t-studyplan-timeline" :style="columnsStylerule(page)">
                                    <!-- add period information -->
                                    <template v-for="(n,index) in (page.periods+1)">
                                        <s-studyline-header-period
                                            mode="edit"
                                            :x-index="index"
                                            :style="'grid-area: 1 / '+ ((2*index)) +';'"
                                            :identifier='Number(page.id)'
                                            v-if="index > 0"
                                            v-model="page.perioddesc[index-1]"
                                            ><t-period-edit
                                                v-if="!coaching"
                                                :ref="'periodeditor-'+index"
                                                @edited="periodEdited"
                                                v-model="page.perioddesc[index-1]"
                                                :minstart="(index > 1) ? addDay(page.perioddesc[index-2].startdate,2) : null"
                                                :maxend="(index < page.periods) ? subDay(page.perioddesc[index].enddate,2) : null"
                                            ></t-period-edit
                                        ></s-studyline-header-period>
                                        <div class="s-studyline-header-filter"
                                            :x-index="index"
                                            :style="'grid-area: 1 / '+ ((2*index)+1) +';'"
                                        ></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')"
                                                    :style="'grid-area: '+ (1+sumLineLayers(lineindex,page)+layernr)
                                                        + ' / ' + (2 * index)
                                                        + ' / ' + (1 + sumLineLayers(lineindex,page)+layernr)
                                                        + ' / ' + ( (2 * index) + (2*span(line,index,layeridx) - 1)) + ';'"
                                                    type='gradable'
                                                    :ref='"slot-" + line.id + "-" + index + "-" + layeridx'
                                                    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'
                                                    :style="'grid-area: '+ (1+sumLineLayers(lineindex,page)+layernr) + ' / ' + (2*index+1) +';'"
                                                    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 v-if="settings.hivizdropslots"
                                        ><template v-for="(line,lineindex) in page.studylines"
                                            ><template v-for="(n,index) in (page.periods+1)"
                                                ><drop v-if="index > 0"
                                                    :style="'grid-area: '+ (2 + sumLineLayers(lineindex,page))
                                                        + ' / ' + (2 * index)
                                                        + ' / ' + (1 + sumLineLayers(lineindex + 1,page))
                                                        + ' / ' + (2 * index) + '; overflow: hidden;'"
                                                    :class="'t-slot-drop t-slot-linedrop course hiviz'"
                                                    :accepts-type="checkTypeCourse"
                                                    @drop="onDrop($event,line,index)"
                                                    mode="cut"
                                                ><span>{{text.drophere}}</span></drop
                                                ><drop
                                                    :style="'grid-area: '+ (2 + sumLineLayers(lineindex,page))
                                                        + ' / ' + ((2 * index) + 1)
                                                        + ' / ' + (1 + sumLineLayers(lineindex + 1,page))
                                                        + ' / ' + ( (2 * index) + 1 ) + '; overflow: hidden;'"
                                                    :class="'t-slot-drop t-slot-linedrop filter hiviz'"
                                                    :accepts-type="checkTypeFilter"
                                                    @drop="onDrop($event,line,index)"
                                                    mode="cut"
                                                ><span>{{text.drophere}}</span></drop
                                            ></template
                                        ></template
                                    ></template
                                    ></div>
                                </div>
                            </div>
                            <div v-if="edit.studyline.editmode" class='t-studyline-add ml-2 mt-1'>
                                <a href="#" v-b-modal="'modal-add-studyline-'+page.id" @click.prevent="false;"
                                    ><i class='fa fa-plus'></i>{{ text.studyline_add }}</a>
                            </div>
                            <b-modal
                                :id="'modal-add-studyline-'+page.id"
                                :ref="'addmodal-'+page.id"
                                size="lg"
                                ok-variant="primary"
                                :title="text.studyline_add"
                                @ok="addStudyLine(page,create.studyline,$event)"
                                :ok-disabled="Math.min(create.studyline.name.length,create.studyline.shortname.length) == 0"
                                >
                                <template #modal-ok>
                                    <span v-if="!edit.studyline.saving">{{ text.add }}</span>
                                    <span v-else class='spinner-border spinner-border-sm'><span class='sr-only'>Saving...</span></span>
                                </template>
                                <b-container>
                                    <b-row>
                                        <b-col cols="3">{{text.studyline_name}}</b-col>
                                        <b-col>
                                            <b-form-input v-model="create.studyline.name" :placeholder="text.studyline_name_ph"></b-form-input>
                                        </b-col>
                                    </b-row>
                                    <b-row>
                                        <b-col cols="3">{{text.studyline_shortname}}</b-col>
                                        <b-col>
                                        <b-form-input
                                            v-model="create.studyline.shortname"
                                            :placeholder="text.studyline_shortname_ph"></b-form-input>
                                        </b-col>
                                    </b-row>
                                    <b-row>
                                        <b-col cols="3">{{text.studyline_color}}</b-col>
                                        <b-col>
                                            <input type="color" v-model="create.studyline.color" />
                                            <!-- hsluv-picker v-model="create.studyline.color" horizontal displaysize="175" ></hsluv-picker -->
                                        </b-col>
                                    </b-row>
                                    <b-row>
                                    <b-col cols="3">{{ text.studyline_enrollable}}</b-col>
                                        <b-col>
                                            <b-form-select v-model="create.studyline.enrol.enrollable">
                                                <b-form-select-option
                                                    v-for="(nr,n) in 4"
                                                    :value="n"
                                                    :key="n"
                                                >{{text['line_enrollable_'+n]}}</b-form-select-option>
                                            </b-form-select>
                                        </b-col>
                                    </b-row>
                                    <b-row v-if='[2,3].includes(create.studyline.enrol.enrollable)'>
                                        <b-col cols="3">{{ text.studyline_enrolroles}}</b-col>
                                        <b-col>
                                        <b-form-select
                                            v-model="create.studyline.enrol.enrolroles"
                                            :options="availableroles"
                                            multiple
                                            value-field="id"
                                            text-field="name"
                                            :select-size="6"
                                            ></b-form-select>
                                        </b-col>
                                    </b-row>
                                </b-container>
                            </b-modal>
                            <b-modal
                                :id="'modal-edit-studyline-'+page.id"
                                :ref="'editormodal-'+page.id"
                                size="lg"
                                ok-variant="primary"
                                :title="text.studyline_edit"
                                @ok="editLineFinish(page, $event)"
                                :ok-disabled="Math.min(edit.studyline.data.name.length,edit.studyline.data.shortname.length) == 0"
                                >
                                <template #modal-ok>
                                    <span v-if="!edit.studyline.saving">{{ text.save }}</span>
                                    <span v-else class='spinner-border spinner-border-sm'><span class='sr-only'>Saving...</span></span>
                                </template>
                                <b-container>
                                    <b-row>
                                        <b-col cols="3">{{ text.studyline_name}}</b-col>
                                        <b-col>
                                            <b-form-input
                                                v-model="edit.studyline.data.name"
                                                :placeholder="text.studyline_name_ph"></b-form-input>
                                        </b-col>
                                    </b-row>
                                    <b-row>
                                        <b-col cols="3">{{ text.studyline_shortname}}</b-col>
                                        <b-col>
                                        <b-form-input
                                            v-model="edit.studyline.data.shortname"
                                            :placeholder="text.studyline_shortname_ph"></b-form-input>
                                        </b-col>
                                    </b-row>
                                    <b-row>
                                        <b-col cols="3">{{ text.studyline_color}}</b-col>
                                        <b-col>
                                            <input type="color" v-model="edit.studyline.data.color" />
                                        </b-col>
                                    </b-row>
                                    <b-row>
                                    <b-col cols="3">{{ text.studyline_enrollable}}</b-col>
                                        <b-col>
                                            <b-form-select v-model="edit.studyline.data.enrol.enrollable">
                                                <b-form-select-option
                                                    v-for="(nr,n) in 4"
                                                    :value="n"
                                                >{{text['line_enrollable_'+n]}}</b-form-select-option>
                                            </b-form-select>
                                        </b-col>
                                    </b-row>
                                    <b-row v-if='[2,3].includes(edit.studyline.data.enrol.enrollable)'>
                                        <b-col cols="3">{{ text.studyline_enrolroles}}</b-col>
                                        <b-col>
                                        <b-form-select
                                            v-model="edit.studyline.data.enrol.enrolroles"
                                            :options="availableroles"
                                            multiple
                                            value-field="id"
                                            text-field="name"
                                            :select-size="6"
                                            ></b-form-select>
                                        </b-col>
                                    </b-row>
                                </b-container>
                            </b-modal>
                        </b-tab>
                    </b-tabs>
                </b-card>
            </div>
            `
        });

       /*
        * T-STUDYLINE-HEADER
        */
       Vue.component('t-studyline-heading', {
            props: {
                value: {
                    type: Object, // Studyline
                    default() {
                        return {};
                    },
                },
                layers: {
                    type: Number,
                    'default': 1,
                },
            },
            data() {
                return {
                    layerHeights: {}
                };
            },
            created() {
                // Listener for the signal that a new connection was made and needs to be drawn
                // Sent by the incoming item  - By convention, outgoing items are responsible for drawing the lines
                ItemEventBus.$on('lineHeightChange', this.onLineHeightChange);
            },
            computed: {

            },
            methods: {
                onLineHeightChange(lineid) { // Is called with parameters (lineid, layerid, newheight)
                    // All layers for this line have the first slot send an update message on layer height change.
                    // When one of those updates is received, record the height and recalculate the total height of the
                    // header
                    if (this.$refs.main && lineid == this.value.id) {
                        const items = document.querySelectorAll(
                            `.t-studyline-slot-0[data-studyline='${this.value.id}']`);

                        // Determine the height of all the lines and add them up.
                        let heightSum = 0;
                        items.forEach((el) => {
                            // Func getBoundingClientRect() Gets the actual fractional height instead of rounded to integer pixels
                            const r = el.getBoundingClientRect();
                            const height = r.height;
                            heightSum += height;
                        });

                        const heightStyle = `${heightSum}px`;
                        this.$refs.main.style.height = heightStyle;
                    }
                }
            },
            template: `
            <div class="t-studyline t-studyline-heading "
                :data-studyline="value.id" ref="main"
                ><div class="t-studyline-handle" :style="'background-color: ' + value.color"></div>
                <div class="t-studyline-title">
                    <abbr v-b-tooltip.hover.right :title="value.name">{{ value.shortname }}</abbr>
                </div>
            </div>
            `,
        });

        /*
        * T-STUDYLINE (Used only for study line edit mode)
        */
        Vue.component('t-studyline-edit', {
            props: {
                value: {
                    type: Object, // Studyline
                    default() {
                        return {};
                    },
                }
           },
            data() {
                return {
                };
            },
            computed: {
                deletable() {
                    // Check if all the slots are empty
                    const slots = this.value.slots;
                    if (Array.isArray(slots)) {
                        let count = 0;
                        for (let i = 0; i < slots.length; i++) {
                            if (Array.isArray(slots[i].courses)) {
                                count += slots[i].courses.length;
                            }
                            if (Array.isArray(slots[i].filters)) {
                                count += slots[i].filters.length;
                            }
                        }
                        return (count == 0);
                    } else {
                        return false;
                    }
                },
                editable() {
                    return true;
                }
            },
            methods: {
                onEdit() {
                    this.$emit('edit', this.value);
                },
                onDelete() {
                    this.$emit('delete', this.value);
                },

            },
            template: `
            <div :class="'t-studyline '" >
                <div class="t-studyline-handle" :style="'background-color: ' + value.color"></div>
                <div class="t-studyline-title">
                    <div>
                        <i class='fa fa-arrows text-primary'></i>
                        <abbr v-b-tooltip.hover :title="value.name">{{ value.shortname }}</abbr>
                    </div>
                </div>
                <div class='t-studyline-editmode-content'>
                    <slot></slot>
                </div>
                <div class='controlbox'>
                    <template v-if='editable || deletable'>
                    <span class='control editable' v-if='editable'>
                        <a href='#' @click.prevent='onEdit'><i class='fa fa-pencil'></i></a>
                    </span>
                    <span class='control deletable' v-if='deletable'>
                        <a v-if='deletable' href='#' @click.prevent='onDelete'><i class='text-danger fa fa-trash'></i></a>
                    </span>
                    </template>
                </div>
            </div>
            `,
        });

        /*
         * During a redisign it was decided to have the studyline still get the entire array as a value,
         * even though it only shows one drop slot for the layer it is in. This is to make repainting easier,
         * since we modify the array for the layer we handle. FIXME: Make this less weird
         */
        Vue.component('t-studyline-slot', {
            props: {
                type: {
                    type: String,
                    'default': 'gradable',
                },
                slotindex: {
                    type: Number,
                    'default': '',
                },
                line: {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                layer: {
                    type: Number,
                },
                value: {
                    type: Array, // Dict with layer as index
                    default() {
                        return [];
                    },
                },
                plan: {
                    type: Object, // Studyplan data
                    default() {
                        return null;
                    },
                },
                page: {
                    type: Object, // Studyplan data
                    default() {
                        return null;
                    },
                },
                period: {
                    type: Object, // Studyplan data
                    default() {
                        return null;
                    },
                },
            },
            mounted() {
                const self = this;
                if (self.type == "gradable" && self.slotindex == 1) {
                    self.resizeListener = new ResizeObserver(() => {
                        if (self.$refs.main) {
                            const size = self.$refs.main.getBoundingClientRect();

                            ItemEventBus.$emit('lineHeightChange', self.line.id, self.layer, size.height);
                        }
                    }).observe(self.$refs.main);
                }
            },
            unmounted() {
                if (this.resizeListener) {
                    this.resizeListener.disconnect();
                }
            },
            computed: {
                slotkey() {
                    return `${this.type}'-'${this.line.id}-${this.slotindex}-${this.layer}`;
                },
                itemidx() {
                    for (const ix in this.value) {
                        const itm = this.value[ix];
                        if (itm.layer == this.layer) {
                            return ix;
                        }
                    }
                    return null;
                },
                item() {
                    for (const ix in this.value) {
                        const itm = this.value[ix];
                        if (itm.layer == this.layer) {
                            return itm;
                        }
                    }
                    return null;
                },
                listtype() {
                    return this.type;
                },
                courseHoverDummy() {
                    return {course: this.hover.component};
                },
                spanCss() {
                    if (this.item && this.item.span > 1) {
                        // Calculate span like this:
                        // const span = (2 * this.item.span) - 1;
                        return `width: 100%; `;
                    } else {
                        return "";
                    }
                }
            },
            data() {
                return {
                    settings: settings,
                    text: strings.courseTiming,
                    plantext: strings.studyplanText,
                    resizeListener: null,
                    hover: {
                        component: null,
                        type: null,
                    },
                    datechanger: {
                        coursespan: null,
                        periodspan: null,
                        'default': false,
                        defaultchoice: false,
                        hidewarn: false,
                    }
                };
            },
            methods: {
                onDrop(event) {
                    this.hover.component = null;
                    this.hover.type = null;
                    debug.info("dropping (slot)", event);
                    const self = this;
                    if (event.type.item) {
                        let item = event.data;

                        // To avoid weird visuals with the lines,
                        // we add the item to the proper place in the front-end first
                        item.layer = this.layer;
                        item.slot = this.slotindex;
                        self.value.push(item);
                        self.$emit("input", self.value);

                        // Then on the next tick, we inform the back end
                        // Since moving things around has never been unsuccessful, unless you have other problems,
                        // it's better to have nice visuals.
                        self.relocateStudyItem(item).then(() => {
                            if (this.$refs.timingChecker) {
                                this.$refs.timingChecker.validateCoursePeriod();
                            }
                            return;
                        }).catch(notification.exception);
                    } else if (event.type.component) {
                        debug.info("Adding new component");
                        if (event.type.type == "gradable") {

                            // Setup placeholder.
                            const placeholder = {
                                type: "spinner",
                                layer: self.layer,
                            };
                            self.value.push(placeholder);

                            call([{
                                methodname: 'local_treestudyplan_add_studyitem',
                                args: {
                                    "line_id": self.line.id,
                                    "slot": self.slotindex,
                                    "layer": self.layer,
                                    "type": 'course',
                                    "details": {
                                        "competency_id": null,
                                        'conditions': '',
                                        'course_id': event.data.id,
                                        'badge_id': null,
                                        'continuation_id': null,
                                    }
                                }
                            }])[0].then((response) => {
                                // Remove placeholder.
                                const index = self.value.indexOf(placeholder);
                                if (index > -1) {
                                    self.value.splice(index, 1);
                                }

                                let item = response;
                                self.relocateStudyItem(item).then(()=>{
                                    self.value.push(item);
                                    self.$emit("input", self.value);

                                    // Call the validate period function on next tick,
                                    // since it paints the item in the slot first
                                    this.$nextTick(() => {
                                        if (this.$refs.timingChecker) {
                                            this.$refs.timingChecker.validateCoursePeriod();
                                        }
                                    });
                                    ItemEventBus.$emit('coursechange');
                                    return;
                                }).catch(notification.exception);
                                return;
                            }).catch(notification.exception);
                        } else if (event.type.type == "filter") {
                            debug.info("Adding new filter compenent");
                            // Setup placeholder.
                            const placeholder = {
                                type: "spinner",
                                layer: self.layer,

                            };
                            self.value.push(placeholder);
                            call([{
                                methodname: 'local_treestudyplan_add_studyitem',
                                args: {
                                    "line_id": self.line.id,
                                    "slot": self.slotindex,
                                    "type": event.data.type,
                                    "details": {
                                        "badge_id": event.data.badge ? event.data.badge.id : undefined,
                                    }
                                }
                            }])[0].then((response) => {
                                // Remove placeholder.
                                const index = self.value.indexOf(placeholder);
                                if (index > -1) {
                                    self.value.splice(index, 1);
                                }

                                let item = response;
                                self.relocateStudyItem(item).then(() => {
                                    item.layer = this.layer;
                                    self.value.push(item);
                                    self.$emit("input", self.value);
                                    return;
                                }).catch(notification.exception);
                                return;
                            }).catch(notification.exception);
                        }
                    }
                },
                onCut(event) {
                    const self = this;
                    let id = event.data.id;
                    for (let i = 0; i < self.value.length; i++) {
                        if (self.value[i].id == id) {
                            self.value.splice(i, 1); i--;
                            break; // Just remove one
                        }
                    }
                    // Do something to signal that this item has been removed
                    this.$emit("input", this.value);
                    ItemEventBus.$emit('coursechange');
                },
                relocateStudyItem(item) {
                    const iteminfo = {'id': item.id, 'layer': this.layer, 'slot': this.slotindex, 'line_id': this.line.id};
                    return call([{
                        methodname: 'local_treestudyplan_reorder_studyitems',
                        args: {'items': [iteminfo]} // Function was designed to relocate multiple items at once, hence the array
                    }])[0].catch(notification.exception);
                },
                onDragEnter(event) {
                    this.hover.component = event.data;
                    this.hover.type = event.type;
                },
                onDragLeave() {
                    this.hover.component = null;
                    this.hover.type = null;
                },
                maxSpan() {
                    // Determine the maximum span for components in this slot
                    return this.page.periods - this.slotindex + 1;
                },
                makeType(item) {
                    return {
                        item: true,
                        component: false,
                        span: item.span,
                        type: this.type,
                    };
                },
                checkType(type) {
                    if (type.type == this.type) {
                        if (settings.hivizdropslots && !type.item) {
                            return false;
                        } else {
                            if (type == 'filter') {
                                return true;
                            } else if (type.span <= this.maxSpan()) {
                                return true;
                            } else {
                                return false;
                            }
                        }
                    } else {
                        return false;
                    }
                },
            },
            template: `
            <div :class=" 't-studyline-slot '+type
                        + ' t-studyline-slot-'+slotindex + ' '
                        + ((slotindex==0)?' t-studyline-firstcolumn ':' ')
                        + 'periodcount-' + page.periods + ' '"
                :data-studyline="line.id" ref="main"
                :style='spanCss'
                ><drag v-if="item"
                    :key="item.id"
                    class="t-slot-item"
                    :data="item"
                    :type="makeType(item)"
                    @cut="onCut"
                        ><span
                            v-if="value[itemidx].type == 'spinner'"
                            class="text-primary spinner-border t-studyline-slot-spinner"
                        ></span
                        ><t-item v-else
                            @deleted="onCut"
                            v-model="value[itemidx]"
                            :plan="plan"
                            :line='line'
                            :page='page'
                            :period='period'
                            :maxspan='maxSpan()'
                        ></t-item
                    ></drag
                ><drop v-else
                    :class="'t-slot-drop '+type + (layer > 0?' secondary':' primary') + (settings.hivizdropslots?' hiviz':'')"
                    :accepts-type="checkType"
                    @drop="onDrop"
                    mode="cut"
                    @dragenter="onDragEnter"
                    @dragleave="onDragLeave"
                ><template v-if="hover.component">
                        <div v-if="hover.type.item"
                            class="t-slot-item feedback"
                            :key="hover.component.id"
                            ><t-item v-model="hover.component" dummy></t-item
                            ></div
                        ><div v-else-if="hover.type.type == 'gradable'"
                            class="t-slot-item feedback"
                            :key="'course-'+hover.component.id"
                            ><t-item-course v-model="courseHoverDummy"></t-item-course></div
                        ><div v-else-if="hover.type.type == 'filter'"
                            class="t-slot-item feedback"
                            key="tooldrop"
                            ><t-item-junction    v-if="hover.component.type == 'junction'" ></t-item-junction
                            ><t-item-start       v-else-if="hover.component.type == 'start'"    ></t-item-start
                            ><t-item-finish      v-else-if="hover.component.type == 'finish'"   ></t-item-finish
                            ><t-item-badge       v-else-if="hover.component.type == 'badge'"    ></t-item-badge
                        ></div
                        ><div v-else
                            class="t-slot-item feedback"
                            :key="hover.type">--{{ hover.type }}--</div
                    ></template
                ><span v-else-if="settings.hivizdropslots">{{plantext.drophere}}</span></drop>
                <t-item-timing-checker hidden
                    v-if="value && value[itemidx] && value[itemidx].course"
                    ref="timingChecker"
                    :maxspan="maxSpan()"
                    :page="page"
                    :line="line"
                    :period="period"
                    v-model="value[itemidx]"
                    ></t-item-timing-checker>
            </div>
            `,
        });


        Vue.component('t-item-timing-checker', {
            props: {
                value: {
                    type: Object, // T-item model
                },
                page: {
                    type: Object, // Studyplanpage
                },
                line: {
                    type: Object, // Studyline
                },
                period: {
                    type: Object, // Studyplan data
                },
                maxspan: {
                    type: Number,
                },
                hidden: {
                    type: Boolean,
                    'default': false,
                }
            },
            computed: {
                endperiod() {
                    const endperiodnr = Math.min(this.page.periods, this.period.period + (this.value.span - 1));
                    return this.page.perioddesc[endperiodnr - 1];
                },
                coursePeriodMatches() {
                    const self = this;
                    if (self.page.timeless) {
                        // Always return true in timeless mode.
                        return true;
                    }
                    if (self.value && self.value.type == 'course') {
                        self.datechanger.coursespan = datespaninfo(self.value.course.startdate, self.value.course.enddate);
                        self.datechanger.periodspan = datespaninfo(self.period.startdate, self.endperiod.enddate);
                        if (self.datechanger.coursespan.first.getTime() == self.datechanger.periodspan.first.getTime()
                            && self.datechanger.coursespan.last.getTime() == self.datechanger.periodspan.last.getTime()) {
                            return true;
                        } else {
                            return false;
                        }
                    } else {
                        debug.warn("Timing thing not properly configured", self.value, self.period, self.maxspan);
                        return false;
                    }
                },
            },
            data() {
                return {
                    // Create random id to avoid opening the wrong modals
                    id: Math.floor(Math.random() * Date.now()).toString(16),
                    text: strings.courseTiming,
                    datechanger: {
                        coursespan: null,
                        periodspan: null,
                        globals: {
                            'default': false,
                            defaultchoice: false,
                            hidewarn: false,
                        },
                    }
                };
            },
            methods: {
                validateCoursePeriod() {
                    const self = this;

                    if (!self.page.timeless) {
                        debug.info("Validating course and period");
                        if (!(self.coursePeriodMatches)) {
                            debug.info("Course timing does not match period timing");

                            if (self.value.course.canupdatecourse) {
                                if (!self.hidden || !self.datechanger.globals.default) {
                                    // Periods do not match, pop up the date change request
                                    this.$bvModal.show('t-course-timing-matching-' + this.id);
                                } else if (self.datechanger.globals.defaultvalue) {
                                    // G for it without asking
                                    self.changeCoursePeriod();
                                }
                            } else {
                                // User is not able to change course timing - show a warning
                                if (!self.hidden || !self.datechanger.globals.hidewarn) {
                                    this.$bvModal.show('t-course-timing-warning-' + this.id);
                                }
                            }
                        } else {
                            debug.info("Course timing matches period", self.datechanger);
                        }
                    } else {
                        debug.info("Skipping course timing check because of timeless mode", self.datechanger);
                    }
                },
                changeCoursePeriod() {
                    const self = this;
                    // Save the state
                    if (self.datechanger.globals.default) {
                        self.datechanger.globals.defaultvalue = true;
                    }
                    return call([{
                        methodname: 'local_treestudyplan_course_period_timing',
                        args: {
                                'period_id': self.period.id,
                                'course_id': this.value.course.id,
                                span: this.value.span,
                            }
                    }])[0].catch(notification.exception).then((response) => {
                        self.value.course.startdate = response.startdate;
                        self.value.course.enddate = response.enddate;
                        self.value.course.timing = response.timing;
                        self.$emit("input", self.value);
                        return;
                    });
                },
                checkFilterSlotBusy(slotindex) {
                    debug.info("checking filter", this.line.slots, slotindex, this.value.layer);
                    if (this.line.slots[slotindex]) {
                        const list = this.line.slots[slotindex].filters;
                        for (const ix in list) {
                            if (list[ix].layer == this.value.layer) {
                                debug.info("Busy:", list[ix]);
                                return list[ix];
                            }
                        }
                    }
                    return null;
                },
                nextFreeFilterLayer(slotindex) {
                    const layer = this.value.layer;
                    const list = this.line.slots[slotindex].filters;
                    const usedLayers = [];
                    for (const ix in list) {
                        usedLayers.push(list[ix].layer);
                    }
                    let nextlyr = layer + 1;
                    while (usedLayers.includes(nextlyr)) {
                        nextlyr++;
                    }
                    return nextlyr;
                },
                checkCourseSlotBusy(slotindex) {
                    debug.info("checking ", this.line.slots, slotindex, this.value.layer);
                    if (this.line.slots[slotindex]) {
                        const list = this.line.slots[slotindex].courses;
                        for (const ix in list) {
                            if (list[ix].layer == this.value.layer) {
                                debug.info("Busy:", list[ix]);
                                return list[ix];
                            }
                        }
                    }
                    return null;
                },
                nextFreeCourseLayer(slotindex) {
                    const layer = this.value.layer;
                    const list = this.line.slots[slotindex].courses;
                    const usedLayers = [];
                    for (const ix in list) {
                        usedLayers.push(list[ix].layer);
                    }
                    let nextlyr = layer + 1;
                    while (usedLayers.includes(nextlyr)) {
                        nextlyr++;
                    }
                    return nextlyr;
                },
                shiftCollisions(span) {
                    // Check all periods for collision
                    const items = [];
                    for (let i = this.value.slot; i < this.value.slot + span; i++) {
                        const busyFilter = this.checkFilterSlotBusy(i);
                        if (busyFilter) {
                            const nextlyr = this.nextFreeFilterLayer(i);
                            items.push({
                                id: busyFilter.id,
                                layer: nextlyr,
                                'line_id': this.line.id,
                                slot: busyFilter.slot,
                            });
                            busyFilter.layer = nextlyr;
                        }
                        const busyCourse = this.checkCourseSlotBusy(i);
                        if (busyCourse && busyCourse.id != this.value.id) {
                            const nextlyr = this.nextFreeCourseLayer(i);
                            items.push({
                                id: busyCourse.id,
                                layer: nextlyr,
                                'line_id': this.line.id,
                                slot: busyCourse.slot,
                            });
                            busyCourse.layer = nextlyr;
                        }
                    }
                    if (items.length > 0) {
                        call([{
                            methodname: 'local_treestudyplan_reorder_studyitems',
                            args: {items: items}
                        }])[0].catch(notification.exception);
                    }
                },
                changeSpan(span) {
                    const self = this;
                    this.shiftCollisions(span);
                    return call([{
                        methodname: 'local_treestudyplan_set_studyitem_span',
                        args: {
                                id: self.value.id,
                                span: span
                            }
                    }])[0].catch(notification.exception).then((response) => {
                        self.value.span = response.span;
                        self.$emit('input', self.value);
                        self.$nextTick(() => {
                            self.validateCoursePeriod();
                        });
                        return;
                    });
                },
                formatDuration(dsi) {
                    let s = "";
                    if (dsi.years == 1) {
                        s += `1 ${this.text.year}, `;
                    } else if (dsi.years > 1) {
                        s += `${dsi.years} ${this.text.years}, `;
                    }
                    if (dsi.weeks == 1) {
                        s += `1 ${this.text.week}, `;
                    } else if (dsi.weeks > 1) {
                        s += `${dsi.weeks} ${this.text.weeks}, `;
                    }
                    if (dsi.days == 1) {
                        s += `1 ${this.text.day}, `;
                    } else if (dsi.days > 1) {
                        s += `${dsi.days} ${this.text.days}, `;
                    }

                    return s.toLocaleLowerCase();
                },
            },
            // To avoid the span creeping in the dom where it shouldn't, set display to none if it is hidden
            // This does not affect the modals, which are rendered outside of this element when needed
            template: `
            <div :class="'t-item-timing-checker'" :style="hidden?'display: none ':''">
                <template v-if="!hidden" >
                    <span class="mr-1" v-if="coursePeriodMatches">
                        <i  class="text-success fa fa-calendar-check-o"
                            v-b-tooltip.hover.topright :title="text.timing_ok"
                        ></i>
                    </span>
                    <span class="mr-1" v-else>
                        <a  href='#' @click.prevent="validateCoursePeriod()" class="text-warning"
                            v-b-tooltip.hover.bottomleft :title="text.timing_off"
                            ><i class="fa fa-calendar-times-o"
                            ></i
                            ><i class="fa fa-question-circle text-black-50"
                                style="font-size: 0.8em; top: -0.3em; position: relative;"

                            ></i
                        ></a>
                    </span>
                    <span class="ml-1" v-b-tooltip.hover.bottomleft :title="text.periodspan_desc"
                        >{{ text.periodspan
                        }}&nbsp;<b-form-select v-if="maxspan > 1"
                            class=""
                            size="sm" @change="changeSpan" v-model="value.span">
                            <b-form-select-option v-for="(n,i) in maxspan" :value='n' :key='i'
                            >{{ n }}</b-form-select-option>
                        </b-form-select
                        ><span v-else>{{value.span}}</span>&nbsp;{{
                            (value.span == 1)?text.period.toLowerCase():text.periods.toLowerCase()
                        }}<i
                            class="fa fa-question-circle text-black-50"
                            style="font-size: 0.8em; top: -0.3em; position: relative;"
                            ></i>
                    </span>
                </template>
                <b-modal
                    :id="'t-course-timing-matching-'+this.id"
                    size="lg"
                    :title="text.title"
                    @ok="changeCoursePeriod"
                    :ok-title="text.yes"
                    ok-variant="danger"
                    :cancel-title="text.no"
                    cancel-variant="primary"
                    >
                    <b-container v-if="datechanger.coursespan && datechanger.periodspan && value && value.course">
                        <b-row><b-col cols="12">{{ text.desc }}</b-col></b-row>
                        <b-row><b-col cols="12"><div class="generalbox alert alert-warning">{{ text.question }}</div></b-col></b-row>
                        <b-row>
                            <b-col cols="6">
                                <h3> {{ text.course }} </h3>
                                <p class="mb-0"><b><span v-html="value.course.fullname"></span></b></p>
                                <p class="mb-1"><b><span v-html="value.course.shortname"></span></b></p>
                                <p class="mb-1">{{ datechanger.coursespan.formatted.first}} - {{ datechanger.coursespan.formatted.last}}</p>
                                <p class="mb-0"><b>{{ text.duration }}</b><br>
                                {{ formatDuration(datechanger.coursespan)}}</p>
                            </b-col>
                            <b-col cols="6">
                                <h3> {{ text.period }} </h3>
                                <p class="mb-0"><b><span v-html="period.display.fullname"></span></b
                                                ><b v-if="value.span > 1"> - <span v-html="endperiod.display.fullname"></span></b></p>
                                <p class="mb-1"><b><span v-html="period.display.shortname"></span></b
                                                ><b v-if="value.span > 1"> - <span v-html="endperiod.display.shortname"></span></b></p>
                                <p class="mb-1">{{ datechanger.periodspan.formatted.first}} - {{ datechanger.periodspan.formatted.last}}</p>
                                <p class="mb-0"><b>{{ text.duration }}</b><br>
                                {{ formatDuration(datechanger.periodspan)}}</p>
                            </b-col>
                        </b-row>
                        <b-row v-if='hidden' class="pt-2"><b-col cols="12">
                            <b-form-checkbox type="checkbox" v-model="datechanger.globals.default">{{ text.rememberchoice }}</b-form-checkbox>
                        </b-col></b-row>
                    </b-container>
                </b-modal>
                <b-modal
                    :id="'t-course-timing-warning-'+this.id"
                    size="lg"
                    ok-variant="primary"
                    :title="text.title"
                    :ok-title="text.yes"
                    ok-only
                    >
                    <b-container v-if="datechanger.coursespan && datechanger.periodspan && value && value.course">
                        <b-row><b-col cols="12">{{ text.desc }}</b-col></b-row>
                        <b-row><b-col cols="12"><div class="generalbox alert alert-warning">{{ text.warning }}</div></b-col></b-row>
                        <b-row>
                            <b-col cols="6">
                                <h3> {{ text.course }} </h3>
                                <p class="mb-0"><b><span v-html="value.course.fullname"></span></b></p>
                                <p class="mb-1"><b><span v-html="value.course.shortname"></span></b></p>
                                <p class="mb-1">{{ datechanger.coursespan.formatted.first}} - {{ datechanger.coursespan.formatted.last}}</p>
                                <p class="mb-0"><b>{{ text.duration }}</b><br>
                                {{ formatDuration(datechanger.coursespan)}}</p>
                            </b-col>
                            <b-col cols=>"6">
                                <h3> {{ text.period }} </h3>
                                <p class="mb-0"><b><span v-html="period.display.fullname"></span></b
                                    ><b v-if="value.span > 1"> - <span v-html="endperiod.display.fullname"></span></b></p>
                                <p class="mb-1"><b><span v-html="period.display.shortname"></span></b
                                    ><b v-if="value.span > 1"> - <span v-html="endperiod.display.shortname"></span></b></p>
                                <p class="mb-1">{{ datechanger.periodspan.formatted.first}} - {{ datechanger.periodspan.formatted.last}}</p>
                                <p class="mb-0"><b>{{ text.duration }}</b><br>
                                {{ formatDuration(datechanger.periodspan)}}</p>
                            </b-col>
                        </b-row>
                        <b-row v-if='hidden' class="pt-2"><b-col cols="12">
                            <b-form-checkbox type="checkbox" v-model="datechanger.globals.hidewarn">{{ text.hidewarning }}</b-form-checkbox>
                        </b-col></b-row>
                    </b-container>
                </b-modal>
            </div>
            `,
        });


        Vue.component('t-item', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                dummy: {
                    type: Boolean,
                    default() {
                        return false;
                    },
                },
                plan: {
                    type: Object, // Studyplan page
                    default() {
                        return null;
                    },
                },
                line: {
                    type: Object, // Studyplan page
                    default() {
                        return null;
                    },
                },
                page: {
                    type: Object, // Studyplan page
                    default() {
                        return null;
                    },
                },
                period: {
                    type: Object, // Studyplan page
                    default() {
                        return null;
                    },
                },
                maxspan: {
                    type: Number,
                    default() {
                        return 0;
                    },
                },
            },
            data() {
                return {
                    dragLine: null,
                    dragEventListener: null,
                    deleteMode: false,
                    conditionOptions: stringKeys.conditions,
                    text: strings.itemText,
                    showContext: false,
                    lines: [],
                };
            },
            methods: {
                dragStart(event) {
                    // Add line between start point and drag image
                    this.deleteMode = false;
                    let start = document.getElementById('studyitem-' + this.value.id);
                    let dragelement = document.getElementById('t-item-cdrag-' + this.value.id);
                    dragelement.style.position = 'fixed';
                    dragelement.style.left = event.position.x + 'px';
                    dragelement.style.top = event.position.y + 'px';
                    this.dragLine = new SimpleLine(start, dragelement, {
                            color: "#777",
                            gravity: {
                                start: LINE_GRAVITY,
                                end: LINE_GRAVITY,
                            },
                        });
                    // Add separate event listener to reposition mouse move
                    document.addEventListener("mousemove", this.onMouseMove);
                },
                dragEnd() {
                    if (this.dragLine !== null) {
                        this.dragLine.remove();
                    }
                    let dragelement = document.getElementById('t-item-cdrag-' + this.value.id);
                    dragelement.style.removeProperty('left');
                    dragelement.style.removeProperty('top');
                    dragelement.style.removeProperty('position');
                    document.removeEventListener("mousemove", this.onMouseMove);
                },
                onMouseMove: function(event) {
                    let dragelement = document.getElementById('t-item-cdrag-' + this.value.id);
                    dragelement.style.position = 'fixed';
                    dragelement.style.left = event.clientX + 'px';
                    dragelement.style.top = event.clientY + 'px';
                    // Line will follow automatically
                },
                onDrop(event) {
                    let fromid = event.data.id;
                    let toid = this.value.id;
                    this.redrawLines();
                    call([{
                        methodname: 'local_treestudyplan_connect_studyitems',
                        args: {'from_id': fromid, 'to_id': toid}
                    }])[0].then((response)=>{
                        let conn = {'id': response.id, 'from_id': response.from_id, 'to_id': response.to_id};
                        ItemEventBus.$emit("createdConnection", conn);
                        this.value.connections.in.push(conn);
                        return;
                    }).catch(notification.exception);
                },
                redrawLine(conn) {
                    const lineColor = "var(--color-pass)";
                    const start = document.getElementById(`studyitem-${conn.from_id}`);
                    const end = document.getElementById(`studyitem-${conn.to_id}`);

                    // Delete old line
                    if (this.lines[conn.to_id]) {
                        this.lines[conn.to_id].remove();
                        delete this.lines[conn.to_id];
                    }
                    // Create a new line if the start and finish items are visible
                    if (start !== null && end !== null && isVisible(start) && isVisible(end)) {
                        this.lines[conn.to_id] = new SimpleLine(start, end, {
                            color: lineColor,
                            gravity: {
                                start: LINE_GRAVITY,
                                end: LINE_GRAVITY,
                            },
                        });
                    }

                },
                deleteLine(conn) {
                    const self = this;
                    call([{
                        methodname: 'local_treestudyplan_disconnect_studyitems',
                        args: {'from_id': conn.from_id, 'to_id': conn.to_id}
                    }])[0].then((response)=>{
                        if (response.success) {
                            this.removeLine(conn);
                            // Send disconnect event on message bus, so the connection on the other end can delete it too
                            ItemEventBus.$emit("connectionDisconnected", conn);
                            // Remove connection from our outgoing list
                            let index = self.value.connections.out.indexOf(conn);
                            self.value.connections.out.splice(index, 1);
                        }
                        return;
                    }).catch(notification.exception);
                },
                highlight(conn) {
                    if (this.lines[conn.to_id]) {
                        this.lines[conn.to_id].setConfig({color: "var(--color-danger)"});
                    }
                },
                normalize(conn) {
                    if (this.lines[conn.to_id]) {
                        this.lines[conn.to_id].setConfig({color: "var(--color-pass)"});
                    }
                },
                updateItem() {
                    call([{
                        methodname: 'local_treestudyplan_edit_studyitem',
                        args: {
                                'id': this.value.id,
                                'conditions': this.value.conditions,
                                'continuation_id': this.value.continuation_id,
                            }
                    }])[0].catch(notification.exception);
                },
                doShowContext(event) {
                    if (this.hasContext) {
                        this.showContext = true;
                        event.preventDefault();
                    }
                },
                redrawLines() {
                    if (this.value.connections && this.value.connections.out) {
                        for (let i in this.value.connections.out) {
                            let conn = this.value.connections.out[i];
                            this.redrawLine(conn);
                        }
                    }
                },

                // EVENT LISTENERS
                onCreatedConnection(conn) {
                    if (conn.from_id == this.value.id) {
                        this.value.connections.out.push(conn);
                        this.redrawLine(conn);
                    }
                },
                // Listener for the signal that a connection was removed by the outgoing item
                onRemovedConnection(conn) {
                    if (this.value.connections && this.value.connections.out) {
                        for (let i in this.value.connections.in) {
                            let cin = this.value.connections.in[i];
                            if (conn.id == cin.id) {
                                self.value.connections.out.splice(i, 1);
                            }
                        }
                    }
                },
                // Listener for reposition events
                // When an item in the list is repositioned, all lines need to be redrawn
                onRePositioned() {
                    this.redrawLines();
                },
                // When an item is disPositioned - (temporarily) removed from the list,
                // all connections need to be deleted.
                onDisPositioned(reid) {
                    if (this.value.connections && this.value.connections.out) {
                        for (let i in this.value.connections.out) {
                            let conn = this.value.connections.out[i];
                            if (conn.to_id == reid) {
                                this.removeLine(conn);
                            }
                        }
                    }
                },

                // When an item is deleted
                // all connections to/from that item need to be cleaned up
                onItemDeleted(itemid) {
                    const self = this;
                    if (this.value.connections && this.value.connections.out) {
                        for (const i in this.value.connections.out) {
                            let conn = this.value.connections.out[i];
                            if (conn.to_id == itemid) {
                                self.removeLine(conn);
                                self.value.connections.out.splice(i, 1);
                            }
                        }
                    }
                    if (this.value.connections && this.value.connections.in) {
                        for (const i in this.value.connections.in) {
                            let conn = this.value.connections.in[i];
                            if (conn.from_id == itemid) {
                                self.value.connections.out.splice(i, 1);
                            }
                        }
                    }
                },

                onRedrawLines() {
                    this.redrawLines();
                },

                removeLine(conn) {
                    if (this.lines[conn.to_id]) {
                        this.lines[conn.to_id].remove();
                        delete this.lines[conn.to_id];
                    }
                },
                deleteItem() {
                    const self = this;
                    const msgparams = {
                        item: this.text['type_' + this.value.type].toLocaleLowerCase(),
                        name: (this.value.type == 'course') ? this.value.course.displayname : "",
                        line: (this.line) ? this.line.name : "",
                        period: (this.period) ? this.period.display.fullname : this.plan.display.name,
                    };

                    this.$bvModal.msgBoxConfirm(strformat(this.text.item_delete_message, msgparams), {
                        okVariant: 'danger',
                        okTitle: this.text.ok,
                        cancelTitle: this.text.cancel,
                    }).then(value => {
                        if (value) {
                            // Confirmed to delete.
                            call([{
                                methodname: 'local_treestudyplan_delete_studyitem',
                                args: {
                                    'id': self.value.id,
                                }
                            }])[0].then((response) => {
                                if (response.success == true) {
                                    self.$emit("deleted", {data: self.value});
                                }
                                return;
                            }).catch(notification.exception);
                        }
                        return;
                    }).catch(err => {
                        debug.console.error(err);
                    });
                },
                acceptLines() {
                    return this.hasConnectionsIn;
                },
                sendReposition: debounce(function() {
                    /* Debounced, since the event only needs to be emitted once on load by one component */
                    console.debug("Sending repositioned");
                    ItemEventBus.$emit("rePositioned");
                }, 200, false),
            },
            computed: {
                hasConnectionsOut() {
                    return !(["finish"].includes(this.value.type));
                },
                hasConnectionsIn() {
                    return !(["start"].includes(this.value.type));
                },
                hasContext() {
                    return ['start', 'junction', 'finish'].includes(this.value.type);
                }
            },
            created() {
                // Add event listeners on the message bus
                // But only if not in "dummy" mode - mode which is used for droplist placeholders
                // Since an item is "fully made" with all references, not specifying dummy mode really messes things up
                if (!this.dummy) {
                    // Listener for the signal that a new connection was made and needs to be drawn
                    // Sent by the incoming item  - By convention, outgoing items are responsible for drawing the lines
                    ItemEventBus.$on('createdConnection', this.onCreatedConnection);
                    // Listener for the signal that a connection was removed by the outgoing item
                    ItemEventBus.$on('removedConnection', this.onRemovedConnection);
                    // Listener for reposition events
                    // When an item in the list is repositioned, all lines need to be redrawn
                    ItemEventBus.$on('rePositioned', this.onRePositioned);
                    // When an item is disPositioned - (temporarily) removed from the list,
                    // all connections need to be deleted.
                    ItemEventBus.$on('disPositioned', this.onDisPositioned);
                    // When an item is deleted
                    // all connections to/from that item need to be cleaned up
                    ItemEventBus.$on('itemDeleted', this.onItemDeleted);
                    ItemEventBus.$on('redrawLines', this.onRedrawLines);
                }
            },
            mounted() {
                // Initialize connection lines when mounting
                // But only if not in "dummy" mode - mode which is used for droplist placeholders
                // Since an item is "fully made" with all references, not specifying dummy mode really messes things up

                if (!this.dummy) {
                    this.redrawLines();
                    this.$nextTick(()=>{
                        this.sendReposition();
                    });
                }
            },
            beforeDestroy() {
                if (!this.dummy) {
                    for (let i in this.value.connections.out) {
                        let conn = this.value.connections.out[i];
                        this.removeLine(conn);
                    }
                    ItemEventBus.$emit("disPositioned", this.value.id);

                    // Remove event listeners
                    ItemEventBus.$off('createdConnection', this.onCreatedConnection);
                    ItemEventBus.$off('removedConnection', this.onRemovedConnection);
                    ItemEventBus.$off('rePositioned', this.onRePositioned);
                    ItemEventBus.$off('disPositioned', this.onDisPositioned);
                    ItemEventBus.$off('itemDeleted', this.onItemDeleted);
                    ItemEventBus.$off('redrawLines', this.onRedrawLines);
                }
            },
            updated() {
                if (!this.dummy) {
                    this.redrawLines();
                }
            },
            template: `
            <div class="t-item-base" :id="'studyitem-'+value.id">
                <drop :accepts-type="(!dummy) ?'linestart':'nothing'"
                    :accepts-data="acceptLines"
                    :id="'t-item-cend-'+value.id"
                    class="t-item-connector-drop"
                    mode="copy"
                    @drop="onDrop"
                    >
                    <t-item-course
                        v-if="value.type == 'course'"
                        @deleterq="deleteItem"
                        v-model="value"
                        :plan='plan'
                        :line='line'
                        :page='page'
                        :period='period'
                        :maxspan='maxspan'
                    ></t-item-course>
                    <t-item-junction
                        v-if="value.type == 'junction'"
                        @deleterq="deleteItem"
                        v-model="value"
                    ></t-item-junction>
                    <t-item-start
                        v-if="value.type == 'start'"
                        @deleterq="deleteItem"
                        v-model="value"
                    ></t-item-start>
                    <t-item-finish
                        v-if="value.type == 'finish'"
                        @deleterq="deleteItem"
                        v-model="value"
                    ></t-item-finish>
                    <t-item-badge
                        v-if="value.type == 'badge'"
                        @deleterq="deleteItem"
                        v-model="value"
                    ></t-item-badge>
                    <t-item-invalid
                        v-if="value.type == 'invalid'"
                        @deleterq="deleteItem"
                        v-model="value"
                    ></t-item-invalid>
                </drop>
                <drag v-if='!dummy && hasConnectionsOut' type="linestart"
                    :id="'t-item-cstart-'+value.id"
                    :class="'t-item-connector-start ' + ((deleteMode&&value.connections.out.length)?'deleteMode':'')"
                    :data="value"
                    @dragstart="dragStart"
                    @dragend="dragEnd"
                    @click.prevent="deleteMode = (value.connections.out.length)?(!deleteMode):false"
                    >
                    <svg  width='5px' height='10px'><rect ry="1px" rx="1px" y="0px" x="0px" height="10px" width="5px"/></svg>
                    <template v-slot:drag-image="{data}"> <i :id="'t-item-cdrag-'+value.id" class="fa"></i>
                    </template>
                </drag>
                <div class="deletebox" v-if="deleteMode && value.connections.out.length > 0"
                    >
                    <a v-for="conn in value.connections.out"
                        @click.prevent="deleteLine(conn)"
                        @mouseenter="highlight(conn)"
                        @mouseleave="normalize(conn)"
                        class="t-connection-delete text-danger"
                        :title="conn.id">
                        <i class="fa fa-trash"></i>
                    </a>
                </div>
                <a  v-if="hasContext" class="t-item-config"
                    v-b-modal="'t-item-config-'+value.id" href="#" @click.prevent=""><i  class="fa fa-gear"></i></a>
                <b-modal no-body class=""
                    :id="'t-item-config-'+value.id"
                    :title="text['item_configuration']"
                    scrollable
                    ok-only
                    class="t-item-contextview b-modal-justify-footer-between"
                >
                    <b-form-group
                        v-if="value.type != 'start'"
                        :label="text.select_conditions"
                    >
                        <b-form-select size="sm"
                            @input="updateItem"
                            v-model="value.conditions"
                            :options="conditionOptions"
                        ></b-form-select>
                    </b-form-group>

                   <template #modal-footer="{ ok, cancel, hide }" >
                    <a href='#' @click.prevent='deleteItem()' class="text-danger"
                        ><i class="fa fa-trash"></i>
                        {{ text.delete }}
                    </a>
                    <b-button size="sm" variant="primary" @click.prevent="ok()">
                      {{ text.ok }}
                    </b-button>
                  </template>

                </b-modal>
            </div>
            `,
        });

        Vue.component('t-item-invalid', {
            props: {
                'value': {
                    type: Object,
                    default() {
                        return null;
                    },
                },
            },
            data() {
                return {
                    text: strings.invalid,
                };
            },
            methods: {
            },
            template: `
            <div class="t-item-invalid">
            <b-card no-body >
                <b-row no-gutters>
                    <b-col md="1">
                        <span class="t-timing-indicator timing-invalid"></span>
                    </b-col>
                    <b-col md="11">
                        <b-card-body class="align-items-center">
                            <i class="fa fa-exclamation"></i> {{text.error}}
                            <a href='#' @click.prevent='$emit("deleterq")' class="text-danger"
                            ><i class="fa fa-trash"></i></a>
                        </b-card-body>
                    </b-col>
                </b-row>
            </b-card>
            </div>
            `,
        });

        // TAG: Course item
        Vue.component('t-item-course', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                plan: {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                line: {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                page: {
                    type: Object, // PAge data
                    default() {
                        return null;
                    }
                },
                period: {
                    type: Object, // Period data
                    default() {
                        return null;
                    }
                },
                maxspan: {
                    type: Number,
                    default() {
                        return 0;
                    }
                },
            },
            data() {
                return {
                    conditionOptions: stringKeys.conditions,
                    text: strings.itemCourseText,
                };
            },
            computed: {
                useItemConditions() {
                    if (this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useItemConditions !== undefined) {
                        return this.plan.aggregation_info.useItemConditions;
                    } else {
                        return false;
                    }
                },

                configurationState() {
                    if (this.hasGrades() || this.hasCompletions() || this.hasCompetencies()) {
                        return "t-configured-ok";
                    } else {
                        return "t-configured-alert";
                    }
                },

                configurationIcon() {
                    if (this.hasGrades() || this.hasCompletions() || this.hasCompetencies()) {
                        return "check";
                    } else {
                        return "exclamation-circle";
                    }
                },
                startdate() {
                    return formatDate(this.value.course.startdate);
                },
                enddate() {
                    if (this.value.course.enddate.length > 0) {
                        return formatDate(this.value.course.enddate);
                    } else {
                        return this.text.noenddate;
                    }
                },
                wwwroot() {
                    return Config.wwwroot;
                }

            },
            methods: {
                hasGrades() {
                    if (this.value.course.grades && this.value.course.grades.length > 0) {
                        for (const g of this.value.course.grades) {
                            if (g.selected) {
                                return true;
                            }
                        }
                    }
                    return false;
                },
                hasCompletions() {
                    if (this.value.course.completion && this.value.course.completion.conditions) {
                        for (const cgroup of this.value.course.completion.conditions) {
                            if (cgroup.items && cgroup.items.length > 0) {
                                return true;
                            }
                        }
                    }
                    return false;
                },
                hasCompetencies() {
                    if (this.value.course.competency && this.value.course.competency.competencies) {
                        return (this.value.course.competency.competencies.length > 0);
                    }
                    return false;
                },
                includeChanged(newValue, g) {
                    call([{
                        methodname: 'local_treestudyplan_include_grade',
                        args: {
                                'grade_id': g.id,
                                'item_id': this.value.id,
                                'include': newValue,
                                'required': g.required,
                                }
                    }])[0].catch(notification.exception);
                },
                requiredChanged(newValue, g) {
                    call([{
                        methodname: 'local_treestudyplan_include_grade',
                        args: {
                                'grade_id': g.id,
                                'item_id': this.value.id,
                                'include': g.selected,
                                'required': newValue,
                                }
                    }])[0].catch(notification.exception);
                },
                updateConditions() {
                    call([{
                        methodname: 'local_treestudyplan_edit_studyitem',
                        args: {
                                'id': this.value.id,
                                'conditions': this.value.conditions,
                                }
                    }])[0].catch(notification.exception);
                },
            },
            template: `
            <div class="t-item-course card">
                <div class='t-item-course-cardwrapper mr-0 ml-0 h-100 '>
                    <div
                        :title="text['coursetiming_'+value.course.timing]"
                        v-b-popover.hover.top="startdate+' - '+enddate"
                        :class="'h-100 t-timing-indicator timing-'+value.course.timing"
                    ></div>
                    <div class="t-item-course-title card-body h-100">
                        <fittext maxsize="12pt" minsize="9pt">
                        <a  v-b-modal="'t-item-course-config-'+value.id"
                            :id="'t-item-course-details-'+value.id"
                            :href="wwwroot+'/course/view.php?id='+value.course.id"
                            @click.prevent.stop=""><span v-html="value.course.displayname"></span></a>
                        </fittext>
                    </div>
                    <div class="h-100 t-item-course-indicator ">
                        <a  class="t-item-course-config"
                        v-b-modal="'t-item-course-config-'+value.id"
                        href="#" @click.prevent=""
                        ><i :class="'fa fa-'+configurationIcon+' ' + configurationState"></i></a>
                    </div>
                </div>
                <b-modal class=""
                    :id="'t-item-course-config-'+value.id"
                    :title="value.course.displayname + ' - ' + value.course.fullname"
                    ok-only
                    size="lg"
                    scrollable
                    class="b-modal-justify-footer-between"
                    >
                    <template #modal-header>
                        <div>
                            <h1><a :href="wwwroot+'/course/view.php?id='+value.course.id" target="_blank"
                                ><i class="fa fa-graduation-cap"></i> <span v-html="value.course.fullname"></span></a>
                                <a v-if='!!value.course.completion'
                                    :href="wwwroot+'/course/completion.php?id='+value.course.id"  target="_blank"
                                :title="text.configure_completion"><i class="fa fa-gear"></i></a>
                                </h1>
                            <span v-html="value.course.category.path.join(' / ')"></span> / {{value.course.shortname}}
                        </div>
                        <div class="r-course-detail-header-right">
                            <div :class="'r-timing-'+value.course.timing">
                                {{text['coursetiming_'+value.course.timing]}}<br>
                                {{ startdate }} - {{ enddate }}
                            </div>
                            <t-item-timing-checker
                                class="mt-1"
                                :maxspan="maxspan"
                                :page="page"
                                :line="line"
                                :period="period"
                                v-model="value"
                             ></t-item-timing-checker>
                        </div>
                    </template>

                    <s-course-extrafields
                        v-if="value.course.extrafields"
                        v-model="value.course.extrafields"
                        position="above"
                        ></s-course-extrafields>
                    <t-item-course-grades
                        v-if='!!value.course.grades && value.course.grades.length > 0'
                        v-model='value' :plan="plan"
                        ></t-item-course-grades>
                    <t-item-course-completion
                        v-if='!!value.course.completion'
                        v-model='value.course.completion'
                        :course='value.course'
                        ></t-item-course-completion>
                    <t-item-course-competency
                        v-if='!!value.course.competency'
                        v-model='value.course.competency'
                        :item='value'
                        ></t-item-course-competency>
                    <s-course-extrafields
                        v-if="value.course.extrafields"
                        v-model="value.course.extrafields"
                        position="below"
                        ></s-course-extrafields>
                    <template #modal-footer="{ ok, cancel, hide }" >
                        <a href='#' @click.prevent='$emit("deleterq")' class="text-danger"
                            ><i class="fa fa-trash"></i>
                            {{ text.delete }}
                        </a>
                        <b-button size="sm" variant="primary" @click.prevent="ok()">
                          {{ text.ok }}
                        </b-button>
                    </template>
                </b-modal>
            </div>
            `,
        });


        Vue.component('t-item-course-grades', {
            props: {
                'value': {
                    type: Object,
                    default() {
                        return null;
                    },
                },
                'plan': {
                    type: Object,
                    default() {
                        return null;
                    },
                },
            },
            data() {
                return {
                    conditionOptions: stringKeys.conditions,
                    text: strings.itemCourseText,
                };
            },
            computed: {
                useRequiredGrades() {
                    if (this.plan && this.plan.aggregation_info && this.plan.aggregation_info.useRequiredGrades !== undefined) {
                        return this.plan.aggregation_info.useRequiredGrades;
                    } else {
                        return false;
                    }
                },
                selectedgrades() {
                    let list = [];
                    for (let ix in this.value.course.grades) {
                        let g = this.value.course.grades[ix];
                        if (g.selected) {
                            list.push(g);
                        }
                    }
                    return list;
                },
            },
            methods: {
                includeChanged(newValue, g) {
                    call([{
                        methodname: 'local_treestudyplan_include_grade',
                        args: {
                                'grade_id': g.id,
                                'item_id': this.value.id,
                                'include': newValue,
                                'required': g.required,
                                }
                    }])[0].catch(notification.exception);
                },
                requiredChanged(newValue, g) {
                    call([{
                        methodname: 'local_treestudyplan_include_grade',
                        args: {
                                'grade_id': g.id,
                                'item_id': this.value.id,
                                'include': g.selected,
                                'required': newValue,
                                }
                    }])[0].catch(notification.exception);
                },
            },
            template: `
            <div>
                <b-form-group
                    :label="text.select_grades"
                    ><ul class="t-item-module-children">
                        <li class="t-item-course-gradeinfo">
                            <span class='t-item-course-chk-lbl'>{{text.grade_include}}</span
                            ><span v-if="useRequiredGrades"  class='t-item-course-chk-lbl'>{{text.grade_require}}</span>
                        </li>
                        <li class="t-item-course-gradeinfo" v-for="g in value.course.grades">
                            <b-form-checkbox inline
                                @change="includeChanged($event,g)" v-model="g.selected"
                                ></b-form-checkbox>
                            <b-form-checkbox v-if="useRequiredGrades" inline :disabled="!g.selected"
                                @change="requiredChanged($event,g)" v-model="g.required"
                                ></b-form-checkbox>
                            <span :title="g.typename" v-html="g.icon"></span>
                            <s-edit-mod
                                :title="value.course.fullname"
                                @saved="(fd) => g.name = fd.get('name')"
                                v-if="g.cmid > 0"
                                :cmid="g.cmid"
                                :coursectxid="value.course.ctxid"
                                genericonly><span v-html="g.name"></span></s-edit-mod>
                        </li>
                    </ul>
                </b-form-group>
            </div>
            `,
        });

        Vue.component('t-item-course-completion', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
                guestmode: {
                    type: Boolean,
                    'default': false,
                },
                course: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {
                    text: strings.completion,
                };
            },
            computed: {
                hasCompletions() {
                    if (this.value.conditions) {
                        for (const cgroup of this.value.conditions) {
                            if (cgroup.items && cgroup.items.length > 0) {
                                return true;
                            }
                        }
                    }
                    return false;
                },
                wwwroot() {
                    return Config.wwwroot;
                }
            },
            methods: {
                completionIcon(completion) {
                    switch (completion) {
                        case "progress":
                            return "exclamation-circle";
                        case "complete":
                            return "check-circle";
                        case "complete-pass":
                            return "check-circle";
                        case "complete-fail":
                            return "times-circle";
                        default: // Case "incomplete"
                            return "circle-o";
                    }
                },
                completionTag(cgroup) {
                    return cgroup.completion ? 'completed' : 'incomplete';
                }
            },
            template: `
            <table class="r-item-course-grade-details">
                <tr v-if="hasCompletions">
                    <td colspan='2'><span v-if="value.aggregation == 'all'">{{ text.aggregation_overall_all}}</span
                    ><span v-else>{{ text.aggregation_overall_any}}</span></td>
                </tr>
                <tr v-else>
                    <td colspan='2'>{{text.completion_not_configured}}!
                    <br/><a :href="wwwroot+'/course/completion.php?id='+course.id" target='_blank'>{{text.configure_completion}}</a>
                    </td>
                </tr>
                <template v-for='cgroup in value.conditions'>
                    <tr>
                        <th colspan='2'><span v-if="cgroup.items.length > 1"
                        ><span v-if="cgroup.aggregation == 'all'">{{ text.aggregation_all}}</span
                        ><span v-else>{{ text.aggregation_any}}</span></span>
                        {{cgroup.title}}</th>
                    </tr>
                    <tr v-for='ci in cgroup.items'>
                        <td><span v-html='ci.details.criteria'></span>
                        </td>
                        <td v-if="ci.details.requirement" class="font-italic">
                            {{ci.details.requirement}}
                        </td>
                    </tr>
                </template>
            </table>
            `,
        });

        // TAG: Course competency
        Vue.component('t-item-course-competency', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
                guestmode: {
                    type: Boolean,
                    'default': false,
                },
                item: {
                    type: Object,
                    default() {
                        return {id: null};
                    },
                }
            },
            data() {
                return {
                    text: strings.competency,
                };
            },
            computed: {
                hasCompletions() {
                    if (this.value.conditions) {
                        for (const cgroup of this.value.conditions) {
                            if (cgroup.items && cgroup.items.length > 0) {
                                return true;
                            }
                        }
                    }
                    return false;
                },
                wwwroot() {
                    return Config.wwwroot;
                }
            },
            methods: {
                pathtags(competency) {
                    const path = competency.path;
                    let s = "";
                    for (const ix in path) {
                        const p = path[ix];
                        if (ix > 0) {
                            s += " / ";
                        }
                        let url;
                        if (p.type == 'competency') {
                            url = `/admin/tool/lp/competencies.php?competencyid=${p.id}`;
                        } else {
                            url = `/admin/tool/lp/competencies.php?competencyframeworkid=${p.id}&pagecontextid=${p.contextid}`;
                        }
                        s += `<a href="${url}">${p.title}</a>`;
                    }
                    return s;
                },
                requiredChanged(newValue, c) {
                    call([{
                        methodname: 'local_treestudyplan_require_competency',
                        args: {
                                'competency_id': c.id,
                                'item_id': this.item.id,
                                'required': newValue,
                                }
                    }])[0].catch(notification.exception);
                },
            },
            template: `
            <table class="t-item-course-competency-list">
                <tr v-if="value.competencies.length == 0">
                    <td colspan='2'>{{text.competency_not_configured}}
                    <br><a :href="wwwroot+'/admin/tool/lp/coursecompetencies.php?courseid='+item.course.id" target='_blank'>{{text.configure_competency}}</a>
                    </td>
                </tr>
                <template v-else>
                    <tr class='t-item-course-competency-headers'>
                        <th>{{text.heading}}</th>
                        <th></th>
                        <th>{{text.required}}</th>
                    </tr>
                    <tr v-for='c in value.competencies'>
                        <td :colspan="(c.details)?1:2"><a href='#' v-b-modal="'modal-competency-id-'+c.id"><span v-html='c.title'></span></a></td>
                        <td class='details' v-if="c.details">
                            <a href='#' v-b-modal="'modal-competency-id-'+c.id"><span v-html='c.details'></span></a>
                        </td>
                        <td>
                            <b-form-checkbox inline
                                @change="requiredChanged($event,c)"
                                v-model="c.required"
                            >{{ text.required }}</b-form-checkbox>
                        </td>
                        <b-modal :id="'modal-competency-id-'+c.id"
                            size="lg"
                            ok-only
                            centered
                            scrollable
                            >
                            <template #modal-header>
                                <div>
                                    <h1><i class="fa fa-puzzle-piece"></i>
                                        <a :href="wwwroot+'/admin/tool/lp/competencies.php?competencyid='+c.id" target="_blank"
                                        >{{c.title}} {{c.details}} </a
                                    ></h1>
                                    <div><span v-html="pathtags(c)"></span></div>
                                </div>
                            </template>
                            <div class="mb-2" v-if="c.description"><span v-html='c.description'></span></div>

                            <template v-if="c.rule && c.children">
                                <div>{{ c.ruleoutcome }} {{ text.when}} <span v-html="c.rule.toLocaleLowerCase()"></span></div>
                                <table v-if="c.children" class='t-item-course-competency-list'>
                                    <tr class='t-item-course-competency-headers'>
                                        <th>{{text.heading}}</th>
                                        <th></th>
                                        <th>{{text.required}}</th>
                                    </tr>
                                    <tr v-for="cc in c.children">
                                        <td :colspan="(c.details)?1:2" ><span v-html='cc.title'></span></td>
                                        <td class='details' v-if="cc.details"><span v-html='cc.details'></span></td>
                                        <td><span class="text-info">{{ cc.points }} {{ text.points }}</span></td>
                                        <td><span class="text-danger" v-if='cc.required'>{{ text.required }}</span></td>
                                    </tr>
                                </table>
                            </template>
                        </b-modal>
                    </tr>
                </template>
            </table>
            `,
        });
        /* **********************************
        *                                  *
        * Toolbox list components          *
        *                                  *
        ************************************/
        Vue.component('t-item-junction', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {
                    conditionOptions: stringKeys.conditions,
                };
            },
            methods: {

            },
            template: `
            <div class='t-item-junction  t-item-filter'>
                <i class="fa fa-check-circle"></i>
            </div>
            `,
        });

        Vue.component('t-item-finish', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {
                };
            },
            methods: {
            },
            template: `
            <div class='t-item-finish t-item-filter'>
                <i class="fa fa-stop-circle"></i>
            </div>
            `,
        });

        Vue.component('t-item-start', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {};
            },
            methods: {
            },
            template: `
            <div class='t-item-start t-item-filter'>
                <i class="fa fa-play-circle"></i>
            </div>
            `,
        });

        Vue.component('t-item-badge', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {
                            badge: {}
                        };
                    },
                },
            },
            data() {
                return {
                    txt: strings,
                    text: strings.itemText,
                };
            },
            methods: {
            },
            template: `
            <div class='t-item-badge t-item-filter' >
                <svg class="t-badge-backdrop " width='50px' height='50px' viewBox="0 0 100 100">
                    <title>{{value.badge.name}}</title>
                    <circle cx="50" cy="50" r="46"
                        style="stroke: currentcolor; stroke-width: 4; fill: currentcolor; fill-opacity: 0.5;"/>
                    <image class="badge-image" clip-path="circle() fill-box"
                        :href="value.badge.imageurl" x="12" y="12" width="76" height="76"/>
                </svg>
                <a class="t-item-config badge-item"
                v-b-modal="'t-item-badge-details-'+value.id" href="#" @click.prevent=""><i  class="fa fa-gear"></i></a>
                <b-modal class=""
                    :id="'t-item-badge-details-'+value.id"
                    :title="value.badge.name"
                    size="lg"
                    ok-only
                    centered
                    scrollable
                    class="b-modal-justify-footer-between"
                    >
                    <template #modal-header>
                        <div>
                            <h1><i class="fa fa-certificate"></i>
                                <a :href="value.badge.infolink" target="_blank"
                                >{{ value.badge.name }}</a
                            ></h1>
                        </div>
                    </template>
                    <b-container fluid>
                        <b-row><b-col cols="3">
                            <img :src="value.badge.imageurl"/>
                        </b-col><b-col cols="9">
                            <p>{{value.badge.description}}</p>
                            <ul class="list-unstyled w-100 border-grey border-top border-bottom pt-1 pb-1 mb-1"
                             v-if="value.badge.criteriatext"><li v-for="crit in value.badge.criteriatext"
                             ><span v-html='crit'></span></li></ul>
                            <p><strong><i class="fa fa-link"></i>
                                <a :href="value.badge.infolink">{{ txt.badge.badgeinfo }}</a></strong></p>
                        </b-col></b-row>
                    </b-container>
                    <template #modal-footer="{ ok, cancel, hide }" >
                        <a href='#' @click.prevent='$emit("deleterq")' class="text-danger"
                            ><i class="fa fa-trash"></i>
                            {{ text.delete }}
                        </a>
                        <b-button size="sm" variant="primary" @click.prevent="ok()">
                          {{ text.ok }}
                        </b-button>
                    </template>
                </b-modal>

            </div>
            `,
        });

        Vue.component('t-coursecat-list', {
            props: {
                value: {
                    type: Array,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {
                };
            },
            methods: {
            },
            template: `
            <ul class="t-coursecat-list">
                <t-coursecat-list-item
                    v-for="coursecat,idx in value"
                    v-model="value[idx]"
                    :key="coursecat.id"></t-coursecat-list-item>
            </ul>
            `,
        });

        Vue.component('t-coursecat-list-item', {
            props: {
                value: {
                    type: Object,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {
                    loading: false,

                };
            },
            computed: {
                showSpinner() {
                    return this.canLoadMore();
                },
                hasDetails() {
                    return (this.value.haschildren || this.value.hascourses);
                }

            },
            methods: {
                canLoadMore() {
                    return (this.value.haschildren && (!this.value.children)) ||
                        (this.value.hascourses && (!this.value.courses));
                },
                onShowDetails() {
                    const self = this;
                    if (this.canLoadMore()) {
                        call([{
                            methodname: 'local_treestudyplan_get_category',
                            args: {
                                "id": this.value.id,
                            }
                        }])[0].then((response) => {
                            self.$emit('input', response);
                            return;
                        }).catch(notification.exception);
                    }
                }
            },
            template: `
            <li class="t-coursecat-list-item">
                <span v-if="hasDetails" v-b-toggle="'coursecat-'+value.id">
                    <i class="when-closed fa fa-caret-right t-caret"></i>
                    <i class="when-open fa fa-caret-down t-caret"></i>
                    <span class="t-coursecat-heading">
                        <i class="t-coursecat-list-item fa fa-tasks"></i>
                        <span v-html="value.category.name"></span>
                    </span>
                </span>
                <span v-else>
                    <i class="when-closed fa t-caret" style="visibility: hidden"></i>
                    <span class="t-coursecat-heading">
                        <i class="t-coursecat-list-item fa fa-tasks"></i>
                        <span v-html="value.category.name"></span>
                    </span>
                </span>
                <b-collapse v-if="hasDetails" :id="'coursecat-'+value.id"
                    @show="onShowDetails" :visible="!!(value.children) || !!(value.courses)">
                    <b-spinner class="ml-4" v-if="showSpinner" small variant="primary"></b-spinner>
                    <t-coursecat-list v-if="value.children" v-model="value.children"></t-coursecat-list>
                    <t-course-list v-if="value.courses" v-model="value.courses"></t-course-list>
                </b-collapse>
            </li>
            `,
        });

        Vue.component('t-course-list', {
            props: {
                value: {
                    type: Array,
                    default() {
                        return {};
                    },
                },
            },
            data() {
                return {
                };
            },
            methods: {
                makeType() {
                    return {
                        item: false,
                        component: true,
                        span: 1, // TODO: Detect longer courses and give them an appropriate span
                        type: 'gradable',
                    };
                },
            },
            template: `
            <ul class="t-course-list">
                <li class="t-course-list-item" v-for="course in value" :key="course.id">
                    <span class='t-course-heading'>
                    <drag
                        class="draggable-course"
                        :data="course"
                        :type="makeType()"
                        @cut=""
                    >
                    <i class="t-course-list-item fa fa-book"></i> {{ course.shortname }} - <span v-html="course.fullname"></span>
                    </drag>
                    </span>
                </li>
            </ul>
            `,
        });

        Vue.component('t-toolbox', {
            props: {
                value: {
                    type: Boolean,
                    'default': true,
                },
                activepage: {
                    type: Object,
                    default() {
                        return null;
                    }
                },
                coaching: {
                    type: Boolean,
                    'default': false,
                },
                studyplanid: {
                    type: Number,
                    'default': 0,
                }
            },
            data() {
                return {
                    settings: settings,
                    toolboxright: !settings.toolboxleft, // Note that this default may not load correctly over lazyload.
                    text: strings.toolbox,
                    relatedbadges: [{name: strings.toolbox.badgesearchinstruction}],
                    systembadges: [{name: strings.toolbox.badgesearchinstruction}],
                    courses: [],
                    searching: {
                        systembadges: false,
                        relatedbadges: false,
                        courses: false,
                    },
                    filteredcourses: [],
                    filters: {
                        courses: "",
                        systembadges: "",
                        relatedbadges: "",
                    },
                    loadingcourses: false,
                    loadingcategories: [],
                    badgelistshown: {
                        relatedbadges: true,
                        systembadges: false,
                    }
                };
            },
            watch: {
                // Whenever activepage changes, this function will run
                activepage(/* Params newVal, oldVal */) {
                  this.filterRelatedbadges();
                }
              },
            mounted() {
                const self = this;
                this.initialize();

                this.$root.$on('bv::collapse::state', (collapseId, isJustShown) => {
                    self.badgelistshown[collapseId] = !!isJustShown;
                });
            },
            computed: {
                filterComponentType() {
                    return {
                        item: false,
                        component: true,
                        span: 1,
                        type: 'filter',
                    };
                },
            },
            methods: {
                initialize() {
                    const self = this;
                    debug.info("Toolbox Loading courses and categories");
                    const start = Date.now();
                    self.loadingcourses = true;
                    call([{
                        methodname: 'local_treestudyplan_map_categories',
                        args: {
                            'studyplan_id': self.studyplanid,
                        }
                    }])[0].then((response) => {
                        const duration = Date.now() - start;
                        debug.info("Toolbox got courses and categories in " + duration + "ms", response);
                        self.courses = response;
                        self.filteredcourses = self.courses;
                        self.loadingcourses = false;
                        return;
                    }).catch(notification.exception);
                },
                filterSystembadges() {
                    debug.warn("Searching site badges for ", this.filters.systembadges);
                    const self = this;
                    if (this.filters.systembadges && this.filters.systembadges.length >= 3) {
                        self.searching.systembadges = true;
                        call([{
                            methodname: 'local_treestudyplan_search_badges',
                            args: {
                                search: this.filters.systembadges || "",
                            }
                        }])[0].then((response) => {
                            self.systembadges = response;
                            self.searching.systembadges = false;
                            return;
                        }).catch(notification.exception);
                    }
                },
                debouncedFilterSystembadges: debounce(function() { // Avoid arrow functions, since those mess up 'this'.
                     this.filterSystembadges();
                }, 600, false),
                filterRelatedbadges() {
                    debug.warn("Searching related badges for ", this.filters.relatedbadges);
                    const self = this;
                    if (this.activepage && this.filters.relatedbadges && this.filters.relatedbadges.length >= 3) {
                        self.searching.relatedbadges = true;
                        call([{
                            methodname: 'local_treestudyplan_search_related_badges',
                            args: {
                                'page_id': this.activepage.id,
                                search: this.filters.relatedbadges
                            }
                        }])[0].then((response) => {
                            self.relatedbadges = response;
                            self.searching.relatedbadges = false;
                            return;
                        }).catch(notification.exception);
                    }
                },
                debouncedFilterRelatedbadges: debounce(function() { // Avoid arrow functions, since those mess up 'this'.
                    this.filterRelatedbadges();
                }, 600, false),
                searchCourses() {
                    debug.warn("Searching courses for ", this.filters.courses);
                    const self = this;
                    // Search only if three characters or more are entered
                    if (this.filters.courses && this.filters.courses.length >= 3) {
                        self.searching.courses = true;
                        call([{
                            methodname: 'local_treestudyplan_search_courses',
                            args: {
                                studyplanid: this.studyplanid,
                                search: this.filters.courses || ""
                            }
                        }])[0].then((response) => {
                            self.filteredcourses = self.buildCatTree(response);
                            self.searching.courses = false;
                            return;
                        }).catch(notification.exception);
                    } else {
                        // Reset to the normal course list with all previously loaded courses.
                        self.filteredcourses = self.courses;
                    }
                },
                debouncedSearchCourses: debounce(function() { // Avoid arrow functions, since those mess up 'this'.
                    this.searchCourses();
                }, 300, false),
                resetSystembadges() {
                    this.filters.systembadges = "";
                    this.systembadges = [{name: this.text.badgesearchinstruction}];
                },
                resetRelatedbadges() {
                    this.filters.relatedbadges = "";
                    this.relatedbadges = [{name: this.text.badgesearchinstruction}];
                },
                resetCourses() {
                    this.filters.courses = "";
                    this.searchCourses();
                },
                buildCatTree(source) {
                    const cats = {}; // Indexed array.
                    const flatcats = []; // Ordinary array.
                    let maxdepth = 0;
                    // Sort by depth first.
                    debug.info("source", source);
                    for (const cat of source) {
                        const depth = cat.category.path.length;
                        if (depth > maxdepth) {
                            maxdepth = depth;
                        }
                        if (!(depth in cats)) {
                            cats[depth] = {};
                        }
                        cats[depth][cat.id] = cat;
                    }

                    for (let i = maxdepth; i > 1; i--) {
                        if (i in cats) {
                            for (const catidx in cats[i]) {
                                const cat = cats[i][catidx];
                                const parent = Number(cat.category.path[i - 2]);
                                if (parent in cats[i - 1]) {
                                    // If parent exists in layer below, push to that one's children.
                                    if (!(cats[i - 1][parent].children)) {
                                        cats[i - 1][parent].children = [];
                                        cats[i - 1][parent].haschildren = true;
                                    }
                                    cats[i - 1][parent].children.push(cat);
                                } else {
                                    // If not, directly push to final list.
                                    flatcats.push(cat);
                                }
                            }
                        }
                    }
                    // Push all first depth cats to flatcats.
                    for (const catidx in cats[1]) {
                        flatcats.push(cats[1][catidx]);
                    }

                    return flatcats;
                }
            },
            template: `
            <div class="t-toolbox">
                <p-sidebar
                    class="t-toolbox-sidebar"
                    :right='toolboxright'
                    shadow
                    v-model="value"
                    offsetRef="#page"
                >
                    <div class="pt-3 pl-3 pr-3 border-bottom-1 border-primary"><h3>{{text.toolbox}}</h3></div>
                    <div class='t-toolbox-preface'>
                        <b-form-checkbox v-model="toolboxright" switch>{{text.toolbarRight}}</b-form-checkbox>
                    </div>
                    <b-tabs content-class='mt-3' class="t-toolbox-tabs">
                        <b-tab :title="text.courses" class="t-toolbox-tab">
                            <div v-if="loadingcourses"
                                ><div class="spinner-border text-primary" role="status"></div
                            ></div>
                            <div v-else class="ml-2 t-toolbox-courses">
                                <div class="flex-grow-0">
                                    <input v-model="filters.courses" @input="debouncedSearchCourses" :placeholder="text.filter"></input>
                                    &nbsp; <a @click="resetCourses" v-if="filters.courses" href='#'
                                        ><i class='fa fa-times'></i></a
                                    ><b-spinner small v-if="searching.courses > 0" variant="primary"></b-spinner>
                                </div>
                                <div class="t-toolbox-courselist">
                                    <t-coursecat-list v-model="filteredcourses"></t-coursecat-list>
                                </div>
                            </div>
                        </b-tab>
                        <b-tab :title="text.flow" v-if="!settings.toolboxcoursesonly" class="t-toolbox-tab">
                            <ul class="t-flow">
                                <li><drag
                                    :type="filterComponentType"
                                    :data="{type: 'junction'}"
                                    @cut=""
                                    ><t-item-junction></t-item-junction>{{ text.toolJunction }}
                                    <template v-slot:drag-image="{data}"><t-item-junction></t-item-junction></template>
                                </drag></li>
                                <li><drag
                                    :type="filterComponentType"
                                    :data="{type: 'finish'}"
                                    @cut=""
                                    ><t-item-finish></t-item-finish>{{ text.toolFinish }}
                                    <template v-slot:drag-image="{data}"><t-item-finish></t-item-finish></template>
                                </drag></li>
                                <li><drag
                                    :type="filterComponentType"
                                    :data="{type: 'start'}"
                                    @cut=""
                                    ><t-item-start></t-item-start>{{ text.toolStart }}
                                    <template v-slot:drag-image="{data}"><t-item-start></t-item-start></template>
                                </drag></li>
                            </ul>
                        </b-tab>
                        <b-tab :title="text.badges" v-if="!settings.toolboxcoursesonly && settings.enablebadges" class="t-toolbox-tab">

                            <b-tabs content-class='mt-2 ml-2' class="t-toolbox-badges">
                            <b-tab :title="text.relatedbadges" v-if="settings.badges_allowcoursebadges">
                                <div class="t-toolbox-badges-filter">
                                    <input v-model="filters.relatedbadges" @input="debouncedFilterRelatedbadges" :placeholder="text.search"></input>
                                    &nbsp;<a @click="resetRelatedbadges" v-if="filters.relatedbadges" href='#'
                                        ><i class='fa fa-times'></i></a
                                    ><b-spinner small v-if="searching.relatedbadges > 0" variant="primary"></b-spinner>
                                </div>
                                <div class="t-toolbox-badges-list">
                                <ul class="t-badges">
                                    <li v-for="b in relatedbadges"><drag
                                    class="t-badge-drag"
                                    :type="filterComponentType"
                                    :data="{type: 'badge', badge: b}"
                                    @cut=""
                                    ><img :class="(!b.active)?'disabled':''" :src="b.imageurl" v-if='b.imageurl'>
                                    <span :class="(!b.active)?'disabled':''">{{b.name}}</span>
                                    <template v-slot:drag-image="{data}"
                                            ><img :class="(!b.active)?'disabled':''" :src="b.imageurl"
                                        ></template>
                                    </drag></li>
                                </ul>
                                </div>
                            </b-tab>
                            <b-tab :title="text.sitebadges">
                                <div class="t-toolbox-badges-filter">
                                    <input v-model="filters.systembadges" @input="debouncedFilterSystembadges" :placeholder="text.search"></input>
                                    &nbsp; <a @click="resetSystembadges" v-if="filters.systembadges" href='#'
                                        ><i class='fa fa-times'></i></a
                                    ><b-spinner small v-if="searching.systembadges > 0" variant="primary"></b-spinner>
                                </div>
                                <div class="t-toolbox-badges-list">
                                <ul class="t-badges">
                                    <li v-for="b in systembadges"><drag
                                    class="t-badge-drag"
                                    :type="filterComponentType"
                                    :data="{type: 'badge', badge: b}"
                                    @cut=""
                                    ><img :class="(!b.active)?'disabled':''" :src="b.imageurl" v-if='b.imageurl'>
                                        <span :class="(!b.active)?'disabled':''">{{b.name}}</span>
                                        <template v-slot:drag-image="{data}"
                                            ><img :class="(!b.active)?'disabled':''" :src="b.imageurl"
                                        ></template>
                                    </drag></li>
                                </ul>
                                </div>
                            </b-tab>
                            </b-tabs>
                        </b-tab>
                    </b-tabs>
                </p-sidebar>
            </div>
            `,
        });

    },

};
