Diretivas mad-*
mad:click, mad:model, mad:change, mad-bind, mad-text, mad-if, mad-show.
O MAD expõe duas famílias de diretivas no Blade, e a diferença entre elas é a coisa mais importante a entender nesta página:
mad:click="onSalvar" ← FAMILIA 1: server action (POST AJAX, dois pontos)
mad-on:click="contador++" ← FAMILIA 2: Alpine puro client-side (hifen, sem POST)
mad:* — Server actions PHPMadComponentHandler, executa um método PHP, atualiza state. Sintaxe: dois pontos (mad:click).mad-* — Alpine reativo JSx-* do Alpine.js, 100% client-side. Sintaxe: hífen (mad-text, mad-on:click).Diretivas de server action (mad:*)
Convertidas para data-mad-* em tempo de compilação
(Mad\View\MadBlade) e processadas no client por
packages/mad-framework/assets/mad-livewire.js. Cada uma dispara
um POST para o endpoint do componente.
| Diretiva | Dispara em | Resolve para |
|---|---|---|
mad:click="metodo" | click | data-mad-click |
mad:model="prop" | próximo POST do wrapper | data-mad-model |
mad:model.live="prop" | input, debounced 300ms | data-mad-model-live |
mad:submit="metodo" | submit do <form> | data-mad-submit |
mad:change="metodo" | change (select/input/checkbox) | data-mad-change |
mad:loading | visível só durante a requisição | data-mad-loading |
mad:loading.remove | escondido durante a requisição | data-mad-loading-remove |
mad:click
Chama um método público do MadComponent atual.
<mad-btn mad:click="incrementar">+1</mad-btn>
<mad-btn mad:click="resetar" variant="ghost">Resetar</mad-btn>
Com parâmetros — literais (number/string/bool) ou interpolados do Blade:
MAD__BLADE_COMMENT__1__
<mad-btn mad:click="aprovar(true)">Aprovar</mad-btn>
<mad-btn mad:click="onEditar({{ $pedido->id }})">Editar</mad-btn>
<mad-btn mad:click="onMudarStatus('cancelado')">Cancelar</mad-btn>
MAD__BLADE_COMMENT__2__
public function onEditar(int $id): MadResponse
{
$pedido = Pedido::findOrFail($id);
// ...
}
Os parâmetros de mad:click="metodo(1, 'x')" são parseados
como JSON no client (mad-livewire.js) e enviados
posicionalmente em mad_params. O PHP resolve por
posição/tipo via reflection — mesmo mecanismo usado em
mount(array $params).
Atalhos mágicos — $set e $refresh
Quando a única coisa que uma action faria é setar uma prop, pule o método PHP
inteiramente — $set resolve direto no client e dispara um POST
sem mad_action (só mad_model com os valores).
MAD__BLADE_COMMENT__3__
<mad-btn mad:click="$set('periodo', 'mes')">Este mes</mad-btn>
<mad-btn mad:click="$set('periodo', 'dia')">Hoje</mad-btn>
MAD__BLADE_COMMENT__4__
<mad-btn mad:click="$set({ periodo: 'mes', categoria: 'todas' })">Resetar filtros</mad-btn>
MAD__BLADE_COMMENT__5__
<mad-btn mad:click="$refresh">Atualizar</mad-btn>
mad:model e mad:model.live
Two-way binding entre um input e uma prop pública. Detalhes completos em Two-way binding.
MAD__BLADE_COMMENT__6__
<input type="text" mad:model="nome" value="{{ $that->nome }}">
MAD__BLADE_COMMENT__7__
<input type="text" mad:model.live="busca" value="{{ $that->busca }}"
placeholder="Buscar produto...">
MAD__BLADE_COMMENT__8__
<mad-input-field name="nome" label="Nome" />
<mad-money-field name="valor" label="Valor" mad:model.live="valor" />
mad:submit
<form mad:submit="onSalvar">
<mad-input-field name="nome" label="Nome" required />
<mad-money-field name="valor" label="Valor" required />
<mad-form-actions>
<mad-btn type="submit" variant="primary">Salvar</mad-btn>
</mad-form-actions>
</form>
public function onSalvar(): MadResponse
{
$produto = Produto::findOrNew($this->registroId);
$this->form->save($produto);
return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
}
mad:change
Útil para cascatas (estado → cidade, categoria → produto). O valor atual do elemento vai como último parâmetro:
MAD__BLADE_COMMENT__9__
<mad-dbselect-field name="estado" model="Estado" display="nome"
mad:change="onChangeEstado" />
// mad:change="onChangeEstado" -> onChangeEstado($value) — $value = valor do <select>
public function onChangeEstado(string $value): MadResponse
{
$this->cidade = '';
$html = MadBlade::render('components.dbcombo-field', [
'name' => 'cidade', 'label' => 'Cidade', 'display' => 'nome',
'query' => Cidade::where('estado_id', $value)->orderBy('nome'),
]);
return (new MadResponse())->html('[data-mad-target="cidade-combo"]', $html);
}
data-mad-loading e data-mad-loading-remove
Toda requisição desabilita automaticamente todo [data-mad-click]
e os botões de submit do form de origem — mad:loading/
.remove dão controle fino sobre o que mais aparece ou
some durante o request.
<mad-btn mad:click="onSalvar" mad:loading>
Salvar
</mad-btn>
MAD__BLADE_COMMENT__10__
<i data-lucide="check-circle" mad:loading.remove></i>
MAD__BLADE_COMMENT__11__
<span mad:loading class="mad-spinner"></span>
<span mad:loading.remove>Pronto</span>
data-mad-confirm — confirmação antes do clique
Não é uma diretiva mad:* (não tem prefixo reescrito) — é um
atributo HTML simples que o mesmo listener de mad:click lê
antes de disparar o POST. Usa window.madConfirm se disponível
(dialog estilizado), com fallback para confirm() nativo.
MAD__BLADE_COMMENT__12__
<mad-btn mad:click="onExcluir({{ $produto->id }})"
data-mad-confirm="Excluir este produto? Essa ação não pode ser desfeita.">
Excluir
</mad-btn>
Atributos reservados — nunca reescritos
O wrapper de todo MadComponent carrega estes atributos de
controle interno. Não os declare manualmente nem os use como nome de prop:
mad-component identifica o wrapper do MadComponent no DOM
mad-id ID unico da instancia (usado em querySelector/escopo de ops)
mad-state state serializado + criptografado (AES-256-GCM)
mad-endpoint URL do wire endpoint (/app/_mad-wire ou /public/_mad-wire)
mad-action nome da action — uso interno do handler
mad-params params extras — uso interno do handler
mad-form id do form associado
mad-name identificador generico
Diretivas Alpine reativas (mad-*)
Família separada: aliases 1:1 de x-* do Alpine.js, reescritos
client-side por um plugin (mad-ui.js) que roda em
alpine:init e via MutationObserver para
fragmentos injetados depois (drawer, modal, morph). Combinadas com a op
bind do MadWire, qualquer mudança de prop pública server-side
propaga pro Alpine state automaticamente — sem full re-render.
| Padrão MAD | Alpine equivalente | Uso |
|---|---|---|
mad-data="..." | x-data | Estado reativo (normalmente via @madWire) |
mad-init="..." | x-init | Código de inicialização |
mad-text="prop" | x-text | Texto reativo |
mad-html="prop" | x-html | HTML reativo (cuidado com XSS) |
mad-show="cond" | x-show | Visibilidade (display:none) |
mad-if="cond" | x-if (dentro de <template>) | Render condicional |
mad-for="x in lista" | x-for (dentro de <template>) | Loop |
mad-bind:attr="..." | x-bind:attr | Bind de atributo/classe/style |
mad-on:evt="..." | x-on:evt | Event listener client puro |
mad-model="prop" | x-model | Two-way binding (Alpine puro — não sai do navegador) |
mad-cloak | x-cloak | Esconde até Alpine inicializar |
mad-ref="nome" | x-ref | Referência ($refs.nome) |
mad-effect="..." | x-effect | Efeito reativo |
mad-ignore | x-ignore | Ignorar Alpine nesse nó |
mad-transition | x-transition | Transições de show/hide |
mad-modelable | x-modelable | Expor prop de componente Alpine para x-model externo |
mad-teleport | x-teleport | Portal de DOM (Alpine, não confundir com MadResponse->teleport()) |
@madWire — estado reativo a partir das props PHP
Lê props públicas do MadComponent atual via
MadRenderContext e emite mad-data='{"prop":valor}'
— o plugin reescreve para x-data, criando um scope Alpine com
os valores iniciais já vindos do PHP.
<div @madWire(['contador', 'mensagem'])>
<div mad-text="contador" style="font-size:3rem;"></div>
<span mad-text="contador > 5 ? 'Muito!' : 'Pouco'"></span>
<template mad-if="mensagem">
<div class="mad-alert" mad-text="mensagem"></div>
</template>
<div mad-show="contador > 0">Tem valor</div>
<button mad:click="incrementar">+1 servidor</button>
<button mad-on:click="contador++">+1 cliente (só visual)</button>
</div>
Quando uma action server muda $this->contador, o handler emite
uma op bind que atualiza tanto qualquer
[data-mad-bind="contador"] quanto o Alpine state de qualquer
[x-data] do wrapper que contenha a prop — mad-text,
mad-if, mad-show re-renderizam sozinhos, sem POST
novo e sem full re-render.
mad-for — loop reativo
<div @madWire(['alertas'])>
<template mad-if="alertas.length > 0">
<ul>
<template mad-for="alerta in alertas">
<li mad-text="alerta.mensagem"></li>
</template>
</ul>
</template>
<template mad-if="alertas.length === 0">
<p>Nenhum alerta.</p>
</template>
</div>
mad-bind:*, mad-on:*, mad-cloak
<div @madWire(['status', 'online'])>
MAD__BLADE_COMMENT__13__
<span mad-bind:class="online ? 'badge-success' : 'badge-danger'"
mad-text="online ? 'Online' : 'Offline'"></span>
MAD__BLADE_COMMENT__14__
<input mad-on:keyup.enter="$refs.busca.focus()" mad-ref="busca">
MAD__BLADE_COMMENT__15__
<div mad-cloak mad-show="status === 'pronto'">Pronto!</div>
</div>
@madBind — patch direto, sem Alpine
Mais leve que @madWire para mostrar um valor escalar em vários
pontos da tela: emite <span data-mad-bind="prop">valor</span>,
atualizável pela op bind sem precisar de scope Alpine nenhum.
@madBind('contador')
| Cenário | Usar |
|---|---|
| Texto simples, valor escalar direto | @madBind('prop') (mais leve) |
Render condicional (@if → mad-if) | @madWire + mad-if |
| Transformação (ternário, formatação) | @madWire + mad-text="expr" |
| Class/style/atributo dinâmico | @madWire + mad-bind:* |
| Loop reativo | @madWire + mad-for |
| Bind de input puro client (rascunho não persistido) | @madWire + mad-model |
mad-model vs mad:model — a confusão mais comum
O nome é quase igual e o separador é a única diferença visual — mas o comportamento é completamente diferente:
MAD__BLADE_COMMENT__16__
<div @madWire(['rascunho'])>
<textarea mad-model="rascunho"></textarea>
<span mad-text="rascunho.length + ' caracteres'"></span>
</div>
MAD__BLADE_COMMENT__17__
<input type="text" mad:model="nome" value="{{ $that->nome }}">
mad-model (hífen) é x-model do Alpine — nunca sai do
navegador, não existe em PHP. mad:model (dois pontos) sincroniza
com uma prop pública do MadComponent no próximo POST. A mesma
regra vale para mad-on:click (JS puro) vs mad:click
(action PHP).
NUNCA fazer
MAD__BLADE_COMMENT__18__
<button mad-click="contador++">+1</button>
MAD__BLADE_COMMENT__19__
<button mad-on:click="contador++">+1 cliente</button>
<button mad:click="onIncrementar">+1 servidor</button>
MAD__BLADE_COMMENT__20__
<input mad-model="nome"> MAD__BLADE_COMMENT__21__
MAD__BLADE_COMMENT__22__
<input mad:model="nome" value="{{ $that->nome }}">
MAD__BLADE_COMMENT__23__
<div @madWire(['contador'])>
@madBind('contador') MAD__BLADE_COMMENT__24__
<span mad-text="contador"></span>
</div>
MAD__BLADE_COMMENT__25__
<span data-mad-bind="contador">@madBind('contador')</span>