Docs›Componentes (Admin)›mad-cep-field
Componentes (Admin)

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:

  1. estado_id é preenchido primeiro
  2. Evento change dispara no <mad-dbcombo-field name="estado_id">
  3. O <mad-dbcombo-field name="cidade_id" depends-on="estado_id"> recarrega as options via AJAX
  4. 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']" />