|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+<template lang="pug">
|
|
|
2
|
+.form.f-col.start.mr-1.w-full
|
|
|
3
|
+ header
|
|
|
4
|
+ p answers to save: {{ answers }}
|
|
|
5
|
+
|
|
|
6
|
+ ul(v-for="(step, i) in formSteps").w-full
|
|
|
7
|
+ li(v-for="prompt in step" v-show="(i + 1) == state.step").f-row.start.b-solid
|
|
|
8
|
+ h3 {{ prompt.question }}?
|
|
|
9
|
+ .response-wrapper(v-if="prompt.type === 'input-string'")
|
|
|
10
|
+ label {{prompt.type}}
|
|
|
11
|
+ input(v-model="answers[makeKebob(prompt.question)]")
|
|
|
12
|
+ .response-wrapper(v-else-if="prompt.type === 'tag-cloud'")
|
|
|
13
|
+ label {{prompt.type}}
|
|
|
14
|
+ button(
|
|
|
15
|
+ v-for="response in prompt.responses"
|
|
|
16
|
+ :prompt-question="makeKebob(prompt.question)"
|
|
|
17
|
+ @click="respondFromTag"
|
|
|
18
|
+ :disabled="response == answers[makeKebob(prompt.question)]"
|
|
|
19
|
+ ).p-0 {{ response }}
|
|
|
20
|
+
|
|
|
21
|
+ footer.f-row.w-full
|
|
|
22
|
+ button(@click="back" :disabled="state.step == 1").p-1 back
|
|
|
23
|
+ button(@click="next" :disabled="state.step == formSteps.length").p-1 next
|
|
|
24
|
+ button(@click="next" :disabled="state.step != formSteps.length").p-1 save
|
|
|
25
|
+ .f-grow
|
|
|
26
|
+ p step: {{ state.step }} of {{ formSteps.length }}
|
|
|
27
|
+</template>
|
|
|
28
|
+
|
|
|
29
|
+<script setup>
|
|
|
30
|
+import Joi from 'joi'
|
|
|
31
|
+import { validatorMapping, makeKebob } from '@/utils'
|
|
|
32
|
+import { defineProps, reactive, getCurrentInstance } from 'vue'
|
|
|
33
|
+
|
|
|
34
|
+const instance = getCurrentInstance()
|
|
|
35
|
+
|
|
|
36
|
+const props = defineProps({
|
|
|
37
|
+ form: {
|
|
|
38
|
+ type: Array,
|
|
|
39
|
+ required: true
|
|
|
40
|
+ }
|
|
|
41
|
+})
|
|
|
42
|
+
|
|
|
43
|
+/**
|
|
|
44
|
+ * Our form is comprised of steps, and each step has a series of questions
|
|
|
45
|
+ */
|
|
|
46
|
+const formSteps = props.form
|
|
|
47
|
+/**
|
|
|
48
|
+ * Create state object to store answers based on the questions in formSteps
|
|
|
49
|
+ */
|
|
|
50
|
+const answers = reactive({})
|
|
|
51
|
+const resetAnswers = () => {
|
|
|
52
|
+ formSteps.forEach(step => {
|
|
|
53
|
+ step.forEach(prompt => {
|
|
|
54
|
+ answers[makeKebob(prompt.question)] = null
|
|
|
55
|
+ })
|
|
|
56
|
+ })
|
|
|
57
|
+}
|
|
|
58
|
+resetAnswers()
|
|
|
59
|
+/**
|
|
|
60
|
+ * Callback for clicking a tag to respond
|
|
|
61
|
+ */
|
|
|
62
|
+const respondFromTag = e => {
|
|
|
63
|
+ const answer = e.target.textContent
|
|
|
64
|
+ const questionKey = e.target.attributes.getNamedItem('prompt-question').nodeValue
|
|
|
65
|
+ answers[questionKey] = answer
|
|
|
66
|
+}
|
|
|
67
|
+/**
|
|
|
68
|
+ * Check answered fields in current step, build a validator, then validate the answers by prompt.type
|
|
|
69
|
+ * @param {number} step // The current step
|
|
|
70
|
+ * @returns {object} // Joi object
|
|
|
71
|
+ */
|
|
|
72
|
+const isValid = step => {
|
|
|
73
|
+ const schema = {}
|
|
|
74
|
+ const answeredThisStep = {}
|
|
|
75
|
+ formSteps[step].forEach(prompt => {
|
|
|
76
|
+ const key = makeKebob(prompt.question)
|
|
|
77
|
+ schema[key] = validatorMapping[prompt.type]
|
|
|
78
|
+ answeredThisStep[key] = answers[key]
|
|
|
79
|
+ })
|
|
|
80
|
+ return Joi.object(schema).validate(answeredThisStep)
|
|
|
81
|
+}
|
|
|
82
|
+const state = reactive({ step: 1 })
|
|
|
83
|
+/**
|
|
|
84
|
+ * Save or take the-nNext step in the form
|
|
|
85
|
+ */
|
|
|
86
|
+const next = e => {
|
|
|
87
|
+ const validity = isValid(state.step - 1)
|
|
|
88
|
+ if(validity.error) return console.error(validity.error)
|
|
|
89
|
+
|
|
|
90
|
+ // Save or next
|
|
|
91
|
+ if(state.step === formSteps.length) {
|
|
|
92
|
+ alert('saved...')
|
|
|
93
|
+ resetAnswers()
|
|
|
94
|
+ state.step = 1
|
|
|
95
|
+ } else if(state.step < formSteps.length) {
|
|
|
96
|
+ state.step++
|
|
|
97
|
+ } else {
|
|
|
98
|
+ console.error(`Cannot perform action: next() | on form step: ${state.step} of ${props.formSteps.length}`)
|
|
|
99
|
+ }
|
|
|
100
|
+}
|
|
|
101
|
+/**
|
|
|
102
|
+ * Back a step in the form
|
|
|
103
|
+ */
|
|
|
104
|
+const back = e => {
|
|
|
105
|
+ if(state.step > 1) {
|
|
|
106
|
+ state.step--
|
|
|
107
|
+ } else {
|
|
|
108
|
+ console.error(`Cannot perform action: back() | on form step: ${state.step} of ${props.formSteps.length}`)
|
|
|
109
|
+ }
|
|
|
110
|
+}
|
|
|
111
|
+</script>
|
|
|
112
|
+
|
|
|
113
|
+<style lang="postcss">
|
|
|
114
|
+@import '@/sss/theme.sss'
|
|
|
115
|
+
|
|
|
116
|
+.form
|
|
|
117
|
+ color: $light
|
|
|
118
|
+ ul
|
|
|
119
|
+ list-style: none
|
|
|
120
|
+ li
|
|
|
121
|
+ padding: 1em
|
|
|
122
|
+</style>
|