320 lines
8.4 KiB
Vue
320 lines
8.4 KiB
Vue
<template>
|
|
<div class="survey-page">
|
|
<h1 class="survey-page__title">{{ title }}</h1>
|
|
<div id="survey" />
|
|
|
|
<solution
|
|
:value="solution"
|
|
v-if="showSolution"
|
|
/>
|
|
<div v-if="surveyComplete">
|
|
<a
|
|
class="button button--primary"
|
|
@click="reopen"
|
|
>Übung bearbeiten</a
|
|
>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import '@/styles/survey.modern.css';
|
|
import '@/styles/survey.reset.css';
|
|
import '@/styles/_survey.scss';
|
|
import { css } from '@/survey.config';
|
|
import gql from 'graphql-tag';
|
|
import { Model, StylesManager } from 'survey-knockout';
|
|
// we are switching to the knockout version because the Vue version only works with Vue 2 (as of July 2022)
|
|
|
|
import SURVEY_QUERY from '@/graphql/gql/queries/surveyQuery.gql';
|
|
import UPDATE_ANSWER from '@/graphql/gql/mutations/updateAnswer.gql';
|
|
|
|
import { extractSurveySolutions } from '@/helpers/survey-solutions';
|
|
import { isTeacher } from '@/helpers/is-teacher';
|
|
|
|
import { meQuery } from '@/graphql/queries';
|
|
|
|
import { defineAsyncComponent } from 'vue';
|
|
import { matomoTrackEvent } from '@/helpers/matomo-client';
|
|
const Solution = defineAsyncComponent(() => import('@/components/content-blocks/Solution.vue'));
|
|
|
|
StylesManager.applyTheme('modern');
|
|
|
|
const MODULE_QUERY = gql`
|
|
query ModuleSolutions($slug: String) {
|
|
module(slug: $slug) {
|
|
solutionsEnabled
|
|
slug
|
|
}
|
|
}
|
|
`;
|
|
|
|
export default {
|
|
props: ['id'],
|
|
components: {
|
|
Solution,
|
|
},
|
|
|
|
data() {
|
|
return {
|
|
survey: this.initSurvey(),
|
|
currentPage: null,
|
|
surveyData: null,
|
|
title: '',
|
|
module: {},
|
|
completed: false,
|
|
me: {
|
|
permissions: [],
|
|
},
|
|
saveDisabled: false,
|
|
};
|
|
},
|
|
|
|
computed: {
|
|
surveyComplete() {
|
|
return this.survey && this.survey.isCompleted;
|
|
},
|
|
showSolution() {
|
|
return (this.module.solutionsEnabled || this.isTeacher) && !this.survey.isCompleted;
|
|
},
|
|
solution() {
|
|
// todo: should this be done inside of Solution.vue?
|
|
return {
|
|
text: this.answers.reduce((previous, answer) => {
|
|
if (!answer.answer) {
|
|
return previous;
|
|
}
|
|
const type = answer.type;
|
|
if (type === 'matrix' || type === 'matrixdropdown' || type === 'checkbox') {
|
|
// wrap all the answers inside li tags and convert to a single string
|
|
const answerText = answer.answer.map((a) => `<li class="solution-text__list-item">${a}</li>`).join('');
|
|
return `
|
|
${previous}
|
|
<h2 class="solution-text__heading">${answer.title}</h2>
|
|
<ul class="solution-text__answer solution-text__list">${answerText}</ul>
|
|
`;
|
|
} else {
|
|
return `
|
|
${previous}
|
|
<h2 class="solution-text__heading">${answer.title}</h2>
|
|
<p class="solution-text__answer">${answer.answer}</p>
|
|
`;
|
|
}
|
|
}, ''),
|
|
};
|
|
},
|
|
answers() {
|
|
return this.currentPage && this.currentPage.elements
|
|
? this.currentPage.elements.reduce(extractSurveySolutions, [])
|
|
: [];
|
|
},
|
|
isTeacher() {
|
|
return isTeacher(this);
|
|
},
|
|
},
|
|
|
|
mounted() {
|
|
if (this.surveyData) {
|
|
this.loadSurveyFromServer(this.surveyData);
|
|
}
|
|
},
|
|
|
|
unmounted() {},
|
|
methods: {
|
|
initSurvey(data, answers) {
|
|
let survey = new Model(data);
|
|
const flatAnswers = {};
|
|
for (let k in answers) {
|
|
flatAnswers[k] = answers[k].answer;
|
|
}
|
|
|
|
if (Object.keys(flatAnswers).length > 0) {
|
|
// answers are not empty
|
|
survey.data = flatAnswers;
|
|
}
|
|
|
|
this.currentPage = survey.currentPage;
|
|
|
|
const updatePage = (sender, { oldCurrentPage, newCurrentPage }) => {
|
|
console.log(oldCurrentPage, newCurrentPage);
|
|
this.currentPage = newCurrentPage;
|
|
};
|
|
|
|
const saveSurvey = (sender, { exit }) => {
|
|
if (this.saveDisabled) {
|
|
return;
|
|
}
|
|
|
|
this.completed = true;
|
|
|
|
const data = {};
|
|
|
|
for (let k in survey.data) {
|
|
// if (survey.data.hasOwnProperty(k)) {
|
|
if (Object.prototype.hasOwnProperty.call(survey.data, k)) {
|
|
let question = sender.getQuestionByName(k);
|
|
data[k] = {
|
|
answer: survey.data[k],
|
|
correct: question && question.correctAnswer ? question.correctAnswer : '',
|
|
};
|
|
}
|
|
}
|
|
if (Object.keys(data).length === 0) {
|
|
// data is empty
|
|
return;
|
|
}
|
|
const answer = {
|
|
surveyId: this.id,
|
|
data: JSON.stringify(data),
|
|
};
|
|
|
|
this.$apollo.mutate({
|
|
mutation: UPDATE_ANSWER,
|
|
variables: {
|
|
input: {
|
|
answer,
|
|
},
|
|
},
|
|
update: (
|
|
store,
|
|
{
|
|
data: {
|
|
updateAnswer: { answer },
|
|
},
|
|
}
|
|
) => {
|
|
const query = SURVEY_QUERY;
|
|
const variables = { id: this.id };
|
|
const { survey } = store.readQuery({ query, variables });
|
|
if (survey) {
|
|
const newData = {
|
|
// data is already in use in parent scope
|
|
survey: {
|
|
...survey,
|
|
answer,
|
|
},
|
|
};
|
|
store.writeQuery({ query, variables, data: newData });
|
|
}
|
|
},
|
|
});
|
|
if (exit) {
|
|
matomoTrackEvent('Übung', 'Übung erfolgreich abgeschlossen', this.surveyData.title);
|
|
this.$router.go(-1);
|
|
} else {
|
|
matomoTrackEvent('Übung', 'Übungsschritt abgeschlossen', this.surveyData.title);
|
|
}
|
|
};
|
|
|
|
survey.onComplete.add((sender, options) => {
|
|
saveSurvey(sender, {
|
|
...options,
|
|
exit: true,
|
|
});
|
|
});
|
|
survey.onCurrentPageChanged.add(saveSurvey);
|
|
survey.onCurrentPageChanged.add(updatePage);
|
|
|
|
survey.css = css;
|
|
survey.locale = 'de';
|
|
survey.showProgressBar = 'bottom';
|
|
survey.pageNextText = 'Speichern & Weiter';
|
|
|
|
survey.render('survey');
|
|
return survey;
|
|
},
|
|
reopen() {
|
|
this.saveDisabled = true; // disable saving, because resetting triggers a page change which we don't want to save
|
|
this.completed = false;
|
|
let data = this.survey.data; // save the data
|
|
this.survey.clear();
|
|
this.survey.data = data; // reapply it
|
|
this.saveDisabled = false;
|
|
},
|
|
loadSurveyFromServer(survey) {
|
|
let json = JSON.parse(survey.data);
|
|
json.showTitle = false;
|
|
json.showProgressBar = 'bottom';
|
|
let answer = {};
|
|
if (survey.answer && survey.answer.data) {
|
|
answer = JSON.parse(survey.answer.data);
|
|
}
|
|
// we now need to traverse all the `pages` and all the `elements` inside them, which themselves have `elements`,
|
|
// and we need to delete their `isRequired` keys
|
|
json = {
|
|
...json,
|
|
pages: json.pages.map((page) => {
|
|
return {
|
|
...page,
|
|
elements: page.elements?.map((element) => {
|
|
return {
|
|
...element,
|
|
elements: element.elements?.map((subElement) => {
|
|
return {
|
|
...subElement,
|
|
isRequired: false,
|
|
};
|
|
}),
|
|
};
|
|
}),
|
|
};
|
|
}),
|
|
};
|
|
|
|
if (!this.completed) {
|
|
this.survey = this.initSurvey(json, answer);
|
|
}
|
|
this.title = json.title;
|
|
},
|
|
},
|
|
|
|
apollo: {
|
|
survey: {
|
|
query: SURVEY_QUERY,
|
|
variables() {
|
|
return {
|
|
id: this.id,
|
|
};
|
|
},
|
|
manual: true,
|
|
result({ data, loading }) {
|
|
if (!loading) {
|
|
this.surveyData = data.survey;
|
|
this.loadSurveyFromServer(data.survey);
|
|
const module = data.survey.module;
|
|
matomoTrackEvent('Übung', 'Übung angezeigt', this.surveyData.title);
|
|
|
|
this.$apollo.addSmartQuery('module', {
|
|
query: MODULE_QUERY,
|
|
variables: {
|
|
slug: module.slug,
|
|
},
|
|
});
|
|
}
|
|
},
|
|
},
|
|
me: meQuery,
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
@import 'styles/helpers';
|
|
|
|
.survey-page {
|
|
max-width: 800px;
|
|
display: grid;
|
|
grid-template-rows: auto 1fr;
|
|
grid-auto-rows: auto;
|
|
grid-row-gap: $large-spacing;
|
|
justify-self: center;
|
|
padding: 100px 0;
|
|
width: 100%;
|
|
|
|
&__title {
|
|
@include meta-title;
|
|
margin: 0;
|
|
}
|
|
}
|
|
</style>
|