<template> <form action="#" method="post" enctype="multipart/form-data" class="questionnaire_edit default" @submit.prevent="submit()" :data-dialog="asDialog ? true : null" :data-secure="activateFormSecure" > <div class="editor"> <div class="rightside" aria-live="polite" tabindex="0" ref="rightside"> <div class="admin" v-if="activeTab === 'admin'"> <article aria-live="assertive" class="validation_notes studip"> <header> <h1> <studip-icon shape="info-circle" role="info" class="text-bottom validation_notes_icon"></studip-icon> {{ $gettext('Hinweise zum Ausfüllen des Formulars') }} </h1> </header> <div class="required_note"> <div aria-hidden="true"> {{ $gettext('Pflichtfelder sind mit Sternchen gekennzeichnet.') }} </div> <div class="sr-only"> {{ $gettext('Dieses Formular enthält Pflichtfelder.') }} </div> </div> <div v-if="validationNotice && !data.title"> {{ $gettext('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') }} <ul> <li aria-describedby="questionnaire_title">{{ $gettext('Titel des Fragebogens') }}</li> </ul> </div> </article> <div class="formpart"> <label class="studiprequired" for="questionnaire_title"> <span class="textlabel">{{ $gettext('Titel des Fragebogens') }}</span> <span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span> </label> <input type="text" id="questionnaire_title" v-model="data.title" ref="autofocus"> </div> <div class="hgroup"> <label> {{ $gettext('Startzeitpunkt') }} <datetimepicker v-model="data.startdate"></datetimepicker> </label> <label> {{ $gettext('Endzeitpunkt') }} <datetimepicker v-model="data.stopdate"></datetimepicker> </label> </div> <label> <input type="checkbox" v-model="data.copyable" true-value="1" false-value="0"> {{ $gettext('Fragebogen zum Kopieren freigeben') }} </label> <label> <input type="checkbox" v-model="data.anonymous" true-value="1" false-value="0"> {{ $gettext('Teilnehmende anonymisieren') }} </label> <label> <input type="checkbox" v-model="data.editanswers" true-value="1" false-value="0"> {{ $gettext('Teilnehmende dürfen ihre Antworten revidieren') }} </label> <label> {{ $gettext('Ergebnisse einsehbar') }} <select v-model="data.resultvisibility"> <option value="always">{{ $gettext('Immer') }}</option> <option value="afterending">{{ $gettext('Nach Ende der Befragung') }}</option> <option value="afterparticipation">{{ $gettext('Nach der Teilnahme') }}</option> <option value="never">{{ $gettext('Niemals') }}</option> </select> </label> </div> <div class="add_question file_select_possibilities" v-else-if="activeTab === 'add_question'"> <div> <button v-for="(questiontype, key) in questionTypes" :key="key" :ref="key == Object.keys(questionTypes)[0] ? 'autofocus' : ''" href="" @click.prevent="addQuestion(questiontype.type)" > <studip-icon :shape="questiontype.icon" :size="40"></studip-icon> {{questiontype.name}} </button> </div> </div> <div v-else> <component :is="componentForQuestionIndex(indexForQuestion)" v-model="data.questions[indexForQuestion].questiondata" :question_id="data.questions[indexForQuestion].id" :key="data.questions[indexForQuestion].id"> </component> </div> </div> <aside> <a class="admin" :class="{active: activeTab === 'admin'}" href="#" @click.prevent="switchTab('admin')"> <span class="icon"><studip-icon shape="evaluation" :size="30" alt=""></studip-icon></span> {{ $gettext('Einstellungen') }} </a> <draggable v-if="data.questions.length > 0" v-model="data.questions" handle=".drag-handle" group="questions" class="questions_container questions"> <div v-for="question in data.questions" :key="question.id" @mouseenter="hoverTab = question.id" @mouseleave="hoverTab = null" :class="(activeTab === question.id || activeTab === 'meta_' + question.id ? 'active' : '') + (hoverTab === question.id ? ' hovered' : '')"> <a href="#" @click.prevent="switchTab(question.id)"> <span class="drag-handle"></span> <span class="icon type"> <studip-icon :shape="questionTypes[question.questiontype].icon" :size="30" alt=""></studip-icon> </span> <div v-if="editInternalName !== question.id">{{ question.internal_name || questionTypes[question.questiontype].name}}</div> <div v-else class="inline_editing"> <input type="text" ref="editInternalName" v-model="tempInternalName" class="inlineediting_internal_name"> <button @click="saveInternalName(question.id)"> <studip-icon shape="accept" :size="20" :title="$gettext('Internen Namen speichern')"></studip-icon> </button> <button @click="editInternalName = null"> <studip-icon shape="decline" :size="20" :title="$gettext('Internen Namen nicht speichern')"></studip-icon> </button> </div> </a> <studip-action-menu :items="actionMenuItems" @copy="duplicateQuestion(question.id)" @rename="renameInternalName(question.id)" @moveup="moveQuestionUp(question.id)" @movedown="moveQuestionDown(question.id)" @delete="deleteQuestion(question.id)"></studip-action-menu> </div> </draggable> <a :class="activeTab === 'add_question' ? 'add_question active' : 'add_question'" href="#" @click.prevent="switchTab('add_question')"> <span class="icon"><studip-icon shape="add" :size="30" alt=""></studip-icon></span> {{ $gettext('Element hinzufügen') }} </a> </aside> </div> <footer data-dialog-button> <button class="button" name="questionnaire_store"> {{ $gettext('Speichern') }} </button> <a href="#" class="button cancel"> {{ $gettext('Abbrechen') }} </a> </footer> </form> </template> <script> import draggable from 'vuedraggable'; import md5 from 'md5'; import StudipIcon from '../StudipIcon.vue'; import StudipActionMenu from '../StudipActionMenu.vue'; import Datetimepicker from '../Datetimepicker.vue'; const loadedComponents = {}; export default { name: 'questionnaireeditor', components: { Datetimepicker, StudipActionMenu, StudipIcon, draggable, }, props: { asDialog: { type: Boolean, default: false, }, questionData: Object, questionTypes: Object, rangeId: String, rangeType: String, }, data() { return { activeTab: 'admin', data: {...this.questionData}, editInternalName: null, form_secured: true, hoverTab: null, oldData: JSON.parse(JSON.stringify(this.questionData)), tempInternalName: '', validationNotice: false, }; }, methods: { componentForQuestionIndex(index) { const componentInfo = this.questionTypes[this.data.questions[index].questiontype].component; if (loadedComponents[componentInfo[0]] === undefined) { loadedComponents[componentInfo[0]] = componentInfo[1] === '' ? () => import(`./${componentInfo[0]}.vue`) : () => import(/* webpackIgnore: true */componentInfo[1]); } return loadedComponents[componentInfo[0]]; }, addQuestion(questiontype) { let id = md5(`${STUDIP.USER_ID}_QUESTIONTYPE_${Math.random()}`); this.data.questions.push({ id: id, questiontype: questiontype, internal_name: '', questiondata: {}, }); this.activeTab = id; }, submit() { if (!this.data.title) { this.switchTab('admin'); this.validationNotice = true; return; } const data = { title: this.data.title, copyable: this.data.copyable, anonymous: this.data.anonymous, editanswers: this.data.editanswers, startdate: this.data.startdate, stopdate: this.data.stopdate, resultvisibility: this.data.resultvisibility }; const questions = this.data.questions.map(question => ({ id: question.id, questiontype: question.questiontype, internal_name: question.internal_name, questiondata: question.questiondata, })); $.post(STUDIP.URLHelper.getURL('dispatch.php/questionnaire/store/' + (this.data.id || '')), { questionnaire: data, questions_data: JSON.stringify(questions), range_type: this.rangeType, range_id: this.rangeId }).done(() => { this.form_secured = false; this.$nextTick(() => { location.reload(); }); }).fail(() => { STUDIP.Report.error('Could not save questionnaire.', ''); }); }, getIndexForQuestion(question_id) { for (let i in this.data.questions) { if ( this.data.questions[i].id === question_id || this.data.questions[i].id === question_id.substring(5) ) { return parseInt(i, 10); } } return null; }, duplicateQuestion(question_id) { const i = this.getIndexForQuestion(question_id); const id = md5(`${STUDIP.USER_ID}_QUESTIONTYPE_${Math.random()}`); this.data.questions.push({ id: id, questiontype: this.data.questions[i].questiontype, internal_name: this.data.questions[i].internal_name, questiondata: JSON.parse(JSON.stringify(this.data.questions[i].questiondata)), }); this.activeTab = id; }, deleteQuestion(question_id) { STUDIP.Dialog.confirm(this.$gettext('Wirklich löschen?')).done(() => { this.$delete(this.data.questions, this.getIndexForQuestion(question_id)); this.switchTab('add_question'); }) }, switchTab(tab_id) { this.activeTab = tab_id; this.$nextTick(function () { if (this.$refs.autofocus !== undefined) { if (Array.isArray(this.$refs.autofocus)) { if (typeof this.$refs.autofocus[0] !== "undefined") { this.$refs.autofocus[0].focus(); } } else { this.$refs.autofocus.focus(); } } }); }, objectsEqual(obj1, obj2) { return _.isEqual(obj1, obj2); }, renameInternalName(question_id) { this.editInternalName = question_id; let index = this.getIndexForQuestion(question_id); this.tempInternalName = this.data.questions[index].internal_name; this.$nextTick(() => { this.$refs.editInternalName[0].focus(); }); }, saveInternalName(question_id) { let index = this.getIndexForQuestion(question_id); this.data.questions[index].internal_name = this.tempInternalName; this.editInternalName = null; }, moveQuestionDown(question_id) { let index = this.getIndexForQuestion(question_id); if (index < this.data.questions.length - 1) { let question = this.data.questions[index]; this.data.questions[index] = this.data.questions[index + 1]; this.data.questions[index + 1] = question; this.$forceUpdate(); } }, moveQuestionUp(question_id) { let index = this.getIndexForQuestion(question_id); if (index > 0) { let question = this.data.questions[index]; this.data.questions[index] = this.data.questions[index - 1]; this.data.questions[index - 1] = question; this.$forceUpdate(); } } }, computed: { actionMenuItems() { return [ {label: this.$gettext('Umbenennen'), icon: 'edit', emit: 'rename'}, {label: this.$gettext('Frage kopieren'), icon: 'copy', emit: 'copy'}, {label: this.$gettext('Frage nach oben verschieben'), icon: 'arr_1up', emit: 'moveup'}, {label: this.$gettext('Frage nach unten verschieben'), icon: 'arr_1down', emit: 'movedown'}, {label: this.$gettext('Frage löschen'), icon: 'trash', emit: 'delete'}, ]; }, activateFormSecure() { return this.form_secured && !this.objectsEqual(this.oldData, this.data); }, indexForQuestion() { return this.getIndexForQuestion(this.activeTab); }, }, mounted() { this.$refs.autofocus.focus(); }, } </script>