Editable Questionnaire with VueJS
by bikeshedder
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.1.2/vuex.js"></script>
<div id="main">
<toggle-mode-button></toggle-mode-button>
<clear-answers-button></clear-answers-button>
<hr>
<h1>Questionnaire</h1>
<questions :questions="questions"></questions>
<hr>
<h1>Answer overview</h1>
<questions-overview></questions-overview>
</div>
<script type="text/x-template" id="toggle-mode-button-template">
<div>
<span v-if="mode === 'view'">view mode</span>
<span v-if="mode === 'edit'">edit mode</span>
<button v-on:click="toggleMode">
Toggle
</button>
</div>
</script>
<script type="text/x-template" id="clear-answers-button">
<button v-on:click="clearAnswers">Clear answers</button>
</script>
<script type="text/x-template" id="questions-template">
<div>
<ul v-for="question in questions">
<li><question :question="question"></li>
</ul>
</div>
</script>
<script type="text/x-template" id="question-template">
<div>
<div v-if="mode === 'view'">
{{ question.text }}
<div v-if="question.type === 'text'">
<input v-model="question.answer"></input>
</div>
<div v-if="question.type === 'yesno'">
<label><input type="radio" value="true" v-model="question.answer" v-bind:value="true"> Yes</label>
<label><input type="radio" value="false" v-model="question.answer" v-bind:value="false"> No</label>
</div>
<div v-if="question.type === 'choice'">
<select v-model="question.answer">
<option v-for="choice in question.choices" v-bind:value="choice">
{{ choice.text }}
</option>
</select>
</div>
<div v-if="question.showSubquestions()">
<questions :questions="question.subquestions"></questions>
</div>
</div>
<div v-if="mode === 'edit'">
<div><input v-model="question.text"></div>
<select v-model="question.type">
<option v-for="choice in ['text', 'yesno', 'choice']" v-bind:value="choice">
{{ choice }}
</option>
</select>
<questions...
JavaScript
class QuestionChoice {
constructor(data) {
this.value = data.value;
this.text = data.text;
this.showSubquestions = data.showSubquestions || false;
}
}
class Question {
constructor(data) {
this.text = data.text;
this.type = data.type;
this.subquestions = data.subquestions ? data.subquestions.map(q => new Question(q)) : [];
this.choices = data.choices ? data.choices.map(c => new QuestionChoice(c)) : null;
this.answer = data.answer || null;
}
showSubquestions() {
switch (this.type) {
case 'yesno':
return this.answer === true;
case 'choice':
return this.answer && this.answer.showSubquestions;
default:
return this.answer && true;
}
}
getTextAnswer() {
if (this.answer === null) {
return '-';
}
switch (this.type) {
case 'yesno':
return this.answer ? 'Yes' : 'No';
case 'choice':
return this.answer && this.answer.text || '';
default:
return this.answer;
}
}
}
var questionsData = [
{
text: 'Are you cool?',
type: 'yesno',
subquestions: [
{
text: 'How cool are you?',
type: 'choice',
choices: [
{value: '0', text: 'Absolute zero. (0°K)', showSubquestions: true},
{value: '255.372', text: 'Fricking cold. (0°F)'},
{value: '273.15', text: 'Water freezes. (0°C)'},
],
subquestions: [
{
'text': 'That is rather impressive! Are you sure about that?',
'type': 'yesno',
}
]
}
]
}
];
Vue.component('toggle-mode-button', {
template: '#toggle-mode-button-template',
computed: {
mode() {
return this.$store.state.mode;
}
},
methods: {
toggleMode() {
this.$store.commit('toggleMode');
}
}
});
Vue.component('clear-answers-button', {
template: '#clear-answers-button',
methods: {
clearAnswers() {
this.$store.commit('clearAnswers');
}
}
});
Vue.component('questions', {
template: '#questions-template',
props: ['questions'],
});
Vue.component('question', {
template: '#question-template',
props:...