From 9b22a4c6ca532866570ee9095bbf153108df14f4 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+studip@gmail.com>
Date: Mon, 18 Sep 2023 09:00:17 +0000
Subject: [PATCH] fixes #3161

Closes #3161

Merge request studip/studip!2139
---
 app/views/questionnaire/edit.php              |  79 ++++----
 lib/models/Questionnaire.php                  |  21 ++-
 .../assets/javascripts/lib/questionnaire.js   | 103 ++++++-----
 .../components/questionnaires/LikertEdit.vue  | 167 ++++++++---------
 .../questionnaires/RangescaleEdit.vue         | 173 ++++++++----------
 5 files changed, 255 insertions(+), 288 deletions(-)

diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php
index 1be8e8051eb..20215c96032 100644
--- a/app/views/questionnaire/edit.php
+++ b/app/views/questionnaire/edit.php
@@ -10,19 +10,20 @@ $questiontypes['Vote'] = [
     'component' => Vote::getEditingComponent()
 ];
 foreach (get_declared_classes() as $class) {
-    if (is_subclass_of($class, 'QuestionType')) {
-        if (!isset($questiontypes[$class])) {
-            $questiontypes[$class] = [
-                'name' => $class::getName(),
-                'type' => $class,
-                'icon' => $class::getIconShape(),
-                'component' => $class::getEditingComponent()
-            ];
-        }
+    if (
+        is_subclass_of($class, QuestionType::class)
+        && !isset($questiontypes[$class])
+    ) {
+        $questiontypes[$class] = [
+            'name' => $class::getName(),
+            'type' => $class,
+            'icon' => $class::getIconShape(),
+            'component' => $class::getEditingComponent()
+        ];
     }
 }
 $questionnaire_data = [
-    'id' => $questionnaire->getId(),
+    'id' => $questionnaire->id,
     'title' => $questionnaire['title'],
     'startdate' => $questionnaire->isNew() ? _('sofort') : $questionnaire['startdate'],
     'stopdate' => $questionnaire['stopdate'],
@@ -34,14 +35,14 @@ $questionnaire_data = [
 $questions_data = [];
 foreach ($questionnaire->questions as $question) {
     $questions_data[] = [
-        'id' => $question->getId(),
+        'id' => $question->id,
         'questiontype' => $question['questiontype'],
         'internal_name' => $question['internal_name'],
         'questiondata' => $question['questiondata']->getArrayCopy()
     ];
 }
 ?>
-<form action="<?= URLHelper::getLink("dispatch.php/questionnaire/edit/".(!$questionnaire->isNew() ? $questionnaire->getId() : "")) ?>"
+<form action="<?= URLHelper::getLink('dispatch.php/questionnaire/edit/' . (!$questionnaire->isNew() ? $questionnaire->getId() : '')) ?>"
       method="post"
       enctype="multipart/form-data"
       class="questionnaire_edit default"
@@ -50,19 +51,17 @@ foreach ($questionnaire->questions as $question) {
       data-questions_data="<?= htmlReady(json_encode($questions_data)) ?>"
       data-range_type="<?= htmlReady(Request::get('range_type')) ?>"
       data-range_id="<?= htmlReady(Request::get('range_id')) ?>"
-    <?= Request::isAjax() ? "data-dialog" : "" ?>
+      <?= Request::isAjax() ? 'data-dialog' : '' ?>
       :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">
+                <article aria-live="assertive" class="validation_notes studip">
                     <header>
                         <h1>
-                            <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(17, ['class' => "text-bottom validation_notes_icon"]) ?>
+                            <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(['class' => 'text-bottom validation_notes_icon']) ?>
                             <?= _('Hinweise zum Ausfüllen des Formulars') ?>
                         </h1>
                     </header>
@@ -128,26 +127,25 @@ foreach ($questionnaire->questions as $question) {
                        :ref="key == Object.keys(questiontypes)[0] ? 'autofocus' : ''"
                        href=""
                        @click.prevent="addQuestion(questiontype.type)">
-                        <studip-icon :shape="questiontype.icon" :size="40" role="clickable"></studip-icon>
+                        <studip-icon :shape="questiontype.icon" :size="40"></studip-icon>
                         {{questiontype.name}}
                     </button>
                 </div>
             </div>
             <div v-else>
-                <? foreach ($questiontypes as $questiontype) : ?>
-                <component is="<?= htmlReady($questiontype['component'][0]) ?>"
-                           v-if="questiontypes[questions[getIndexForQuestion(activeTab)].questiontype].component[0] === '<?= htmlReady($questiontype['component'][0]) ?>'"
-                           v-model="questions[getIndexForQuestion(activeTab)].questiondata"
-                           :question_id="questions[getIndexForQuestion(activeTab)].id">
+                <component :is="questiontypes[questions[indexForQuestion].questiontype].component[0]"
+                           v-model="questions[indexForQuestion].questiondata"
+                           :question_id="questions[indexForQuestion].id"
+                           :key="questions[indexForQuestion].id">
                 </component>
-                <? endforeach ?>
             </div>
         </div>
         <aside>
-            <a :class="activeTab === 'admin' ? 'admin active' : 'admin'"
-               href=""
+            <a class="admin"
+               :class="{active: activeTab === 'admin'}"
+               href="#"
                @click.prevent="switchTab('admin')">
-                <span class="icon"><studip-icon shape="evaluation" role="clickable" :size="30" alt=""></studip-icon></span>
+                <span class="icon"><studip-icon shape="evaluation" :size="30" alt=""></studip-icon></span>
                 <?= _('Einstellungen') ?>
             </a>
             <draggable v-if="questions.length > 0" v-model="questions" handle=".handle" group="questions" class="questions_container questions">
@@ -160,17 +158,17 @@ foreach ($questionnaire->questions as $question) {
                        @click.prevent="switchTab(question.id)">
                         <span class="handle"></span>
                         <span class="icon type">
-                            <studip-icon :shape="questiontypes[question.questiontype].icon" role="clickable" :size="30" alt=""></studip-icon>
+                            <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" role="clickable" :size="20" title="<?= _('Internen Namen speichern') ?>"></studip-icon>
+                                <studip-icon shape="accept" :size="20" title="<?= _('Internen Namen speichern') ?>"></studip-icon>
                             </button>
                             <button @click="editInternalName = null">
-                                <studip-icon shape="decline" role="clickable" :size="20" title="<?= _('Internen Namen nicht speichern') ?>"></studip-icon>
+                                <studip-icon shape="decline" :size="20" title="<?= _('Internen Namen nicht speichern') ?>"></studip-icon>
                             </button>
                         </div>
                     </a>
@@ -180,33 +178,20 @@ foreach ($questionnaire->questions as $question) {
                                         @rename="renameInternalName(question.id)"
                                         @moveup="moveQuestionUp(question.id)"
                                         @movedown="moveQuestionDown(question.id)"
-                                        @delete="askForDeletingTheQuestion(question.id)"></studip-action-menu>
+                                        @delete="deleteQuestion(question.id)"></studip-action-menu>
                 </div>
             </draggable>
             <a :class="activeTab === 'add_question' ? 'add_question active' : 'add_question'"
-               href=""
+               href="#"
                @click.prevent="switchTab('add_question')">
-                <span class="icon"><studip-icon shape="add" role="clickable" :size="30" alt=""></studip-icon></span>
+                <span class="icon"><studip-icon shape="add" :size="30" alt=""></studip-icon></span>
                 <?= _('Element hinzufügen') ?>
             </a>
         </aside>
     </div>
 
 
-    <studip-dialog
-        v-if="askForDeletingQuestions"
-        title="<?= _('Bitte bestätigen Sie die Aktion') ?>"
-        question="<?= _('Wirklich löschen?') ?>"
-        confirmText="<?= _('Ja') ?>"
-        closeText="<?= _('Nein') ?>"
-        closeClass="cancel"
-        height="180"
-        @confirm="deleteQuestion"
-        @close="askForDeletingQuestions = false"
-    >
-    </studip-dialog>
-
     <footer data-dialog-button>
-        <?= \Studip\LinkButton::create(_("Speichern"), 'questionnaire_store', ['onclick' => 'STUDIP.Questionnaire.Editor.submit(); return false;']) ?>
+        <?= Studip\LinkButton::create(_('Speichern'), 'questionnaire_store', ['onclick' => 'STUDIP.Questionnaire.Editor.submit(); return false;']) ?>
     </footer>
 </form>
diff --git a/lib/models/Questionnaire.php b/lib/models/Questionnaire.php
index d69068de89f..7a250442879 100644
--- a/lib/models/Questionnaire.php
+++ b/lib/models/Questionnaire.php
@@ -1,5 +1,24 @@
 <?php
-
+/**
+ * @property string $id
+ * @property string $questionnaire_id
+ * @property string $title
+ * @property string $description
+ * @property string $user_id
+ * @property int|null $startdate
+ * @property int|null $stopdate
+ * @property bool $visible
+ * @property bool $anonymous
+ * @property string $resultvisibility
+ * @property bool $editanswers
+ * @property bool $copyable
+ * @property string $chdate
+ * @property string $mkdate
+ *
+ * @property QuestionnaireQuestion[]|SimpleORMapCollection $questions
+ * @property QuestionnaireAssignment[]|SimpleORMapCollection $assignments
+ * @property QuestionnaireAnonymousAnswer[]|SimpleORMapCollection $anonymousanswers
+ */
 class Questionnaire extends SimpleORMap implements PrivacyObject
 {
     protected static function configure($config = [])
diff --git a/resources/assets/javascripts/lib/questionnaire.js b/resources/assets/javascripts/lib/questionnaire.js
index 95263784c91..16ee461684a 100644
--- a/resources/assets/javascripts/lib/questionnaire.js
+++ b/resources/assets/javascripts/lib/questionnaire.js
@@ -7,7 +7,7 @@ const Questionnaire = {
     delayedQueue: [],
     Editor: null,
     initEditor () {
-        $('.questionnaire_edit').each(function () {
+        $('.questionnaire_edit:not(.vueified)').addClass('vueified').each(function () {
             STUDIP.Vue.load().then(({createApp}) => {
                 let form = this;
                 let components = {};
@@ -26,15 +26,15 @@ const Questionnaire = {
                 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,
-                            questiontypes: questiontypes,
                             data: $(form).data('questionnaire_data'),
-                            askForDeletingQuestions: false,
-                            whatQuestionIndexShouldBeDeleted: null,
                             form_secured: true,
                             oldData: {
                                 questions: [],
@@ -44,21 +44,23 @@ const Questionnaire = {
                             range_id: $(form).data('range_id'),
                             editInternalName: null,
                             tempInternalName: '',
-                            validationNotice: false
+                            validationNotice: false,
                         };
                     },
                     methods: {
-                        addQuestion: function (questiontype) {
+                        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: function () {
+                        submit() {
                             if (!this.data.title) {
                                 this.switchTab('admin');
                                 this.validationNotice = true;
@@ -82,25 +84,18 @@ const Questionnaire = {
                                     questiondata: Object.assign({}, this.questions[i].questiondata),
                                 });
                             }
-                            let v = this;
-                            $.ajax({
-                                url: STUDIP.URLHelper.getURL(STUDIP.ABSOLUTE_URI_STUDIP + 'dispatch.php/questionnaire/store/' + (this.data.id || '')),
-                                data: {
-                                    questionnaire: data,
-                                    questions_data: questions,
-                                    range_type: this.range_type,
-                                    range_id: this.range_id
-                                },
-                                type: 'post',
-                                success: function () {
-                                    v.form_secured = false;
-                                    v.$nextTick(function () {
-                                        location.reload();
-                                    });
-                                },
-                                error: function () {
-                                    window.alert('Could not save questionnaire.');
-                                }
+                            $.post(STUDIP.URLHelper.getURL('dispatch.php/questionnaire/store/' + (this.data.id || '')), {
+                                questionnaire: data,
+                                questions_data: 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) {
@@ -121,19 +116,16 @@ const Questionnaire = {
                             });
                             this.activeTab = id;
                         },
-                        askForDeletingTheQuestion: function (question_id) {
-                            this.askForDeletingQuestions = true;
-                            this.whatQuestionIndexShouldBeDeleted = this.getIndexForQuestion(question_id);
-                        },
-                        deleteQuestion: function () {
-                            this.$delete(this.questions, this.whatQuestionIndexShouldBeDeleted);
-                            this.switchTab('add_question');
-                            this.askForDeletingQuestions = false;
+                        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: function (tab_id) {
+                        switchTab(tab_id) {
                             this.activeTab = tab_id;
                             this.$nextTick(function () {
-                                if (typeof this.$refs.autofocus !== "undefined") {
+                                if (this.$refs.autofocus !== undefined) {
                                     if (Array.isArray(this.$refs.autofocus)) {
                                         if (typeof this.$refs.autofocus[0] !== "undefined") {
                                             this.$refs.autofocus[0].focus();
@@ -144,23 +136,23 @@ const Questionnaire = {
                                 }
                             });
                         },
-                        objectsEqual: function (obj1, obj2) {
+                        objectsEqual(obj1, obj2) {
                             return _.isEqual(obj1, obj2);
                         },
-                        renameInternalName: function (question_id) {
+                        renameInternalName(question_id) {
                             this.editInternalName = question_id;
                             let index = this.getIndexForQuestion(question_id);
                             this.tempInternalName = this.questions[index].internal_name;
-                            this.$nextTick(function () {
+                            this.$nextTick(() => {
                                 this.$refs.editInternalName[0].focus();
                             });
                         },
-                        saveInternalName: function (question_id) {
+                        saveInternalName(question_id) {
                             let index = this.getIndexForQuestion(question_id);
                             this.questions[index].internal_name = this.tempInternalName;
                             this.editInternalName = null;
                         },
-                        moveQuestionDown: function (question_id) {
+                        moveQuestionDown(question_id) {
                             let index = this.getIndexForQuestion(question_id);
                             if (index < this.questions.length - 1) {
                                 let question = this.questions[index];
@@ -169,7 +161,7 @@ const Questionnaire = {
                                 this.$forceUpdate();
                             }
                         },
-                        moveQuestionUp: function (question_id) {
+                        moveQuestionUp(question_id) {
                             let index = this.getIndexForQuestion(question_id);
                             if (index > 0) {
                                 let question = this.questions[index];
@@ -180,22 +172,33 @@ const Questionnaire = {
                         }
                     },
                     computed: {
-                        activateFormSecure: function () {
+                        activateFormSecure() {
                             let newData = {
-                                'questions': this.questions,
-                                'data': this.data
+                                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: function () {
+                    mounted() {
                         this.$refs.autofocus.focus();
                         this.oldData = {
-                            'questions': [...this.questions],
-                            'data': Object.assign({}, this.data)
+                            questions: [...this.questions],
+                            data: Object.assign({}, this.data)
                         };
                     },
-                    components: components
                 });
 
             });
diff --git a/resources/vue/components/questionnaires/LikertEdit.vue b/resources/vue/components/questionnaires/LikertEdit.vue
index 66c187eb34e..7bb36f62628 100644
--- a/resources/vue/components/questionnaires/LikertEdit.vue
+++ b/resources/vue/components/questionnaires/LikertEdit.vue
@@ -2,7 +2,7 @@
     <div class="likert_edit">
 
         <div class="formpart" tabindex="0" ref="autofocus">
-            {{$gettext('Einleitungstext')}}
+            {{ $gettext('Einleitungstext' )}}
             <studip-wysiwyg v-model="val_clone.description"></studip-wysiwyg>
         </div>
 
@@ -22,7 +22,7 @@
                     <td class="dragcolumn">
                         <a class="dragarea"
                            tabindex="0"
-                           :title="$gettextInterpolate('Sortierelement für Aussage %{statement}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {statement: statement})"
+                           :title="$gettextInterpolate($gettext('Sortierelement für Aussage %{statement}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'), {statement: statement})"
                            @keydown="keyHandler($event, index)"
                            :ref="'draghandle_' + index">
                             <span class="handle"></span>
@@ -36,34 +36,38 @@
                                v-model="val_clone.statements[index]">
                     </td>
                     <td v-for="(option, index2) in val_clone.options" :key="index2">
-                        <input type="radio" value="1" disabled :title="option">
+                        <input type="radio" disabled :title="option">
                     </td>
                     <td class="actions">
-                        <button class="as-link"
-                           @click.prevent="askForDeletingStatement(index)"
-                           :title="$gettext('Aussage löschen')">
-                            <studip-icon shape="trash" role="clickable" :size="20" alt=""></studip-icon>
-                        </button>
+                        <studip-icon name="delete"
+                                     shape="trash"
+                                     :size="20"
+                                     @click.prevent="deleteStatement(index)"
+                                     :title="$gettext('Aussage löschen')"
+                        ></studip-icon>
                     </td>
                 </tr>
             </draggable>
             <tfoot>
                 <tr>
-                    <td :colspan="typeof val_clone.options !== 'undefined' ? val_clone.options.length + 3 : 3">
-                        <button @click.prevent="addStatement" class="as-link" :title="$gettext('Aussage hinzufügen')">
-                            <studip-icon shape="add" role="clickable" :size="20" alt=""></studip-icon>
-                        </button>
+                    <td :colspan="val_clone.options.length + 3">
+                        <studip-icon name="add"
+                                     shape="add"
+                                     :size="20"
+                                     @click.prevent="addStatement()"
+                                     :title="$gettext('Aussage hinzufügen')"
+                        ></studip-icon>
                     </td>
                 </tr>
             </tfoot>
         </table>
 
         <label>
-            <input type="checkbox" v-model.number="val_clone.mandatory" true-value="1" false-value="0">
+            <input type="checkbox" v-model.number="val_clone.mandatory">
             {{ $gettext('Pflichtfrage') }}
         </label>
         <label>
-            <input type="checkbox" v-model.number="val_clone.randomize" true-value="1" false-value="0">
+            <input type="checkbox" v-model.number="val_clone.randomize">
             {{ $gettext('Antworten den Teilnehmenden zufällig präsentieren') }}
         </label>
 
@@ -71,39 +75,30 @@
             <div>{{ $gettext('Antwortmöglichkeiten konfigurieren') }}</div>
             <input-array v-model="val_clone.options"></input-array>
         </div>
-
-        <studip-dialog
-            v-if="askForDeleting"
-            :title="$gettext('Bitte bestätigen Sie die Aktion.')"
-            :question="$gettext('Wirklich löschen?')"
-            :confirmText="$gettext('Ja')"
-            :closeText="$gettext('Nein')"
-            closeClass="cancel"
-            height="180"
-            @confirm="deleteStatement"
-            @close="askForDeleting = false"
-        >
-        </studip-dialog>
     </div>
 </template>
 
 <script>
-import StudipIcon from "../StudipIcon.vue";
-import StudipDialog from "../StudipDialog.vue";
 import draggable from 'vuedraggable';
-import StudipWysiwyg from "../StudipWysiwyg.vue";
 import InputArray from "./InputArray.vue";
-import { $gettext } from '../../../assets/javascripts/lib/gettext.js';
-const default_value = {
+import { $gettext } from '../../../assets/javascripts/lib/gettext';
+
+const default_value = () => ({
+    description: '',
     statements: ['', '', '', ''],
-        options: [$gettext('trifft zu'), $gettext('trifft eher zu'), $gettext('teils-teils'), $gettext('trifft eher nicht zu'), $gettext('trifft nicht zu')]
-};
+    mandatory: false,
+    randomize: false,
+    options: [
+        $gettext('trifft zu'),
+        $gettext('trifft eher zu'),
+        $gettext('teils-teils'),
+        $gettext('trifft eher nicht zu'),
+        $gettext('trifft nicht zu'),
+    ],
+});
 export default {
     name: 'likert-edit',
     components: {
-        StudipWysiwyg,
-        StudipIcon,
-        StudipDialog,
         draggable,
         InputArray
     },
@@ -111,8 +106,8 @@ export default {
         value: {
             type: Object,
             required: false,
-            default: function () {
-                return default_value;
+            default() {
+                return {...default_value()};
             }
         },
         question_id: {
@@ -120,45 +115,29 @@ export default {
             required: false
         }
     },
-    data: function () {
+    data() {
         return {
-            val_clone: {},
-            askForDeleting: false,
-            indexOfDeletingStatement: 0,
+            val_clone: null,
             assistiveLive: ''
         };
     },
     methods: {
-        addStatement: function (val, position) {
-            if (val.target) {
-                val = '';
-            }
-            let data = this.value;
-            if (typeof position === "undefined") {
-                data.statements.push(val || '');
-                position = this.value.length - 1
+        addStatement(val = '', position = null) {
+            if (position === null) {
+                this.val_clone.statements.push(val || '');
             } else {
-                data.statements.splice(position, 0, val || '');
+                this.val_clone.statements.splice(position, 0, val || '');
             }
-            this.$emit('input', data);
-            let v = this;
-            this.$nextTick(function () {
-                v.$refs['statement_' + (v.value.statements.length - 1)][0].focus();
+            this.$nextTick(() => {
+                this.$refs['statement_' + (this.val_clone.statements.length - 1)][0].focus();
             });
         },
-        askForDeletingStatement: function (index) {
-            this.indexOfDeletingStatement = index;
-            if (this.value.statements[index]) {
-                this.askForDeleting = true;
-            } else {
-                this.deleteStatement();
-            }
-        },
-        deleteStatement: function () {
-            this.$delete(this.value.statements, this.indexOfDeletingStatement);
-            this.askForDeleting = false;
+        deleteStatement(index) {
+            STUDIP.Dialog.confirm(this.$gettext('Wirklich löschen?')).done(() => {
+                this.$delete(this.val_clone.statements, index);
+            });
         },
-        onPaste: function (ev, position) {
+        onPaste(ev, position) {
             let data = ev.clipboardData.getData("text").split("\n");
             for (let i = 0; i < data.length; i++) {
                 if (data[i].trim()) {
@@ -172,11 +151,11 @@ export default {
                     e.preventDefault();
                     if (index > 0) {
                         this.moveUp(index);
-                        this.$nextTick(function () {
+                        this.$nextTick(() => {
                             this.$refs['draghandle_' + (index - 1)][0].focus();
                             this.assistiveLive = this.$gettextInterpolate(
-                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
-                                , {pos: index, listLength: this.val_clone.statements.length}
+                                this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
+                                {pos: index, listLength: this.val_clone.statements.length}
                             );
                         });
                     }
@@ -185,40 +164,46 @@ export default {
                     e.preventDefault();
                     if (index < this.val_clone.statements.length - 1) {
                         this.moveDown(index);
-                        this.$nextTick(function () {
+                        this.$nextTick(() => {
                             this.$refs['draghandle_' + (index + 1)][0].focus();
                             this.assistiveLive = this.$gettextInterpolate(
-                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
-                                , {pos: index + 2, listLength: this.val_clone.statements.length}
+                                this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
+                                {pos: index + 2, listLength: this.val_clone.statements.length}
                             );
                         });
                     }
                     break;
             }
         },
-        moveDown: function (index) {
-            let statement = this.val_clone.statements[index];
-            this.val_clone.statements[index] = this.val_clone.statements[index + 1];
-            this.val_clone.statements[index + 1] = statement;
-            this.$forceUpdate();
+        moveDown(index) {
+            this.val_clone.statements.splice(
+                index,
+                2,
+                this.val_clone.statements[index + 1],
+                this.val_clone.statements[index]
+            )
         },
-        moveUp: function (index) {
-            let statement = this.val_clone.statements[index];
-            this.val_clone.statements[index] = this.val_clone.statements[index - 1];
-            this.val_clone.statements[index - 1] = statement;
-            this.$forceUpdate();
+        moveUp(index) {
+            this.val_clone.statements.splice(
+                index - 1,
+                2,
+                this.val_clone.statements[index],
+                this.val_clone.statements[index - 1]
+            )
         }
     },
-    mounted: function () {
-        this.val_clone = this.value;
-        if (!this.value.statements) {
-            this.$emit('input', default_value);
-        }
+    created() {
+        this.val_clone = Object.assign({}, default_value(), this.value ?? {});
+    },
+    mounted() {
         this.$refs.autofocus.focus();
     },
     watch: {
-        value (new_val) {
-            this.val_clone = new_val;
+        val_clone: {
+            handler(current) {
+                this.$emit('input', current);
+            },
+            deep: true
         }
     }
 }
diff --git a/resources/vue/components/questionnaires/RangescaleEdit.vue b/resources/vue/components/questionnaires/RangescaleEdit.vue
index adccc9df8b1..8ba2560f411 100644
--- a/resources/vue/components/questionnaires/RangescaleEdit.vue
+++ b/resources/vue/components/questionnaires/RangescaleEdit.vue
@@ -2,7 +2,7 @@
     <div class="rangescale_edit">
 
         <div class="formpart" tabindex="0" ref="autofocus">
-            {{$gettext('Einleitungstext')}}
+            {{ $gettext('Einleitungstext') }}
             <studip-wysiwyg v-model="val_clone.description"></studip-wysiwyg>
         </div>
 
@@ -13,10 +13,8 @@
                 <tr>
                     <th class="dragcolumn"></th>
                     <th>{{ $gettext('Aussagen') }}</th>
-                    <template v-if="typeof val_clone.maximum !== 'undefined' && typeof val_clone.minimum !== 'undefined'">
                     <th v-for="i in (val_clone.maximum - val_clone.minimum + 1)" :key="i" class="number">{{ (val_clone.minimum - 1 + i) }}</th>
-                    </template>
-                    <th v-if="typeof val_clone.alternative_answer !== 'undefined' && val_clone.alternative_answer.trim().length > 0">{{ val_clone.alternative_answer }}</th>
+                    <th v-if="val_clone.alternative_answer.trim().length > 0">{{ val_clone.alternative_answer }}</th>
                     <th class="actions"></th>
                 </tr>
             </thead>
@@ -25,7 +23,7 @@
                     <td class="dragcolumn">
                         <a class="dragarea"
                            tabindex="0"
-                           :title="$gettextInterpolate('Sortierelement für Aussage %{statement}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {statement: statement})"
+                           :title="$gettextInterpolate($gettext('Sortierelement für Aussage %{statement}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.'), {statement: statement})"
                            @keydown="keyHandler($event, index)"
                            :ref="'draghandle_' + index">
                             <span class="handle"></span>
@@ -38,29 +36,31 @@
                                @paste="(ev) => onPaste(ev, index)"
                                v-model="val_clone.statements[index]">
                     </td>
-                    <template v-if="typeof val_clone.maximum !== 'undefined' && typeof val_clone.minimum !== 'undefined'">
-                    <td v-for="i in (val_clone.maximum - value.minimum + 1)" :key="i">
-                        <input type="radio" value="1" disabled :title="i + val_clone.minimum - 1">
+                    <td v-for="i in (val_clone.maximum - val_clone.minimum + 1)" :key="i">
+                        <input type="radio" disabled :title="i + val_clone.minimum - 1">
                     </td>
-                    </template>
-                    <td v-if="typeof val_clone.alternative_answer !== 'undefined' && val_clone.alternative_answer.trim().length > 0 > 0">
-                        <input type="radio" value="1" disabled :title="val_clone.alternative_answer">
+                    <td v-if="val_clone.alternative_answer.trim().length > 0">
+                        <input type="radio" disabled :title="val_clone.alternative_answer">
                     </td>
                     <td class="actions">
-                        <button class="as-link"
-                           @click.prevent="askForDeletingStatement(index)"
-                           :title="$gettext('Aussage löschen')">
-                            <studip-icon shape="trash" role="clickable" :size="20" alt=""></studip-icon>
-                        </button>
+                        <studip-icon name="delete"
+                                     shape="trash"
+                                     :size="20"
+                                     @click.prevent="deleteStatement(index)"
+                                     :title="$gettext('Aussage löschen')"
+                        ></studip-icon>
                     </td>
                 </tr>
             </draggable>
             <tfoot>
                 <tr>
-                    <td :colspan="val_clone.maximum - val_clone.minimum + 4 + (typeof val_clone.alternative_answer !== 'undefined' && val_clone.alternative_answer.trim().length > 0 ? 1 : 0)">
-                        <button @click.prevent="addStatement" class="as-link" :title="$gettext('Aussage hinzufügen')">
-                            <studip-icon shape="add" role="clickable" :size="20" alt=""></studip-icon>
-                        </button>
+                    <td :colspan="val_clone.maximum - val_clone.minimum + 4 + (val_clone.alternative_answer.trim().length > 0 ? 1 : 0)">
+                        <studip-icon name="add"
+                                     shape="add"
+                                     :size="20"
+                                     @click.prevent="addStatement()"
+                                     :title="$gettext('Aussage hinzufügen')"
+                        ></studip-icon>
                     </td>
                 </tr>
             </tfoot>
@@ -77,59 +77,44 @@
 
         <label>
             {{ $gettext('Maximum') }}
-            <input type="number" v-model.number="val_clone.maximum">
+            <input type="number" v-model.number="val_clone.maximum" :min="val_clone.minimum">
         </label>
 
         <label>
             {{ $gettext('Minimum') }}
-            <input type="number" v-model.number="val_clone.minimum">
+            <input type="number" v-model.number="val_clone.minimum" min="1">
         </label>
 
         <label>
             {{ $gettext('Ausweichantwort (leer lassen für keine)') }}
             <input type="text" v-model="val_clone.alternative_answer">
         </label>
-
-        <studip-dialog
-            v-if="askForDeleting"
-            :title="$gettext('Bitte bestätigen Sie die Aktion.')"
-            :question="$gettext('Wirklich löschen?')"
-            :confirmText="$gettext('Ja')"
-            :closeText="$gettext('Nein')"
-            closeClass="cancel"
-            height="180"
-            @confirm="deleteStatement"
-            @close="askForDeleting = false"
-        >
-        </studip-dialog>
     </div>
 </template>
 
 <script>
-import StudipIcon from "../StudipIcon.vue";
-import StudipDialog from "../StudipDialog.vue";
 import draggable from 'vuedraggable';
-import StudipWysiwyg from "../StudipWysiwyg.vue";
-const default_value = {
+
+const default_value = () => ({
+    description: '',
     statements: ['', '', '', ''],
+    mandatory: false,
+    randomize: false,
     minimum: 1,
     maximum: 5,
     alternative_answer: ''
-};
+});
 export default {
     name: 'likert-edit',
     components: {
-        StudipIcon,
-        StudipDialog,
         draggable,
-        StudipWysiwyg
     },
     props: {
         value: {
             type: Object,
             required: false,
-            default: function () {
-                return default_value;
+            default() {
+                return default_value();
             }
         },
         question_id: {
@@ -137,46 +122,30 @@ export default {
             required: false
         }
     },
-    data: function () {
+    data() {
         return {
-            val_clone: {},
-            askForDeleting: false,
-            indexOfDeletingStatement: 0,
+            val_clone: null,
             assistiveLive: ''
         };
     },
     methods: {
-        addStatement: function (val, position) {
-            if (val.target) {
-                val = '';
-            }
-            let data = this.value;
-            if (typeof position === "undefined") {
-                data.statements.push(val || '');
-                position = this.value.length - 1
+        addStatement(val = '', position = null) {
+            if (position === null) {
+                this.val_clone.statements.push(val || '');
             } else {
-                data.statements.splice(position, 0, val || '');
+                this.val_clone.statements.splice(position, 0, val || '');
             }
-            this.$emit('input', data);
-            let v = this;
-            this.$nextTick(function () {
-                v.$refs['statement_' + (v.value.statements.length - 1)][0].focus();
+            this.$nextTick(() => {
+                this.$refs['statement_' + (v.value.statements.length - 1)][0].focus();
             });
         },
-        askForDeletingStatement: function (index) {
-            this.indexOfDeletingStatement = index;
-            if (this.value.statements[index]) {
-                this.askForDeleting = true;
-            } else {
-                this.deleteStatement();
-            }
-        },
-        deleteStatement: function () {
-            this.$delete(this.value.statements, this.indexOfDeletingStatement);
-            this.askForDeleting = false;
+        deleteStatement(index) {
+            STUDIP.Dialog.confirm(this.$gettext('Wirklich löschen?')).done(() => {
+                this.$delete(this.value.statements, index);
+            });
         },
-        onPaste: function (ev, position) {
-            let data = ev.clipboardData.getData("text").split("\n");
+        onPaste(ev, position) {
+            let data = ev.clipboardData.getData('text').split("\n");
             for (let i = 0; i < data.length; i++) {
                 if (data[i].trim()) {
                     this.addStatement(data[i], position + i);
@@ -189,11 +158,11 @@ export default {
                     e.preventDefault();
                     if (index > 0) {
                         this.moveUp(index);
-                        this.$nextTick(function () {
+                        this.$nextTick(() => {
                             this.$refs['draghandle_' + (index - 1)][0].focus();
                             this.assistiveLive = this.$gettextInterpolate(
-                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
-                                , {pos: index, listLength: this.val_clone.statements.length}
+                                this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
+                                {pos: index, listLength: this.val_clone.statements.length}
                             );
                         });
                     }
@@ -202,40 +171,46 @@ export default {
                     e.preventDefault();
                     if (index < this.val_clone.statements.length - 1) {
                         this.moveDown(index);
-                        this.$nextTick(function () {
+                        this.$nextTick(() => {
                             this.$refs['draghandle_' + (index + 1)][0].focus();
                             this.assistiveLive = this.$gettextInterpolate(
-                                'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
-                                , {pos: index + 2, listLength: this.val_clone.statements.length}
+                                this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
+                                {pos: index + 2, listLength: this.val_clone.statements.length}
                             );
                         });
                     }
                     break;
             }
         },
-        moveDown: function (index) {
-            let statement = this.val_clone.statements[index];
-            this.val_clone.statements[index] = this.val_clone.statements[index + 1];
-            this.val_clone.statements[index + 1] = statement;
-            this.$forceUpdate();
+        moveDown(index) {
+            this.val_clone.statements.splice(
+                index,
+                2,
+                this.val_clone.statements[index + 1],
+                this.val_clone.statements[index]
+            );
+        },
+        moveUp(index) {
+            this.val_clone.statements.splice(
+                index - 1,
+                2,
+                this.val_clone.statements[index],
+                this.val_clone.statements[index - 1]
+            );
         },
-        moveUp: function (index) {
-            let statement = this.val_clone.statements[index];
-            this.val_clone.statements[index] = this.val_clone.statements[index - 1];
-            this.val_clone.statements[index - 1] = statement;
-            this.$forceUpdate();
-        }
     },
-    mounted: function () {
-        this.val_clone = this.value;
-        if (!this.value.statements) {
-            this.$emit('input', default_value);
-        }
+    created() {
+        this.val_clone = Object.assign({}, default_value(), this.value ?? {});
+    },
+    mounted() {
         this.$refs.autofocus.focus();
     },
     watch: {
-        value (new_val) {
-            this.val_clone = new_val;
+        val_clone: {
+            handler(current) {
+                this.$emit('input', current);
+            },
+            deep: true
         }
     }
 }
-- 
GitLab