aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan-Hendrik Willms <tleilax+studip@gmail.com>2024-10-30 12:25:38 +0000
committerDavid Siegfried <david.siegfried@uni-vechta.de>2024-10-30 12:25:38 +0000
commitfb0acb3e51d1d8e354a2eca2dd02ba69caebdd32 (patch)
tree45e69316fc2eeb1e4297126b4dc9610fd6f573f3
parent4012408cf37b8c9b834826e12488bfae60f1b50d (diff)
introduce vue directive v-autofocus, fixes #3986
Closes #3986 Merge request studip/studip!2836
-rw-r--r--app/views/questionnaire/edit.php155
-rw-r--r--resources/vue/base-directives.js3
-rw-r--r--resources/vue/components/questionnaires/FreetextEdit.vue5
-rw-r--r--resources/vue/components/questionnaires/LikertEdit.vue6
-rw-r--r--resources/vue/components/questionnaires/QuestionnaireEditor.vue17
-rw-r--r--resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue3
-rw-r--r--resources/vue/components/questionnaires/RangescaleEdit.vue5
-rw-r--r--resources/vue/components/questionnaires/VoteEdit.vue5
-rw-r--r--resources/vue/directives/autofocus.ts23
9 files changed, 188 insertions, 34 deletions
diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php
index e5ef007..19d3ef9 100644
--- a/app/views/questionnaire/edit.php
+++ b/app/views/questionnaire/edit.php
@@ -50,3 +50,158 @@ $questionnaire_data = [
'range-id' => Request::get('range_id'),
'range-type' => Request::get('range_type'),
]) ?>
+=======
+<form action="<?= URLHelper::getLink('dispatch.php/questionnaire/edit/' . (!$questionnaire->isNew() ? $questionnaire->getId() : '')) ?>"
+ method="post"
+ enctype="multipart/form-data"
+ class="questionnaire_edit default"
+ data-questiontypes="<?= htmlReady(json_encode($questiontypes)) ?>"
+ data-questionnaire_data="<?= htmlReady(json_encode($questionnaire_data)) ?>"
+ 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' : '' ?>
+ :data-secure="activateFormSecure">
+
+ <div class="editor">
+ <div class="rightside" aria-live="polite" tabindex="0" ref="rightside">
+ <div class="admin" v-if="activeTab === 'admin'">
+
+ <article aria-live="assertive" class="validation_notes studip">
+ <header>
+ <h1>
+ <?= Icon::create('info-circle', Icon::ROLE_INFO)->asImg(['class' => 'text-bottom validation_notes_icon']) ?>
+ <?= _('Hinweise zum Ausfüllen des Formulars') ?>
+ </h1>
+ </header>
+ <div class="required_note">
+ <div aria-hidden="true">
+ <?= _('Pflichtfelder sind mit Sternchen gekennzeichnet.') ?>
+ </div>
+ <div class="sr-only">
+ <?= _('Dieses Formular enthält Pflichtfelder.') ?>
+ </div>
+ </div>
+ <div v-if="validationNotice && !data.title">
+ <?= _('Folgende Angaben müssen korrigiert werden, um das Formular abschicken zu können:') ?>
+ <ul>
+ <li aria-describedby="questionnaire_title"><?= _('Titel des Fragebogens') ?></li>
+ </ul>
+ </div>
+ </article>
+
+ <div class="formpart">
+ <label class="studiprequired" for="questionnaire_title">
+ <span class="textlabel"><?= _('Titel des Fragebogens') ?></span>
+ <span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
+ </label>
+ <input type="text" id="questionnaire_title" v-model="data.title" v-autofocus>
+ </div>
+
+ <div class="hgroup">
+ <label>
+ <?= _('Startzeitpunkt') ?>
+ <datetimepicker v-model="data.startdate"></datetimepicker>
+ </label>
+ <label>
+ <?= _('Endzeitpunkt') ?>
+ <datetimepicker v-model="data.stopdate"></datetimepicker>
+ </label>
+ </div>
+ <label>
+ <input type="checkbox" v-model="data.copyable" true-value="1" false-value="0">
+ <?= _('Fragebogen zum Kopieren freigeben') ?>
+ </label>
+ <label>
+ <input type="checkbox" v-model="data.anonymous" true-value="1" false-value="0">
+ <?= _('Teilnehmende anonymisieren') ?>
+ </label>
+ <label>
+ <input type="checkbox" v-model="data.editanswers" true-value="1" false-value="0">
+ <?= _('Teilnehmende dürfen ihre Antworten revidieren') ?>
+ </label>
+ <label>
+ <?= _('Ergebnisse einsehbar') ?>
+ <select v-model="data.resultvisibility">
+ <option value="always"><?= _('Immer') ?></option>
+ <option value="afterending"><?= _('Nach Ende der Befragung') ?></option>
+ <option value="afterparticipation"><?= _('Nach der Teilnahme') ?></option>
+ <option value="never"><?= _('Niemals') ?></option>
+ </select>
+ </label>
+ </div>
+ <div class="add_question file_select_possibilities" v-else-if="activeTab === 'add_question'">
+ <div>
+ <button v-for="(questiontype, key) in questiontypes" :key="key"
+ :ref="key == Object.keys(questiontypes)[0] ? 'autofocus' : ''"
+ href=""
+ @click.prevent="addQuestion(questiontype.type)">
+ <studip-icon :shape="questiontype.icon" :size="40"></studip-icon>
+ {{questiontype.name}}
+ </button>
+ </div>
+ </div>
+ <div v-else>
+ <component :is="questiontypes[questions[indexForQuestion].questiontype].component[0]"
+ v-model="questions[indexForQuestion].questiondata"
+ :question_id="questions[indexForQuestion].id"
+ :key="questions[indexForQuestion].id">
+ </component>
+ </div>
+ </div>
+ <aside>
+ <a class="admin"
+ :class="{active: activeTab === 'admin'}"
+ href="#"
+ @click.prevent="switchTab('admin')">
+ <span class="icon"><studip-icon shape="evaluation" :size="30" alt=""></studip-icon></span>
+ <?= _('Einstellungen') ?>
+ </a>
+ <draggable v-if="questions.length > 0" v-model="questions" handle=".drag-handle" group="questions" class="questions_container questions">
+ <div v-for="question in questions"
+ :key="question.id"
+ @mouseenter="hoverTab = question.id"
+ @mouseleave="hoverTab = null"
+ :class="(activeTab === question.id || activeTab === 'meta_' + question.id ? 'active' : '') + (hoverTab === question.id ? ' hovered' : '')">
+ <a href="#"
+ @click.prevent="switchTab(question.id)">
+ <span class="drag-handle"></span>
+ <span class="icon type">
+ <studip-icon :shape="questiontypes[question.questiontype].icon" :size="30" alt=""></studip-icon>
+ </span>
+
+ <div v-if="editInternalName !== question.id">{{ question.internal_name || questiontypes[question.questiontype].name}}</div>
+ <div v-else class="inline_editing">
+ <input type="text" ref="editInternalName" v-model="tempInternalName" class="inlineediting_internal_name">
+ <button @click="saveInternalName(question.id)">
+ <studip-icon shape="accept" :size="20" title="<?= _('Internen Namen speichern') ?>"></studip-icon>
+ </button>
+ <button @click="editInternalName = null">
+ <studip-icon shape="decline" :size="20" title="<?= _('Internen Namen nicht speichern') ?>"></studip-icon>
+ </button>
+ </div>
+ </a>
+
+ <studip-action-menu :items="[{label: '<?= _('Umbenennen') ?>', icon: 'edit', emit: 'rename'}, {label: '<?= _('Frage kopieren') ?>', icon: 'copy', emit: 'copy'}, {label: '<?= _('Frage nach oben verschieben') ?>', icon: 'arr_1up', emit: 'moveup'}, {label: '<?= _('Frage nach unten verschieben') ?>', icon: 'arr_1down', emit: 'movedown'}, {label: '<?= _('Frage löschen') ?>', icon: 'trash', emit: 'delete'}]"
+ @copy="duplicateQuestion(question.id)"
+ @rename="renameInternalName(question.id)"
+ @moveup="moveQuestionUp(question.id)"
+ @movedown="moveQuestionDown(question.id)"
+ @delete="deleteQuestion(question.id)"></studip-action-menu>
+ </div>
+ </draggable>
+ <a :class="activeTab === 'add_question' ? 'add_question active' : 'add_question'"
+ href="#"
+ @click.prevent="switchTab('add_question')">
+ <span class="icon"><studip-icon shape="add" :size="30" alt=""></studip-icon></span>
+ <?= _('Element hinzufügen') ?>
+ </a>
+ </aside>
+ </div>
+
+
+ <footer data-dialog-button>
+ <?= Studip\LinkButton::create(_('Speichern'), 'questionnaire_store', ['onclick' => 'STUDIP.Questionnaire.Editor.submit(); return false;']) ?>
+ </footer>
+</form>
+>>>>>>> 166e475f04 (introduce vue directive v-autofocus, fixes #3986)
diff --git a/resources/vue/base-directives.js b/resources/vue/base-directives.js
index a2b8ae1..8f46b03 100644
--- a/resources/vue/base-directives.js
+++ b/resources/vue/base-directives.js
@@ -1,4 +1,7 @@
+import autofocus from './directives/autofocus';
+
const BaseDirectives = {
+ autofocus,
};
export default BaseDirectives;
diff --git a/resources/vue/components/questionnaires/FreetextEdit.vue b/resources/vue/components/questionnaires/FreetextEdit.vue
index 58848ed..60852ff 100644
--- a/resources/vue/components/questionnaires/FreetextEdit.vue
+++ b/resources/vue/components/questionnaires/FreetextEdit.vue
@@ -1,6 +1,6 @@
<template>
<div>
- <div class="formpart" tabindex="0" ref="autofocus">
+ <div class="formpart" tabindex="0">
{{ $gettext('Frage') }}
<StudipWysiwyg v-model="val_clone.description" />
</div>
@@ -23,9 +23,6 @@ export default {
description: '',
mandatory: '0',
});
- },
- mounted() {
- this.$refs.autofocus.focus();
}
}
</script>
diff --git a/resources/vue/components/questionnaires/LikertEdit.vue b/resources/vue/components/questionnaires/LikertEdit.vue
index 736be6b..311bf6d 100644
--- a/resources/vue/components/questionnaires/LikertEdit.vue
+++ b/resources/vue/components/questionnaires/LikertEdit.vue
@@ -1,6 +1,7 @@
<template>
<div class="likert_edit">
- <div class="formpart" tabindex="0" ref="autofocus">
+
+ <div class="formpart" tabindex="0">
{{ $gettext('Einleitungstext' )}}
<StudipWysiwyg v-model="val_clone.description" />
</div>
@@ -65,9 +66,6 @@ export default {
mixins: [ QuestionnaireComponent ],
created() {
this.setDefaultValues(default_values());
- },
- mounted() {
- this.$refs.autofocus.focus();
}
}
</script>
diff --git a/resources/vue/components/questionnaires/QuestionnaireEditor.vue b/resources/vue/components/questionnaires/QuestionnaireEditor.vue
index d87305a..089353b 100644
--- a/resources/vue/components/questionnaires/QuestionnaireEditor.vue
+++ b/resources/vue/components/questionnaires/QuestionnaireEditor.vue
@@ -39,7 +39,7 @@
<span class="textlabel">{{ $gettext('Titel des Fragebogens') }}</span>
<span title="Dies ist ein Pflichtfeld" aria-hidden="true" class="asterisk">*</span>
</label>
- <input type="text" id="questionnaire_title" v-model="data.title" ref="autofocus">
+ <input type="text" id="questionnaire_title" v-model="data.title" v-autofocus>
</div>
<div class="hgroup">
@@ -77,7 +77,6 @@
<div class="add_question file_select_possibilities" v-else-if="activeTab === 'add_question'">
<div>
<button v-for="(questiontype, key) in questionTypes" :key="key"
- :ref="key == Object.keys(questionTypes)[0] ? 'autofocus' : ''"
href=""
@click.prevent="addQuestion(questiontype.type)"
>
@@ -283,17 +282,6 @@ export default {
},
switchTab(tab_id) {
this.activeTab = tab_id;
- this.$nextTick(function () {
- if (this.$refs.autofocus !== undefined) {
- if (Array.isArray(this.$refs.autofocus)) {
- if (typeof this.$refs.autofocus[0] !== "undefined") {
- this.$refs.autofocus[0].focus();
- }
- } else {
- this.$refs.autofocus.focus();
- }
- }
- });
},
objectsEqual(obj1, obj2) {
return _.isEqual(obj1, obj2);
@@ -347,8 +335,5 @@ export default {
return this.getIndexForQuestion(this.activeTab);
},
},
- mounted() {
- this.$refs.autofocus.focus();
- },
}
</script>
diff --git a/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue b/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
index 83d5fa2..57452d4 100644
--- a/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
+++ b/resources/vue/components/questionnaires/QuestionnaireInfoEdit.vue
@@ -2,7 +2,7 @@
<div class="vote_edit">
<label>
{{ $gettext('Link eines Videos oder einer anderen Informationsseite (optional)') }}
- <input type="url" v-model="val_clone.url" ref="infoUrl"
+ <input type="url" v-model="val_clone.url" v-autofocus ref="infoUrl"
@input="checkValidity()">
</label>
@@ -26,7 +26,6 @@ export default {
});
},
mounted() {
- this.$refs.infoUrl.focus();
this.checkValidity();
},
methods: {
diff --git a/resources/vue/components/questionnaires/RangescaleEdit.vue b/resources/vue/components/questionnaires/RangescaleEdit.vue
index cd7ce3b..044c921 100644
--- a/resources/vue/components/questionnaires/RangescaleEdit.vue
+++ b/resources/vue/components/questionnaires/RangescaleEdit.vue
@@ -1,7 +1,7 @@
<template>
<div class="rangescale_edit">
- <div class="formpart" tabindex="0" ref="autofocus">
+ <div class="formpart" tabindex="0">
{{ $gettext('Einleitungstext') }}
<StudipWysiwyg v-model="val_clone.description" />
</div>
@@ -68,9 +68,6 @@ export default {
statements: ['', '', '', '']
});
},
- mounted() {
- this.$refs.autofocus.focus();
- },
computed: {
options() {
let result = [];
diff --git a/resources/vue/components/questionnaires/VoteEdit.vue b/resources/vue/components/questionnaires/VoteEdit.vue
index 1d6d9cf..56dd160 100644
--- a/resources/vue/components/questionnaires/VoteEdit.vue
+++ b/resources/vue/components/questionnaires/VoteEdit.vue
@@ -1,6 +1,6 @@
<template>
<div class="vote_edit">
- <div class="formpart" tabindex="0" ref="autofocus">
+ <div class="formpart" tabindex="0">
{{ $gettext('Frage') }}
<StudipWysiwyg v-model="val_clone.description" />
</div>
@@ -39,9 +39,6 @@ export default {
options: ['', '', '', ''],
randomize: '0',
});
- },
- mounted() {
- this.$refs.autofocus.focus();
}
}
</script>
diff --git a/resources/vue/directives/autofocus.ts b/resources/vue/directives/autofocus.ts
new file mode 100644
index 0000000..554dc5a
--- /dev/null
+++ b/resources/vue/directives/autofocus.ts
@@ -0,0 +1,23 @@
+// Shamelessly copied from https://github.com/byteboomers/vue-autofocus-directive
+
+function focusElement(el: HTMLElement, binding: any) : void {
+ if (binding.value && !binding.value) {
+ return;
+ }
+
+ el.focus()
+}
+
+export default {
+ bind(el: HTMLElement, binding: any, vnode: any) {
+ if (vnode.componentInstance?.focus instanceof Function) {
+ // When the component itself has a focus method
+ vnode.componentInstance.focus();
+ } else {
+ // When the component of the element gets activated
+ vnode.context.$on('hook:activated', () => focusElement(el, binding));
+ }
+
+ },
+ inserted: focusElement
+}