You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

form.vue 3.6KB

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