diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php index 1be8e8051eb8ceec79d6cc967c7bfbc9bf2a1ad7..20215c96032ce0927c022dd9282384031b6bd35f 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 d69068de89f9f82e90b595c460c955cdae22319b..7a2504428794a283f110a5b1129f23cabec39f0c 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 95263784c91362cfc0e09bc3b23c7542116359de..16ee461684af3cd109769c4cdcd05729aebae66c 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 66c187eb34e9ca6c3b70008d6b31808ead401212..7bb36f62628019ab4b495ff9cb65378ba07dedda 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 adccc9df8b11322d21b5f3e69407ab728e4c5d41..8ba2560f4112fffb5bef083047c57cdf8b6a700f 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 } } }