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.
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... | Retorno | Exemplo |
|---|---|---|
| Só mudar estado (campos, arrays) | void | $this->form->set(...) |
| Toast, fechar drawer, redirect | MadResponse | ->toast()->closeDrawer() |
| Injetar HTML em seletor | MadResponse | ->html('td[...]', $html) |
| Mesclar estado + ops | MadResponse | form->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:
fillRecord($record)— preenche o record com os dados do form (respeitando$fillable), processa upload single-file (storage="disk").$record->save()— persiste no banco via Eloquent._afterStore($record)— processa multi-file (comma/table), single-filestorage="db"(BLOB via prepared statement) e auto-save de field-lists/detail-forms commodel+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
MadFormsempre nomount()—$this->form = new MadForm('form');onEdit()recebeint $id— a tipagem garante cast automático.- SEMPRE usar
form->save($record)— encapsula fillRecord + save + afterStore (files, multi-files, blobs). - NUNCA manipular
$_FILESmanualmente — os componentes de upload +form->save()cuidam de tudo. - Declarar props de storage no Blade —
storage,folder,mode,model,foreign-key,path-column,name-column. - Validação — usar
Modelo::rules($id)e capturarMadValidationException. - Resposta de sucesso —
MadResponsecom->toast()+->closeDrawer(). - 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"