diff --git a/app/views/questionnaire/edit.php b/app/views/questionnaire/edit.php index e5ef007b62e06576ba403ac5e202a81b67ca188c..19d3ef93deeea923ef8a6698bcc57f161267cc62 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 a2b8ae126e2b157c1daa6b1678ffdc5526ce0d65..8f46b03f7f03645161abe14ccdba497f4532c3f0 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 58848edd1d51e58c718734c1b24c41b7d921c06e..60852ffa858a249fa45821e5e87c683fc55aa273 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 736be6b93fd586911e4a87661c931e0b9db75513..311bf6d4109b7b80392a9d67f67231df5cfcaa58 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 d87305a2b7a019193f5d430d3ade88121554bcb6..089353bbfcae65fb0a172a60a0dc6da8c6bc58e8 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 83d5fa2d5e877b95c9b8d38523086d1c7cd518c8..57452d49dd531e73517b9f4477ea7d02136b9154 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 cd7ce3b215745987e0e4b1e7123c07a5520996a2..044c921cd349681ef431916573ea93e76fb83efe 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 1d6d9cf7c066977535535e4c3b87cfcc52e714ec..56dd160f4446d683aaa0b3a02bab7ab178fb4f77 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 0000000000000000000000000000000000000000..554dc5a7889614d9567cb9ea29370a52aef11e0f --- /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 +}