Composables
useDetailForm
Gestione stato form di dettaglio. Dirty tracking, loading guard, canSave, wrapper saving e reset.
Signature
function useDetailForm(options?: UseDetailFormOptions): UseDetailFormResult
Astrae lo stato base di un form di dettaglio: loading, isFormDirty, canSave derivato, wrapper saving() per operazioni async.
Opzioni UseDetailFormOptions
interface UseDetailFormOptions {
initialLoading?: boolean
canDelete?: boolean
}
| Opzione | Tipo | Default | Descrizione |
|---|---|---|---|
initialLoading | boolean | false | Valore iniziale di loading (utile se il form parte caricando un record) |
canDelete | boolean | true | Flag statico — esposto come result.canDelete. Tipicamente derivato da permessi |
Risultato UseDetailFormResult
interface UseDetailFormResult {
loading: Ref<boolean>
isFormDirty: Ref<boolean>
canDelete: boolean
canSave: ComputedRef<boolean>
saving: (operation: () => Promise<void>) => Promise<void>
markDirty: () => void
markClean: () => void
reset: () => void
}
| Campo | Descrizione |
|---|---|
loading | Ref<boolean> di stato caricamento/salvataggio |
isFormDirty | Ref<boolean> — true se ci sono modifiche non salvate |
canDelete | Boolean statico (passato in opzioni) |
canSave | computed(() => isFormDirty.value && !loading.value) |
saving(op) | Wrapper: imposta loading = true, esegue op(), in finally riporta loading = false. Non gestisce errori: catcha tu nel chiamante |
markDirty() | isFormDirty = true |
markClean() | isFormDirty = false (chiamare dopo save riuscito) |
reset() | Riporta loading a initialLoading e isFormDirty = false |
Esempio completo
<script setup lang="ts">
import { reactive, watch } from 'vue'
import { useMfeContext, useDetailForm } from '@pzeta/mfe-contracts/composables'
const props = defineProps<{ clienteId: string }>()
const ctx = useMfeContext()
const form = reactive({
ragioneSociale: '',
piva: '',
email: '',
})
const {
loading,
isFormDirty,
canSave,
saving,
markDirty,
markClean,
reset,
} = useDetailForm({ initialLoading: true })
// Carica record iniziale
await saving(async () => {
const { data } = await ctx.http.get<Cliente>(`/api/clienti/${props.clienteId}`)
Object.assign(form, data)
markClean() // reset dirty dopo load
})
// Marca dirty al primo cambio
watch(form, () => markDirty(), { deep: true })
async function onSave() {
try {
await saving(async () => {
await ctx.http.put(`/api/clienti/${props.clienteId}`, form)
markClean()
})
} catch (err) {
console.error('Save failed:', err)
// mostra toast errore...
}
}
</script>
<template>
<form @submit.prevent="onSave">
<input v-model="form.ragioneSociale" placeholder="Ragione sociale" />
<input v-model="form.piva" placeholder="P.IVA" />
<input v-model="form.email" placeholder="Email" />
<button type="submit" :disabled="!canSave">
{{ loading ? 'Salvataggio...' : 'Salva' }}
</button>
<button type="button" @click="reset">Annulla modifiche</button>
</form>
</template>
Pattern: prompt "Conferma uscita"
Combina con MFEModule.onBeforeUnmount o con un router guard per intercettare la navigazione mentre il form è dirty:
import { onBeforeRouteLeave } from 'vue-router'
const { isFormDirty } = useDetailForm()
onBeforeRouteLeave((to, from, next) => {
if (isFormDirty.value && !confirm('Hai modifiche non salvate. Continuare?')) {
next(false)
} else {
next()
}
})
Pattern: disabilita Save mentre carica
canSave è già computed, quindi rispecchia automaticamente sia isFormDirty sia loading:
isFormDirty | loading | canSave |
|---|---|---|
false | false | false (niente da salvare) |
true | false | true ✓ |
false | true | false |
true | true | false (operazione in corso) |
Note
saving()non gestisce gli errori: l'eccezione si propaga al chiamante. Usatry/catcho un wrapperuseErrorToast.canDeleteè statico (nonRef): se i permessi cambiano durante la vita del form, ricalcolalo passando un nuovouseDetailForm({ canDelete: hasPermission })oppure gestiscilo separatamente con uncomputedesterno.
Tipi correlati
useDialog— gestione dialog (overlap parziale)useCrudView— alternativa per liste con dialog modale