Form.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. <!--
  2. Formulaire générique
  3. @see https://vuetifyjs.com/en/components/forms/#usage
  4. -->
  5. <template>
  6. <main>
  7. <v-form
  8. ref="form"
  9. lazy-validation
  10. :readonly="readonly"
  11. >
  12. <!-- Top action bar -->
  13. <v-container fluid class="container btnActions">
  14. <v-row>
  15. <v-col cols="12" sm="12">
  16. <slot name="form.button"/>
  17. <UiButtonSubmit
  18. v-if="!readonly"
  19. @submit="submit"
  20. :actions="actions"
  21. ></UiButtonSubmit>
  22. </v-col>
  23. </v-row>
  24. </v-container>
  25. <!-- Content -->
  26. <slot name="form.input" v-bind="{model, entity}"/>
  27. <!-- Bottom action bar -->
  28. <v-container fluid class="container btnActions">
  29. <v-row>
  30. <v-col cols="12" sm="12">
  31. <slot name="form.button"/>
  32. <UiButtonSubmit
  33. @submit="submit"
  34. :actions="actions"
  35. ></UiButtonSubmit>
  36. </v-col>
  37. </v-row>
  38. </v-container>
  39. </v-form>
  40. <!-- Confirmation dialog -->
  41. <LazyLayoutDialog
  42. :show="showDialog"
  43. >
  44. <template #dialogText>
  45. <v-card-title class="text-h5 grey lighten-2">
  46. {{ $t('caution') }}
  47. </v-card-title>
  48. <v-card-text>
  49. <br>
  50. <p>{{ $t('quit_without_saving_warning') }}</p>
  51. </v-card-text>
  52. </template>
  53. <template #dialogBtn>
  54. <v-btn class="mr-4 submitBtn ot_green ot_white--text" @click="closeDialog">
  55. {{ $t('back_to_form') }}
  56. </v-btn>
  57. <v-btn class="mr-4 submitBtn ot_green ot_white--text" @click="saveAndQuit">
  58. {{ $t('save_and_quit') }}
  59. </v-btn>
  60. <v-btn class="mr-4 submitBtn ot_danger ot_white--text" @click="quitForm">
  61. {{ $t('quit_form') }}
  62. </v-btn>
  63. </template>
  64. </LazyLayoutDialog>
  65. </main>
  66. </template>
  67. <script setup lang="ts">
  68. import {computed, ComputedRef, Ref} from "@vue/reactivity";
  69. import {AnyJson} from "~/types/data";
  70. import {FORM_FUNCTION, SUBMIT_TYPE, TYPE_ALERT} from "~/types/enums";
  71. import {useNuxtApp, useRouter} from "#app";
  72. import { useFormStore } from "~/store/form";
  73. import {Route} from "@intlify/vue-router-bridge";
  74. import {useEntityManager} from "~/composables/data/useEntityManager";
  75. import ApiModel from "~/models/ApiModel";
  76. import {usePageStore} from "~/store/page";
  77. const props = defineProps({
  78. model: {
  79. type: Object as () => typeof ApiModel,
  80. required: true
  81. },
  82. entity: {
  83. type: Object as () => Ref<ApiModel>,
  84. required: true
  85. },
  86. submitActions: {
  87. type: Object,
  88. required: false,
  89. default: () => {
  90. let actions: AnyJson = {}
  91. actions[SUBMIT_TYPE.SAVE] = {}
  92. return actions
  93. }
  94. }
  95. })
  96. const { i18n } = useNuxtApp()
  97. const router = useRouter()
  98. const { em } = useEntityManager()
  99. /**
  100. * Réference au component v-form
  101. */
  102. const form: Ref<any> = ref(null)
  103. /**
  104. * Handle events if the form is dirty to prevent submission
  105. * @param e
  106. */
  107. const preventingEventHandler = (e: any) => {
  108. // Cancel the event
  109. e.preventDefault()
  110. // Chrome requires returnValue to be set
  111. e.returnValue = ''
  112. }
  113. /**
  114. * Définit l'état dirty (modifié) du formulaire
  115. * // TODO: renommer, on ne se rend pas compte au nom que cette méthode fait plus que changer l'état dirty
  116. */
  117. const setIsDirty = (dirty: boolean) => {
  118. useFormStore().setDirty(dirty)
  119. if (process.browser) {
  120. if (dirty) {
  121. window.addEventListener('beforeunload', preventingEventHandler)
  122. } else {
  123. window.removeEventListener('beforeunload', preventingEventHandler)
  124. }
  125. }
  126. }
  127. const readonly: ComputedRef<boolean> = computed(() => {
  128. return useFormStore().readonly
  129. })
  130. // <--- TODO: revoir
  131. /**
  132. * Action Sauvegarder qui redirige vers la page d'edition si on est en mode create
  133. * @param route
  134. * @param id
  135. * @param router
  136. */
  137. function save(route: Route, id: number, router: any){
  138. if(useFormStore().formFunction === FORM_FUNCTION.CREATE){
  139. route.path += id
  140. router.push(route)
  141. }
  142. }
  143. /**
  144. * Action sauvegarder et route suivante qui redirige vers une route
  145. * @param route
  146. * @param router
  147. */
  148. function saveAndGoTo(route: Route, router: any){
  149. router.push(route)
  150. }
  151. /**
  152. * Factory des fonction permettant d'assurer l'étape suivant à la soumission d'un formulaire
  153. *
  154. * @param args
  155. * @param response
  156. * @param router
  157. */
  158. function nextStepFactory(args: any, response: AnyJson, router: any){
  159. const factory: AnyJson = {}
  160. factory[SUBMIT_TYPE.SAVE] = () => save(args, response.id, router)
  161. factory[SUBMIT_TYPE.SAVE_AND_BACK] = () => saveAndGoTo(args, router)
  162. return factory
  163. }
  164. const nextStep = (next: string | null, response: AnyJson) => {
  165. if (next === null)
  166. return
  167. nextStepFactory(props.submitActions[next], response, router)[next]()
  168. }
  169. // --->
  170. /**
  171. * Utilise la méthode validate() de v-form pour valider le formulaire
  172. *
  173. * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
  174. */
  175. const validate = function () {
  176. return form.value.validate()
  177. }
  178. /**
  179. * Soumet le formulaire
  180. *
  181. * @param next
  182. */
  183. const submit = async (next: string|null = null) => {
  184. if (!await validate()) {
  185. usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
  186. return
  187. }
  188. setIsDirty(false)
  189. try {
  190. const updatedEntity = await em.persist(props.model, props.entity.value)
  191. usePageStore().addAlerts(TYPE_ALERT.SUCCESS, ['saveSuccess'])
  192. nextStep(next, updatedEntity)
  193. } catch (error: any) {
  194. if (error.response.status === 422 && error.response.data['violations']) {
  195. const violations: Array<string> = []
  196. let fields: AnyJson = {}
  197. for (const violation of error.response.data['violations']) {
  198. violations.push(i18n.t(violation['message']) as string)
  199. fields = Object.assign(fields, {[violation['propertyPath']] : violation['message']})
  200. }
  201. useFormStore().addViolations(fields)
  202. usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
  203. }
  204. }
  205. }
  206. const showDialog: ComputedRef<boolean> = computed(() => {
  207. return useFormStore().showConfirmToLeave
  208. })
  209. const closeDialog = () => {
  210. useFormStore().setShowConfirmToLeave(false)
  211. }
  212. const saveAndQuit = async () => {
  213. await submit()
  214. quitForm()
  215. }
  216. const quitForm = () => {
  217. setIsDirty(false)
  218. useFormStore().setShowConfirmToLeave(false)
  219. em.reset(props.model, props.entity.value)
  220. if (router) {
  221. // @ts-ignore
  222. router.push(useFormStore().goAfterLeave)
  223. }
  224. }
  225. const actions = computed(()=>{
  226. return useKeys(props.submitActions)
  227. })
  228. </script>
  229. <style scoped>
  230. .btnActions {
  231. text-align: right;
  232. }
  233. </style>