Docs›Formulários›Visão geral do MadForm
Formulários

Visão geral do MadForm

Padrão CRUD: mount, onEdit, onSave, getData, validate, save.

MadForm é o objeto de valor (propriedade pública $form) que todo formulário MAD usa para receber, transformar e persistir dados. Dois níveis de uso: simples (CRUD em drawer/modal — a maioria dos casos) e avançado (field-list, checklist, auto-bind, transforms).

Formulário simples (CRUD)

Controller

use Mad\Component\MadComponent;
use Mad\Form\MadForm;
use Mad\Ui\MadMessage;
use Mad\Http\MadResponse;
use Mad\Form\MadValidationException;

class ClienteForm extends MadComponent
{
    protected static string $wrapper = self::DRAWER;
    protected static string $title   = 'Cliente';
    protected static string $size    = '800px';
    protected static string $side    = 'right';

    public MadForm $form;
    public ?int $registroId = null;

    public function mount(array $params = []): void
    {
        $this->form = new MadForm('form');
    }

    public function onEdit(int $id): void
    {
        $cliente = Cliente::findOrFail($id);
        $this->registroId = (int) $cliente->id;
        $this->form->fill($cliente);
    }

    public function onSave(): MadResponse
    {
        try {
            $this->form->validate(Cliente::rules($this->registroId));

            $cliente = Cliente::findOrNew($this->registroId);
            $this->form->save($cliente);
            $this->registroId = (int) $cliente->id;

            return (new MadResponse())
                ->toast('Cliente salvo!', 'success')
                ->closeDrawer();

        } catch (MadValidationException $e) {
            return $e->asModal();
        } catch (\Throwable $e) {
            return MadMessage::error('Erro', $e->getMessage());
        }
    }

    protected function view(): string|array
    {
        return 'cliente.cliente-form';
    }
}

View

<mad-form submit="onSave">
    <mad-form-section title="Dados" icon="user">
        <mad-form-grid :cols="2">
            <mad-input-field name="nome" label="Nome" required />
            <mad-input-field name="email" label="Email" />
        </mad-form-grid>
        <mad-dbcombo-field name="cidade_id" label="Cidade" model="Cidade" display="nome" />
    </mad-form-section>

    <mad-separator />

    <mad-form-actions>
        <mad-btn perm-action="onSave" variant="primary" type="submit" icon="save">Salvar</mad-btn>
    </mad-form-actions>
</mad-form>

Formulário avançado

Para field-list, checklist com transform, auto-bind e autocomplete reativo, dois traits opcionais ficam disponíveis:

class MeuForm extends MadComponent
{
    use \Mad\Form\MadChecklistTrait;   // saveChecklist() / loadChecklist()
    use \Mad\Form\MadFieldListTrait;   // saveDetailItems() / loadDetailRows()
}

Auto-bind — setar campos via PHP sem MadResponse

Mude o estado no PHP e o framework gera as ops automaticamente (sem full re-render):

// form->set() atualiza inputs automaticamente, sem MadResponse explícito
public function onPessoaChange(string $pessoaId): void
{
    $pessoa = Pessoa::find($pessoaId);
    $this->form->set('nome', $pessoa->nome ?? '');
    $this->form->set('email', $pessoa->email ?? '');
}

// Array público com nome = data-mad-autocomplete → reload_completion automático
public array $methods = [];

public function onControllerChange(string $controller): void
{
    $this->form->set('name', preg_replace('/([a-z])([A-Z])/', '$1 $2', $controller));
    $this->methods = $this->getControllerMethods($controller);
    // Framework detecta: name mudou → val op; methods mudou → reload_completion op
}

Ver set() e setItems() para a referência completa do auto-bind.

Field-list — ler dados sem informar campos

// getFieldList() descobre os campos automaticamente via MadFormRegistry
$rows = $this->form->getFieldList('acoes');
// [['act_name' => 'Editar', 'act_method' => 'onEdit'], ...]

$actions = array_map(fn($r) => [
    'name'   => $r['act_name'],
    'action' => $r['act_method'],
], $rows);

Field-list — on-change chamando PHP

Quando on-change é o nome de um método PHP (sem $, sem espaços), o framework chama via AJAX (mad:fl-change):

<mad-field-list name="acoes" addable removable>
    <mad-field-list-column field="act_name" label="Nome" type="text" required
        on-change="onActionsChange" />
    <mad-field-list-column field="act_method" label="Método" type="text" required
        on-change="onActionsChange"
        attrs='data-mad-autocomplete="methods" autocomplete="off"' />
</mad-field-list>
// Método PHP chamado pelo on-change — regenera HTML via MadResponse::html()
public function onActionsChange(): MadResponse
{
    $this->acoes = $this->form->getFieldList('acoes');
    $response = new MadResponse();

    $groups = SystemGroup::query()->get();

    foreach ($groups as $g) {
        $gid  = (string) $g->id;
        $html = self::buildGroupCheckboxes($gid, $this->acoes, []);
        $response->html("td[data-mad-cl-col='__actions'][data-mad-cl-row='{$gid}']", $html);
    }
    return $response;
}

Autocomplete reativo — convenção de nome

O nome da prop PHP precisa bater com o data-mad-autocomplete do template:

public array $methods = [];  // ← nome: "methods"
attrs='data-mad-autocomplete="methods"'  

Ao mudar $this->methods, o framework gera reload_completion automaticamente.

Checklist e field-list de tabela 1:N

Para um detail tradicional 1:N (mestre/detalhe persistido em tabela própria), prefira <mad-detail-form> com model + foreign-key — auto load/save sem precisar de trait nenhum. Veja também Field-list.

Quando usar void vs MadResponse

Preciso...RetornoExemplo
Só mudar estado (campos, arrays)void$this->form->set(...)
Toast, fechar drawer, redirectMadResponse->toast()->closeDrawer()
Injetar HTML em seletorMadResponse->html('td[...]', $html)
Mesclar estado + opsMadResponseform->set(...) + return MadToast::success(...)
// Só mudar estado (campos, arrays) → void
public function onFiltrar(): void
{
    $this->form->set('status', 'ativo');
}

// Toast, fechar drawer, redirect, injetar HTML → MadResponse
public function onSave(): MadResponse
{
    $this->form->set('status', 'Salvo');
    return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
}

form->save() — obrigatório para persistência

$this->form->save($record) é o único método que deve ser usado para salvar registros em formulários MAD. Encapsula todo o ciclo de persistência:

  1. fillRecord($record) — preenche o record com os dados do form (respeitando $fillable), processa upload single-file (storage="disk").
  2. $record->save() — persiste no banco via Eloquent.
  3. _afterStore($record) — processa multi-file (comma/table), single-file storage="db" (BLOB via prepared statement) e auto-save de field-lists/detail-forms com model + foreign-key.

Padrão obrigatório no onSave

public function onSave(): MadResponse
{
    try {
        $this->form->validate(Cliente::rules($this->registroId));

        $cliente = Cliente::findOrNew($this->registroId);

        // Setar campos que NÃO vêm do form ANTES do save
        $cliente->created_by = session('userid');

        $this->form->save($cliente); // fillRecord + store + afterStore (files, multi-files, blobs)
        $this->registroId = (int) $cliente->id;

        return (new MadResponse())
            ->toast('Salvo!', 'success')
            ->closeDrawer();

    } catch (MadValidationException $e) {
        return $e->asModal();
    } catch (\Throwable $e) {
        return MadMessage::error('Erro', $e->getMessage());
    }
}

Campos extras que não vêm do form

Setar diretamente no $record antes de chamar save():

$record->system_user_id = session('userid');
$record->dt_criacao     = now();
$this->form->save($record); // fillRecord preenche o resto, save() persiste tudo

Formulários com upload de arquivos

form->save() cuida automaticamente de salvar arquivos quando o componente Blade declara as props de storage. NUNCA manipular $_FILES manualmente, move_uploaded_file(), ou criar lógica de salvamento de arquivo no controller — ver Upload de arquivos para a referência completa dos 4 componentes de upload.

<mad-form submit="onSave">
    <mad-image-field name="foto" label="Foto"
        storage="disk" folder="uploads/produtos/fotos" name-column="foto_nome"
        crop aspect-ratio="4:3" />

    <mad-input-field name="nome" label="Nome" required />

    <mad-multi-file-field name="anexos" label="Documentos"
        storage="disk" folder="uploads/produtos/docs" mode="table"
        model="ProdutoAnexo" foreign-key="produto_id"
        path-column="file_path" name-column="file_name"
        accept=".pdf,.doc,.docx" :max-files="5" />

    <mad-btn perm-action="onSave" type="submit" variant="primary" icon="save">Salvar</mad-btn>
</mad-form>
public function onSave(): MadResponse
{
    try {
        $this->form->validate(Produto::rules($this->registroId));

        $produto = Produto::findOrNew($this->registroId);
        $this->form->save($produto); // salva TUDO: campos, foto, anexos
        $this->registroId = (int) $produto->id;

        return (new MadResponse())
            ->toast('Produto salvo!', 'success')
            ->closeDrawer();

    } catch (MadValidationException $e) {
        return $e->asModal();
    } catch (\Throwable $e) {
        return MadMessage::error('Erro', $e->getMessage());
    }
}

Um único $this->form->save($produto) persiste tudo: campos, foto, anexos.

Checklist obrigatório

  1. MadForm sempre no mount() — $this->form = new MadForm('form');
  2. onEdit() recebe int $id — a tipagem garante cast automático.
  3. SEMPRE usar form->save($record) — encapsula fillRecord + save + afterStore (files, multi-files, blobs).
  4. NUNCA manipular $_FILES manualmente — os componentes de upload + form->save() cuidam de tudo.
  5. Declarar props de storage no Blade — storage, folder, mode, model, foreign-key, path-column, name-column.
  6. Validação — usar Modelo::rules($id) e capturar MadValidationException.
  7. Resposta de sucesso — MadResponse com ->toast() + ->closeDrawer().
  8. View — retornar o path da view como string (ou [string, array] quando precisar de dado extra que não é prop pública).

DRAWER vs MODAL

  • Drawer (self::DRAWER): formulários com muitos campos, edição detalhada.
  • Modal (self::MODAL): formulários curtos, confirmações, edição rápida.

NUNCA fazer

// ERRADO: salvar sem form->save() — perde upload automático de arquivos
$this->form->fillRecord($registro);
$registro->save();

// CERTO: save() encapsula fillRecord + store + afterStore
$this->form->save($registro);

// ERRADO: manipular $_FILES manualmente
$files = $_FILES['anexos'];
move_uploaded_file($files['tmp_name'][0], $dest);
$att = new Anexo();
$att->file_path = $dest;
$att->save();

// CERTO: declarar storage no Blade e usar form->save()
// Blade: <mad-multi-file-field storage="disk" folder="uploads" mode="table" model="Anexo" ...>
// PHP:   $this->form->save($record);

// ERRADO: script() para setar valor
$response->script("document.querySelector(...).value = '{$val}'");
// CERTO:
$this->form->set('campo', $val);

// ERRADO: script() para autocomplete
$response->script("window.methods = " . json_encode($m));
// CERTO:
$this->methods = $m;

// ERRADO: on-change com JS inline para chamar PHP
on-change="window._sync && window._sync()"
// CERTO: nome do método PHP direto
on-change="onActionsChange"

Próximos