Docs›Formulários›Field-list
Formulários

Field-list

Linhas dinâmicas inline: colunas, on-change, upload por linha e props condicionais por-row.

Field-list é uma tabela editável inline dentro de um formulário — adicionar/remover linhas dinamicamente, com validação por linha, autocomplete e callbacks ao mudar o valor de uma célula. Diferente do detail-form (que persiste em tabela 1:N automaticamente quando declara model + foreign-key), o field-list por padrão trabalha com um array bruto — só persiste sozinho em tabela relacionada se você também declarar model + foreign-key na tag.

Estrutura básica

<mad-field-list name="acoes" addable removable>
    <mad-field-list-column field="act_name"   label="Nome"   type="text" required />
    <mad-field-list-column field="act_method" label="Método" type="text" required />
    <mad-field-list-column field="act_icon"   label="Ícone"  type="text" />
</mad-field-list>
Linhas dinâmicas com header + ações

O componente gera um grid CSS com header, linhas dinâmicas e botão de adicionar/remover por linha — totalmente client-side (Alpine), sem round-trip ao servidor para inserir/remover linha.

Tipos de coluna

typeDescrição
textInput texto padrão
numberInput numérico nativo
numericDecimal formatado pt-BR (decimals, min, max)
moneyReverse-mask monetário (decimals, prefix)
discount / combo_inputValor + seletor de tipo companion (ex: % / R$)
dateData dd/mm/aaaa com datepicker
selectCombo com options fixas (:items ou :options)
dbcomboCombo via Eloquent (model + display, com where/depends-on opcionais)
checkbox / toggleBooleano
radioGrupo de radio inline (:options)
textareaMultilinha
file / multifileUpload por linha — ver Upload de arquivos
filesUpload multi-arquivo por linha em tabela neto (modal de galeria)
hiddenNão renderiza input visível — útil com compute

Combo dentro do field-list

<mad-field-list name="itens" addable removable>
    <mad-field-list-column field="produto_id" label="Produto"
        type="dbcombo" model="Produto" display="nome" required />
    <mad-field-list-column field="quantidade" label="Qtd"
        type="number" required />
    <mad-field-list-column field="valor" label="Valor"
        type="money" required sum />
</mad-field-list>

on-change — chamar PHP ao mudar célula

Quando o valor de uma coluna muda, dispara um método PHP (atributo on-change sem $, sem espaços) que pode atualizar outras colunas da mesma linha via form->set('campo[]', $valor):

<mad-field-list name="itens" addable removable>
    <mad-field-list-column field="produto_id" label="Produto"
        type="dbcombo" model="Produto" display="nome"
        on-change="onChangeProduto" />
    <mad-field-list-column field="quantidade" label="Qtd"
        type="number" on-change="onChangeQuantidade" />
    <mad-field-list-column field="valor"      label="Valor unitário"
        type="money" />
    <mad-field-list-column field="total"      label="Total"
        type="money" attrs="readonly" />
</mad-field-list>
public function onChangeProduto($value): void
{
    if (empty($value)) return;

    $produto = Produto::find($value);
    if (!$produto) return;

    // Atualiza apenas a row de origem (auto-bind via convenção campo[])
    $this->form->set('valor[]', $produto->preco);
    $this->form->set('total[]', $produto->preco);
}

public function onChangeQuantidade($value): void
{
    // Recalcula o total da row atual a partir do valor unitário já na row
    $row    = $_POST['mad_row'] ?? [];
    $valor  = (float) ($row['valor'] ?? 0);
    $total  = $valor * (float) $value;
    $this->form->set('total[]', $total);
}
Convenção [] para a row atual

Dentro de on-change, form->set('campo[]', $v) patcha apenas a row de origem do evento — sem afetar outras linhas. Funciona porque o JS captura row = el.closest('.mad-fl-row') antes do POST. Ver set() e setItems().

Coluna dependente (cascata)

depends-on faz uma coluna dbcombo recarregar suas options automaticamente quando o valor de outra coluna da mesma linha muda — sem precisar escrever um on-change manual:

<mad-field-list name="itens" addable removable>
    <mad-field-list-column field="categoria_id" label="Categoria"
        type="dbcombo" model="Categoria" display="nome" />
    <mad-field-list-column field="produto_id" label="Produto"
        type="dbcombo" model="Produto" display="nome"
        depends-on="categoria_id" depends-column="categoria_id" />
</mad-field-list>

Coluna computada

compute recalcula o valor da coluna no client (Alpine x-effect) a partir de uma expressão JS que recebe a row inteira — útil combinado com type="hidden" para totais por linha sem round-trip:

<mad-field-list name="itens" addable removable>
    <mad-field-list-column field="quantidade" label="Qtd" type="number" />
    <mad-field-list-column field="valor"      label="Valor" type="money" />
    <mad-field-list-column field="total"      label="Total" type="hidden"
        compute="madCalcLineTotal(row)" />
</mad-field-list>

Upload por linha

Colunas file, multifile e files gravam arquivo por linha no mesmo $form->save() do mestre — nenhum código de arquivo no controller. As props ficam na própria coluna:

PropTipoDefaultDescrição
storagestring'' (vira disk no save)disk ou db
folderstring'uploads'Pasta no disco quando storage="disk"
file-namestring'prefix'Estratégia do nome no disco: prefix, unique, original, record
name-columnstring''Coluna do filho para o nome original
acceptstring''Filtro de tipos: .pdf,.jpg,image/*
max-sizeint (KB)0Tamanho máximo, 0 = sem limite
foreign-keystring''Só type="files": FK do NETO apontando para a linha do field-list
path-columnstring''Só type="files": coluna do neto com o path (disk) ou o BLOB base64 (db)

type="files" abre um modal de galeria e grava uma linha de tabela neto por arquivo (reusando model, storage, folder, file-name, name-column da coluna). Os discos e a estratégia de nome são os mesmos do formulário — ver Upload de arquivos.

Props condicionais por-row

Quatro atributos ligam o estado de uma célula ao conteúdo da própria linha, sem round-trip: a condição vira um bind Alpine avaliado por row.

AtributoTipoDefaultDescrição
disabled-whenstring''Desabilita o controle da célula (:disabled)
readonly-whenstring''readonly condicional — só nos controles de texto
required-whenstring''required condicional
visible-whenstring''x-show na célula inteira
<mad-field-list name="campos" addable removable>
    <mad-field-list-column field="field_type" label="Tipo" type="select"
        :options="['text' => 'Texto', 'select' => 'Combo']" />

    <mad-field-list-column field="options" label="Opções" type="text"
        disabled-when="{field_type} != 'select'"
        required-when="{field_type} == 'select'" />

    <mad-field-list-column field="codigo" label="Código" type="text"
        readonly-when="{field_type} == 'select'" />

    <mad-field-list-column field="mascara" label="Máscara" type="text"
        visible-when="{field_type} == 'text'" />
</mad-field-list>
Sintaxe {campo} e o que visible-when NÃO faz

{campo} é compilado para row['campo'] (FieldListColumn::compileCondition); uma expressão sem {token} passa crua como JS Alpine, com row no escopo. Atenção: visible-when usa x-show — é só CSS, a célula continua sendo submetida. Controle desabilitado não posta, então o framework emite um hidden companion nos controles com name próprio para o POST nunca desalinhar as colunas.

Ler dados do field-list

public function onSave(): MadResponse
{
    // Lê todas as linhas — descobre os campos automaticamente via MadFormRegistry
    $rows = $this->form->getFieldList('itens');
    // [
    //   ['produto_id' => '5', 'quantidade' => '2', 'valor' => '10.00', 'total' => '20.00'],
    //   ['produto_id' => '7', 'quantidade' => '1', 'valor' => '50.00', 'total' => '50.00'],
    // ]

    foreach ($rows as $row) {
        // ... persistir cada linha conforme a regra do app ...
    }

    return MadToast::success('Salvo!');
}

Persistência manual via MadFieldListTrait

Quando o field-list mapeia para uma tabela 1:N mas você não quer declarar model/foreign-key direto na tag (ex: precisa de filtro extra na query de load), use MadFieldListTrait + loadDetailRows()/saveDetailItems():

class PedidoForm extends MadComponent
{
    use \Mad\Form\MadFieldListTrait;

    public function onEdit(int $id): void
    {
        $pedido = PedidoVenda::findOrFail($id);
        $this->form->fill($pedido);

        // Carrega itens da tabela filha e já injeta no field-list "itens"
        $this->loadDetailRows('PedidoItem', 'pedido_id', $id, fieldListName: 'itens');
    }

    public function onSave(): MadResponse
    {
        try {
            $pedido = PedidoVenda::findOrNew($this->registroId);
            $this->form->save($pedido);

            // Salva itens via smart-sync (update/insert + delete dos ausentes)
            $this->saveDetailItems(
                'PedidoItem',
                'pedido_id',
                $pedido->id,
                $this->form->getFieldList('itens')
            );

            return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
        } catch (\Throwable $e) {
            return MadMessage::error('Erro', $e->getMessage());
        }
    }
}
detail-form é mais simples para o caso comum

Para tabela 1:N sem filtro extra, prefira <mad-detail-form>, que faz auto-load e auto-save só com model + foreign-key na tag — sem precisar do trait. Use field-list quando precisar de uma tabela inline mais leve, OU quando os dados não vão para uma tabela relacionada (ex: array serializado num campo do model pai).

Atributos da tag <mad-field-list>

AtributoTipoDefaultDescrição
namestring—Nome do field-list (usado em form->getFieldList())
addableboolfalseMostra botão de adicionar linha
removableboolfalseMostra botão de remover por linha
sortableboolfalsePermite reordenar por drag-and-drop
max-rowsint0Linhas máximas (0 = ilimitado)
add-labelstring'Adicionar linha'Texto do botão de adicionar
modelstring''Classe Eloquent do filho — habilita auto-load/auto-save 1:N
foreign-keystring''Coluna FK no model filho
databasestring''Conexão do banco (default: conexão do model)
label / hint / errorstring''Label do campo, texto de ajuda, mensagem de erro
on-add / on-remove / on-totalizestring''Hooks JS opcionais para eventos do field-list

Autocomplete numa coluna

Mesma convenção do form principal: prop pública com array de sugestões + data-mad-autocomplete:

class MeuForm extends MadComponent
{
    public array $methods = []; // nome bate com data-mad-autocomplete="methods"

    public function mount(): void
    {
        $this->methods = ['onSave', 'onCancel', 'onPrint', 'onExport'];
    }
}
<mad-field-list name="acoes" addable removable>
    <mad-field-list-column field="method" label="Método" type="text"
        attrs='data-mad-autocomplete="methods" autocomplete="off"' />
</mad-field-list>

Field-list vs. detail-form

Preciso de...Usar
Tabela editável inline, poucas colunas, cabe no form<mad-field-list>
Form longo por item (drawer/modal próprio)<mad-detail-form mode="drawer">
Persistência automática em tabela 1:N<mad-detail-form> ou <mad-field-list>, ambos com model + foreign-key
Array num campo serializado do model pai (sem tabela filha)<mad-field-list> sem model/foreign-key + leitura manual via getFieldList()
Smart-sync (update existentes, insert novos, delete sumidos)Ambos fazem — é o comportamento padrão quando model/foreign-key estão declarados

Próximos