mad-cep-field
Campo de CEP com busca automática e auto-fill.
Campo de CEP com integração nativa à API de CEP do madbuilder. Encapsula tudo: máscara, chamada AJAX, e preenchimento automático dos campos do form via o atributo fill-fields. Zero código PHP no controller para o caso básico.
Por trás dos panos, o blur (ou clique na lupa) dispara Mad\Service\MadCepService::onSearch(), que consulta a API de CEP do madbuilder diretamente (cURL) e devolve os campos crus — sem cache local, sem persistência. Os parâmetros (fill-fields + config de resolução, ver abaixo) viajam num token criptografado (AES-256-GCM via MadStateCrypt) para o endpoint, então o cliente não pode forjar qual model/coluna é consultado.
Props
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| name | string | '' |
Nome do campo (obrigatório) |
| label | string | '' |
Label |
| value | string | '' |
Valor inicial (sobrescrito pelo MadRenderContext quando o form está populado) |
| fill-fields | array | [] |
Mapa 'form_field' => 'api_field' — ver seção abaixo |
| auto | bool | true |
Dispara a busca automaticamente no blur quando o CEP tem 8 dígitos. Desligue com :auto="false" para exigir clique no botão |
| strip-mask | bool | false |
Remove a máscara no getData() — o backend recebe só os dígitos |
| placeholder | string | '00000-000' |
|
| hint | string | '' |
Texto de ajuda |
| error | string | '' |
Mensagem de erro |
| required | bool | false |
|
| disabled | bool | false |
|
| attrs | string | '' |
Atributos HTML extras |
| id | string | $name |
ID do elemento no DOM |
O id do <input> é gerado automaticamente (mad_{name}_{random}) — não é uma prop configurável.
Resolução automática de cidade/estado (opt-in)
Sem essas props, fill-fields só preenche o que a API de CEP devolve cru (texto). Para resolver/preencher os IDs de Cidade/Estado no banco, declare city-model e/ou state-model — o componente busca o registro via Eloquent (casando codigo_ibge por padrão) e injeta o ID resolvido na resposta:
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
| city-model | string | '' |
Model Eloquent da cidade — ativa a resolução. Ex: "Cidade" |
| state-model | string | '' |
Model Eloquent do estado — ativa a resolução. Ex: "Estado" |
| city-match-column | string | 'codigo_ibge' |
Coluna do model casada com a API |
| state-match-column | string | 'codigo_ibge' |
Coluna do model casada com a API |
| city-api-field | string | 'cidade_cod_ibge' |
Campo da API usado no match |
| state-api-field | string | 'estado_cod_ibge' |
Campo da API usado no match |
| city-key | string | 'id' |
Coluna do model devolvida como ID |
| state-key | string | 'id' |
Coluna do model devolvida como ID |
| city-target | string | 'cidade_id' |
Campo injetado na resposta com o ID da cidade |
| state-target | string | 'estado_id' |
Campo injetado na resposta com o ID do estado |
| city-state-fk | string | '' |
Coluna FK do estado no model de cidade — preenche o estado a partir da cidade quando state-model não é informado. Ex: 'estado_id' |
| city-scope-by-state | bool | false |
Restringe o lookup da cidade ao estado resolvido (WHERE city-state-fk = estado_id). Essencial quando city-match-column é 'nome' — o Brasil tem dezenas de cidades homônimas em estados diferentes. Requer city-state-fk + state-model |
| database | string | '' |
Override da conexão do lookup/criação. Default: a conexão declarada no próprio model Eloquent |
| city-create | array|bool | false |
Cria a cidade se o lookup não achar. array = mapa [modelColumn => apiField] (recomendado); true = mapa mínimo ['nome' => 'cidade']. false = somente leitura (default) |
| state-create | array|bool | false |
Idem para o estado. true = ['nome' => 'estado'] |
Obrigatório: declarar city-model/state-model não basta — o ID resolvido só chega ao form se o campo-alvo (cidade_id/estado_id, ou o city-target/state-target customizado) também estiver mapeado em fill-fields. Sem isso o ID é descartado silenciosamente e o combo fica vazio.
<mad-cep-field name="cep"
city-model="Cidade" state-model="Estado"
:fill-fields="[
'endereco' => 'rua',
'bairro' => 'bairro',
'estado_id' => 'estado_id', {{-- liga o estado resolvido ao combo --}}
'cidade_id' => 'cidade_id', {{-- liga a cidade resolvida ao combo --}}
]" />
ATENÇÃO (footgun) com city-create/state-create: o registro novo é inserido apenas com as colunas do mapa de criação + a coluna de match (codigo_ibge) + a FK do estado (via city-state-fk). Qualquer outra coluna NOT NULL do model que a API não fornece (ex: sigla do estado, flag ativo, FK de empresa/tenant) faz o store() falhar — e a resolução degrada em silêncio (os campos de endereço preenchem, mas cidade_id/estado_id voltam vazios, sem erro visível). Garanta que toda coluna obrigatória esteja no mapa de criação ou tenha default no banco. Recomenda-se também índice UNIQUE em codigo_ibge.
{{-- Auto-criar com colunas explicitas --}}
<mad-cep-field name="cep"
city-model="Cidade" state-model="Estado" city-state-fk="estado_id"
:state-create="['nome' => 'estado', 'sigla' => 'uf']"
:city-create="['nome' => 'cidade']"
:fill-fields="['estado_id' => 'estado_id', 'cidade_id' => 'cidade_id']" />
Mapeamento fill-fields
Sintaxe: 'nome_do_input_no_form' => 'nome_do_campo_na_resposta_da_api'.
Campos disponíveis na resposta:
| Campo API | Descrição |
|---|---|
cep |
CEP limpo (8 dígitos) |
rua |
Construído: tipo_logradouro + ' ' + logradouro |
logradouro |
Nome puro da rua |
tipo_logradouro |
Avenida / Rua / Praça / etc |
bairro |
|
cidade |
Nome da cidade |
uf |
Sigla do estado |
estado |
Nome completo do estado |
cidade_cod_ibge |
Código IBGE da cidade |
estado_cod_ibge |
Código IBGE do estado |
cidade_id |
ID da cidade no banco — só presente quando city-model está ativo |
estado_id |
ID do estado no banco — só presente quando state-model está ativo |
Uso básico
<mad-cep-field name="cep" label="CEP" :fill-fields="[
'endereco' => 'rua',
'bairro' => 'bairro',
'cidade_id' => 'cidade_id',
'estado_id' => 'estado_id',
]" city-model="Cidade" state-model="Estado" />
<mad-input-field name="endereco" label="Endereco" />
<mad-input-field name="bairro" label="Bairro" />
<mad-dbcombo-field name="estado_id" label="Estado" model="Estado" display="nome" />
<mad-dbcombo-field name="cidade_id" label="Cidade" model="Cidade" display="nome"
depends-on="estado_id" />
O componente já sabe que o usuário vai querer preencher combos com depends-on — ele adia o preenchimento do campo com "cidade" no nome em 150ms para dar tempo do combo filho recarregar após o estado_id ser setado (ver "Ordem de preenchimento" abaixo).
Dentro de formulário completo
<mad-form submit="onSave">
<mad-form-section title="Endereço" icon="map-pin">
<mad-form-grid :cols="3">
<mad-cep-field name="cep" label="CEP" city-model="Cidade" state-model="Estado"
:fill-fields="[
'logradouro' => 'rua',
'bairro' => 'bairro',
'estado_id' => 'estado_id',
'cidade_id' => 'cidade_id',
]" />
<mad-input-field name="logradouro" label="Logradouro" />
<mad-input-field name="numero" label="Numero" />
</mad-form-grid>
<mad-form-grid :cols="3">
<mad-input-field name="bairro" label="Bairro" />
<mad-dbcombo-field name="estado_id" label="Estado"
model="Estado" display="nome" />
<mad-dbcombo-field name="cidade_id" label="Cidade"
model="Cidade" display="nome" depends-on="estado_id" />
</mad-form-grid>
</mad-form-section>
<mad-form-actions>
<mad-btn type="submit" variant="primary" icon="save">Salvar</mad-btn>
</mad-form-actions>
</mad-form>
use Illuminate\Support\Facades\DB;
// Controller — NENHUMA linha de codigo de CEP necessaria
public function onSave(): MadResponse
{
try {
$this->form->validate(Pessoa::rules($this->registroId));
DB::connection('business')->transaction(function () {
$pessoa = Pessoa::findOrNew($this->registroId);
$this->form->save($pessoa);
});
return (new MadResponse())->toast('Salvo!', 'success')->closeDrawer();
} catch (MadValidationException $e) {
return $e->asModal();
} catch (\Throwable $e) {
return MadMessage::error('Erro', $e->getMessage());
}
}
Desligando o auto-blur (só manual)
<mad-cep-field name="cep" label="CEP"
:auto="false"
:fill-fields="['endereco' => 'rua']" />
Nesse modo, a busca só acontece quando o usuário clica no botão de lupa.
Desligar o fill-fields
Se você não passar fill-fields, o componente ainda funciona como campo de CEP com máscara. O blur dispara a chamada, mas nenhum campo é preenchido — útil quando você só quer guardar o CEP digitado sem popular endereço.
Comportamento do blur
O auto dispara a busca quando o usuário sai do campo e o valor limpo tem exatamente 8 dígitos. Se a busca falhar (CEP inválido, CEP inexistente, erro de rede), um madToast('warning') aparece e os campos alvo não são alterados.
Durante a chamada AJAX o botão de lupa mostra um spinner e fica desabilitado. O toast de sucesso aparece quando pelo menos um campo foi preenchido.
Ordem de preenchimento — combos com depends-on
Campos cujo nome contém "cidade" são setados 150ms depois dos demais. Isso garante que:
estado_idé preenchido primeiro- Evento
changedispara no<mad-dbcombo-field name="estado_id"> - O
<mad-dbcombo-field name="cidade_id" depends-on="estado_id">recarrega as options via AJAX - 150ms depois, o
cidade_idé setado — o<option>já existe no DOM e o MAD Select/select encontra
Se sua convenção de nome para cidade for diferente (ex: municipio_id), o auto-delay não vai acionar — nesse caso, use :auto="false" e dispare manualmente, ou renomeie o campo para conter "cidade".
Valor enviado no form
O mad:model coleta o valor com máscara (12345-678). Se o seu model precisa do valor limpo, use a prop strip-mask para que o getData() já devolva só os dígitos:
<mad-cep-field name="cep" label="CEP" strip-mask :fill-fields="[...]" />
Ou normalize no mutator do model Eloquent:
use Illuminate\Database\Eloquent\Casts\Attribute;
class Pessoa extends \Illuminate\Database\Eloquent\Model
{
protected function cep(): Attribute
{
return Attribute::make(
set: fn ($value) => preg_replace('/\D/', '', (string) $value),
);
}
}
NUNCA fazer
{{-- ERRADO: input manual + JS manual --}}
<mad-input-field name="cep" label="CEP" placeholder="00000-000" />
<script>/* fetch ViaCEP manual */</script>
{{-- CERTO --}}
<mad-cep-field name="cep" label="CEP" :fill-fields="[...]" />
{{-- ERRADO: chamar uma API de CEP direto no controller --}}
public function onBuscarCep(): MadResponse {
$json = file_get_contents("https://viacep.com.br/ws/{$cep}/json/");
// ...
return (new MadResponse())->val('#logradouro', $data['logradouro']);
}
{{-- CERTO: deixar o componente cuidar de tudo --}}
<mad-cep-field name="cep" label="CEP" :fill-fields="[
'logradouro' => 'rua', 'bairro' => 'bairro'
]" />
{{-- ERRADO: chamar MadCepService::onSearch diretamente no form/controller --}}
$dados = \Mad\Service\MadCepService::onSearch(['token' => $t, 'value' => $cep]);
$this->form->set('endereco', $dados['rua']);
{{-- CERTO --}}
<mad-cep-field name="cep" label="CEP" :fill-fields="[...]" />
{{-- ERRADO: direcao invertida do mapa --}}
:fill-fields="['rua' => 'endereco']"
{{-- Isso tenta ler campo 'rua' do form e preencher 'endereco' na API, o oposto do esperado --}}
{{-- CERTO: chave = form, valor = api --}}
:fill-fields="['endereco' => 'rua']"
{{-- ERRADO: nomes de campos da API inexistentes --}}
:fill-fields="['endereco' => 'street', 'uf' => 'state']"
{{-- A API de CEP do madbuilder retorna 'rua', 'uf', etc. --}}
{{-- CERTO: conferir a tabela de campos desta regra --}}
:fill-fields="['endereco' => 'rua', 'estado' => 'uf']"
{{-- ERRADO: mapear cidade_id/estado_id em fill-fields sem declarar city-model/state-model --}}
<mad-cep-field name="cep" :fill-fields="['cidade_id' => 'cidade_id']" />
{{-- A API nao devolve o ID do banco sozinha — sem city-model, o campo nunca e preenchido --}}
{{-- CERTO: ativar a resolucao --}}
<mad-cep-field name="cep" city-model="Cidade"
:fill-fields="['cidade_id' => 'cidade_id']" />