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 i = 1; | ||||
|         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 += `    </div>\n`; | ||||
|             i++; | ||||
|  |  | |||
|  | @ -3,6 +3,8 @@ | |||
|     import type { UseQueryReturnType } from "@tanstack/vue-query"; | ||||
|     import { useLearningObjectHTMLQuery } from "@/queries/learning-objects.ts"; | ||||
|     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 }>(); | ||||
| 
 | ||||
|  | @ -11,6 +13,49 @@ | |||
|         () => props.language, | ||||
|         () => 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> | ||||
| 
 | ||||
| <template> | ||||
|  | @ -22,6 +67,7 @@ | |||
|             class="learning-object-container" | ||||
|             v-html="learningPathHtml.data.body.innerHTML" | ||||
|         ></div> | ||||
|         {{ currentAnswer }} | ||||
|     </using-query-result> | ||||
| </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); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger