|
|
@@ -1,6 +1,9 @@
|
|
|
<!--
|
|
|
Formulaire générique
|
|
|
|
|
|
+Assure la validation des données, les actions de base (enregistrement, annulation, ...), et la confirmation avant
|
|
|
+de quitter si des données ont été modifiées.
|
|
|
+
|
|
|
@see https://vuetifyjs.com/en/components/forms/#usage
|
|
|
-->
|
|
|
|
|
|
@@ -48,7 +51,7 @@ Formulaire générique
|
|
|
|
|
|
<!-- Confirmation dialog -->
|
|
|
<LazyLayoutDialog
|
|
|
- :show="showDialog"
|
|
|
+ :show="isConfirmationDialogShowing"
|
|
|
>
|
|
|
<template #dialogText>
|
|
|
<v-card-title class="text-h5 theme-neutral">
|
|
|
@@ -60,7 +63,7 @@ Formulaire générique
|
|
|
</v-card-text>
|
|
|
</template>
|
|
|
<template #dialogBtn>
|
|
|
- <v-btn class="mr-4 submitBtn theme-primary" @click="closeDialog">
|
|
|
+ <v-btn class="mr-4 submitBtn theme-primary" @click="closeConfirmationDialog">
|
|
|
{{ $t('back_to_form') }}
|
|
|
</v-btn>
|
|
|
<v-btn class="mr-4 submitBtn theme-primary" @click="saveAndQuit">
|
|
|
@@ -88,18 +91,30 @@ import {AnyJson} from "~/types/data";
|
|
|
import * as _ from 'lodash-es'
|
|
|
|
|
|
const props = defineProps({
|
|
|
+ /**
|
|
|
+ * Classe de l'ApiModel (ex: Organization, Notification, ...)
|
|
|
+ */
|
|
|
model: {
|
|
|
type: Function as any as () => typeof ApiModel,
|
|
|
required: true
|
|
|
},
|
|
|
+ /**
|
|
|
+ * Instance de l'objet
|
|
|
+ */
|
|
|
entity: {
|
|
|
type: Object as () => ApiModel,
|
|
|
required: true
|
|
|
},
|
|
|
+ /**
|
|
|
+ * TODO: compléter
|
|
|
+ */
|
|
|
onChanged: {
|
|
|
type: Function,
|
|
|
required: false
|
|
|
},
|
|
|
+ /**
|
|
|
+ * Types de soumission disponibles (enregistrer / enregistrer et quitter)
|
|
|
+ */
|
|
|
submitActions: {
|
|
|
type: Object,
|
|
|
required: false,
|
|
|
@@ -111,203 +126,225 @@ const props = defineProps({
|
|
|
}
|
|
|
})
|
|
|
|
|
|
-const { i18n } = useNuxtApp()
|
|
|
+// ### Définitions
|
|
|
+
|
|
|
+const i18n = useI18n()
|
|
|
const router = useRouter()
|
|
|
const { em } = useEntityManager()
|
|
|
|
|
|
+// Le formulaire est-il valide
|
|
|
const isValid: Ref<boolean> = ref(true)
|
|
|
+
|
|
|
+// Erreurs de validation
|
|
|
const errors: Ref<Array<string>> = ref([])
|
|
|
|
|
|
-/**
|
|
|
- * Référence au component v-form
|
|
|
- */
|
|
|
+// Référence au component v-form
|
|
|
const form: Ref = ref(null)
|
|
|
|
|
|
+// Le formulaire est-il en lecture seule
|
|
|
const readonly: ComputedRef<boolean> = computed(() => {
|
|
|
return useFormStore().readonly
|
|
|
})
|
|
|
|
|
|
-/**
|
|
|
- * Utilise la méthode validate() de v-form pour valider le formulaire et mettre à jour les variables isValid et errors
|
|
|
- *
|
|
|
- * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
|
|
|
- */
|
|
|
-const validate = async function () {
|
|
|
- const validation = await form.value.validate()
|
|
|
-
|
|
|
- isValid.value = validation.valid
|
|
|
- errors.value = validation.errors
|
|
|
-}
|
|
|
+// La fenêtre de confirmation est-elle affichée
|
|
|
+const isConfirmationDialogShowing: ComputedRef<boolean> = computed(() => {
|
|
|
+ return useFormStore().showConfirmToLeave
|
|
|
+})
|
|
|
|
|
|
/**
|
|
|
- * Handle events if the form is dirty to prevent submission
|
|
|
- * @param e
|
|
|
+ * Ferme la fenêtre de confirmation
|
|
|
*/
|
|
|
-// TODO: voir si encore nécessaire avec le @submit.prevent
|
|
|
-const preventSubmit = (e: any) => {
|
|
|
- // Cancel the event
|
|
|
- e.preventDefault()
|
|
|
- // Chrome requires returnValue to be set
|
|
|
- e.returnValue = ''
|
|
|
+const closeConfirmationDialog = () => {
|
|
|
+ useFormStore().setShowConfirmToLeave(false)
|
|
|
}
|
|
|
|
|
|
+// ### Actions du formulaire
|
|
|
/**
|
|
|
- * Définit l'état dirty (modifié) du formulaire
|
|
|
+ * Soumet le formulaire
|
|
|
+ *
|
|
|
+ * @param next
|
|
|
*/
|
|
|
-const setIsDirty = (dirty: boolean) => {
|
|
|
- useFormStore().setDirty(dirty)
|
|
|
+const submit = async (next: string|null = null) => {
|
|
|
+ // Valide les données
|
|
|
+ await validate()
|
|
|
|
|
|
- // If dirty, add the preventSubmit event listener
|
|
|
- // TODO: voir si encore nécessaire avec le @submit.prevent
|
|
|
- if (process.browser) {
|
|
|
- if (dirty) {
|
|
|
- window.addEventListener('beforeunload', preventSubmit)
|
|
|
- } else {
|
|
|
- window.removeEventListener('beforeunload', preventSubmit)
|
|
|
+ if (!isValid.value) {
|
|
|
+ usePageStore().addAlert(TYPE_ALERT.ALERT, ['invalid_form'])
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // TODO: est-ce qu'il faut re-fetch l'entité après le persist?
|
|
|
+ const updatedEntity = await em.persist(props.model, props.entity)
|
|
|
+
|
|
|
+ usePageStore().addAlert(TYPE_ALERT.SUCCESS, ['saveSuccess'])
|
|
|
+
|
|
|
+ // On retire l'état 'dirty'
|
|
|
+ setIsDirty(false)
|
|
|
+
|
|
|
+ afterSubmissionAction(next, updatedEntity)
|
|
|
+
|
|
|
+ } catch (error: any) {
|
|
|
+
|
|
|
+ if (error.response.status === 422 && error.response.data['violations']) {
|
|
|
+
|
|
|
+ // TODO: à revoir
|
|
|
+ const violations: Array<string> = []
|
|
|
+ let fields: AnyJson = {}
|
|
|
+
|
|
|
+ for (const violation of error.response.data['violations']) {
|
|
|
+ violations.push(i18n.t(violation['message']) as string)
|
|
|
+ fields = Object.assign(fields, {[violation['propertyPath']] : violation['message']})
|
|
|
+ }
|
|
|
+
|
|
|
+ useFormStore().addViolation(fields)
|
|
|
+
|
|
|
+ usePageStore().addAlert(TYPE_ALERT.ALERT, ['invalid_form'])
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-watch(props.entity, async (newEntity, oldEntity) => {
|
|
|
- await onFormChange()
|
|
|
-})
|
|
|
-
|
|
|
/**
|
|
|
- * Update store when form is changed (if valid)
|
|
|
+ * Enregistre et quitte
|
|
|
*/
|
|
|
-const onFormChange = async () => {
|
|
|
- console.log('form save')
|
|
|
+const saveAndQuit = async () => {
|
|
|
+ await submit()
|
|
|
+ quitForm()
|
|
|
+}
|
|
|
|
|
|
- await validate()
|
|
|
+/**
|
|
|
+ * Retourne l'action à effectuer après la soumission du formulaire
|
|
|
+ * @param action
|
|
|
+ * @param updatedEntity
|
|
|
+ */
|
|
|
+const afterSubmissionAction = (action: string | null, updatedEntity: AnyJson) => {
|
|
|
+ if (action === null) {
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- if (isValid.value) {
|
|
|
- em.save(props.model, props.entity)
|
|
|
- setIsDirty(true)
|
|
|
+ const actionArgs = props.submitActions[action]
|
|
|
|
|
|
- if (props.onChanged) {
|
|
|
- // Execute the custom onChange method, if defined
|
|
|
- // TODO: voir quelles variables passer à cette méthode custom ; d'ailleurs, vérifier aussi si cette méthode est utilisée
|
|
|
- props.onChanged()
|
|
|
- }
|
|
|
+ if (action === SUBMIT_TYPE.SAVE) {
|
|
|
+ afterSaveAction(actionArgs, updatedEntity.id, router)
|
|
|
+ } else if (action === SUBMIT_TYPE.SAVE_AND_BACK) {
|
|
|
+ afterSaveAndQuitAction(actionArgs, router)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-// <--- TODO: revoir les 4 méthodes qui suivent
|
|
|
/**
|
|
|
- * Action Sauvegarder qui redirige vers la page d'édition si on est en mode create
|
|
|
+ * Après l'action Sauvegarder
|
|
|
+ *
|
|
|
+ * Si on était en mode édition, on reste sur cette page (on ne fait rien).
|
|
|
+ * Si on était en mode création, on bascule sur le mode édition
|
|
|
+ *
|
|
|
* @param route
|
|
|
* @param id
|
|
|
* @param router
|
|
|
*/
|
|
|
-function save(route: Route, id: number, router: any){
|
|
|
- if(useFormStore().formFunction === FORM_FUNCTION.CREATE){
|
|
|
+function afterSaveAction(route: Route, id: number, router: any){
|
|
|
+ if (useFormStore().formFunction === FORM_FUNCTION.CREATE) {
|
|
|
route.path += id
|
|
|
router.push(route)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Action sauvegarder et route suivante qui redirige vers une route
|
|
|
+ * Après l'action Sauvegarder et Quitter
|
|
|
+ *
|
|
|
+ * On redirige vers la route donnée
|
|
|
+ *
|
|
|
* @param route
|
|
|
* @param router
|
|
|
*/
|
|
|
-function saveAndGoTo(route: Route, router: any){
|
|
|
+function afterSaveAndQuitAction(route: Route, router: any){
|
|
|
router.push(route)
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Factory des fonctions permettant d'assurer l'étape suivant à la soumission d'un formulaire
|
|
|
- *
|
|
|
- * @param args
|
|
|
- * @param response
|
|
|
- * @param router
|
|
|
+ * Quitte le formulaire sans enregistrer
|
|
|
*/
|
|
|
-function nextStepFactory(args: any, response: AnyJson, router: any){
|
|
|
- const factory: AnyJson = {}
|
|
|
- factory[SUBMIT_TYPE.SAVE] = () => save(args, response.id, router)
|
|
|
- factory[SUBMIT_TYPE.SAVE_AND_BACK] = () => saveAndGoTo(args, router)
|
|
|
- return factory
|
|
|
-}
|
|
|
+const quitForm = () => {
|
|
|
+ setIsDirty(false)
|
|
|
|
|
|
-const nextStep = (next: string | null, response: AnyJson) => {
|
|
|
- if (next === null)
|
|
|
- return
|
|
|
- nextStepFactory(props.submitActions[next], response, router)[next]()
|
|
|
-}
|
|
|
+ useFormStore().setShowConfirmToLeave(false)
|
|
|
+
|
|
|
+ em.reset(props.model, props.entity.value)
|
|
|
|
|
|
-// ---> Fin du todo
|
|
|
+ if (router) {
|
|
|
+ // @ts-ignore
|
|
|
+ router.push(useFormStore().goAfterLeave) // TODO: voir si on peut pas passer ça comme prop du component
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
+const actions = computed(()=>{
|
|
|
+ return _.keys(props.submitActions)
|
|
|
+})
|
|
|
|
|
|
+// #### Validation et store
|
|
|
/**
|
|
|
- * Soumet le formulaire
|
|
|
- *
|
|
|
- * @param next
|
|
|
+ * Update store when form is changed (if valid)
|
|
|
*/
|
|
|
-const submit = async (next: string|null = null) => {
|
|
|
+const onFormChange = async () => {
|
|
|
await validate()
|
|
|
|
|
|
- if (!isValid.value) {
|
|
|
- usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- setIsDirty(false)
|
|
|
-
|
|
|
- try {
|
|
|
- const updatedEntity = await em.persist(props.model, props.entity)
|
|
|
-
|
|
|
- usePageStore().addAlerts(TYPE_ALERT.SUCCESS, ['saveSuccess'])
|
|
|
-
|
|
|
- // nextStep(next, updatedEntity)
|
|
|
-
|
|
|
- } catch (error: any) {
|
|
|
-
|
|
|
- if (error.response.status === 422 && error.response.data['violations']) {
|
|
|
- const violations: Array<string> = [] // TODO: cette variable est-elle utile?
|
|
|
- let fields: AnyJson = {}
|
|
|
-
|
|
|
- for (const violation of error.response.data['violations']) {
|
|
|
- violations.push(i18n.t(violation['message']) as string)
|
|
|
- fields = Object.assign(fields, {[violation['propertyPath']] : violation['message']})
|
|
|
- }
|
|
|
-
|
|
|
- useFormStore().addViolations(fields)
|
|
|
+ if (isValid.value) {
|
|
|
+ em.save(props.model, props.entity)
|
|
|
+ setIsDirty(true)
|
|
|
|
|
|
- usePageStore().addAlerts(TYPE_ALERT.ALERT, ['invalid_form'])
|
|
|
+ if (props.onChanged) {
|
|
|
+ // Execute the custom onChange method, if defined
|
|
|
+ // TODO: voir quelles variables passer à cette méthode custom ; d'ailleurs, vérifier aussi si cette méthode est utilisée
|
|
|
+ props.onChanged()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-const showDialog: ComputedRef<boolean> = computed(() => {
|
|
|
- return useFormStore().showConfirmToLeave
|
|
|
-})
|
|
|
+/**
|
|
|
+ * Utilise la méthode validate() de v-form pour valider le formulaire et mettre à jour les variables isValid et errors
|
|
|
+ *
|
|
|
+ * @see https://vuetifyjs.com/en/api/v-form/#functions-validate
|
|
|
+ */
|
|
|
+const validate = async function () {
|
|
|
+ const validation = await form.value.validate()
|
|
|
|
|
|
-const closeDialog = () => {
|
|
|
- useFormStore().setShowConfirmToLeave(false)
|
|
|
+ isValid.value = validation.valid
|
|
|
+ errors.value = validation.errors
|
|
|
}
|
|
|
|
|
|
-const saveAndQuit = async () => {
|
|
|
- await submit()
|
|
|
- quitForm()
|
|
|
-}
|
|
|
|
|
|
-const quitForm = () => {
|
|
|
- setIsDirty(false)
|
|
|
+// #### Gestion de l'état dirty
|
|
|
+watch(props.entity, async (newEntity, oldEntity) => {
|
|
|
+ await onFormChange()
|
|
|
+})
|
|
|
|
|
|
- useFormStore().setShowConfirmToLeave(false)
|
|
|
+/**
|
|
|
+ * Handle events if the form is dirty to prevent submission
|
|
|
+ * @param e
|
|
|
+ */
|
|
|
+// TODO: voir si encore nécessaire avec le @submit.prevent
|
|
|
+const preventSubmit = (e: any) => {
|
|
|
+ // Cancel the event
|
|
|
+ e.preventDefault()
|
|
|
+ // Chrome requires returnValue to be set
|
|
|
+ e.returnValue = ''
|
|
|
+}
|
|
|
|
|
|
- em.reset(props.model, props.entity.value)
|
|
|
+/**
|
|
|
+ * Applique ou retire l'état dirty (modifié) du formulaire
|
|
|
+ */
|
|
|
+const setIsDirty = (dirty: boolean) => {
|
|
|
+ useFormStore().setDirty(dirty)
|
|
|
|
|
|
- if (router) {
|
|
|
- // @ts-ignore
|
|
|
- router.push(useFormStore().goAfterLeave)
|
|
|
+ // If dirty, add the preventSubmit event listener
|
|
|
+ // TODO: voir si encore nécessaire avec le @submit.prevent
|
|
|
+ if (process.browser) {
|
|
|
+ if (dirty) {
|
|
|
+ window.addEventListener('beforeunload', preventSubmit)
|
|
|
+ } else {
|
|
|
+ window.removeEventListener('beforeunload', preventSubmit)
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-const actions = computed(()=>{
|
|
|
- return _.keys(props.submitActions)
|
|
|
-})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|