feat(frontend): Vue can now interact with the chosen answers for questions.
This commit is contained in:
parent
6d452c7f72
commit
63d1ed8bd2
6 changed files with 99 additions and 1 deletions
|
@ -38,7 +38,7 @@ class GiftProcessor extends StringProcessor {
|
||||||
let html = "<div class='learning-object-gift'>\n";
|
let html = "<div class='learning-object-gift'>\n";
|
||||||
let i = 1;
|
let i = 1;
|
||||||
for (const question of quizQuestions) {
|
for (const question of quizQuestions) {
|
||||||
html += ` <div class='gift-question' id='gift-q${i}'>\n`;
|
html += ` <div class='gift-question gift-question-type-${question.type}' id='gift-q${i}'>\n`;
|
||||||
html += ' ' + this.renderQuestion(question, i).replaceAll(/\n(.+)/g, '\n $1'); // Replace for indentation.
|
html += ' ' + this.renderQuestion(question, i).replaceAll(/\n(.+)/g, '\n $1'); // Replace for indentation.
|
||||||
html += ` </div>\n`;
|
html += ` </div>\n`;
|
||||||
i++;
|
i++;
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
import type { UseQueryReturnType } from "@tanstack/vue-query";
|
import type { UseQueryReturnType } from "@tanstack/vue-query";
|
||||||
import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts";
|
import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts";
|
||||||
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
import UsingQueryResult from "@/components/UsingQueryResult.vue";
|
||||||
|
import {nextTick, onMounted, reactive, watch} from "vue";
|
||||||
|
import {getGiftAdapterForType} from "@/views/learning-paths/gift-adapters/gift-adapters.ts";
|
||||||
|
|
||||||
const props = defineProps<{ hruid: string; language: Language; version: number }>();
|
const props = defineProps<{ hruid: string; language: Language; version: number }>();
|
||||||
|
|
||||||
|
@ -11,6 +13,49 @@
|
||||||
() => props.language,
|
() => props.language,
|
||||||
() => props.version,
|
() => props.version,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const currentAnswer = reactive([]);
|
||||||
|
|
||||||
|
function forEachQuestion(
|
||||||
|
doAction: (questionIndex: number, questionName: string, questionType: string, questionElement: Element) => void
|
||||||
|
) {
|
||||||
|
const questions = document.querySelectorAll(".gift-question");
|
||||||
|
questions.forEach(question => {
|
||||||
|
const name = question.id.match(/gift-q(\d+)/)?.[1]
|
||||||
|
const questionType = question.classList.values()
|
||||||
|
.find(it => it.startsWith("gift-question-type"))
|
||||||
|
.match(/gift-question-type-([^ ]*)/)?.[1];
|
||||||
|
|
||||||
|
if (!name || isNaN(parseInt(name)) || !questionType) return;
|
||||||
|
|
||||||
|
const index = parseInt(name) - 1;
|
||||||
|
|
||||||
|
doAction(index, name, questionType, question);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function attachQuestionListeners() {
|
||||||
|
forEachQuestion((index, name, type, element) => {
|
||||||
|
getGiftAdapterForType(type)?.installListener(
|
||||||
|
element, (newAnswer) => currentAnswer[index] = newAnswer
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAnswers(answers: (object | string | number)[]) {
|
||||||
|
forEachQuestion((index, name, type, element) => {
|
||||||
|
getGiftAdapterForType(type)?.setAnswer(element, answers[index]);
|
||||||
|
});
|
||||||
|
currentAnswer.fill(answers);
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => nextTick(() => attachQuestionListeners()));
|
||||||
|
|
||||||
|
watch(learningObjectHtmlQueryResult.data, async () => {
|
||||||
|
await nextTick();
|
||||||
|
attachQuestionListeners();
|
||||||
|
setAnswers([1]);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -22,6 +67,7 @@
|
||||||
class="learning-object-container"
|
class="learning-object-container"
|
||||||
v-html="learningPathHtml.data.body.innerHTML"
|
v-html="learningPathHtml.data.body.innerHTML"
|
||||||
></div>
|
></div>
|
||||||
|
{{ currentAnswer }}
|
||||||
</using-query-result>
|
</using-query-result>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export const essayQuestionAdapter: GiftAdapter = {
|
||||||
|
questionType: "Essay",
|
||||||
|
|
||||||
|
installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void {
|
||||||
|
const textArea = questionElement.querySelector('textarea')!;
|
||||||
|
textArea.addEventListener('input', () => answerUpdateCallback(textArea.value));
|
||||||
|
},
|
||||||
|
|
||||||
|
setAnswer(questionElement: Element, answer: string | number | object): void {
|
||||||
|
const textArea = questionElement.querySelector('textarea')!;
|
||||||
|
textArea.value = String(answer);
|
||||||
|
}
|
||||||
|
}
|
5
frontend/src/views/learning-paths/gift-adapters/gift-adapter.d.ts
vendored
Normal file
5
frontend/src/views/learning-paths/gift-adapters/gift-adapter.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
interface GiftAdapter {
|
||||||
|
questionType: string;
|
||||||
|
installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void;
|
||||||
|
setAnswer(questionElement: Element, answer: string | number | object): void;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
import {multipleChoiceQuestionAdapter} from "@/views/learning-paths/gift-adapters/multiple-choice-question-adapter.ts";
|
||||||
|
import {essayQuestionAdapter} from "@/views/learning-paths/gift-adapters/essay-question-adapter.ts";
|
||||||
|
|
||||||
|
export const giftAdapters = [multipleChoiceQuestionAdapter, essayQuestionAdapter];
|
||||||
|
|
||||||
|
export function getGiftAdapterForType(questionType: string): GiftAdapter | undefined {
|
||||||
|
return giftAdapters.find(it => it.questionType === questionType);
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
export const multipleChoiceQuestionAdapter: GiftAdapter = {
|
||||||
|
questionType: "MC",
|
||||||
|
|
||||||
|
installListener(questionElement: Element, answerUpdateCallback: (newAnswer: string | number | object) => void): void {
|
||||||
|
questionElement.querySelectorAll('input[type=radio]').forEach(element => {
|
||||||
|
const input = element as HTMLInputElement;
|
||||||
|
|
||||||
|
input.addEventListener('change', () => {
|
||||||
|
answerUpdateCallback(parseInt(input.value));
|
||||||
|
});
|
||||||
|
// Optional: initialize value if already selected
|
||||||
|
if (input.checked) {
|
||||||
|
answerUpdateCallback(parseInt(input.value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
setAnswer(questionElement: Element, answer: string | number | object): void {
|
||||||
|
questionElement.querySelectorAll('input[type=radio]').forEach(element => {
|
||||||
|
const input = element as HTMLInputElement;
|
||||||
|
console.log(`input: ${input.value}, answer: ${answer}`);
|
||||||
|
input.checked = String(answer) === String(input.value);
|
||||||
|
console.log(input.checked);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue