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); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Reference in a new issue
	
	 Gerald Schmittinger
						Gerald Schmittinger