Components

SelectRemote

Dropdown con caricamento opzioni da sorgente remota, filtri dinamici e template personalizzabile.

Panoramica

SelectRemote è un componente dropdown che carica le opzioni in modo asincrono tramite una fetchFn. Supporta filtri dinamici basati su altri campi del form, template personalizzati per le etichette e immagini nelle opzioni.

Caratteristiche principali:

  • Caricamento asincrono opzioni tramite fetchFn
  • In alternativa a fetchFn, la chiamata PostgREST può essere composta dal componente dichiarando la risorsa: Sorgente dati endpoint
  • Filtri dinamici: quando i valori di altri campi cambiano, i dati vengono ricaricati automaticamente (filterFields + fullData)
  • Template etichette con interpolazione (optionTemplate)
  • Immagini nelle opzioni (optionImage)
  • Ricerca/filtro integrato

Casi d'uso tipici: selezione categorie, comuni/province, utenti, prodotti, qualsiasi lookup su API.

Anteprima live (con fetchFn mockato)

Nel preview qui sotto la fetchFn simula una chiamata API ritornando un array statico — in produzione la stessa funzione effettua un fetch() verso il backend.

Pattern fetchFn

type FetchFn<T = Record<string, unknown>> = (
  filters?: FilterCriteria,
  signal?: AbortSignal
) => Promise<T[]>

// Esempio base
const fetchCategorie = async () => {
  const res = await fetch('/api/categorie')
  return res.json()
}

// Con filtri passati dal composable
const fetchComuni = async (filters?: FilterCriteria) => {
  const params = new URLSearchParams()
  if (filters?.provinciaId) params.set('provinciaId', String(filters.provinciaId.value))
  const res = await fetch(`/api/comuni?${params}`)
  return res.json()
}

Utilizzo nello schema

const schema = [{
  name: 'idCategoria',
  type: 'selectremote',
  label: 'Categoria',
  props: {
    fetchFn: fetchCategorie,
    optionLabel: 'nome',
    optionValue: 'id',
    showFilter: true,
    showClear: true,
  }
}, {
  // Filtro dinamico: ricarica comuni quando cambia la provincia
  name: 'idComune',
  type: 'selectremote',
  label: 'Comune',
  props: {
    fetchFn: fetchComuni,
    optionLabel: 'nome',
    optionValue: 'id',
    filterFields: ['idProvincia'],   // Osserva questo campo
    // fullData viene iniettato automaticamente da DynamicFormLayouts
  }
}, {
  // Con template personalizzato per etichetta
  name: 'idUtente',
  type: 'selectremote',
  label: 'Assegna a',
  props: {
    fetchFn: fetchUtenti,
    optionTemplate: '{cognome} {nome} ({email})',
    optionValue: 'id',
  }
}]

Utilizzo diretto

<SelectRemote
  v-model="idSelezionato"
  :fetch-fn="fetchCategorie"
  option-label="nome"
  option-value="id"
  label="Categoria"
  show-filter
  show-clear
/>

Con filtro dinamico (fuori DynamicFormLayouts)

<SelectRemote
  v-model="idComune"
  :fetch-fn="fetchComuni"
  option-label="nome"
  option-value="id"
  :filter-fields="['idProvincia']"
  :full-data="formData"
  label="Comune"
/>

Con immagini nelle opzioni

<SelectRemote
  v-model="idPaese"
  :fetch-fn="fetchPaesi"
  option-label="nome"
  option-value="codice"
  option-image="urlBandiera"
  label="Paese"
/>

Sorgente dati endpoint

In alternativa a fetchFn, SelectRemote può comporre da sé la chiamata PostgREST a partire dalla risorsa dichiarata:

props: {
  endpoint: 'anagrafica.clienti',
  select: 'idcliente,ragionesociale,idprovincia',
  order: 'ragionesociale.asc',
  filterFields: ['idprovincia'],
  optionLabel: 'ragionesociale',
  optionValue: 'idcliente'
}

fetchFn ha sempre la precedenza: se sono presenti entrambe, endpoint viene ignorato. La forma dell'endpoint, la traduzione dei filtri e l'ordine con cui viene risolto il trasporto sono descritti in Sorgente dati endpoint.

Props

PropTipoDefaultDescrizione
modelValuestring | number | boolean | nullnullValore selezionato (v-model)
labelstringEtichetta del campo
helperTextstringTesto di supporto sotto il campo
errorstringMessaggio di errore (attiva stato invalid)
disabledbooleanfalseDisabilita il campo
invalidbooleanfalseAttiva visivamente lo stato di errore
requiredbooleanfalseIndica campo obbligatorio
fetchFnFetchFnFunzione asincrona per caricare le opzioni
filterstring | FilterCriteriaFiltro statico passato alla fetchFn
filterFieldsRemoteFilterField[][]Campi del form da osservare per filtri dinamici; la forma estesa { field, column, operator } permette colonna e operatore diversi
fullDataRecord<string, unknown>{}Dati completi del form (per filtri dinamici)
endpointstringRisorsa PostgREST [schema.]risorsa, alternativa a fetchFn — vedi Sorgente dati endpoint
selectstringColonne da selezionare (solo con endpoint)
orderstringOrdinamento PostgREST, es. 'nome.asc' (solo con endpoint)
limitnumberNumero massimo di righe restituite (solo con endpoint)
baseUrlstring''Prefisso del gateway PostgREST, se l'endpoint dichiara uno schema
keyNamestring'id'Chiave primaria della risorsa (solo client PostgREST standalone)
optionLabelstring'label'Proprietà dell'oggetto da usare come etichetta
optionTemplatestringTemplate interpolato per l'etichetta, es. '{nome} ({codice})'
optionValuestring'value'Proprietà dell'oggetto da usare come valore
optionImagestringProprietà dell'oggetto contenente URL immagine
showClearbooleanfalseMostra pulsante per resettare la selezione
showFilterbooleanfalseAbilita ricerca nel dropdown
filterPlaceholderstring(da i18n)Placeholder del campo di ricerca

Metodi esposti

MetodoDescrizione
reload()Ricarica i dati chiamando nuovamente la fetchFn
<SelectRemote ref="selectRef" :fetch-fn="fetchCategorie" ... />

// Nel codice
selectRef.value?.reload()

Eventi

EventoPayloadDescrizione
update:modelValuestring | number | boolean | nullValore aggiornato

Validazione suggerita

import { z } from 'zod'

const schema = z.object({
  idCategoria: z.number({ required_error: 'Seleziona una categoria' })
    .positive('ID categoria non valido')
    .nullable()
    .refine(v => v !== null, { message: 'La categoria è obbligatoria' }),
})