Skip to content
Snippets Groups Projects
Commit 2a0d4673 authored by Jan-Hendrik Willms's avatar Jan-Hendrik Willms Committed by Jan-Hendrik Willms
Browse files

unify questionnaire editing vue components, re #3914, fixes #3915

Closes #3915 and #3914

Merge request studip/studip!2833
parent 2843d101
No related branches found
No related tags found
No related merge requests found
Showing
with 295 additions and 754 deletions
...@@ -25,7 +25,7 @@ $responseData = isset($response->answerdata['answers']) ? $response->answerdata[ ...@@ -25,7 +25,7 @@ $responseData = isset($response->answerdata['answers']) ? $response->answerdata[
<tr> <tr>
<th><?= _('Aussage') ?></th> <th><?= _('Aussage') ?></th>
<? foreach ($answers as $answer) : ?> <? foreach ($answers as $answer) : ?>
<th><?= htmlReady($answer) ?></th> <th class="option-cell"><?= htmlReady($answer) ?></th>
<? endforeach ?> <? endforeach ?>
</tr> </tr>
</thead> </thead>
...@@ -35,7 +35,7 @@ $responseData = isset($response->answerdata['answers']) ? $response->answerdata[ ...@@ -35,7 +35,7 @@ $responseData = isset($response->answerdata['answers']) ? $response->answerdata[
<? $html_id = md5(uniqid($index)) ?> <? $html_id = md5(uniqid($index)) ?>
<td id="<?= $html_id ?>"><?= htmlReady($statements[$index]) ?></td> <td id="<?= $html_id ?>"><?= htmlReady($statements[$index]) ?></td>
<? foreach ($answers as $answer_index => $answer) : ?> <? foreach ($answers as $answer_index => $answer) : ?>
<td> <td class="option-cell">
<input type="radio" <input type="radio"
title="<?= htmlReady($answer) ?>" title="<?= htmlReady($answer) ?>"
aria-labelledby="<?= $html_id ?>" aria-labelledby="<?= $html_id ?>"
......
...@@ -27,7 +27,7 @@ $options = $vote->questiondata['options']; ...@@ -27,7 +27,7 @@ $options = $vote->questiondata['options'];
<tr> <tr>
<th><?= _('Aussage') ?></th> <th><?= _('Aussage') ?></th>
<? foreach ($options as $option) : ?> <? foreach ($options as $option) : ?>
<th><?= htmlReady($option) ?></th> <th class="option-cell"><?= htmlReady($option) ?></th>
<? endforeach ?> <? endforeach ?>
</tr> </tr>
</thead> </thead>
......
...@@ -25,7 +25,7 @@ $responseData = $response['answerdata'] && $response['answerdata']['answers'] ? ...@@ -25,7 +25,7 @@ $responseData = $response['answerdata'] && $response['answerdata']['answers'] ?
<tr> <tr>
<th><?= _('Aussage') ?></th> <th><?= _('Aussage') ?></th>
<? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?> <? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?>
<th><?= htmlReady($i) ?></th> <th class="option-cell"><?= htmlReady($i) ?></th>
<? endfor ?> <? endfor ?>
</tr> </tr>
</thead> </thead>
...@@ -35,7 +35,7 @@ $responseData = $response['answerdata'] && $response['answerdata']['answers'] ? ...@@ -35,7 +35,7 @@ $responseData = $response['answerdata'] && $response['answerdata']['answers'] ?
<? $html_id = md5(uniqid($index)) ?> <? $html_id = md5(uniqid($index)) ?>
<td id="<?= $html_id ?>"><?= htmlReady($statements[$index]) ?></td> <td id="<?= $html_id ?>"><?= htmlReady($statements[$index]) ?></td>
<? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?> <? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?>
<td> <td class="option-cell">
<input type="radio" <input type="radio"
title="<?= htmlReady($i) ?>" title="<?= htmlReady($i) ?>"
aria-labelledby="<?= $html_id ?>" aria-labelledby="<?= $html_id ?>"
......
...@@ -27,7 +27,7 @@ $options = range($vote->questiondata['minimum'], $vote->questiondata['maximum']) ...@@ -27,7 +27,7 @@ $options = range($vote->questiondata['minimum'], $vote->questiondata['maximum'])
<tr> <tr>
<th><?= _('Aussage') ?></th> <th><?= _('Aussage') ?></th>
<? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?> <? for ($i = $vote->questiondata['minimum'] ?? 1; $i <= $vote->questiondata['maximum']; $i++) : ?>
<th class="rangescale_center"><?= htmlReady($i) ?></th> <th class="option-cell"><?= htmlReady($i) ?></th>
<? endfor ?> <? endfor ?>
</tr> </tr>
</thead> </thead>
......
$width: 270px; $width: 270px;
.questionnaire_edit { .questionnaire_edit {
.editor { .editor {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
...@@ -14,14 +12,16 @@ $width: 270px; ...@@ -14,14 +12,16 @@ $width: 270px;
min-width: $width; min-width: $width;
width: $width; width: $width;
.questions_container { .questions_container {
padding: 0px; padding: 0;
.questions { .questions {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
} }
> .admin, > .add_question, .questions > * { > .admin,
> .add_question,
.questions > * {
width: calc(100% - 8px); width: calc(100% - 8px);
padding: 4px; padding: 4px;
border-bottom: 1px solid var(--content-color-40); border-bottom: 1px solid var(--content-color-40);
...@@ -42,8 +42,8 @@ $width: 270px; ...@@ -42,8 +42,8 @@ $width: 270px;
&::before { &::before {
content: ''; content: '';
position: absolute; position: absolute;
height: 0px; height: 0;
width: 0px; width: 0;
border-top: 25px transparent solid; border-top: 25px transparent solid;
border-bottom: 25px transparent solid; border-bottom: 25px transparent solid;
border-left: 7px var(--content-color-40) solid; border-left: 7px var(--content-color-40) solid;
...@@ -52,8 +52,8 @@ $width: 270px; ...@@ -52,8 +52,8 @@ $width: 270px;
&::after { &::after {
content: ''; content: '';
position: absolute; position: absolute;
height: 0px; height: 0;
width: 0px; width: 0;
border-top: 25px transparent solid; border-top: 25px transparent solid;
border-bottom: 25px transparent solid; border-bottom: 25px transparent solid;
border-left: 7px var(--yellow-40) solid; border-left: 7px var(--yellow-40) solid;
...@@ -93,42 +93,11 @@ $width: 270px; ...@@ -93,42 +93,11 @@ $width: 270px;
border: 1px solid var(--content-color-40); border: 1px solid var(--content-color-40);
border-left: none; border-left: none;
flex-grow: 1; flex-grow: 1;
padding: 10px; padding: 10px 10px 10px 15px;
padding-left: 15px;
min-height: 150px; min-height: 150px;
min-width: 0; min-width: 0;
} }
.vote_edit {
.options {
> li {
display: flex;
align-items: center;
> * {
margin-right: 10px;
}
}
}
}
.rangescale_edit table.default > thead > tr > th.number {
padding-left: 12px;
}
.dragcolumn {
max-width: 1px;
padding-bottom: 0px;
> .dragarea {
display: inline-block;
height: 27px;
}
}
.input-array {
margin-left: 4px;
}
.likert_edit .input-array {
margin-left: 7px;
}
.inline_editing { .inline_editing {
width: 100%; width: 100%;
display: flex; display: flex;
...@@ -150,116 +119,27 @@ $width: 270px; ...@@ -150,116 +119,27 @@ $width: 270px;
justify-items: center; justify-items: center;
} }
} }
.drag-handle {
display: inline-block;
height: 24px;
}
}
/* ab hier der alte kram */
section { .dragcolumn {
border: thin solid var(--black); max-width: 1px;
margin: 3px; padding-bottom: 0;
} > .dragarea {
.options {
padding: 0;
list-style-type: none;
> li {
margin-top: 5px;
margin-bottom: 5px;
> .move {
cursor: move;
display: inline-block;
vertical-align: middle;
}
> input {
display: inline-block;
vertical-align: middle;
}
> input[type=text] {
width: calc(100% - 70px);
}
.delete {
display: inline-block; display: inline-block;
vertical-align: middle; height: 27px;
cursor: pointer;
}
.add {
display: none;
vertical-align: middle;
cursor: pointer;
}
} }
> li:last-child .delete {
display: none;
} }
> li:last-child .add { .drag-handle {
display: inline-block; display: inline-block;
height: 24px;
} }
> li:only-child .move { .option-cell {
display: none;
}
}
.all_questions {
.question:first-child .move_up {
display: none;
}
.question:last-child .move_down {
display: none;
}
}
.add_questions {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: stretch;
border: thin dashed var(--content-color-40);
> a {
background-color: transparent;
margin: 10px;
border: thin solid var(--content-color-20);
padding: 5px;
width: 100px;
min-width: 100px;
max-width: 100px;
height: 100px;
min-height: 100px;
max-height: 100px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
text-align: center; text-align: center;
> img {
margin-left: auto;
margin-right: auto;
} }
} }
} }
.questionnaire_metadata {
margin-top: 10px;
}
}
.questionnaire_results { .questionnaire_results {
> article { > article {
padding: 7px; padding: 7px;
...@@ -308,7 +188,8 @@ $width: 270px; ...@@ -308,7 +188,8 @@ $width: 270px;
} }
.questionnaire_answer, .questionnaire_results { .questionnaire_answer,
.questionnaire_results {
.description_container { .description_container {
display: flex; display: flex;
> .icon_container { > .icon_container {
...@@ -335,7 +216,7 @@ $width: 270px; ...@@ -335,7 +216,7 @@ $width: 270px;
border: none; border: none;
> :first-child { > :first-child {
margin-top: 0px; margin-top: 0;
} }
.invalidation_notice { .invalidation_notice {
...@@ -351,9 +232,6 @@ $width: 270px; ...@@ -351,9 +232,6 @@ $width: 270px;
font-size: 0.7em; font-size: 0.7em;
padding-left: 5px; padding-left: 5px;
} }
.rangescale_center {
text-align: center;
}
.centerline { .centerline {
border-top: 1px solid var(--base-color); border-top: 1px solid var(--base-color);
position: relative; position: relative;
...@@ -389,6 +267,14 @@ $width: 270px; ...@@ -389,6 +267,14 @@ $width: 270px;
} }
} }
.questionnaire_edit,
.questionnaire_answer,
.questionnaire_results {
.option-cell {
text-align: center;
}
}
.courseselector, .courseselector,
.instituteselector, .instituteselector,
.statusgroupselector { .statusgroupselector {
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<div> <div>
<div class="formpart" tabindex="0" ref="autofocus"> <div class="formpart" tabindex="0" ref="autofocus">
{{ $gettext('Frage') }} {{ $gettext('Frage') }}
<studip-wysiwyg v-model="val_clone.description" :key="question_id"></studip-wysiwyg> <StudipWysiwyg v-model="val_clone.description" />
</div> </div>
<label> <label>
...@@ -13,40 +13,19 @@ ...@@ -13,40 +13,19 @@
</template> </template>
<script> <script>
import StudipWysiwyg from "../StudipWysiwyg.vue"; import { QuestionnaireComponent } from '../../mixins/QuestionnaireComponent';
export default { export default {
name: 'freetext-edit', name: 'freetext-edit',
components: { mixins: [ QuestionnaireComponent ],
StudipWysiwyg created() {
this.setDefaultValues({
description: '',
mandatory: '0',
});
}, },
props: { mounted() {
value: {
type: Object,
required: false,
default: function () {
return {};
}
},
question_id: {
type: String,
required: false
}
},
data: function () {
return {
val_clone: ''
};
},
mounted: function () {
this.val_clone = this.value;
this.$refs.autofocus.focus(); this.$refs.autofocus.focus();
},
watch: {
value (new_val) {
this.val_clone = new_val;
} }
} }
}
</script> </script>
<template> <template>
<div class="input-array"> <div class="input-array">
<span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span> <span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span>
<draggable v-model="options" handle=".dragarea" tag="ol" class="clean options">
<li v-for="(option, index) in options" :key="index"> <table class="default nohover">
<colgroup>
<col style="width: 16px">
<col>
<col v-for="i in additionalColspan" :key="`colspan-${i}`">
<col style="width: 24px">
</colgroup>
<thead>
<tr>
<th class="dragcolumn"></th>
<th>{{ labelPlural }}</th>
<slot name="header-cells" />
<th class="actions"></th>
</tr>
</thead>
<Draggable v-model="options" handle=".dragarea" tag="tbody" class="statements">
<tr v-for="(option, index) in options" :key="index">
<td class="dragcolumn">
<a class="dragarea" <a class="dragarea"
v-if="options.length > 1"
tabindex="0" tabindex="0"
:ref="'draghandle_' + index" :title="$gettextInterpolate($gettext(`Sortierelement für %{label} %{option}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.`), {option, label})"
:title="$gettextInterpolate('Sortierelement für Option %{option}. Drücken Sie die Tasten Pfeil-nach-oben oder Pfeil-nach-unten, um dieses Element in der Liste zu verschieben.', {option: option})" @keydown="keyHandler($event, index)"
@keydown="keyHandler($event, index)"> ref="draghandle">
<span class="drag-handle"></span> <span class="drag-handle"></span>
</a> </a>
</td>
<td>
<input type="text" <input type="text"
:placeholder="$gettext('Option')" ref="inputs"
:ref="'option_' + index" :placeholder="label"
@paste="(ev) => onPaste(ev, index)" @paste="(ev) => onPaste(ev, index)"
v-model="options[index]"> v-model="options[index]">
</td>
<slot name="body-cells" />
<td class="actions">
<StudipIcon name="delete"
shape="trash"
:size="20"
@click.prevent="deleteOption(index)"
:title="$gettextInterpolate($gettext('%{label} löschen'), {label})"
/>
</td>
</tr>
</Draggable>
<tfoot>
<tr>
<td :colspan="3 + additionalColspan">
<button class="as-link" <button class="as-link"
:title="$gettext('Option löschen')" :title="$gettextInterpolate($gettext('%{label} hinzufügen'), {label})"
@click.prevent="askForDeletingOption(index)"> @click.prevent="addOption()">
<studip-icon shape="trash" :role="options.length > 1 ? 'clickable' : 'inactive'" :size="20" alt=""></studip-icon> <StudipIcon shape="add" :size="20" alt="" />
</button>
</li>
</draggable>
<button class="as-link"
:title="$gettext('Option hinzufügen')"
@click.prevent="addOption">
<studip-icon shape="add" :size="20" alt=""></studip-icon>
</button> </button>
</td>
<studip-dialog </tr>
v-if="askForDeleting" </tfoot>
:title="$gettext('Bitte bestätigen Sie die Aktion.')" </table>
:question="$gettext('Wirklich löschen?')"
:confirmText="$gettext('Ja')"
:closeText="$gettext('Nein')"
closeClass="cancel"
height="180"
@confirm="deleteOption"
@close="askForDeleting = false"
>
</studip-dialog>
</div> </div>
</template> </template>
<script> <script>
import StudipIcon from "../StudipIcon.vue"; import Draggable from 'vuedraggable';
import StudipDialog from "../StudipDialog.vue"; import { $gettext } from '../../../assets/javascripts/lib/gettext';
import draggable from 'vuedraggable';
export default { export default {
name: 'input-array', name: 'input-array',
components: { components: { Draggable },
StudipIcon,
StudipDialog,
draggable
},
props: { props: {
value: { additionalColspan: {
type: Array, type: Number,
required: false default: 0,
} },
label: {
type: String,
default: $gettext('Option'),
},
labelPlural: {
type: String,
default: $gettext('Optionen'),
}, },
data: function () { value: Array,
},
data() {
return { return {
options: [], options: [],
askForDeleting: false, assistiveLive: '',
indexOfDeletingOption: 0,
unique_id: null,
assistiveLive: ''
}; };
}, },
methods: { methods: {
addOption: function (val, position) { addOption(val = '', position = this.options.length) {
let data = this.value; this.$set(this.options, position, val.trim());
if (val.target) {
val = ''; this.$nextTick(() => {
} this.$refs.inputs[position].focus();
if (typeof position === "undefined") {
data.push(val || '');
position = this.value.length - 1
} else {
data.splice(position, 0, val || '');
}
this.$emit('input', data);
let v = this;
this.$nextTick(function () {
v.$refs['option_' + position][0].focus();
}); });
}, },
askForDeletingOption: function (index) { deleteOption(index) {
if (this.options.length <= 1) { const question = this.options[index] ? this.$gettext('Wirklich löschen?') : true;
return; STUDIP.Dialog.confirm(question).done(() => {
} this.$delete(this.options, index);
});
this.indexOfDeletingOption = index;
if (this.value[index]) {
this.askForDeleting = true;
} else {
this.deleteOption();
}
}, },
deleteOption: function () { onPaste(ev, position) {
this.$delete(this.value, this.indexOfDeletingOption); ev.clipboardData
this.askForDeleting = false; .getData('text')
}, .split("\n")
onPaste: function (ev, position) { .filter(str => str.trim().length > 0)
let data = ev.clipboardData.getData("text").split("\n"); .forEach((value, index) => this.addOption(value, position + index));
for (let i = 0; i < data.length; i++) { ev.preventDefault();
if (data[i].trim()) {
this.addOption(data[i], position + i);
}
}
}, },
keyHandler(e, index) { keyHandler(e, index) {
switch (e.keyCode) { if (e.keyCode !== 38 && e.keyCode !== 40) {
case 38: // up return;
e.preventDefault();
if (index > 0) {
this.moveUp(index);
this.$nextTick(function () {
this.$refs['draghandle_' + (index - 1)][0].focus();
this.assistiveLive = this.$gettextInterpolate(
'Aktuelle Position in der Liste: %{pos} von %{listLength}.'
, {pos: index, listLength: this.options.length}
);
});
} }
break;
case 40: // down
e.preventDefault(); e.preventDefault();
if (index < this.options.length - 1) {
this.moveDown(index); const moveUp = e.keyCode === 38;
this.$nextTick(function () {
this.$refs['draghandle_' + (index + 1)][0].focus(); this.moveElement(index, moveUp ? -1 : 1).then((newIndex) => {
this.assistiveLive = this.$gettextInterpolate( this.assistiveLive = this.$gettextInterpolate(
'Aktuelle Position in der Liste: %{pos} von %{listLength}.' this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
, {pos: index + 2, listLength: this.options.length} {pos: newIndex + 1, listLength: this.options.length}
); );
this.$nextTick(() => {
this.$refs['draghandle'][newIndex].focus();
}); });
} })
break;
}
}, },
moveDown: function (index) { moveElement(index, direction) {
if (index == this.options.length - 1) { if (this.options[index + direction] === undefined) {
return; return Promise.resolve(index);
}
let option = this.options[index];
this.options[index] = this.options[index + 1];
this.options[index + 1] = option;
this.$forceUpdate();
},
moveUp: function (index) {
if (index === 0) {
return;
} }
let option = this.options[index];
this.options[index] = this.options[index - 1]; const indices = [index, index + direction].sort();
this.options[index - 1] = option;
this.$forceUpdate(); this.options.splice(
Math.min(...indices),
2,
...indices.reverse().map(idx => this.options[idx])
);
return Promise.resolve(index + direction);
} }
}, },
mounted: function () {
this.options = this.value;
this.unique_id = 'array_input_' + Math.floor(Math.random() * 100000000);
},
watch: { watch: {
options (new_data, old_data) { options: {
if (typeof old_data === 'undefined' || typeof new_data === 'undefined') { handler(current) {
return; this.$emit('input', current);
} },
this.$emit('input', new_data); deep: true
},
value: {
handler(current) {
this.options = current;
}, },
value (new_val) { immediate: true
this.options = new_val;
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style scoped>
.input-array { .input-array input[type="text"] {
display: grid; max-width: unset;
grid-template-areas:
"sr sr"
"options button";
grid-template-columns: calc(100% - 24px) 24px;
grid-template-rows: auto;
> .sr-only {
grid-area: sr;
}
> .options {
grid-area: options;
}
> button.as-link {
align-self: end;
grid-area: button;
justify-self: left;
margin-bottom: 8px;
}
} }
</style> </style>
<template> <template>
<div class="likert_edit"> <div class="likert_edit">
<div class="formpart" tabindex="0" ref="autofocus"> <div class="formpart" tabindex="0" ref="autofocus">
{{ $gettext('Einleitungstext' )}} {{ $gettext('Einleitungstext' )}}
<studip-wysiwyg v-model="val_clone.description"></studip-wysiwyg> <StudipWysiwyg v-model="val_clone.description" />
</div> </div>
<span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span> <InputArray v-model="val_clone.statements"
:label="$gettext('Aussage')"
:label-plural="$gettext('Aussagen')"
:additional-colspan="val_clone.options.length"
>
<template #header-cells>
<th v-for="(option, index) in val_clone.options" class="option-cell" :key="index">
{{ option }}
</th>
</template>
<table class="default nohover"> <template #body-cells>
<thead> <td v-for="(option, index) in val_clone.options" class="option-cell" :key="index">
<tr>
<th class="dragcolumn"></th>
<th>{{ $gettext('Aussagen') }}</th>
<th v-for="(option, index) in val_clone.options" :key="index">{{ option }}</th>
<th class="actions"></th>
</tr>
</thead>
<draggable v-model="val_clone.statements" handle=".dragarea" tag="tbody" class="statements">
<tr v-for="(statement, index) in val_clone.statements" :key="index">
<td class="dragcolumn">
<a class="dragarea"
tabindex="0"
: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="drag-handle"></span>
</a>
</td>
<td>
<input type="text"
:ref="'statement_' + index"
:placeholder="$gettext('Aussage')"
@paste="(ev) => onPaste(ev, index)"
v-model="val_clone.statements[index]">
</td>
<td v-for="(option, index2) in val_clone.options" :key="index2">
<input type="radio" disabled :title="option"> <input type="radio" disabled :title="option">
</td> </td>
<td class="actions"> </template>
<studip-icon name="delete" </InputArray>
shape="trash"
:size="20"
@click.prevent="deleteStatement(index)"
:title="$gettext('Aussage löschen')"
></studip-icon>
</td>
</tr>
</draggable>
<tfoot>
<tr>
<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> <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" true-value="1" false-value="0">
...@@ -73,17 +34,18 @@ ...@@ -73,17 +34,18 @@
<div> <div>
<div>{{ $gettext('Antwortmöglichkeiten konfigurieren') }}</div> <div>{{ $gettext('Antwortmöglichkeiten konfigurieren') }}</div>
<input-array v-model="val_clone.options"></input-array> <InputArray v-model="val_clone.options" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import draggable from 'vuedraggable';
import InputArray from "./InputArray.vue";
import { $gettext } from '../../../assets/javascripts/lib/gettext'; import { $gettext } from '../../../assets/javascripts/lib/gettext';
import InputArray from "./InputArray.vue";
import { QuestionnaireComponent } from '../../mixins/QuestionnaireComponent';
const default_value = () => ({ // This is necesssar since $gettext does not seem to work in data() or created()
const default_values = () => ({
description: '', description: '',
statements: ['', '', '', ''], statements: ['', '', '', ''],
mandatory: 0, mandatory: 0,
...@@ -96,115 +58,16 @@ const default_value = () => ({ ...@@ -96,115 +58,16 @@ const default_value = () => ({
$gettext('trifft nicht zu'), $gettext('trifft nicht zu'),
], ],
}); });
export default { export default {
name: 'likert-edit', name: 'likert-edit',
components: { components: { InputArray },
draggable, mixins: [ QuestionnaireComponent ],
InputArray
},
props: {
value: {
type: Object,
required: false,
default() {
return {...default_value()};
}
},
question_id: {
type: String,
required: false
}
},
data() {
return {
val_clone: null,
assistiveLive: ''
};
},
methods: {
addStatement(val = '', position = null) {
if (position === null) {
this.val_clone.statements.push(val || '');
} else {
this.val_clone.statements.splice(position, 0, val || '');
}
this.$nextTick(() => {
this.$refs['statement_' + (this.val_clone.statements.length - 1)][0].focus();
});
},
deleteStatement(index) {
STUDIP.Dialog.confirm(this.$gettext('Wirklich löschen?')).done(() => {
this.$delete(this.val_clone.statements, index);
});
},
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);
}
}
},
keyHandler(e, index) {
switch (e.keyCode) {
case 38: // up
e.preventDefault();
if (index > 0) {
this.moveUp(index);
this.$nextTick(() => {
this.$refs['draghandle_' + (index - 1)][0].focus();
this.assistiveLive = this.$gettextInterpolate(
this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
{pos: index, listLength: this.val_clone.statements.length}
);
});
}
break;
case 40: // down
e.preventDefault();
if (index < this.val_clone.statements.length - 1) {
this.moveDown(index);
this.$nextTick(() => {
this.$refs['draghandle_' + (index + 1)][0].focus();
this.assistiveLive = this.$gettextInterpolate(
this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
{pos: index + 2, listLength: this.val_clone.statements.length}
);
});
}
break;
}
},
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]
)
}
},
created() { created() {
this.val_clone = Object.assign({}, default_value(), this.value ?? {}); this.setDefaultValues(default_values());
}, },
mounted() { mounted() {
this.$refs.autofocus.focus(); this.$refs.autofocus.focus();
},
watch: {
val_clone: {
handler(current) {
this.$emit('input', current);
},
deep: true
}
} }
} }
</script> </script>
...@@ -8,39 +8,26 @@ ...@@ -8,39 +8,26 @@
<div class="formpart"> <div class="formpart">
{{ $gettext('Hinweistext (optional)') }} {{ $gettext('Hinweistext (optional)') }}
<studip-wysiwyg v-model="val_clone.description" :key="question_id"></studip-wysiwyg> <StudipWysiwyg v-model="val_clone.description" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import StudipWysiwyg from "../StudipWysiwyg.vue"; import { QuestionnaireComponent } from '../../mixins/QuestionnaireComponent';
export default { export default {
name: 'questionnaire-info-edit', name: 'questionnaire-info-edit',
components: { mixins: [ QuestionnaireComponent ],
StudipWysiwyg created() {
}, this.setDefaultValues({
props: {
value: {
type: Object,
required: false,
default() {
return {
url: '', url: '',
description: '' description: ''
}; });
}
},
question_id: {
type: String,
required: false
}
}, },
data () { mounted() {
return { this.$refs.infoUrl.focus();
val_clone: this.value, this.checkValidity();
};
}, },
methods: { methods: {
checkValidity() { checkValidity() {
...@@ -53,15 +40,6 @@ export default { ...@@ -53,15 +40,6 @@ export default {
this.$refs.infoUrl.reportValidity(); this.$refs.infoUrl.reportValidity();
} }
} }
},
mounted() {
this.$refs.infoUrl.focus();
this.checkValidity();
},
watch: {
value (new_val) {
this.val_clone = new_val;
}
} }
} }
</script> </script>
...@@ -3,68 +3,25 @@ ...@@ -3,68 +3,25 @@
<div class="formpart" tabindex="0" ref="autofocus"> <div class="formpart" tabindex="0" ref="autofocus">
{{ $gettext('Einleitungstext') }} {{ $gettext('Einleitungstext') }}
<studip-wysiwyg v-model="val_clone.description"></studip-wysiwyg> <StudipWysiwyg v-model="val_clone.description" />
</div> </div>
<span aria-live="assertive" class="sr-only">{{ assistiveLive }}</span> <InputArray v-model="val_clone.statements"
:label="$gettext('Aussage')"
<table class="default nohover"> :label-plural="$gettext('Aussagen')"
<thead> :additional-colspan="options.length"
<tr> >
<th class="dragcolumn"></th> <template #header-cells>
<th>{{ $gettext('Aussagen') }}</th> <th v-for="(option, index) in options" class="option-cell" :key="index">
<th v-for="i in (val_clone.maximum - val_clone.minimum + 1)" :key="i" class="number">{{ (val_clone.minimum - 1 + i) }}</th> {{ option }}
<th v-if="val_clone.alternative_answer.trim().length > 0">{{ val_clone.alternative_answer }}</th> </th>
<th class="actions"></th> </template>
</tr> <template #body-cells>
</thead> <td v-for="(option, index) in options" class="option-cell" :key="index">
<draggable v-model="val_clone.statements" handle=".dragarea" tag="tbody" class="statements"> <input type="radio" disabled :title="option">
<tr v-for="(statement, index) in val_clone.statements" :key="index">
<td class="dragcolumn">
<a class="dragarea"
tabindex="0"
: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="drag-handle"></span>
</a>
</td>
<td>
<input type="text"
:ref="'statement_' + index"
:placeholder="$gettext('Aussage')"
@paste="(ev) => onPaste(ev, index)"
v-model="val_clone.statements[index]">
</td>
<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>
<td v-if="val_clone.alternative_answer.trim().length > 0">
<input type="radio" disabled :title="val_clone.alternative_answer">
</td>
<td class="actions">
<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 + (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> </td>
</tr> </template>
</tfoot> </InputArray>
</table>
<label> <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" true-value="1" false-value="0">
...@@ -82,135 +39,48 @@ ...@@ -82,135 +39,48 @@
<label> <label>
{{ $gettext('Minimum') }} {{ $gettext('Minimum') }}
<input type="number" v-model.number="val_clone.minimum" min="1"> <input type="number" v-model.number="val_clone.minimum" min="1" :max="val_clone.maximum">
</label> </label>
<label> <label>
{{ $gettext('Ausweichantwort (leer lassen für keine)') }} {{ $gettext('Ausweichantwort (leer lassen für keine)') }}
<input type="text" v-model="val_clone.alternative_answer"> <input type="text" v-model.trim="val_clone.alternative_answer">
</label> </label>
</div> </div>
</template> </template>
<script> <script>
import draggable from 'vuedraggable'; import InputArray from './InputArray.vue';
import { QuestionnaireComponent } from '../../mixins/QuestionnaireComponent';
const default_value = () => ({ export default {
name: 'rangescale-edit',
components: { InputArray },
mixins: [ QuestionnaireComponent ],
created() {
this.setDefaultValues({
alternative_answer: '',
description: '', description: '',
statements: ['', '', '', ''],
mandatory: 0, mandatory: 0,
randomize: 0,
minimum: 1,
maximum: 5, maximum: 5,
alternative_answer: '' minimum: 1,
}); randomize: 0,
export default { statements: ['', '', '', '']
name: 'likert-edit',
components: {
draggable,
},
props: {
value: {
type: Object,
required: false,
default() {
return default_value();
}
},
question_id: {
type: String,
required: false
}
},
data() {
return {
val_clone: null,
assistiveLive: ''
};
},
methods: {
addStatement(val = '', position = null) {
if (position === null) {
this.val_clone.statements.push(val || '');
} else {
this.val_clone.statements.splice(position, 0, val || '');
}
this.$nextTick(() => {
this.$refs['statement_' + (this.value.statements.length - 1)][0].focus();
});
},
deleteStatement(index) {
STUDIP.Dialog.confirm(this.$gettext('Wirklich löschen?')).done(() => {
this.$delete(this.value.statements, index);
}); });
}, },
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);
}
}
},
keyHandler(e, index) {
switch (e.keyCode) {
case 38: // up
e.preventDefault();
if (index > 0) {
this.moveUp(index);
this.$nextTick(() => {
this.$refs['draghandle_' + (index - 1)][0].focus();
this.assistiveLive = this.$gettextInterpolate(
this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
{pos: index, listLength: this.val_clone.statements.length}
);
});
}
break;
case 40: // down
e.preventDefault();
if (index < this.val_clone.statements.length - 1) {
this.moveDown(index);
this.$nextTick(() => {
this.$refs['draghandle_' + (index + 1)][0].focus();
this.assistiveLive = this.$gettextInterpolate(
this.$gettext('Aktuelle Position in der Liste: %{pos} von %{listLength}.'),
{pos: index + 2, listLength: this.val_clone.statements.length}
);
});
}
break;
}
},
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]
);
},
},
created() {
this.val_clone = Object.assign({}, default_value(), this.value ?? {});
},
mounted() { mounted() {
this.$refs.autofocus.focus(); this.$refs.autofocus.focus();
}, },
watch: { computed: {
val_clone: { options() {
handler(current) { let result = [];
this.$emit('input', current); for (let i = this.val_clone.minimum; i <= this.val_clone.maximum; i += 1) {
}, result.push(i);
deep: true }
if (this.val_clone.alternative_answer.length > 0) {
result.push(this.val_clone.alternative_answer);
}
return result;
} }
} }
} }
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
<div class="vote_edit"> <div class="vote_edit">
<div class="formpart" tabindex="0" ref="autofocus"> <div class="formpart" tabindex="0" ref="autofocus">
{{ $gettext('Frage') }} {{ $gettext('Frage') }}
<studip-wysiwyg v-model="val_clone.description" :key="question_id"></studip-wysiwyg> <StudipWysiwyg v-model="val_clone.description" />
</div> </div>
<input-array v-model="val_clone.options"></input-array> <InputArray v-model="val_clone.options" />
<label> <label>
<input type="checkbox" v-model.number="val_clone.multiplechoice" true-value="1" false-value="0"> <input type="checkbox" v-model.number="val_clone.multiplechoice" true-value="1" false-value="0">
...@@ -24,47 +24,24 @@ ...@@ -24,47 +24,24 @@
</template> </template>
<script> <script>
import StudipWysiwyg from "../StudipWysiwyg.vue";
import InputArray from "./InputArray.vue"; import InputArray from "./InputArray.vue";
import { QuestionnaireComponent } from '../../mixins/QuestionnaireComponent';
export default { export default {
name: 'vote-edit', name: 'vote-edit',
components: { components: { InputArray },
StudipWysiwyg, mixins: [QuestionnaireComponent],
InputArray created() {
}, this.setDefaultValues({
props: { description: '',
value: { mandatory: '0',
type: Object, multiplechoice: '1',
required: false,
default: function () {
return {};
}
},
question_id: {
type: String,
required: false
}
},
data: function () {
return {
val_clone: {}
};
},
mounted: function () {
this.val_clone = this.value;
if (!this.value.description) {
this.$emit('input', {
multiplechoice: 1,
options: ['', '', '', ''], options: ['', '', '', ''],
randomize: '0',
}); });
}
this.$refs.autofocus.focus();
}, },
watch: { mounted() {
value (new_val) { this.$refs.autofocus.focus();
this.val_clone = new_val;
}
} }
} }
</script> </script>
export const QuestionnaireComponent = {
props: {
value: Object
},
data () {
return {val_clone: this.value};
},
methods: {
setDefaultValues(value) {
this.val_clone = Object.assign(value, this.value);
}
},
watch: {
val_clone: {
handler(current) {
this.$emit('input', current);
},
deep: true
},
value (new_val) {
this.val_clone = new_val;
}
}
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment