aboutsummaryrefslogtreecommitdiff
path: root/resources
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-07-04 15:23:16 +0000
committerDavid Siegfried <david.siegfried@uni-vechta.de>2024-07-04 15:23:16 +0000
commitc8d06fae923e69ff015124975813fdc0ba924a08 (patch)
tree20c9099429ee05671f6bfaa88adb5f87d9c1ea33 /resources
parenta9c40e363b3e13223bb771105c190be9475021a5 (diff)
refactor questionnaire editor to sfc, fixes #4303
Closes #4303 Merge request studip/studip!3155
Diffstat (limited to 'resources')
-rw-r--r--resources/assets/javascripts/bootstrap/dialog.js5
-rw-r--r--resources/assets/javascripts/bootstrap/questionnaire.js5
-rw-r--r--resources/assets/javascripts/bootstrap/vue.js24
-rw-r--r--resources/assets/javascripts/lib/dialog.js5
-rw-r--r--resources/assets/javascripts/lib/questionnaire.js204
-rw-r--r--resources/assets/stylesheets/scss/forms.scss36
-rw-r--r--resources/vue/components/questionnaires/QuestionnaireEditor.vue354
7 files changed, 410 insertions, 223 deletions
diff --git a/resources/assets/javascripts/bootstrap/dialog.js b/resources/assets/javascripts/bootstrap/dialog.js
index f186307..58d01fd 100644
--- a/resources/assets/javascripts/bootstrap/dialog.js
+++ b/resources/assets/javascripts/bootstrap/dialog.js
@@ -1,3 +1,8 @@
STUDIP.domReady(function () {
STUDIP.Dialog.initialize();
});
+
+$(document).on('click', '[data-vue-app] [data-dialog-button] .cancel.button', () => {
+ STUDIP.Dialog.close();
+ return false;
+});
diff --git a/resources/assets/javascripts/bootstrap/questionnaire.js b/resources/assets/javascripts/bootstrap/questionnaire.js
index 4970b64..3b31764 100644
--- a/resources/assets/javascripts/bootstrap/questionnaire.js
+++ b/resources/assets/javascripts/bootstrap/questionnaire.js
@@ -1,8 +1,3 @@
-import {dialogReady, ready} from "../lib/ready";
-STUDIP.ready(() => {
- STUDIP.Questionnaire.initEditor();
-});
-
jQuery(document).on('change', '.show_validation_hints .questionnaire_answer [data-question_type=Vote] input', function() {
STUDIP.Questionnaire.Vote.validator.call($(this).closest("article")[0]);
});
diff --git a/resources/assets/javascripts/bootstrap/vue.js b/resources/assets/javascripts/bootstrap/vue.js
index 64d2492..637241a 100644
--- a/resources/assets/javascripts/bootstrap/vue.js
+++ b/resources/assets/javascripts/bootstrap/vue.js
@@ -11,7 +11,29 @@ STUDIP.ready(() => {
let components = {};
config.components.forEach(component => {
const name = component.split('/').reverse()[0];
- components[name] = () => import(`../../../vue/components/${component}.vue`);
+ components[name] = () => {
+ // TODO: I wonder if this works with Vue3
+
+ const temp = import(`../../../vue/components/${component}.vue`);
+ temp.then(({default: c}) => {
+ const mounted = c.mounted ?? null;
+ c.mounted = function (...args) {
+ if (
+ this.$el instanceof Element
+ && this.$el.querySelector('[data-dialog-button]')
+ ) {
+ this.$el.closest('.studip-dialog')
+ .querySelector('.ui-dialog-buttonpane')
+ .remove();
+ }
+ if (mounted) {
+ mounted.call(this, args);
+ }
+ };
+ return c;
+ })
+ return temp;
+ };
});
STUDIP.Vue.load().then(async ({createApp, store}) => {
diff --git a/resources/assets/javascripts/lib/dialog.js b/resources/assets/javascripts/lib/dialog.js
index c602a29..46f9d98 100644
--- a/resources/assets/javascripts/lib/dialog.js
+++ b/resources/assets/javascripts/lib/dialog.js
@@ -420,7 +420,10 @@ Dialog.show = function(content, options = {}) {
});
// Create buttons
- if (options.buttons === undefined || (options.buttons && !$.isPlainObject(options.buttons))) {
+ if (
+ options.buttons === undefined
+ || (options.buttons && !$.isPlainObject(options.buttons))
+ ) {
dialog_options.buttons = extractButtons.call(this, instance.element);
// Create 'close' button
if (dialog_options.buttons.cancel === undefined) {
diff --git a/resources/assets/javascripts/lib/questionnaire.js b/resources/assets/javascripts/lib/questionnaire.js
index 9a89348..5251617 100644
--- a/resources/assets/javascripts/lib/questionnaire.js
+++ b/resources/assets/javascripts/lib/questionnaire.js
@@ -1,209 +1,7 @@
-import { $gettext } from '../lib/gettext';
-import md5 from 'md5';
-//import html2canvas from "html2canvas";
-//import {jsPDF} from "jspdf";
+import { $gettext } from './gettext';
const Questionnaire = {
delayedQueue: [],
- Editor: null,
- initEditor () {
- $('.questionnaire_edit:not(.vueified)').addClass('vueified').each(function () {
- STUDIP.Vue.load().then(({createApp}) => {
- let form = this;
- let components = {};
- let questiontypes = $(form).data('questiontypes');
- for (let i in questiontypes) {
- if (questiontypes[i].component[0] && questiontypes[i].component[1]) {
- //for plugins to be able to import their vue components:
- components[questiontypes[i].component[0]] = () => import(/* webpackIgnore: true */ questiontypes[i].component[1]);
- }
- }
- components.draggable = () => import('vuedraggable');
- components['vote-edit'] = () => import('../../../vue/components/questionnaires/VoteEdit.vue');
- components['freetext-edit'] = () => import('../../../vue/components/questionnaires/FreetextEdit.vue');
- components['likert-edit'] = () => import('../../../vue/components/questionnaires/LikertEdit.vue');
- components['rangescale-edit'] = () => import('../../../vue/components/questionnaires/RangescaleEdit.vue');
- components['questionnaire-info-edit'] = () => import('../../../vue/components/questionnaires/QuestionnaireInfoEdit.vue');
- STUDIP.Questionnaire.Editor = createApp({
- el: form,
- components,
- data() {
- return {
- questiontypes,
-
- questions: $(form).data('questions_data'),
- activeTab: 'admin',
- hoverTab: null,
- data: $(form).data('questionnaire_data'),
- form_secured: true,
- oldData: {
- questions: [],
- data: {}
- },
- range_type: $(form).data('range_type'),
- range_id: $(form).data('range_id'),
- editInternalName: null,
- tempInternalName: '',
- validationNotice: false,
- };
- },
- methods: {
- addQuestion(questiontype) {
- let id = md5(STUDIP.USER_ID + '_QUESTIONTYPE_' + Math.random());
-
- this.questions.push({
- id: id,
- questiontype: questiontype,
- internal_name: '',
- questiondata: {},
- });
-
- this.activeTab = id;
- },
- submit() {
- if (!this.data.title) {
- this.switchTab('admin');
- this.validationNotice = true;
- return;
- }
- let 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
- };
- let questions = [];
- for (let i in this.questions) {
- questions.push({
- id: this.questions[i].id,
- questiontype: this.questions[i].questiontype,
- internal_name: this.questions[i].internal_name,
- questiondata: Object.assign({}, this.questions[i].questiondata),
- });
- }
- $.post(STUDIP.URLHelper.getURL('dispatch.php/questionnaire/store/' + (this.data.id || '')), {
- questionnaire: data,
- questions_data: JSON.stringify(questions),
- range_type: this.range_type,
- range_id: this.range_id
- }).done(() => {
- this.form_secured = false;
- this.$nextTick(() => {
- location.reload();
- });
- }).fail(() => {
- STUDIP.Report.error('Could not save questionnaire.');
- });
- },
- getIndexForQuestion: function (question_id) {
- for (let i in this.questions) {
- if (this.questions[i].id === question_id || this.questions[i].id === question_id.substring(5)) {
- return typeof i === "string" ? parseInt(i, 10) : i;
- }
- }
- },
- duplicateQuestion: function (question_id) {
- let i = this.getIndexForQuestion(question_id);
- let id = md5(STUDIP.USER_ID + '_QUESTIONTYPE_' + Math.random());
- this.questions.push({
- id: id,
- questiontype: this.questions[i].questiontype,
- internal_name: this.questions[i].internal_name,
- questiondata: JSON.parse(JSON.stringify(this.questions[i].questiondata)),
- });
- this.activeTab = id;
- },
- deleteQuestion(question_id) {
- STUDIP.Dialog.confirm(this.$gettext('Wirklich löschen?')).done(() => {
- this.$delete(this.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.questions[index].internal_name;
- this.$nextTick(() => {
- this.$refs.editInternalName[0].focus();
- });
- },
- saveInternalName(question_id) {
- let index = this.getIndexForQuestion(question_id);
- this.questions[index].internal_name = this.tempInternalName;
- this.editInternalName = null;
- },
- moveQuestionDown(question_id) {
- let index = this.getIndexForQuestion(question_id);
- if (index < this.questions.length - 1) {
- let question = this.questions[index];
- this.questions[index] = this.questions[index + 1];
- this.questions[index + 1] = question;
- this.$forceUpdate();
- }
- },
- moveQuestionUp(question_id) {
- let index = this.getIndexForQuestion(question_id);
- if (index > 0) {
- let question = this.questions[index];
- this.questions[index] = this.questions[index - 1];
- this.questions[index - 1] = question;
- this.$forceUpdate();
- }
- }
- },
- computed: {
- activateFormSecure() {
- let newData = {
- questions: this.questions,
- data: this.data
- };
- return this.form_secured && !this.objectsEqual(this.oldData, newData);
- },
- indexForQuestion() {
- for (let i in this.questions) {
- if (
- this.questions[i].id === this.activeTab ||
- this.questions[i].id === this.activeTab.substring(5)
- ) {
- return typeof i === "string" ? parseInt(i, 10) : i;
- }
- }
-
- return null;
- },
- },
- mounted() {
- this.$refs.autofocus.focus();
- this.oldData = {
- questions: [...this.questions],
- data: Object.assign({}, this.data)
- };
- },
- });
-
- });
- });
- },
delayedInterval: null,
lastUpdate: null,
filtered: {},
diff --git a/resources/assets/stylesheets/scss/forms.scss b/resources/assets/stylesheets/scss/forms.scss
index 888cf56..2aa105f 100644
--- a/resources/assets/stylesheets/scss/forms.scss
+++ b/resources/assets/stylesheets/scss/forms.scss
@@ -622,23 +622,33 @@ form.inline {
}
.studip-dialog {
- form[data-vue-app] {
- display: flex;
- flex-direction: column;
+ [data-vue-app] {
min-height: 100%;
+ display: flex;
- fieldset {
- flex: 0;
+ > * {
+ flex: 1;
}
- footer[data-dialog-button] {
- background: var(--white);
- border-top-color: var(--base-color-20);
- bottom: -0.5em;
- margin-top: auto;
- padding: 1.3em 0;
- position: sticky;
- text-align: center;
+ form {
+ display: flex;
+ flex-direction: column;
+ min-height: 100%;
+
+ > :not(footer[data-dialog-button]) {
+ flex: 0;
+ margin-bottom: auto;
+ }
+
+ footer[data-dialog-button] {
+ background: var(--white);
+ border-top-color: var(--base-color-20);
+ bottom: -0.5em;
+ margin-top: auto;
+ padding: 1.3em 0;
+ position: sticky;
+ text-align: center;
+ }
}
}
}
diff --git a/resources/vue/components/questionnaires/QuestionnaireEditor.vue b/resources/vue/components/questionnaires/QuestionnaireEditor.vue
new file mode 100644
index 0000000..d87305a
--- /dev/null
+++ b/resources/vue/components/questionnaires/QuestionnaireEditor.vue
@@ -0,0 +1,354 @@
+<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>