Last active
December 27, 2022 09:26
-
-
Save chrisvfritz/37f51eb66e965c0ec5387aac7662b56d to your computer and use it in GitHub Desktop.
Westeros House Quiz with Vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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®ion=' + | |
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