Bladeren bron

:sparkles: adding http requesting of form questions | generating forms on response

master
j 4 jaren geleden
bovenliggende
commit
2598a2829c

+ 10
- 3
backend/db/mock.js Bestand weergeven

48
         {
48
         {
49
             response_key_id: 1,
49
             response_key_id: 1,
50
             response_key_category: 'grit',
50
             response_key_category: 'grit',
51
-            response_key_prompt: 'question for grit',
51
+            response_key_prompt: 'what is your name',
52
             response_key_description: null,
52
             response_key_description: null,
53
         },
53
         },
54
         {
54
         {
55
             response_key_id: 2,
55
             response_key_id: 2,
56
             response_key_category: 'openness',
56
             response_key_category: 'openness',
57
-            response_key_prompt: 'question for openness',
57
+            response_key_prompt: 'what is your favorite color',
58
             response_key_description: null,
58
             response_key_description: null,
59
         },
59
         },
60
         {
60
         {
61
             response_key_id: 3,
61
             response_key_id: 3,
62
+            response_key_category: 'grit',
63
+            response_key_prompt: 'what is your quest',
64
+            response_key_description: null,
65
+        },
66
+        {
67
+            response_key_id: 4,
62
             response_key_category: 'empathy',
68
             response_key_category: 'empathy',
63
-            response_key_prompt: 'question for empathy',
69
+            response_key_prompt:
70
+                'what is the average flight speed of an unladen swallow',
64
             response_key_description: null,
71
             response_key_description: null,
65
         },
72
         },
66
     ],
73
     ],

+ 3
- 1
backend/lib/index.js Bestand weergeven

32
         })
32
         })
33
 
33
 
34
         await server.register(SurveyPlugin, {
34
         await server.register(SurveyPlugin, {
35
-            routes: { prefix: '/survey' },
35
+            routes: {
36
+                prefix: '/survey',
37
+            },
36
         })
38
         })
37
 
39
 
38
         await server.register(ProfilePlugin, {
40
         await server.register(ProfilePlugin, {

+ 1
- 1
backend/lib/routes/survey/questions.js Bestand weergeven

37
         tags: ['api'],
37
         tags: ['api'],
38
         /** Protect this route with authentication? */
38
         /** Protect this route with authentication? */
39
         auth: false,
39
         auth: false,
40
-
40
+        cors: true,
41
         handler: async function (request, h) {
41
         handler: async function (request, h) {
42
             const { responseService } = request.services()
42
             const { responseService } = request.services()
43
             const responseKeys = await responseService.getResponseKeys()
43
             const responseKeys = await responseService.getResponseKeys()

+ 28
- 5
frontend/src/App.vue Bestand weergeven

1
 <template lang="pug">
1
 <template lang="pug">
2
 .cards.f-row
2
 .cards.f-row
3
     card(v-for="card in cards" :card="card" @on-remove="remove")
3
     card(v-for="card in cards" :card="card" @on-remove="remove")
4
-.form.f-row
5
-    siimee-form(:form="profileForm")
4
+.form.f-row(v-if="completedForm")
5
+    siimee-form(:form="completedForm")
6
 hello-world(msg="Hello Vue 3 + Vite")
6
 hello-world(msg="Hello Vue 3 + Vite")
7
 </template>
7
 </template>
8
 
8
 
9
 <script>
9
 <script>
10
 import * as sss from '@/sss/import.css'
10
 import * as sss from '@/sss/import.css'
11
 
11
 
12
-import { ref } from 'vue'
12
+import { ref, onMounted } from 'vue'
13
 import { profileForm } from '@/utils/forms'
13
 import { profileForm } from '@/utils/forms'
14
+import { Connector } from '@/utils/db'
14
 
15
 
15
 import helloWorld from '@/components/HelloWorld.vue'
16
 import helloWorld from '@/components/HelloWorld.vue'
16
 import card from '@/components/card.vue'
17
 import card from '@/components/card.vue'
29
         const remove = card => {
30
         const remove = card => {
30
             const i = cards.value.indexOf(card)
31
             const i = cards.value.indexOf(card)
31
             cards.value.splice(i, 1)
32
             cards.value.splice(i, 1)
32
-            console.log(cards.value)
33
         }
33
         }
34
+
35
+        const db = new Connector()
36
+        const completedForm = ref([])
37
+        const compileForm = async () => {
38
+            const responseKeys = await db.get('/survey/questions')
39
+            const responsesById = responseKeys.reduce((resMap, res) => {
40
+                resMap[res.response_key_id] = res
41
+                return resMap
42
+            }, {})
43
+            completedForm.value = profileForm.map(step => {
44
+                const stepWithPrompts = []
45
+                step.forEach(prompt => {
46
+                    stepWithPrompts.push({
47
+                        ...prompt,
48
+                        category: responsesById[prompt.id]['response_key_category'],
49
+                        question: responsesById[prompt.id]['response_key_prompt'],
50
+                    })
51
+                })
52
+                return stepWithPrompts
53
+            })
54
+        }
55
+        onMounted(compileForm)
56
+
34
         return { 
57
         return { 
35
             cards,
58
             cards,
36
             remove,
59
             remove,
37
-            profileForm
60
+            completedForm
38
         }
61
         }
39
     }
62
     }
40
 }
63
 }

+ 17
- 23
frontend/src/components/form.vue Bestand weergeven

1
 <template lang="pug">
1
 <template lang="pug">
2
-.form.f-col.start.mr-1.w-full
2
+.form.f-col.start.mr-1.w-full(v-if="form.length")
3
     header
3
     header
4
         p answers to save: {{ answers }}
4
         p answers to save: {{ answers }}
5
 
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
6
+    ul(v-for="(step, i) in form").w-full
7
+        li(
8
+            v-if="step && step.length"
9
+            v-for="prompt in step" v-show="(i + 1) == state.step"
10
+        ).f-row.start.b-solid.p-0
8
             h3 {{ prompt.question }}?
11
             h3 {{ prompt.question }}?
9
             .response-wrapper(v-if="prompt.type === 'input-string'")
12
             .response-wrapper(v-if="prompt.type === 'input-string'")
10
                 label {{prompt.type}}
13
                 label {{prompt.type}}
20
     
23
     
21
     footer.f-row.w-full
24
     footer.f-row.w-full
22
         button(@click="back" :disabled="state.step == 1").p-1 back
25
         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
26
+        button(@click="next" :disabled="state.step == form.length").p-1 next
27
+        button(@click="next" :disabled="state.step != form.length").p-1 save
25
         .f-grow
28
         .f-grow
26
-        p step: {{ state.step }} of {{ formSteps.length }}
29
+        p step: {{ state.step }} of {{ form.length }}
27
 </template>
30
 </template>
28
 
31
 
29
 <script setup>
32
 <script setup>
30
 import Joi from 'joi'
33
 import Joi from 'joi'
31
 import { validatorMapping, makeKebob } from '@/utils'
34
 import { validatorMapping, makeKebob } from '@/utils'
32
-import { defineProps, reactive, getCurrentInstance } from 'vue'
33
-
34
-const instance = getCurrentInstance()
35
+import { defineProps, reactive } from 'vue'
35
 
36
 
36
 const props = defineProps({ 
37
 const props = defineProps({ 
37
     form: { 
38
     form: { 
39
         required: true
40
         required: true
40
     } 
41
     } 
41
 })
42
 })
42
-
43
 /**
43
 /**
44
  * Our form is comprised of steps, and each step has a series of questions
44
  * Our form is comprised of steps, and each step has a series of questions
45
- */
46
-const formSteps = props.form
45
+*/
46
+const state = reactive({ step: 1 })
47
+
47
 /**
48
 /**
48
  * Create state object to store answers based on the questions in formSteps 
49
  * Create state object to store answers based on the questions in formSteps 
49
  */
50
  */
50
 const answers = reactive({})
51
 const answers = reactive({})
51
 const resetAnswers = () => {
52
 const resetAnswers = () => {
52
-    formSteps.forEach(step => {
53
-        step.forEach(prompt => {
54
-            answers[makeKebob(prompt.question)] = null
55
-        })
56
-    })
53
+    Object.keys(answers).forEach(key => answers[key] = null)
57
 }
54
 }
58
 resetAnswers()
55
 resetAnswers()
59
 /**
56
 /**
72
 const isValid = step => {
69
 const isValid = step => {
73
     const schema = {}
70
     const schema = {}
74
     const answeredThisStep = {}
71
     const answeredThisStep = {}
75
-    formSteps[step].forEach(prompt => {
72
+    props.form[step].forEach(prompt => {
76
         const key = makeKebob(prompt.question)
73
         const key = makeKebob(prompt.question)
77
         schema[key] = validatorMapping[prompt.type]
74
         schema[key] = validatorMapping[prompt.type]
78
         answeredThisStep[key] = answers[key]
75
         answeredThisStep[key] = answers[key]
79
     })
76
     })
80
     return Joi.object(schema).validate(answeredThisStep)
77
     return Joi.object(schema).validate(answeredThisStep)
81
 }
78
 }
82
-const state = reactive({ step: 1 })
83
 /**
79
 /**
84
  * Save or take the-nNext step in the form
80
  * Save or take the-nNext step in the form
85
  */
81
  */
88
     if(validity.error) return console.error(validity.error)
84
     if(validity.error) return console.error(validity.error)
89
 
85
 
90
     // Save or next
86
     // Save or next
91
-    if(state.step === formSteps.length) {
87
+    if(state.step === props.form.length) {
92
         alert('saved...')
88
         alert('saved...')
93
         resetAnswers()
89
         resetAnswers()
94
         state.step = 1
90
         state.step = 1
95
-    } else if(state.step < formSteps.length) {
91
+    } else if(state.step < props.form.length) {
96
         state.step++
92
         state.step++
97
     } else {
93
     } else {
98
         console.error(`Cannot perform action: next() | on form step: ${state.step} of ${props.formSteps.length}`)        
94
         console.error(`Cannot perform action: next() | on form step: ${state.step} of ${props.formSteps.length}`)        
117
     color: $light
113
     color: $light
118
     ul
114
     ul
119
         list-style: none
115
         list-style: none
120
-        li
121
-            padding: 1em
122
 </style>
116
 </style>

+ 41
- 14
frontend/src/utils/db.js Bestand weergeven

1
 const domain = 'localhost'
1
 const domain = 'localhost'
2
-const port = 3306
3
-const remote = `http://${domain}:${port}`
2
+const port = 3001
3
+const remote = `http://${domain}:${port}/api`
4
 
4
 
5
-class Connector {
6
-    constructor() { this.db = null }
7
-    async put(entry) {
8
-        /** from ajv schema validation */
9
-        if(entry.isValid()) {
10
-            try { }
5
+const headerTemplate = {
6
+    method: null,
7
+    mode: 'cors', // no-cors, *cors, same-origin
8
+    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
9
+    credentials: 'omit', // include, *same-origin, omit
10
+    headers: {
11
+        'Content-Type': 'application/json',
12
+        // 'Content-Type': 'application/x-www-form-urlencoded',
13
+    },
14
+    redirect: 'manual', // manual, *follow, error
15
+    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
16
+    body: null, // body data type must match "Content-Type" header
17
+}
11
 
18
 
12
-            /** valid data but can't save */
13
-            catch (err) {
14
-                console.error('can\'t put', entry)
19
+class Connector {
20
+    constructor() {
21
+        this.db = null
22
+    }
23
+    async get(endpoint) {
24
+        const header = headerTemplate
25
+        header.method = 'GET'
26
+        try {
27
+            let res = await fetch(`${remote}${endpoint}`, header)
28
+            if (!res.ok) {
29
+                throw Error(res.statusText)
15
             }
30
             }
31
+            const jsonRes = await res.json()
32
+            return jsonRes.data
33
+        } catch (error) {
34
+            console.log(error)
16
         }
35
         }
17
-
18
-        else {
36
+    }
37
+    async put(endpoint, entry) {
38
+        /** from ajv schema validation */
39
+        if (entry.isValid()) {
40
+            try {
41
+            } catch (err) {
42
+                /** valid data but can't save */
43
+                console.error("can't put", entry)
44
+            }
45
+        } else {
19
             /** not valid data */
46
             /** not valid data */
20
             console.error('NOT VALID!', entry.isValid())
47
             console.error('NOT VALID!', entry.isValid())
21
         }
48
         }
24
     // async removeAll() { }
51
     // async removeAll() { }
25
 }
52
 }
26
 
53
 
27
-export { Connector }
54
+export { Connector }

+ 26
- 26
frontend/src/utils/forms.js Bestand weergeven

1
 const profileForm = [
1
 const profileForm = [
2
-  [
3
-    {
4
-      type: "input-string",
5
-      question: "what is your name",
6
-      responses: null,
7
-    },
8
-    {
9
-      type: "tag-cloud",
10
-      question: "what is your favorite color",
11
-      responses: ["red", "blue", "green", "white", "black"],
12
-    },
13
-  ],
14
-  [
15
-    {
16
-      type: "input-string",
17
-      question: "what is your quest",
18
-      responses: null,
19
-    },
20
-    {
21
-      type: "input-string",
22
-      question: "what is the average flight speed of an unladen swallow",
23
-      responses: null,
24
-    },
25
-  ],
26
-];
2
+    [
3
+        {
4
+            type: 'input-string',
5
+            id: 1,
6
+            responses: null,
7
+        },
8
+        {
9
+            type: 'tag-cloud',
10
+            id: 2,
11
+            responses: ['red', 'blue', 'green', 'white', 'black'],
12
+        },
13
+    ],
14
+    [
15
+        {
16
+            type: 'input-string',
17
+            id: 3,
18
+            responses: null,
19
+        },
20
+        {
21
+            type: 'input-string',
22
+            id: 4,
23
+            responses: null,
24
+        },
25
+    ],
26
+]
27
 
27
 
28
-export { profileForm };
28
+export { profileForm }

+ 10
- 10
frontend/src/utils/index.js Bestand weergeven

1
-import Joi from "joi";
2
-import { Connector } from "./db";
1
+import Joi from 'joi'
2
+import { Connector } from './db'
3
 
3
 
4
-const api = new Connector("kittens");
4
+const api = new Connector('kittens')
5
 
5
 
6
 const validatorMapping = {
6
 const validatorMapping = {
7
-  "input-string": Joi.string(),
8
-  "tag-cloud": Joi.string(),
9
-};
7
+    'input-string': Joi.string(),
8
+    'tag-cloud': Joi.string(),
9
+}
10
 
10
 
11
-const makeKebob = (input) => {
12
-  return input.toLowerCase().split(" ").join("-");
13
-};
11
+const makeKebob = input => {
12
+    return input.toLowerCase().split(' ').join('-')
13
+}
14
 
14
 
15
-export { api, validatorMapping, makeKebob };
15
+export { api, validatorMapping, makeKebob }

Laden…
Annuleren
Opslaan