Campos de formulário
mad-unique-search-field: select buscável (TomSelect) com options fixas.
Select buscável (TomSelect) com as options pré-carregadas — aqui vindas da fixture TutorDemoCliente (business.tutor_clientes, ~40 registros). Pra busca AJAX no servidor com listas grandes, veja mad-dbunique-search-field (grupo Busca (DB)).
@php
// mad-unique-search-field não tem prop model/database própria (isso é
// do mad-dbunique-search-field, grupo Busca (DB)) — as options vêm de um
// array PHP normal. Aqui usamos a fixture do MAD Lab pra não depender
// de dados inventados.
$tutorBuscaClientesUnico = \App\Models\TutorDemo\TutorDemoCliente::query()
->orderBy('nome')
->get(['id', 'nome', 'cidade', 'uf'])
->mapWithKeys(fn ($c) => [(string) $c->id => "{$c->nome} — {$c->cidade}/{$c->uf}"])
->all();
@endphp
<mad-unique-search-field name="cliente_unico" label="Cliente"
:options="$tutorBuscaClientesUnico" placeholder="Buscar cliente..."
hint="Select buscável (TomSelect) com as options pré-carregadas — aqui vindas da fixture TutorDemoCliente (business.tutor_clientes, ~40 registros). Pra busca AJAX no servidor com listas grandes, veja mad-dbunique-search-field (grupo Busca (DB))." />
mad-multi-search-field: escolhe vários itens buscando por texto.
mode=comma (padrão): salva os ids escolhidos separados por vírgula numa coluna do registro pai. mode=table grava numa tabela pivot via form->save().
@php
$tutorBuscaClientesMulti = \App\Models\TutorDemo\TutorDemoCliente::query()
->orderBy('nome')
->get(['id', 'nome', 'cidade', 'uf'])
->mapWithKeys(fn ($c) => [(string) $c->id => "{$c->nome} — {$c->cidade}/{$c->uf}"])
->all();
@endphp
<mad-multi-search-field name="clientes_multi" label="Clientes (vários)"
:options="$tutorBuscaClientesMulti" placeholder="Buscar e adicionar clientes..."
hint="mode=comma (padrão): salva os ids escolhidos separados por vírgula numa coluna do registro pai. mode=table grava numa tabela pivot via form->save()." />
mad-multi-entry-field: tags livres (digita e Enter) ou lista fixa (com options).
Sem options: o usuário cria qualquer tag digitando e pressionando Enter.
Com options: vira uma lista fechada — só dá pra escolher o que já existe.
<mad-form-grid :cols="2">
<mad-multi-entry-field name="tags_livres" label="Tags livres" placeholder="Digite e Enter..."
hint="Sem options: o usuário cria qualquer tag digitando e pressionando Enter." />
<mad-multi-entry-field name="modulos_fixos" label="Módulos"
:options="['erp' => 'ERP', 'crm' => 'CRM', 'financeiro' => 'Financeiro', 'estoque' => 'Estoque']"
hint="Com options: vira uma lista fechada — só dá pra escolher o que já existe." />
</mad-form-grid>
mad-seek-field: só dispara o evento mad:seek — a busca em si é montada pela página (Alpine puro, neste exemplo).
O botão só dispara mad:seek — este exemplo escuta com Alpine puro e abre a lista abaixo (10 primeiros clientes da fixture).
@php
// mad-seek-field é um PRIMITIVO: ele só renderiza um input + botão de
// lupa que despacha o evento "mad:seek" (window) com {name, onSeek, el}.
// O framework não embute nenhum modal/lista pronta pra essa variante
// manual (isso é o mad-seek / mad-dbseek-field — grupo Busca (DB) — que
// já vêm com grid de busca embutida). Quem abre a busca é a página.
//
// Este exemplo escuta "mad:seek" com Alpine puro e mostra uma lista
// simples logo abaixo do campo, reaproveitando as classes CSS do
// mad-dropdown (mesmo padrão visual, sem duplicar CSS novo).
$tutorBuscaSeekOptions = \App\Models\TutorDemo\TutorDemoCliente::query()
->orderBy('nome')
->limit(10)
->get(['id', 'nome', 'cidade', 'uf'])
->map(fn ($c) => ['id' => $c->id, 'label' => "{$c->nome} — {$c->cidade}/{$c->uf}"])
->values()
->all();
$tutorBuscaSeekJson = json_encode($tutorBuscaSeekOptions, JSON_UNESCAPED_UNICODE);
@endphp
<div class="mad-dropdown" style="position:relative; display:block; max-width:420px;"
x-data="{ open: false, options: {{ $tutorBuscaSeekJson }} }"
x-on:mad:seek.window="if ($event.detail.name === 'cliente_seek') open = true"
x-on:click.outside="open = false">
<mad-seek-field name="cliente_seek" label="Cliente (busca manual)"
placeholder="Clique na lupa para buscar..." on-seek="abrirBuscaClientes"
hint="O botão só dispara mad:seek — este exemplo escuta com Alpine puro e abre a lista abaixo (10 primeiros clientes da fixture)." />
<div class="mad-dropdown-menu" x-show="open" x-cloak @click="open = false">
<template x-for="opt in options" :key="opt.id">
<button type="button" class="mad-dropdown-item"
x-on:click="document.querySelector("input[name='cliente_seek']").value = opt.label">
<span x-text="opt.label"></span>
</button>
</template>
</div>
</div>
mad-form com submit real — dispara TutorFormsBusca::onSubmitDemo.
@php
$tutorBuscaWireClientes = \App\Models\TutorDemo\TutorDemoCliente::query()
->orderBy('nome')
->get(['id', 'nome'])
->mapWithKeys(fn ($c) => [(string) $c->id => $c->nome])
->all();
@endphp
<mad-form submit="onSubmitDemo">
<mad-form-grid :cols="2">
<mad-unique-search-field name="cliente_wire" label="Cliente"
:options="$tutorBuscaWireClientes" required />
<mad-multi-entry-field name="tags_wire" label="Tags" placeholder="Digite e Enter..." />
</mad-form-grid>
<mad-form-actions align="right">
<mad-btn type="submit" variant="primary" icon="send">Enviar (onSubmitDemo)</mad-btn>
</mad-form-actions>
</mad-form>
/**
* Submit do exemplo "Ação wire" — lê $this->form->getData() e devolve um
* toast com o payload recebido (arrays de multi-seleção viram "a+b").
*/
public function onSubmitDemo(): MadResponse
{
$data = $this->form->getData();
$preenchidos = [];
foreach ((array) $data as $campo => $valor) {
if (is_array($valor)) {
$valor = implode('+', $valor);
}
$valor = trim((string) $valor);
if ($valor !== '') {
$preenchidos[] = "{$campo}={$valor}";
}
}
$resumo = $preenchidos !== [] ? implode(' | ', $preenchidos) : 'nenhum campo preenchido';
return (new MadResponse())
->toast("Form recebido: {$resumo}", 'success', 'onSubmitDemo');
}
Código-fonte completo da página
<?php
namespace App\Control\Tutor;
use App\Support\Tutor\TutorDemoSeed;
use App\Support\Tutor\TutorPage;
use Mad\Form\MadForm;
use Mad\Http\MadResponse;
/**
* TutorFormsBusca — página "Busca" do catálogo (grupo forms).
*
* Cobre as variantes MANUAIS (não ligadas a banco) de busca: mad-unique-
* search-field, mad-multi-search-field, mad-multi-entry-field e
* mad-seek-field — as variantes "banco" (mad-dbunique-search-field etc.)
* vivem no grupo "db_busca", fora deste lote.
*
* Como essas tags recebem `options` como array puro (não têm prop model/
* database própria pra auto-carregar do banco), os exemplos consultam a
* fixture TutorDemoCliente direto no partial (mount() aqui só garante que
* ela existe via TutorDemoSeed::ensure() — os controllers do Tutor não
* expõem estado pros partials de exemplo: cada `<x-tutor-demo>` inclui a
* view isolada, sem o `$that` do controller da página).
*/
class TutorFormsBusca extends TutorPage
{
protected string $catalogKey = 'forms_busca';
public MadForm $form;
public function mount(array $params = []): void
{
$this->form = new MadForm('tutor_forms_busca');
TutorDemoSeed::ensure();
}
/**
* Submit do exemplo "Ação wire" — lê $this->form->getData() e devolve um
* toast com o payload recebido (arrays de multi-seleção viram "a+b").
*/
public function onSubmitDemo(): MadResponse
{
$data = $this->form->getData();
$preenchidos = [];
foreach ((array) $data as $campo => $valor) {
if (is_array($valor)) {
$valor = implode('+', $valor);
}
$valor = trim((string) $valor);
if ($valor !== '') {
$preenchidos[] = "{$campo}={$valor}";
}
}
$resumo = $preenchidos !== [] ? implode(' | ', $preenchidos) : 'nenhum campo preenchido';
return (new MadResponse())
->toast("Form recebido: {$resumo}", 'success', 'onSubmitDemo');
}
}
<x-tutor-page page="forms_busca">
<x-tutor-demo demo="tutor.examples.forms-busca.unique-search"
title="Busca única" desc="mad-unique-search-field: select buscável (TomSelect) com options fixas." />
<x-tutor-demo demo="tutor.examples.forms-busca.multi-search"
title="Busca múltipla" desc="mad-multi-search-field: escolhe vários itens buscando por texto." />
<x-tutor-demo demo="tutor.examples.forms-busca.tags"
title="Tags / chips" desc="mad-multi-entry-field: tags livres (digita e Enter) ou lista fixa (com options)." />
<x-tutor-demo demo="tutor.examples.forms-busca.seek"
title="Busca em popup (primitivo)" desc="mad-seek-field: só dispara o evento mad:seek — a busca em si é montada pela página (Alpine puro, neste exemplo)." />
<x-tutor-demo demo="tutor.examples.forms-busca.wire-submit"
title="Ação wire" desc="mad-form com submit real — dispara TutorFormsBusca::onSubmitDemo."
php="TutorFormsBusca::onSubmitDemo" />
</x-tutor-page>