MAD Lab
/
Tutor > Estrutura de formulário > Field list

Field list

Estrutura de formulário

mad-field-list mad-field-list-column mad-field-list-group mad-field-list-action
Básico (modo virtual)

mad-field-list sem model/foreign-key — linhas ad-hoc com compute e soma no rodapé.

Descrição *
Qtd
Valor unit.
Total
Nenhum item adicionado
(/0)
Modo virtual — sem model/foreign-key: as linhas vivem só no estado do formulário.
resources/views/tutor/examples/estrutura-field-list/basico.blade.php
@php
    // O `=>` do array PHP inline quebrava o parser de tag do
    // mad-field-list: o regex que localiza a tag-mãe (MadGridCompiler)
    // não é ciente de aspas (ao contrário do regex dos filhos
    // mad-field-list-column, que já leva esse cuidado) e termina no
    // primeiro '>' que encontrar — inclusive o de dentro de "=>" num
    // :rows="[...]" inline. Resultado: a tag "fecha" no meio do array, o
    // resto vira "corpo descartado" e :rows chega como boolean `true` em
    // vez do array — e o aviso de diagnóstico do MadGridCompiler (que cita
    // "@foreach" na própria mensagem) quebra a compilação Blade da página
    // inteira. Workaround: array fora da tag, referenciado por variável
    // (sem "=>"/"->" no atributo).
    $rowsBasico = [
        ['descricao' => 'Mouse sem fio', 'qtd' => 2, 'valor_unit' => 89.90, 'total' => 179.80],
        ['descricao' => 'Teclado mecânico', 'qtd' => 1, 'valor_unit' => 349.00, 'total' => 349.00],
    ];
@endphp
<mad-field-list name="itens_basico" addable removable add-label="Adicionar item"
    hint="Modo virtual — sem model/foreign-key: as linhas vivem só no estado do formulário."
    :rows="$rowsBasico">
    <mad-field-list-column field="descricao" label="Descrição" type="text" required
        placeholder="Descrição do item" />
    <mad-field-list-column field="qtd" label="Qtd" type="numeric" width="90px" default="1" />
    <mad-field-list-column field="valor_unit" label="Valor unit." type="money" width="130px" prefix="R$" />
    <mad-field-list-column field="total" label="Total" type="money" width="130px" prefix="R$"
        compute="{qtd} * {valor_unit}" readonly sum />
</mad-field-list>
Grupos e tipos de coluna

mad-field-list-group agrupando colunas select, date e money.

Identificação
Comercial
Descrição *
Categoria
Validade
Valor
Nenhum item adicionado
(/0)
resources/views/tutor/examples/estrutura-field-list/grupos-tipos.blade.php
@php
    // Array fora da tag (referenciado por variável): `=>` inline no
    // :rows="[...]" quebra o parser da tag-mãe mad-field-list
    // (MadGridCompiler) — ver o comentário completo em
    // examples/estrutura-field-list/basico.blade.php.
    $rowsGrupos = [
        ['descricao' => 'Cadeira ergonômica', 'categoria' => 'moveis', 'validade' => '2027-01-15', 'valor' => 890.00],
        ['descricao' => 'Resma de papel A4', 'categoria' => 'papelaria', 'validade' => '2026-06-30', 'valor' => 24.90],
    ];
@endphp
<mad-field-list name="itens_grupos" addable removable add-label="Adicionar item"
    :rows="$rowsGrupos">
    <mad-field-list-group label="Identificação">
        <mad-field-list-column field="descricao" label="Descrição" type="text" required />
        <mad-field-list-column field="categoria" label="Categoria" type="select"
            :options="['eletronico' => 'Eletrônico', 'papelaria' => 'Papelaria', 'moveis' => 'Móveis']" />
    </mad-field-list-group>
    <mad-field-list-group label="Comercial">
        <mad-field-list-column field="validade" label="Validade" type="date" />
        <mad-field-list-column field="valor" label="Valor" type="money" prefix="R$" sum />
    </mad-field-list-group>
</mad-field-list>
Coluna ligada a banco + on-change

type=dbcombo lendo TutorDemoCliente; on-change preenche outra coluna da mesma linha.

Cliente
Cidade/UF
Nenhum item adicionado
(/0)
Selecionar um cliente preenche Cidade/UF na mesma linha (on-change chama form-set no servidor).
resources/views/tutor/examples/estrutura-field-list/dbcombo-reativo.blade.php
<mad-field-list name="itens_reativo" addable removable add-label="Adicionar cliente"
    hint="Selecionar um cliente preenche Cidade/UF na mesma linha (on-change chama form-set no servidor).">
    <mad-field-list-column field="cliente_id" label="Cliente" type="dbcombo"
        model="TutorDemoCliente" database="business" display="{nome}"
        on-change="onClienteFieldListChange" />
    <mad-field-list-column field="cidade_uf" label="Cidade/UF" type="text" readonly
        placeholder="Selecione um cliente..." />
</mad-field-list>
app/control/Tutor/TutorEstruturaFieldList.php
/**
 * on-change da coluna dbcombo "cliente_id" (exemplo "dbcombo-reativo").
 * Recebe o novo valor selecionado e preenche "cidade_uf" na MESMA linha
 * via form->set('campo[]', ...) — op fl_val, escopada à row de origem do
 * mad:fl-change (framework resolve o parâmetro $value por nome).
 */
public function onClienteFieldListChange($value = null): MadResponse
{
    $cliente = $value !== null && $value !== ''
        ? TutorDemoCliente::query()->find($value)
        : null;

    $cidadeUf = $cliente ? trim($cliente->cidade . '/' . $cliente->uf, '/') : '';

    $this->form->set('cidade_uf[]', $cidadeUf);

    return new MadResponse();
}
Ações de linha

mad-field-list-action dispara método PHP que duplica a linha via form->setRows().

Descrição *
Qtd
Valor unit.
Nenhum item adicionado
(/0)
resources/views/tutor/examples/estrutura-field-list/acoes-linha.blade.php
@php
    // Array fora da tag (referenciado por variável): `=>` inline no
    // :rows="[...]" quebra o parser da tag-mãe mad-field-list
    // (MadGridCompiler) — ver o comentário completo em
    // examples/estrutura-field-list/basico.blade.php.
    $rowsAcoes = [
        ['descricao' => 'Suporte para monitor', 'qtd' => 1, 'valor_unit' => 159.90],
        ['descricao' => 'Hub USB-C', 'qtd' => 3, 'valor_unit' => 79.90],
    ];
@endphp
<mad-field-list name="itens_acoes" addable removable add-label="Adicionar item"
    :rows="$rowsAcoes">
    <mad-field-list-column field="descricao" label="Descrição" type="text" required />
    <mad-field-list-column field="qtd" label="Qtd" type="numeric" width="90px" default="1" />
    <mad-field-list-column field="valor_unit" label="Valor unit." type="money" width="130px" prefix="R$" />
    <mad-field-list-action method="onDuplicarLinhaDemo" icon="copy" title="Duplicar linha" />
</mad-field-list>
app/control/Tutor/TutorEstruturaFieldList.php
/**
 * mad-field-list-action "Duplicar" (exemplo "acoes-linha"). A linha
 * clicada chega como parâmetros nomeados (merge automático row+params
 * antes do Mad.call); form->setRows() substitui TODAS as linhas do
 * field-list — ao contrário de set()/setItems(), não depende de contexto
 * de row, então funciona numa action de botão comum.
 */
public function onDuplicarLinhaDemo(string $descricao = '', $qtd = null, $valor_unit = null): MadResponse
{
    $rows   = $this->form->getFieldList('itens_acoes');
    $rows[] = [
        'descricao'  => trim($descricao) !== '' ? $descricao . ' (cópia)' : 'Item (cópia)',
        'qtd'        => $qtd,
        'valor_unit' => $valor_unit,
    ];

    $this->form->setRows('itens_acoes', $rows);

    return (new MadResponse())
        ->toast("Linha duplicada: {$descricao}", 'success', 'onDuplicarLinhaDemo');
}
Ação wire

mad-form com submit real — dispara TutorEstruturaFieldList::onSubmitFieldListDemo.

Descrição *
Qtd
Valor unit.
Total
Nenhum item adicionado
(/0)
resources/views/tutor/examples/estrutura-field-list/wire-submit.blade.php
@php
    // Array fora da tag (referenciado por variável): `=>` inline no
    // :rows="[...]" quebra o parser da tag-mãe mad-field-list
    // (MadGridCompiler) — ver o comentário completo em
    // examples/estrutura-field-list/basico.blade.php.
    $rowsWire = [
        ['descricao' => 'Cabo HDMI 2m', 'qtd' => 2, 'valor_unit' => 39.90, 'total' => 79.80],
    ];
@endphp
<mad-form submit="onSubmitFieldListDemo">
    <mad-field-list name="itens_wire" addable removable add-label="Adicionar item"
        :rows="$rowsWire">
        <mad-field-list-column field="descricao" label="Descrição" type="text" required />
        <mad-field-list-column field="qtd" label="Qtd" type="numeric" width="90px" default="1" />
        <mad-field-list-column field="valor_unit" label="Valor unit." type="money" width="130px" prefix="R$" />
        <mad-field-list-column field="total" label="Total" type="money" width="130px" prefix="R$"
            compute="{qtd} * {valor_unit}" readonly sum />
    </mad-field-list>
    <mad-form-actions align="right">
        <mad-btn type="submit" variant="primary" icon="send">Enviar (onSubmitFieldListDemo)</mad-btn>
    </mad-form-actions>
</mad-form>
app/control/Tutor/TutorEstruturaFieldList.php
/**
 * Submit do exemplo "Ação wire" — lê as linhas via getFieldList() (modo
 * virtual, sem persistência) e devolve um toast com o resumo do payload.
 */
public function onSubmitFieldListDemo(): MadResponse
{
    $itens = $this->form->getFieldList('itens_wire');

    $qtdTotal   = 0.0;
    $valorTotal = 0.0;
    foreach ($itens as $row) {
        $qtd = (float) ($row['qtd'] ?? 0);
        $qtdTotal   += $qtd;
        $valorTotal += $qtd * (float) ($row['valor_unit'] ?? 0);
    }

    $resumo = count($itens) . ' linha(s), qtd total ' . rtrim(rtrim(number_format($qtdTotal, 2, ',', '.'), '0'), ',')
        . ', valor total R$ ' . number_format($valorTotal, 2, ',', '.');

    return (new MadResponse())
        ->toast("Field list recebido: {$resumo}", 'success', 'onSubmitFieldListDemo');
}
Código-fonte completo da página
app/control/Tutor/TutorEstruturaFieldList.php
<?php

namespace App\Control\Tutor;

use App\Models\TutorDemo\TutorDemoCliente;
use App\Support\Tutor\TutorDemoSeed;
use App\Support\Tutor\TutorPage;
use Mad\Form\MadForm;
use Mad\Http\MadResponse;

/**
 * TutorEstruturaFieldList — cobre <mad-field-list>, <mad-field-list-column>,
 * <mad-field-list-group> e <mad-field-list-action>.
 *
 * Todos os exemplos usam field-list em MODO VIRTUAL (sem model/foreign-key na
 * tag): nenhuma linha é persistida em banco — on-change, ação de linha e o
 * submit final só leem o payload recebido e devolvem toast (nível demo).
 */
class TutorEstruturaFieldList extends TutorPage
{
    protected string $catalogKey = 'estrutura_field_list';

    public MadForm $form;

    public function mount(array $params = []): void
    {
        TutorDemoSeed::ensure();

        $this->form = new MadForm('tutor_estrutura_field_list');
    }

    /**
     * on-change da coluna dbcombo "cliente_id" (exemplo "dbcombo-reativo").
     * Recebe o novo valor selecionado e preenche "cidade_uf" na MESMA linha
     * via form->set('campo[]', ...) — op fl_val, escopada à row de origem do
     * mad:fl-change (framework resolve o parâmetro $value por nome).
     */
    public function onClienteFieldListChange($value = null): MadResponse
    {
        $cliente = $value !== null && $value !== ''
            ? TutorDemoCliente::query()->find($value)
            : null;

        $cidadeUf = $cliente ? trim($cliente->cidade . '/' . $cliente->uf, '/') : '';

        $this->form->set('cidade_uf[]', $cidadeUf);

        return new MadResponse();
    }

    /**
     * mad-field-list-action "Duplicar" (exemplo "acoes-linha"). A linha
     * clicada chega como parâmetros nomeados (merge automático row+params
     * antes do Mad.call); form->setRows() substitui TODAS as linhas do
     * field-list — ao contrário de set()/setItems(), não depende de contexto
     * de row, então funciona numa action de botão comum.
     */
    public function onDuplicarLinhaDemo(string $descricao = '', $qtd = null, $valor_unit = null): MadResponse
    {
        $rows   = $this->form->getFieldList('itens_acoes');
        $rows[] = [
            'descricao'  => trim($descricao) !== '' ? $descricao . ' (cópia)' : 'Item (cópia)',
            'qtd'        => $qtd,
            'valor_unit' => $valor_unit,
        ];

        $this->form->setRows('itens_acoes', $rows);

        return (new MadResponse())
            ->toast("Linha duplicada: {$descricao}", 'success', 'onDuplicarLinhaDemo');
    }

    /**
     * Submit do exemplo "Ação wire" — lê as linhas via getFieldList() (modo
     * virtual, sem persistência) e devolve um toast com o resumo do payload.
     */
    public function onSubmitFieldListDemo(): MadResponse
    {
        $itens = $this->form->getFieldList('itens_wire');

        $qtdTotal   = 0.0;
        $valorTotal = 0.0;
        foreach ($itens as $row) {
            $qtd = (float) ($row['qtd'] ?? 0);
            $qtdTotal   += $qtd;
            $valorTotal += $qtd * (float) ($row['valor_unit'] ?? 0);
        }

        $resumo = count($itens) . ' linha(s), qtd total ' . rtrim(rtrim(number_format($qtdTotal, 2, ',', '.'), '0'), ',')
            . ', valor total R$ ' . number_format($valorTotal, 2, ',', '.');

        return (new MadResponse())
            ->toast("Field list recebido: {$resumo}", 'success', 'onSubmitFieldListDemo');
    }
}
resources/views/tutor/estrutura/field-list.blade.php
<x-tutor-page page="estrutura_field_list">
    <x-tutor-demo demo="tutor.examples.estrutura-field-list.basico"
        title="Básico (modo virtual)" desc="mad-field-list sem model/foreign-key — linhas ad-hoc com compute e soma no rodapé." />

    <x-tutor-demo demo="tutor.examples.estrutura-field-list.grupos-tipos"
        title="Grupos e tipos de coluna" desc="mad-field-list-group agrupando colunas select, date e money." />

    <x-tutor-demo demo="tutor.examples.estrutura-field-list.dbcombo-reativo"
        title="Coluna ligada a banco + on-change" desc="type=dbcombo lendo TutorDemoCliente; on-change preenche outra coluna da mesma linha."
        php="TutorEstruturaFieldList::onClienteFieldListChange" />

    <x-tutor-demo demo="tutor.examples.estrutura-field-list.acoes-linha"
        title="Ações de linha" desc="mad-field-list-action dispara método PHP que duplica a linha via form->setRows()."
        php="TutorEstruturaFieldList::onDuplicarLinhaDemo" />

    <x-tutor-demo demo="tutor.examples.estrutura-field-list.wire-submit"
        title="Ação wire" desc="mad-form com submit real — dispara TutorEstruturaFieldList::onSubmitFieldListDemo."
        php="TutorEstruturaFieldList::onSubmitFieldListDemo" />
</x-tutor-page>