Skip to content

Instantly share code, notes, and snippets.

@chrisvfritz
Last active December 27, 2022 09:26
Show Gist options
  • Save chrisvfritz/37f51eb66e965c0ec5387aac7662b56d to your computer and use it in GitHub Desktop.
Save chrisvfritz/37f51eb66e965c0ec5387aac7662b56d to your computer and use it in GitHub Desktop.
Westeros House Quiz with Vue
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Westeros House Quiz with Vue</title>
<style>
label {
display: block;
}
</style>
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.js"></script>
</head>
<body>
<div id="app">
<h1>Which house of Westeros do you belong to?</h1>
<!-- https://vuejs.org/v2/guide/conditional.html -->
<p v-if="quizIsComplete">
<!--
See what information we have about a house at:
https://anapioficeandfire.com/Documentation#houses
-->
<!-- https://vuejs.org/v2/guide/syntax.html#Text -->
You'd fit in best with <strong>{{ matchingHouse.name }}</strong>. If you became the leader of that house, you might be called <strong>{{ matchingHouse.titles[0] }}</strong>.
</p>
<!-- https://vuejs.org/v2/guide/conditional.html#Conditional-Groups-with-v-if-on-lt-template-gt -->
<template v-else>
<p>
<!-- https://vuejs.org/v2/guide/syntax.html#Text -->
{{ currentQuestionIndex + 1 }}.
{{ currentQuestion.query }}
</p>
<!-- https://vuejs.org/v2/guide/conditional.html#Conditional-Groups-with-v-if-on-lt-template-gt -->
<template v-if="currentQuestionHasAnswers">
<!-- https://vuejs.org/v2/guide/list.html -->
<label v-for="answer in currentQuestion.answers">
<!-- https://vuejs.org/v2/guide/forms.html#Radio -->
<input
type="radio"
:value="answer.key"
v-model="currentQuestion.choice"
>
<!-- https://vuejs.org/v2/guide/syntax.html#Text -->
{{ answer.displayText }}
</label>
</template>
<!-- https://vuejs.org/v2/guide/conditional.html -->
<p v-else>Loading...</p>
</template>
</div>
<script>
new Vue({
el: '#app',
// All the data we use in our app.
data: {
// Questions are asked in order. Each question must include:
//
// * key :: The name of filtering parameter it's inferring.
// * query :: The query text to be displayed to the user.
// * answers :: Either an array of answers, each with:
//
// * key :: The value we'll store in the
// question's choice.
// * displayText :: Displayed answer text.
// Or an asynchronous function that replaces
// itself with such an array. This function
// is passed one argument, which is the vm
// instance (short for "view model").
//
// * choice :: Where we'll store the user's choice. It should
// always start as null.
//
// When the choice in the final question is set, it must be an
// object with information on the matching house.
questions: [
{
key: 'region',
query: 'What\'s your preferred climate?',
answers: [
{
key: 'Dorne',
displayText: 'Warm and tropical'
},
{
key: 'The North',
displayText: 'Cold and harsh'
},
{
key: 'The Vale',
displayText: 'Misty and mountainous'
},
{
key: 'The Reach',
displayText: 'Moderate and blooming'
},
{
key: 'The Stormlands',
displayText: 'Stormy, by the sea'
},
{
key: 'The Crownlands',
displayText: 'Gritty and wet'
},
{
key: 'The Westerlands',
displayText: 'Bright and hilly'
}
],
choice: null
},
{
key: 'house',
query: 'Which of these best fit your life philosophy?',
answers: function (vm) {
axios.get(
'https://www.anapioficeandfire.com/api/houses?hasWords=true&hasCoatOfArms=true&hasTitles=true&region=' +
encodeURIComponent(vm.choices.region)
).then(function (response) {
vm.currentQuestion.answers = response.data.map(function (house) {
return {
key: house,
displayText: house.words
}
})
}).catch(function (error) {
alert('Oops. Request failed. 😞')
})
},
choice: null
}
],
// We only display one question at a time, so this is
// the index of the question we're currently displaying.
currentQuestionIndex: 0
},
// Computed properties are derived data that automatically
// track their own dependencies and update themselves when
// necessary.
computed: {
// The question being currently displayed.
currentQuestion: function () {
return this.questions[this.currentQuestionIndex]
},
// Whether the current question has answers.
currentQuestionHasAnswers () {
return this.questionHasAnswers(this.currentQuestion)
},
// A convenient lookup dictionary of question choices.
choices: function () {
return this.questions.reduce(function (prevChoices, question) {
var choice = {}
choice[question.key] = question.choice
return Object.assign({}, prevChoices, choice)
}, {})
},
// Whether the quiz is complete yet.
quizIsComplete: function () {
return !this.currentQuestion
},
// The matching house, once the quiz is complete.
matchingHouse: function () {
return this.questions[this.questions.length - 1].choice
}
},
// Whenever these values change, their watcher functions
// will run.
watch: {
currentQuestionHasAnswers: function (isTrue) {
// If the new current question does NOT have answers
// (i.e. it is not already an array)
if (!isTrue) {
// Call the answers function, to set itself using
// the information we've gathered so far and any
// data it wishes to fetch from an API.
this.currentQuestion.answers(this)
}
},
'currentQuestion.choice': function (newChoice, oldChoice) {
// If made a choice when it was previously null
if (!oldChoice && newChoice) {
// Advance to next question
this.currentQuestionIndex++
}
}
},
// Convenient methods with logic specific to our view model.
methods: {
questionHasAnswers: function (question) {
return Array.isArray(question.answers)
}
}
})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment