Docs›Reatividade›MadResponse — todas as ops
Reatividade

MadResponse — todas as ops

toast, html, replace, append, val, openModal, closeDrawer, redirect.

Mad\Http\MadResponse é o builder fluente de operações parciais de UI — cada método empilha uma op (['op' => '...', ...]) no array $ops; send() serializa tudo como JSON e encerra a execução. Toda action que retorna MadResponse tem suas ops mescladas com as geradas automaticamente pelo diff de auto-bind (ver Auto-bind).

void vs MadResponse

// void — so muda estado. O handler faz o diff e gera val/bind/reload_* sozinho.
public function onTipoChange(string $tipo): void
{
    $this->form->set('descricao', TipoProduto::find($tipo)->descricao);
}

// MadResponse — quando voce precisa de UX explicito (toast, fechar, navegar)
public function onSave(): MadResponse
{
    $produto = Produto::findOrNew($this->registroId);
    $this->form->save($produto);

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

// Os dois mundos se mesclam: ops explicitos + auto-bind do diff, na mesma resposta
public function onAprovar(): MadResponse
{
    $this->form->set('status', 'aprovado');       // auto: val op
    return MadToast::success('Pedido aprovado!');  // explicito: toast op
}
SituaçãoRetorno
Só mudar estado (props, form->set())void — handler gera ops automaticamente
Toast / abrir-fechar drawer-modal / redirectMadResponse
Substituir HTML de uma região específicaMadResponse->html()
Atualizar uma row/card sem reload da listagemMadResponse->manageRow() / ->manageCard()
Erro de validação num campo específicoMadResponse->fieldError()
Mesclar estado + ops explícitasMadResponse (mescladas com o auto-bind automaticamente)

Manipulação de DOM

OpFaz
html($seletor, $html)Substitui o innerHTML do elemento
val($seletor, $valor, $onlyEmpty = false)Define o value de input/select/textarea
attr($seletor, $atributo, $valor)Define um atributo HTML qualquer
addClass($seletor, $classe) / removeClass(...)Adiciona/remove classe CSS
show($seletor) / hide($seletor)Mostra/esconde (display)
return (new MadResponse())
    ->html('#preview', $htmlGerado)                 // substitui innerHTML
    ->val('[name="cep"]', '01310-100')               // value de input/select/textarea
    ->attr('#link', 'href', $url)                    // qualquer atributo HTML
    ->addClass('#linha-1', 'mad-highlight')
    ->removeClass('#linha-1', 'mad-pending')
    ->show('#painel-avancado')
    ->hide('#painel-basico');

Erros de campo

OpFaz
fieldError($campo, $mensagem)Mostra erro em [data-field-error="campo"] + marca #campo como inválido
dfFieldError($detailName, $campo, $mensagem)Idem, mas escopado a um <mad-detail-form> (não conflita com campo de mesmo nome no master)
clearFieldError($campo)Remove o erro
public function onSave(): MadResponse
{
    if (empty($this->form->get('email'))) {
        return (new MadResponse())->fieldError('email', 'E-mail é obrigatório.');
    }
    // ...
}

// Limpa quando o usuário corrige (normalmente disparado por mad:change)
public function onEmailChange(string $value): void
{
    if (filter_var($value, FILTER_VALIDATE_EMAIL)) {
        (new MadResponse())->clearFieldError('email')->send();
    }
}

Recarregar options (combo, radio, checkbox-group...)

Mesma família dos ops reload_* que o auto-bind gera sozinho ao detectar mudança num campo registrado — aqui você os dispara explicitamente, sem depender do registry.

OpComponente alvo
reloadCombo($name, $items, $selected?, $placeholder?)<select> nativo / mad-select-field / mad-dbcombo-field
reloadRadio($name, $items, $selected?)mad-radio-field / mad-dbradio-field
reloadCheckboxGroup($name, $items, $selected = [])mad-checkbox-group-field / mad-dbcheckbox-group-field
reloadMultiEntry($name, $items, $selected = [])mad-multi-entry-field
reloadSortList($name, $items, $selected = [])mad-sort-list-field
reloadChecklist($name, $items, $selected = [])mad-checklist-field / mad-dbchecklist-field
reloadCompletion($var, $items)Autocomplete genérico (data-mad-autocomplete="var")
addComboOption($name, $value, $label, $select = true, $scope = '')Insere uma option nova (op combo_add_option) sem recarregar a lista — padrão quick-add. $scope é o mad-id do componente onde procurar o campo antes de cair na busca global (evita acertar um <select> homônimo de outra tela aberta); o JS também casa com name="X[]"
// Combo nativo / mad-dbcombo-field — basta o name, sem renderizar HTML
$response->reloadCombo('cidade_id', $cidades, selected: '42', placeholder: 'Selecione...');

// Radio
$response->reloadRadio('tipo', ['A' => 'Ativo', 'I' => 'Inativo'], selected: 'A');

// Checkbox group / multi-entry / sort-list — mesmo shape (value => label)
$response->reloadCheckboxGroup('perms', ['read' => 'Ler', 'write' => 'Escrever'], selected: ['read']);
$response->reloadMultiEntry('tags', ['pro' => 'Pro', 'free' => 'Free'], selected: ['pro']);
$response->reloadSortList('ordem', ['id' => 'Código', 'nome' => 'Nome'], selected: ['nome', 'id']);

// Checklist — rows associativas (mesmo shape do model->toArray())
$response->reloadChecklist('grupos', $rows, selected: ['1', '3']);

// Autocomplete genérico (data-mad-autocomplete="methods")
$response->reloadCompletion('methods', ['onSave', 'onEdit', 'onDelete']);

// INSERIR uma option nova sem recarregar a lista inteira (padrão quick-add:
// abriu um cadastro em modal, salvou, quer o registro novo já selecionado).
// Em combo de seleção MULTIPLA a option é SOMADA à seleção atual.
$response->addComboOption('cidade_id', (string) $cidade->id, $cidade->nome, select: true, scope: $this->_getId());

Feedback ao usuário

OpFaz
toast($mensagem, $type = 'info', $title = '', $position = 'top-right')Notificação não-bloqueante
alert($mensagem, $type = 'info', $title = '')Diálogo de alerta bloqueante
// type: info | success | warning | danger
$response->toast('Salvo com sucesso!', 'success');
$response->toast('Limite quase atingido.', 'warning', title: 'Atenção');

$response->alert('Esta ação é irreversível.', 'danger', 'Confirmação');
OpFaz
redirect($classeOuMetodo, $params = [], $delayMs = 0)Full-page navigation para uma classe MAD (resolve rota amigável)
redirectUrl($url, $delayMs = 0)Full-page navigation para uma URL pronta
openDrawer($nome) / openModal($nome)Abre overlay por nome (CustomEvent)
closeDrawer($nome = '') / closeModal($nome = '')Fecha por nome, ou o overlay do componente atual se omitido
MadResponse::open($classe, $params = [], $method = 'show')Estático — abre um MadComponent detectando DRAWER/MODAL (overlay) vs INTERNAL (navega)
// Navegação full-page para outra classe MAD (resolve rota amigável /app/slug)
return (new MadResponse())->redirect('PedidoForm@onEdit', ['id' => $pedido->id]);

// URL já pronta (ex.: destino pós-login)
return (new MadResponse())->redirectUrl('/app/dashboard');

// Abrir modal/drawer por nome (CustomEvent — qualquer um na página pode escutar)
return (new MadResponse())->openDrawer('filtros-avancados');
return (new MadResponse())->openModal('confirmar-exclusao');

// Fechar o overlay do PRÓPRIO componente (modal/drawer que o renderizou)
return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();

// Abrir um MadComponent detectando o wrapper sozinho (DRAWER/MODAL -> overlay; INTERNAL -> navega)
return MadResponse::open('DocProdutoForm', ['id' => $id]);
return MadResponse::open('DocProdutoForm', ['id' => $id], 'onEdit');

Render parcial sem full reload

Cobertura detalhada em Render parcial — aqui, a referência rápida das ops:

OpFaz
manageRow($id, $gridClass)Insere/atualiza uma row do MadDataGrid (com highlight)
removeRow($id, $gridClass = '')Remove uma row do MadDataGrid
manageCard($id, $kanbanClass)Insere/atualiza um card do MadKanban
removeCard($id)Remove um card do MadKanban
reload($seletor, 'Classe@metodo', $params = [])Substitui innerHTML pelo retorno (echo) de um método estático
energize($nome, $params = [])Recarrega um MadTransporter nomeado (round-trip client)
teleport($seletor, $classe, $metodo = '', $params = [])Renderiza um MadComponent inteiro direto no servidor, sem round-trip
bind($prop, $valor)Atualiza todo [data-mad-bind="prop"] (spans de @madBind) e o Alpine state de qualquer [x-data] do wrapper que tenha essa prop (scopes criados por @madWire) — escopado ao wrapper, nunca global
// Atualiza (ou insere) UMA row do MadDataGrid sem reload da listagem inteira.
// Já busca o HTML server-side via MadDataGrid::renderSingleRow().
public function onSave(): MadResponse
{
    $produto = Produto::findOrNew($this->registroId);
    $this->form->save($produto);

    return (new MadResponse())
        ->toast('Salvo!', 'success')
        ->closeDrawer()
        ->manageRow($produto->id, ProdutoListagem::class);
}

// Remover uma row (ex: após exclusão)
return (new MadResponse())->removeRow($id, ProdutoListagem::class);
// Mesmo padrão para o MadKanban — cards em vez de rows
return (new MadResponse())
    ->toast('Movido!', 'success')
    ->manageCard($tarefa->id, MeuKanban::class);

return (new MadResponse())->removeCard($tarefa->id);
// Recarrega um trecho chamando um método estático PHP (Classe@metodo) —
// o retorno (echo) vira o novo innerHTML do seletor alvo.
return (new MadResponse())->reload('#painel-itens', 'PedidoForm@renderItens', ['pedido_id' => $id]);
// energize() — round-trip client: dispara Mad.energize(name), que faz um
// POST e troca o conteudo do MadTransporter "name" pelo HTML renderizado.
return (new MadResponse())->energize('painel-financeiro', ['periodo' => 'mes']);

// teleport() — SEM round-trip: monta, faz mount()+method() e injeta o HTML
// direto na resposta atual (síncrono, dentro do mesmo request).
return (new MadResponse())
    ->teleport('#painel-detalhe', DocumentDetail::class, 'onShow', ['id' => 42]);
// Atualiza um span @madBind('prop') sem disparar o diff completo do auto-bind.
// A MESMA op tambem patcha o Alpine state de qualquer scope @madWire(['total'])
// do wrapper — mad-text/mad-if/mad-show reagem sozinhos, sem novo POST.
// O valor e coagido ao tipo que ja esta no state Alpine (number/boolean/string).
public function recalcular(): MadResponse
{
    $this->total = number_format($this->calcularTotal(), 2, ',', '.');
    return (new MadResponse())->bind('total', $this->total);
}

Detail-form e tree-view

OpFaz
dfAdd($detailName, $row, $editIndex = -1)Insere/atualiza row num <mad-detail-form>
dfDelete($detailName, $index)Remove row de um <mad-detail-form>
treeAddNode($tree, $nodeData)Adiciona nó na tree-view
treeRemoveNode($tree, $id)Remove nó (com animação)
treeUpdateNode($tree, $id, $data)Atualiza label/icon/count de um nó existente
treeSetActive($tree, $id)Seleciona um nó, expandindo ancestrais se preciso
// Hooks de before-add/before-delete de um <mad-detail-form> — confirmam (ou
// rejeitam) a row no servidor antes de refletir na listagem inline.
public function onBeforeAddItem(array $row): MadResponse
{
    $produto = Produto::findOrFail($row['produto_id']);
    $row['descricao'] = $produto->nome;
    $row['valor']     = $produto->preco;

    return (new MadResponse())->dfAdd('itens', $row);
}

public function onBeforeDeleteItem(int $index): MadResponse
{
    return (new MadResponse())->dfDelete('itens', $index);
}
return (new MadResponse())
    ->treeAddNode('pastas', ['id' => $f->id, 'parent_id' => $f->parent_id, 'name' => $f->nome])
    ->toast('Pasta criada!', 'success');

(new MadResponse())->treeRemoveNode('pastas', $id)->send();
(new MadResponse())->treeUpdateNode('pastas', $id, ['name' => 'Novo nome', 'count' => 12])->send();
(new MadResponse())->treeSetActive('pastas', $id)->send();
Ops de Gantt

MadResponse também expõe ganttPatchTask(), ganttPatchTasks(), ganttUpsertTask(), ganttRemoveTask(), ganttUpsertRecord() e ganttPatchRecord() para patchar barras do MadGantt sem reload — específicas o bastante para ficarem fora desta página; ver o docblock de cada método em Mad\Http\MadResponse.

Diversos

MétodoFaz
script($js)Executa JavaScript arbitrário — último recurso, prefira um op dedicado
refetchCalendar($calendarId)Manda um MadFullCalendar rebuscar seus eventos (açúcar sobre script(): chama refetchEvents() no scope Alpine do container)
dumpModal($dumps, $meta = [])Overlay de debug client-side (op dump_modal) — normalmente você não chama na mão: send() injeta sozinho o que mad_dump_modal()/mdm() acumulou no request
merge(MadResponse $other)Funde as ops de outra instância na atual
getOps()Retorna o array de ops sem enviar (usado internamente pelo handler)
send()never — serializa como JSON e encerra a execução
emit()Imprime um <script> inline que aplica as ops — para contextos fora do wire (ex: pós-logout)
MadResponse::ok($msg) / ::err($msg)Atalho estático: toast + send() num passo só
// Último recurso — JS arbitrário. Prefira os ops dedicados sempre que existir um.
return (new MadResponse())->script("window.dispatchEvent(new CustomEvent('relatorio:pronto'))");
public function onSave(): MadResponse
{
    $response = new MadResponse();

    if ($this->precisaAvisoEstoque()) {
        $response->toast('Estoque baixo neste produto.', 'warning');
    }

    return $response->merge($this->salvarESincronizar());
}
// Atalhos estáticos — toast + send() num só passo, terminam a execução (never)
MadResponse::ok('Operação realizada com sucesso!');
MadResponse::err('Não foi possível processar.');

Encadeando tudo

Todo método retorna static — encadeie livremente na ordem que fizer sentido para a UX:

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

        $produto = Produto::findOrNew($this->registroId);
        $this->form->save($produto);

        return (new MadResponse())
            ->toast('Produto salvo!', 'success')
            ->closeDrawer()
            ->manageRow($produto->id, ProdutoListagem::class);
    } catch (\Mad\Form\MadValidationException $e) {
        return $e->asInline(); // erros inline nos campos + toast de aviso
    }
}

Próximos passos