I have an array containing data of questions that will be displayed. Only one question must be displayed at a time. Once the user selects an answer to a question, they can move to the next one by, either, the click of a button or using the up and down arrow keys.
Here’s a gif of the present version:
The App.vue
file:
import { defineComponent, ref } from 'vue';
import QuestionCard from "./components/QuestionCard.vue";
export default defineComponent({
components: {
QuestionCard
},
setup() {
const questionArray = ref([
{
id: "123",
question: "Which of these is a colour in the rainbow?",
options: [
'brown', 'red', 'black',
],
}, {
id: "456",
question: "How many continents does Earth have?",
options: [
1, 7, 6, 9
],
}, {
id: "789",
question: "Which of these is a prime number?",
options: [
7, 4, 44,
],
},
]);
let currentQuestion = ref(0);
return {
questionArray, currentQuestion
}
},
methods: {
nextQuestion() {
if (this.currentQuestion < this.questionArray.length - 1) {
this.currentQuestion++;
}
},
previousQuestion() {
if (this.currentQuestion > 0) {
this.currentQuestion--;
}
}
},
mounted() {
console.log('Mounted!!')
window.addEventListener("keyup", (event) => {
if (event.code == 'ArrowDown') {
this.nextQuestion();
}
else if (event.code == 'ArrowUp') {
this.previousQuestion();
}
});
},
});
</script>
<template>
<div>
<div id="top_bar">
<button @click="previousQuestion">Previous Question (Up key)</button>
<button @click="nextQuestion">Next Question (Down key)</button>
</div>
<div id="question_section">
<QuestionCard
:question="questionArray[currentQuestion].question"
:answer_options="questionArray[currentQuestion].options"
></QuestionCard>
</div>
</div>
</template>
I have a component called QuestionCard
that displays the questions and the answer options. Just like in the App
component, I have added an event listener, in order to detect key presses. Each answer option is given a key code beginning from letter A (ASCII – 65). For example, based on the answer options of the first question in questionArray
above, the QuestionCard
for the first question must only respond to just 3 key codes:
- A for brown
- B for red
- C for black
On either
- pressing one of the keys assigned to an answer option button, or
- by clicking it,
I want to record the answer options value and pass it to a method called storeAnswer()
.
I have achieved goal 1 by calling this method and passing the value when a <button>
is clicked. However, I’m not quite sure how to achieve goal 2.
Here’s what I have done – inside the setup()
method of the QuestionCard
, I have added listener to the window to detect key releases. On detection, I check if the key
matches any of the assigned keys to the answer options. If so, I call the storeAnswer()
method. But, I get an error saying:
Uncaught ReferenceError: storeAnswer is not defined
Here’s the code of QuestionCard.vue
:
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
props: {
name: String,
question: String,
answer_options: Array,
},
methods: {
storeAnswer(ans: any) {
this.selectedAns = ans;
console.log("Button clicked: " + ans);
}
},
setup(props) {
let selectedAns = ref(null);
// loop through array of answer_options and assign a key code for each one so that it can be detected
const keyOptions: {keyCode: string, value: any}[] = [];
for (let i=0; i < props.answer_options.length; i++) {
keyOptions.push(
{
keyCode: String.fromCharCode(65+i),
value: props.answer_options[i]
}
);
}
window.addEventListener("keyup", (event) => {
const keyPressed = event.key.toUpperCase();
const i = keyOptions.findIndex(Element => Element.keyCode == keyPressed);
if (i > -1) {
selectedAns.value = keyOptions[i].value;
console.log(`Option you selected is: ${keyPressed + ')' + selectedAns.value}`)
storeAnswer();
}
});
return {
selectedAns
}
},
});
</script>
<template>
<div>
<h4>{{ question }}</h4>
<div>
<ul>
<li v-for="(option, index) in answer_options" :key="index">
<button @click="storeAnswer(option)">
{{ String.fromCharCode(65 + index) + ') ' + option }}
</button>
</li>
</ul>
</div>
<!-- display the selected answer -->
<p>
{{ selectedAns }}
</p>
</div>
</template>
2
Answers
Just out of the blue inside QuestionCard.vue:
You put this.storeAnswer();
I’d recommend you to create a composable function. Define all your functions and state of the app like this:
in your components use like this: