Select Git revision
QuestionnaireEditor.vue
Forked from
Stud.IP / Stud.IP
Source project has a limited visibility.
-
Closes #4303 Merge request studip/studip!3155
Closes #4303 Merge request studip/studip!3155
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
QuestionnaireEditor.vue 15.82 KiB
<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>