Composables

useFieldValidation

Composable per validazione on-blur per campo con errori tradotti — il motore di DynamicFormLayouts.

Panoramica

useFieldValidation gestisce la validazione di campi multipli all'interno di un form. È il composable usato internamente da FormSectionRow (e quindi da DynamicFormLayouts) per ottenere il comportamento "validazione al blur con messaggio inline".

A differenza di useValidation che lavora su un singolo valore, useFieldValidation:

  • Mantiene una mappa fieldErrors[fieldName] = string[] per tutti i campi del form
  • Espone handleBlur(field) da agganciare al @blur del componente input
  • Traduce le chiavi i18n usando una funzione t passata dall'esterno (decoupling da @pzeta/vue-i18n)

API

import { useFieldValidation } from '@pzeta/vue-form'
import type { FieldDefinition } from '@pzeta/vue-form'

type TranslateFn = (key: string, params?: Record<string, string | number>) => string

export function useFieldValidation(
  formDataSource: Record<string, unknown> | (() => Record<string, unknown>),
  t: TranslateFn,
): {
  fieldErrors: Record<string, string[]>
  validateField: (field: FieldDefinition) => boolean
  isFieldValid: (field: FieldDefinition) => boolean
  handleBlur: (field: FieldDefinition) => void
  hasFieldError: (fieldName: string) => boolean
  getFieldError: (fieldName: string) => string | undefined
}

Parametri

ParametroTipoDescrizione
formDataSourceRecord<string, unknown> | (() => Record<string, unknown>)Dati reattivi del form. Accetta sia un oggetto reattivo direttamente, sia un getter () => Record<string, unknown>. Il getter è la forma corretta quando il caller riceve formData come prop: il parent può sostituire l'oggetto sottostante (re-init / reset / cambio schema) e senza getter la closure cattura il riferimento stantio leggendo sempre i valori iniziali
tTranslateFnFunzione di traduzione i18n — tipicamente t di useI18n() di @pzeta/vue-i18n

Valore di ritorno

ProprietàTipoDescrizione
fieldErrorsRecord<string, string[]>Mappa reattiva degli errori per campo (chiavi i18n raw, non tradotte)
validateField(field) => booleanValida un singolo campo, popola fieldErrors[field.name] e ritorna true se valido. Per campi con transform.read, valida il risultato di transform.read(formData) invece di formData[field.name]
isFieldValid(field) => booleanValidazione silenziosa: ritorna true/false senza popolare fieldErrors. Usata per calcolare la validità reattiva del form (es. abilitazione del pulsante "Crea") senza forzare la visualizzazione degli errori prima dell'interazione utente
handleBlur(field) => voidWrapper di validateField da agganciare a @blur
hasFieldError(fieldName) => booleantrue se il campo ha almeno un errore
getFieldError(fieldName) => string | undefinedPrimo errore del campo già tradotto tramite t()
Validazione silenziosa vs visibile: validateField è "rumoroso" (popola fieldErrors → l'errore appare in UI), isFieldValid è "silenzioso" (solo ritorno booleano). Il pattern usato da DynamicFormLayouts è:
  • isFieldValid per calcolare lo stato update:valid aggregato (silent — il pulsante save si abilita/disabilita senza mostrare errori)
  • validateField solo on blur (in modalità edit) o on submit (validate())

Esempi

Uso diretto in un componente custom

Se stai costruendo un form custom (non DynamicFormLayouts) e vuoi la stessa logica di validazione on-blur:

<script setup lang="ts">
import { reactive } from 'vue'
import { useFieldValidation } from '@pzeta/vue-form'
import { useI18n } from '@pzeta/vue-i18n'
import { InputText } from '@pzeta/vue-components'
import type { FieldDefinition } from '@pzeta/vue-form'

const { t } = useI18n()

const formData = reactive<Record<string, unknown>>({
  nome: '',
  email: ''
})

const schema: FieldDefinition[] = [
  { name: 'nome', type: 'text', label: 'Nome', validation: 'required|min:2', required: true },
  { name: 'email', type: 'text', label: 'Email', validation: 'required|email', required: true }
]

const { hasFieldError, getFieldError, handleBlur, validateField } = useFieldValidation(formData, t)

function handleSubmit() {
  const allValid = schema.every(field => validateField(field))
  if (allValid) {
    // Procedi con submit
    console.log('Submit:', formData)
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit">
    <InputText
      v-for="field in schema"
      :key="field.name"
      v-model="formData[field.name]"
      :label="field.label"
      :required="field.required"
      :invalid="hasFieldError(field.name)"
      :error="getFieldError(field.name)"
      @blur="handleBlur(field)"
    />
    <button type="submit">Salva</button>
  </form>
</template>

Validazione manuale on-submit

Quando l'utente clicca "Salva", validi tutti i campi insieme:

function validateAll(fields: FieldDefinition[]): boolean {
  return fields.every(field => validateField(field))
}

Dopo la chiamata, fieldErrors contiene gli errori di tutti i campi e l'UI può mostrarli inline grazie a getFieldError.

Reset errori

import { useFieldValidation } from '@pzeta/vue-form'

const { fieldErrors } = useFieldValidation(formData, t)

function resetErrori() {
  Object.keys(fieldErrors).forEach(key => {
    fieldErrors[key] = []
  })
}

Comportamento di validateField

  1. Legge il valore UI del campo:
    • Se field.transform?.read è definito: field.transform.read(formData) (campo virtuale)
    • Altrimenti: formData[field.name]
  2. Combina automaticamente required con field.validation se field.required === true e la regola required non è già presente
  3. Chiama ValidationService.validate(value, allRules) di ValidationService
  4. Aggiorna fieldErrors[field.name] con l'array di chiavi errore
  5. Ritorna result.isValid

Differenza con useValidation

AspettouseValidationuseFieldValidation
ScopeSingolo valore (Ref)Form completo (Record con N campi)
Watch automatico✅ on Ref change❌ — solo on-blur via handleBlur
Output erroriRef<string[]> (chiavi raw)Mappa per campo + getter tradotto via t
Traduzione i18nEsterna (chiamata dall'utente)Integrata via parametro t
Uso tipicoValidazione di campo singolo isolatoForm multi-campo con feedback inline

Note

  • Le chiavi errore in fieldErrors sono non tradotte (es. validation.core.required|min:3). Il primo errore tradotto si ottiene via getFieldError(fieldName).
  • La funzione t può essere qualsiasi implementazione compatibile con la firma (key, params?) => string — non sei obbligato a usare @pzeta/vue-i18n.
  • Il formData deve essere reattivo. Se ricevuto come prop, passa un getter () => props.formData invece dell'oggetto: senza getter la closure cattura il riferimento iniziale e la validazione legge i valori obsoleti dopo un re-init/reset del parent.
  • Per campi virtuali (con field.transform.read) la validazione viene applicata al valore calcolato — es. un campo _datetimeinizio virtuale che combina datascadenza + orarioinizio, la regola required valuta il risultato della concatenazione, non i singoli campi sottostanti.