Docs›Formulários›set() e setItems()
Formulários

set() e setItems()

Auto-bind: mude estado, framework gera ops.

MadForm::set() e MadForm::setItems() mudam o estado de campos do formulário via PHP. O framework detecta o diff do estado após a action e gera as ops parciais adequadas para atualizar o DOM — sem full re-render e sem MadResponse explícito.

Regra de ouro

Mude o estado no PHP, deixe o framework cuidar do DOM.

Quando usar cada um

Form principal

Preciso atualizar...MétodoOp gerada
Valor de um campo (input, textarea, select, date, etc.)form->set($name, $value)val
Options de um combo/select (mad-select-field / mad-dbcombo-field)form->setItems($name, $items)reload_combo
Options de um radio (mad-radio-field / mad-dbradio-field)form->setItems($name, $items)reload_radio
Options de um checkbox-groupform->setItems($name, $items)reload_checkbox_group
Options de um multi-entry (tags)form->setItems($name, $items)reload_multi_entry
Items de um sort-listform->setItems($name, $items)reload_sort_list
Items de um checklistform->setItems($name, $items)reload_checklist
Carregar options de combo via Eloquent (sem filtro)form->loadOptionsFromModel($name, ...)reload_combo

Field-list (linhas dinâmicas) — convenção nome[]

Preciso atualizar...MétodoOp gerada
Valor de campo de uma row (no mad:fl-change)form->set('campo[]', $v)fl_val
Options de combo de uma row (no mad:fl-change)form->setItems('campo[]', $items)fl_combo
Substituir todas as rows do field-listform->setRows('nome', $rows)fl_rows
Crítico — contexto de row

Ops fl_val/fl_combo só funcionam dentro de actions disparadas por mad:fl-change num <mad-field-list-column> (atributo on-change) — o JS captura row = el.closest('.mad-fl-row') antes do POST e patcha somente essa row na resposta. Outras actions (botão global, submit) não têm contexto de row e essas ops ficam órfãs.

Ambos podem ser chamados em sequência na mesma action — as ops são mescladas. Quando a action só muda estado, retorne void. Quando precisa de toast/drawer/close, retorne MadResponse e o framework funde com os ops auto-gerados.

$this->form->set($field, $value) — mudar valor de campo

$this->form->set(string $field, mixed $value): void;
// Preencher campos ao selecionar uma pessoa
public function onPessoaChange(string $value): void
{
    $p = Pessoa::find($value);
    $this->form->set('pessoa_nome',     $p->nome ?? '');
    $this->form->set('pessoa_email',    $p->email ?? '');
    $this->form->set('pessoa_telefone', $p->telefone ?? '');
}

// Com MadResponse mesclado (toast + val)
public function onConfirmar(): MadResponse
{
    $this->form->set('status', 'Confirmado');
    return MadToast::success('Status atualizado!');
}

Como funciona

  1. set() escreve em $this->form->fields[$field] e invalida o cache de getData().
  2. Ao final da action, MadComponentHandler serializa o MadForm e compara com o snapshot pré-action.
  3. Para cada campo escalar mudado, emite ['op' => 'val', 'target' => '[name="X"]', 'content' => ...].
  4. O JS aplica: el.value = ...; dispatchEvent(change) — notifica Alpine/MAD Select.

$this->form->setItems(...) — mudar options de coleção

$this->form->setItems(
    string $field,
    array $items,                       // mapa [value => label]
    string|array|null $selected = null, // valor(es) pré-selecionado(s)
    ?string $placeholder = null         // só para combo
): void;
// Combo
$this->form->setItems('unit_id', ['1' => 'Matriz', '2' => 'Filial'], '1');

// Combo com placeholder
$this->form->setItems('cidade_id', $cidades, null, '— Selecione —');

// Radio
$this->form->setItems('tipo', ['A' => 'Ativo', 'I' => 'Inativo'], 'A');

// Checkbox group (multi-select)
$this->form->setItems('perms', ['read' => 'Ler', 'write' => 'Escrever'], ['read']);

// Multi-entry / tags
$this->form->setItems('tags', $tagOptions, ['urgente', 'bug']);

// Sort list (a ordem do $selected define a ordem inicial)
$this->form->setItems('ordem_colunas', $campos, ['nome', 'email', 'id']);

// Checklist
$this->form->setItems('grupos', $grupos, ['1', '3']);

Cascata de combos

public function onEstadoChange(string $estadoId): void
{
    $cidades = Cidade::where('estado_id', $estadoId)
        ->orderBy('nome')
        ->pluck('nome', 'id')
        ->all();

    $this->form->setItems('cidade_id', $cidades, null, '— Selecione —');
}

Múltiplos campos numa action (tudo parcial, 1 request)

public function onCarregarTudo(): MadResponse
{
    $this->form->setItems('estado_id',  $estados,  null, '—');
    $this->form->setItems('tipo',       $tipos,    'A');
    $this->form->setItems('permissoes', $perms,    ['read']);
    $this->form->setItems('tags',       $tagOpts);

    return MadToast::success('Tudo populado!');
    // Resultado: 4 ops reload_* + 1 op toast — tudo mesclado, sem re-render.
}

Como funciona

  1. setItems() guarda as options em $this->form->items[$field] e (opcional) atualiza $this->form->fields[$field] com o(s) valor(es) selecionado(s).
  2. Os dois buckets (fields, items) são serializados no mad_state.
  3. No diff pós-action, MadComponentHandler compara items antes × depois.
  4. Para cada campo com items alterados, consulta o tipo registrado em MadFormRegistry::getFields() (preenchido durante o render) e emite a op reload_* correspondente.
  5. O handler JS aplica: combo via options nativas, radio/checkbox-group/sort-list via reconstrução do DOM, multi-entry via API do MAD Select (el._madSelect), checklist via Alpine data.

Padrão recomendado em actions

// Action retorna void — só muda estado
public function onFiltrar(): void
{
    $this->form->set('status', 'ativo');
    $this->form->setItems('categoria_id', $this->getCategorias());
}

// Action retorna MadResponse — estado + op explícita
public function onSalvar(): MadResponse
{
    $this->form->set('status', 'Salvo');
    return MadToast::success('Registro salvo!');
}

Armadilhas — NUNCA fazer

1. Não espelhe items numa prop pública do componente

Props array públicas que mudam durante a action e não têm entry no MadVarRegistry forçam full re-render (fallback seguro). Full re-render recria o DOM, o Alpine reinicializa e ops posteriores como openModal não são ouvidas pela nova instância.

// ERRADO — força full re-render, destrói o modal antes do openModal ser ouvido
public array $unitOptions = [];

public function onLogin(): MadResponse
{
    $this->unitOptions = ['1' => 'Matriz', '2' => 'Filial'];
    $this->form->setItems('unit_id', $this->unitOptions);
    return (new MadResponse())->openModal('login-unit');  // não funciona!
}

// CERTO — items só no form, sem prop array espelhada
public function onLogin(): MadResponse
{
    $unitOptions = ['1' => 'Matriz', '2' => 'Filial']; // variável local
    $this->form->setItems('unit_id', $unitOptions);
    return (new MadResponse())->openModal('login-unit');
}

Na view, o campo começa com :items="[]"; o reload_combo popula no client:

<mad-select-field name="unit_id" :items="[]" />

2. Não use MadResponse->val() quando form->set() resolve

// ERRADO — verboso, ignora o auto-bind
return (new MadResponse())->val('[name="nome"]', $nome);

// CERTO
$this->form->set('nome', $nome);

3. Não use html() manual pra popular options

// ERRADO — HTML injection, sem escape correto, sem sincronizar o MAD Select
$html = '';
foreach ($cidades as $id => $nome) {
    $html .= "<option value=\"{$id}\">{$nome}</option>";
}
return (new MadResponse())->html('select[name="cidade_id"]', $html);

// CERTO
$this->form->setItems('cidade_id', $cidades);

4. Não chame reloadCombo explícito quando setItems resolve

// Verboso — funciona, mas não é o padrão
return (new MadResponse())->reloadCombo('cidade_id', $cidades, null, '—');

// Preferido — auto-bind via setItems, action pode retornar void
$this->form->setItems('cidade_id', $cidades, null, '—');

5. Não esqueça o campo de destino no Blade

Se o Blade não declara um <mad-select-field name="cidade_id">, o MadFormRegistry não registra o tipo, o diff do handler não sabe qual op emitir, e cai no fallback de full re-render. Sempre declare o campo na view, mesmo que inicialmente vazio.

<!-- Campo declarado mesmo vazio — o tipo é registrado no schema -->
<mad-select-field name="cidade_id" label="Cidade" :items="[]" />

6. Não use form->setItems com componentes não suportados

A tabela do início desta página é autoritativa. Outros componentes (input, date, number, file, image, etc.) não têm bucket de items, e setItems não faz nada útil neles. Use form->set() para valores escalares.

Field-list — auto-bind por linha (convenção nome[])

MadForm reconhece o sufixo [] no nome do campo como field-list e emite ops escopadas à row de origem do change event.

Como o roteamento por row funciona

O JS captura row = el.closest('.mad-fl-row') antes do POST e patcha somente essa row na resposta. Por isso, ops fl_val/fl_combo só funcionam quando a action é disparada por mad:fl-change (atributo on-change numa <mad-field-list-column>).

form->set('campo[]', $valor) — valor na row atual

public function onChangeProduto($value): void
{
    $p = Produto::find($value);

    $this->form->set('valor[]',     $p->preco ?? 0);
    $this->form->set('unidade[]',   $p->unidade ?? '');
    $this->form->set('descricao[]', $p->descricao ?? '');
}

form->setItems('campo[]', $items) — combo da row atual

public function onChangeFamilia($value): void
{
    $produtos = Produto::where('familia_id', $value)
        ->orderBy('nome')
        ->pluck('nome', 'id')
        ->all();

    $this->form->setItems('produto_id[]', $produtos);
}

form->loadOptionsFromModel('campo[]', ...) — atalho via Eloquent

public function onChangeTipo($value): void
{
    // loadOptionsFromModel($field, $model, $key='id', $display='nome', $orderBy=null)
    // — não tem parâmetro de filtro/WHERE. Para filtrar, monte a query você
    // mesmo e use setItems() (ver onChangeFamilia acima), ou
    // ModelOptionsLoader::itemsFromQuery() com um builder já filtrado.
    $this->form->loadOptionsFromModel('produto_id[]', \App\Models\Produto::class, 'id', 'nome');
}

Funciona idêntico no form principal — sem [] emite reload_combo (serializado no state), com [] emite fl_combo (escopo de row).

form->setRows('nome', $rows) — substitui todas as rows

Diferente das outras ops de field-list, setRows não depende de contexto de row — pode ser chamado em qualquer action:

public function onCarregarParcelas(): void
{
    $rows = Parcela::where('pedido_id', $this->pedidoId)
        ->orderBy('numero')
        ->get();

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

form->set vs FieldListColumn::setValue

CenárioUsar
Action retorna void (só muda estado)$this->form->set('campo[]', $v)
Precisa combinar com toast, openDrawer etc.FieldListColumn::setValue('campo', $v) + merge()
Mistura form principal + field-list na mesma actionform->set (escolhe a op pelo sufixo [])

FieldListColumn::* continua existindo e funcionando — usar quando precisar do retorno explícito MadResponse para encadear ops de UI.

NUNCA fazer

// ERRADO: usar form->set('campo[]') fora de mad:fl-change
// (botão global, submit, mad:click) — op fl_val fica órfã, sem row de destino
public function onSalvar(): MadResponse
{
    $this->form->set('valor[]', 100); // não tem row de origem
    return (new MadResponse())->closeDrawer();
}

// CERTO: form principal usa nome sem []
$this->form->set('valor', 100);

// CERTO: field-list usa setRows, que opera no field-list inteiro
$this->form->setRows('itens', $rows);
// ERRADO: misturar APIs no mesmo handler de fl-change
public function onChangeProduto($value): MadResponse
{
    $this->form->set('valor[]', 12.50);  // gera _pendingFlOps
    return FieldListColumn::setValue('unidade', 'UN'); // outro fluxo
    // → o pendingFlOps some — o return abandona o caminho void
}

// CERTO: tudo via form->set, return void
public function onChangeProduto($value): void
{
    $this->form->set('valor[]', 12.50);
    $this->form->set('unidade[]', 'UN');
}

// CERTO ALTERNATIVO: tudo via FieldListColumn + merge
public function onChangeProduto($value): MadResponse
{
    return FieldListColumn::setValue('valor', 12.50)
        ->merge(FieldListColumn::setValue('unidade', 'UN'));
}

Quando MadResponse explícito ainda é necessário

  • Abrir/fechar modal ou drawer: openModal, closeModal, openDrawer, closeDrawer
  • Toast/alert: toast, MadToast::*, MadMessage::error
  • Redirect / navigate: redirect(...), redirectUrl(...)
  • Remover/atualizar linha de grid: removeRow, manageRow
  • Elementos fora de um form MAD (ex: popular um <div> com conteúdo): html(selector, content)
  • Recarregar transporter: energize(name)

Nesses casos, combine livremente com set/setItems:

public function onSave(): MadResponse
{
    $this->form->set('ultima_alteracao', date('d/m/Y H:i'));
    $this->form->setItems('historico', $this->loadHistorico());

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

Próximos