DynamicFormListDialog
Panoramica
DynamicFormListDialog è il dialog ideale per gestire collezioni embedded — liste di oggetti già presenti nei dati del parent, dove servono operazioni rapide di aggiunta e rimozione senza un endpoint CRUD dedicato.
A differenza di DynamicFormDrawerWithTable:
- Non fa data fetching: il parent passa la lista
itemse riceve eventiadd/remove - Non ha colonne configurabili: la lista è renderizzata con un template semplice (slot
#itemper personalizzare) - No endpoint backend: tutta la persistenza è responsabilità del parent (es. salvataggio in un ref locale + commit globale al submit del form padre)
Esempi tipici:
- Lista di regole di concatenamento di un tipo attività
- Lista di partecipanti a una riunione
- Lista di assegnatari embedded in un record principale
- Lista di tag/etichette personalizzati
Props
| Prop | Tipo | Default | Descrizione |
|---|---|---|---|
visible | boolean | — | Richiesto. Visibilità dialog (v-model:visible) |
items | T[] | [] | Record esistenti renderizzati come lista |
schema | FormSchema | — | Richiesto. Schema del form di aggiunta |
header | string | — | Titolo del dialog |
keyField | keyof T | 'id' | Chiave usata per :key del v-for |
displayField | keyof T | 'nome' | Campo usato per il testo della riga (fallback se non passi #item) |
emptyMessage | string | '' | Messaggio mostrato quando items è vuoto (override dello slot #empty) |
addLabel | string | — | Etichetta del pulsante "Aggiungi" — di default usa la traduzione ui.common.create |
modal | boolean | true | Dialog modale (overlay) |
dismissableMask | boolean | false | Chiude il dialog al click sull'overlay esterno. Default false per evitare perdita accidentale di dati compilati nel form di aggiunta |
closeOnEscape | boolean | true | Chiude il dialog alla pressione di Escape |
width | string | '500px' | Larghezza CSS del dialog |
showDelete | boolean | true | Mostra pulsante elimina su ogni riga |
resetOnAdd | boolean | true | Resetta il form dopo ogni aggiunta riuscita (bumpa la :key del DynamicFormLayouts interno) |
DynamicFormListDialog<T extends Record<string, unknown>>. TypeScript inferisce T dal tipo passato a items, garantendo type safety in keyField/displayField/eventi.Eventi
| Evento | Payload | Descrizione |
|---|---|---|
update:visible | boolean | Cambio visibilità (compatibile v-model:visible) |
add | Record<string, unknown> | Submit del form di aggiunta (solo se valid) |
remove | T | Click sul pulsante elimina di una riga |
items direttamente. È responsabilità del parent gestire add/remove aggiornando l'array sorgente. Questo permette controlli pre-add (validazione duplicati, conferma utente, fetch lookup) e annullamenti.Slots
| Slot | Slot props | Descrizione |
|---|---|---|
item | { item: T } | Override del rendering della singola riga |
empty | — | Override del messaggio "lista vuota" (override di emptyMessage) |
field-{name}-{childSlot} | (forwardati al DynamicFormLayouts interno) | Espone lo slot {childSlot} del componente del campo {name} nel form di aggiunta. Vedi DynamicFormLayouts → Slot field-{name}-{childSlot} |
section-before-{idx} | { values, valid, dirty, isEdit, submitting, sectionIndex } | Forwardato al DynamicFormLayouts interno (form di aggiunta) — contenuto prima della sezione {idx} |
section-after-{idx} | { values, valid, dirty, isEdit, submitting, sectionIndex } | Forwardato al DynamicFormLayouts interno (form di aggiunta) — contenuto dopo la sezione {idx} |
item ed empty sono gestiti dal Dialog stesso. Tutti gli altri slot vengono forwardati al DynamicFormLayouts interno usato per il form di aggiunta — stesso pattern di DynamicFormDialog.Esempio: lista regole di concatenamento
<script setup lang="ts">
import { ref } from 'vue'
import { DynamicFormListDialog, field, row, section, form } from '@pzeta/vue-form'
interface Concatenamento {
id: number
tipoSuccessivo: string
intervalloGiorni: number
}
const tipo = ref({
nome: 'Telefonata',
concatenamenti: [
{ id: 1, tipoSuccessivo: 'Email', intervalloGiorni: 7 },
] as Concatenamento[],
})
const showDialog = ref(false)
const tipoOptions = [
{ label: 'Email', value: 'Email' },
{ label: 'Riunione', value: 'Riunione' },
{ label: 'Documento', value: 'Documento' },
]
const formSchema = form([
section([
row([
field.required(field.select('tipoSuccessivo', 'Tipo successivo', tipoOptions, {
span: 8,
props: { optionLabel: 'label', optionValue: 'value' },
})),
field.required(field.number('intervalloGiorni', 'Giorni', {
span: 4,
validation: 'required|numeric|positive',
})),
]),
]),
])
let nextId = 2
function handleAdd(data: Record<string, unknown>) {
tipo.value.concatenamenti.push({
id: nextId++,
tipoSuccessivo: data.tipoSuccessivo as string,
intervalloGiorni: data.intervalloGiorni as number,
})
}
function handleRemove(item: Concatenamento) {
tipo.value.concatenamenti = tipo.value.concatenamenti.filter(c => c.id !== item.id)
}
</script>
<template>
<Button label="Gestisci concatenamenti" @click="showDialog = true" />
<DynamicFormListDialog
v-model:visible="showDialog"
:items="tipo.concatenamenti"
:schema="formSchema"
header="Concatenamenti"
key-field="id"
display-field="tipoSuccessivo"
@add="handleAdd"
@remove="handleRemove"
>
<template #item="{ item }">
<span>
{{ item.tipoSuccessivo }}
<small class="text-muted">+ {{ item.intervalloGiorni }} giorni</small>
</span>
</template>
</DynamicFormListDialog>
</template>
Esempio: con conferma di rimozione
<script setup lang="ts">
import { useConfirm } from '@pzeta/vue-components'
const confirm = useConfirm()
function handleRemove(item: Concatenamento) {
confirm.require({
message: `Eliminare il concatenamento con ${item.tipoSuccessivo}?`,
accept: () => {
tipo.value.concatenamenti = tipo.value.concatenamenti.filter(c => c.id !== item.id)
},
})
}
</script>
Differenze vs altri Dialog/Drawer
| Caso d'uso | Componente |
|---|---|
| Form modale singolo (crea/modifica) | DynamicFormDialog |
| Drawer laterale con auto-fetch del record | DynamicFormDrawer |
| CRUD completo con tabella + drawer + endpoint | DynamicFormDrawerWithTable |
| Collezione embedded già in memoria | DynamicFormListDialog |
DynamicFormListDialog non sostituisce DynamicFormDrawerWithTable — coprono scenari diversi:
| Aspetto | ListDialog | DrawerWithTable |
|---|---|---|
| Data source | Prop items (in memoria) | fetchFn + endpoint backend |
| Form di aggiunta | Inline nel dialog | Drawer separato |
| Operazioni | add + remove (via evento) | CRUD completo verso API |
| Persistenza | Responsabilità del parent | Gestita dal componente |
TypeScript
import { DynamicFormListDialog } from '@pzeta/vue-form'
import type { DynamicFormListDialogProps } from '@pzeta/vue-form'
Il componente è generico su T: DynamicFormListDialog<T> con T extends Record<string, unknown>.
DynamicFormLayouts
Form dinamico con validazione on-blur, errori inline per campo, modalità tabs/wizard/accordion, condizioni dinamiche, esclusione campi e propagazione disabled/readonly via provide.
FormSection
Container sezione con header (titolo + sottotitolo + icona), layout vertical/horizontal/grid e comportamento collassabile.