Guia do Componente FK V2
Versão: 2.0.0 Localização:
/public/css/design-system/components/fk-v2.*Documentação:/public/doc/architect/fk/evolucao_componente_fk/
Índice
- Visão Geral
- Instalação
- Uso Básico
- Configuração do Painel
- API PHP
- API JavaScript
- Eventos
- Exemplos Completos
- Migração do FK Legado
Visão Geral
O FK V2 é o novo componente de Foreign Key do Design System SIDASP. Diferente do FK legado que usa modal centralizado, o FK V2 renderiza no Sidebar Right com um painel deslizante para adicionar/editar.
Comparativo
| Aspecto | FK Legado | FK V2 |
|---|---|---|
| Localização | Modal centralizado | Sidebar Right |
| Serialização | Delimitadores | JSON |
| Validação | eval() | JSON.parse() |
| Dark Mode | Parcial | 100% |
| Acessibilidade | Básica | WCAG 2.1 AA |
Arquivos
/public/css/design-system/components/
├── fk-v2.css # Estilos do trigger e cards
├── fk-v2-panel.css # Estilos do painel deslizante
└── fk-v2.js # Lógica JavaScript
/vendor/Agrodefesa/library/
├── Form/Element/FkV2.php # Classe de renderização
└── Controller/Crud/ForeignKeyV2.php # Trait para controllersInstalação
1. Incluir CSS
html
<!-- No layout ou view -->
<link rel="stylesheet" href="/css/design-system/components/fk-v2.css">
<link rel="stylesheet" href="/css/design-system/components/fk-v2-panel.css">2. Incluir JavaScript
html
<!-- Antes de fechar </body> -->
<script src="/css/design-system/components/fk-v2.js"></script>3. Usar no PHP
php
<?php
// Instanciar diretamente
$fk = new \Agrodefesa\Form\Element\FkV2();
// Ou importar
use Agrodefesa\Form\Element\FkV2;
$fk = new FkV2();
// Ou método estático
$fk = FkV2::create();Uso Básico
Na View (sidebar.phtml)
php
<?php
$fkEndereco = new \Agrodefesa\Form\Element\FkV2();
$fkEndereco->setId('fk_endereco')
->setNome('fk_endereco')
->setTitulo('Endereço(s)')
->setDescricao('Adicione um ou mais endereços')
->setRota($this->url('modulo/default', [
'controller' => 'controlador',
'action' => 'fk-endereco-v2'
]))
->setIcone('mdi-home')
->setDados($this->fkEnderecos ?? []);
echo $fkEndereco->render();
?>No Controller (Trait Fk)
php
<?php
namespace MeuModulo\Controller\MeuControlador;
trait Fk
{
/**
* Action do painel FK
* Rota: /meu-modulo/meu-controlador/fk-endereco-v2
*/
public function fkEnderecoV2Action()
{
// Dados para edição vêm via query string
$this->viewModel->dados = \Agrodefesa\Util\Util::arrayToObject(
$this->params()->fromQuery()
);
// Listas via Model
$this->viewModel->tiposEndereco = $this->negocio->getTiposEndereco();
}
/**
* Validação do FK
* Rota: /meu-modulo/meu-controlador/fk-endereco-v2-validar
*/
public function fkEnderecoV2ValidarAction()
{
$dados = $this->params()->fromPost();
$erros = $this->negocio->validarFkEndereco($dados);
return $this->jsonResponse([
'status' => empty($erros) ? 'ok' : 'erro',
'erros' => $erros,
]);
}
}View do Painel (fk-endereco-v2.phtml)
php
<?php $dados = $this->dados ?? new \stdClass(); ?>
<div class="fk-v2-panel__header">
<h3 class="fk-v2-panel__title">
<?= empty($dados->oid) ? 'Adicionar endereço' : 'Editar endereço' ?>
</h3>
<button type="button" class="fk-v2-panel__close" data-fk-v2-panel-close>
<i class="mdi mdi-close"></i>
</button>
</div>
<div class="fk-v2-panel__body">
<div data-fk-v2-errors style="display: none;"></div>
<form class="fk-v2-panel__form">
<input type="hidden" name="oid" value="<?= $dados->oid ?? '' ?>">
<div class="fk-v2-panel__form-group">
<label class="fk-v2-panel__label">Logradouro *</label>
<input type="text" name="logradouro" class="form-control"
value="<?= $dados->logradouro ?? '' ?>">
</div>
<!-- Mais campos... -->
</form>
</div>
<div class="fk-v2-panel__footer">
<button type="button" class="btn btn-outline" data-fk-v2-panel-cancel>
Cancelar
</button>
<button type="button" class="btn btn-primary" data-fk-v2-panel-save>
Salvar
</button>
</div>Configuração do Painel
Largura Personalizada
php
// Via pixels
$fk->setLargura(700);
// Via classe CSS
$fk->setLarguraClasse('lg'); // sm=400px, md=520px, lg=700px, xl=900px, full=100%Template da Ficha
php
// Customizar campos exibidos na ficha
$fk->setTemplateFicha([
'primary' => '{{logradouro}}, {{numero}}',
'secondary' => '{{tipo_endereco}}',
'tertiary' => '{{municipio}}-{{uf}}',
'icon' => 'home'
]);Configurações Adicionais
php
$fk->setConfig([
'required' => true, // FK obrigatório
'minItems' => 1, // Mínimo de itens
'maxItems' => 5, // Máximo de itens
'customValidation' => true, // Validação customizada
]);API PHP
Classe FkV2
php
use Agrodefesa\Form\Element\FkV2;
$fk = new FkV2();
// Métodos de configuração (fluent interface)
$fk->setId(string $id): self
$fk->setNome(string $nome): self
$fk->setTitulo(string $titulo): self
$fk->setDescricao(string $descricao): self
$fk->setRota(string $rota): self
$fk->setIcone(string $icone): self
$fk->setLargura(int $largura): self
$fk->setLarguraClasse(string $classe): self
$fk->setDados(array $dados): self
$fk->setConfig(array $config): self
$fk->setTemplateFicha(array $template): self
// Renderização
$fk->render(): string
echo $fk; // __toString()Trait ForeignKeyV2
php
use Agrodefesa\Controller\Crud\ForeignKeyV2;
class MeuController extends Crud
{
use ForeignKeyV2;
// Métodos disponíveis:
// Configura actions automaticamente
$this->configurarFkV2(): void
// Respostas JSON
$this->fkV2Ok(array $dados = []): JsonModel
$this->fkV2Erro(array $erros): JsonModel
// Processar dados do POST
$this->processarFkV2(string $campo): array
$this->processarMultiplosFkV2(array $campos): array
// Separar itens novos/existentes/removidos
$this->separarItensFkV2(array $items, array $idsOriginais = []): array
// Validação
$this->validarCamposObrigatoriosFkV2($item, array $campos, array &$erros): bool
// Preparar para banco
$this->prepararDadosInsercaoFkV2($item, array $camposPermitidos = []): array
}API JavaScript
Objeto Global FkV2
javascript
// Inicializar (automático no DOMContentLoaded)
FkV2.init('[data-fk-v2]');
// Obter instância
const fk = FkV2.get('fk_endereco');
// Obter todas instâncias
const todas = FkV2.getAll();
// Destruir instância
FkV2.destroy('fk_endereco');
// Destruir todas
FkV2.destroyAll();Instância FkV2Controller
javascript
const fk = FkV2.get('fk_endereco');
// Obter itens
const items = fk.getItems();
// Definir itens
fk.setItems([
{ oid: '1', logradouro: 'Rua A', numero: '100' },
{ oid: '2', logradouro: 'Rua B', numero: '200' }
]);
// Adicionar item
fk.addItem({ logradouro: 'Rua C', numero: '300' });
// Atualizar item
fk.updateItem('1', { numero: '150' });
// Remover item
fk.removeItem('1');
// Limpar todos
fk.clear();
// Contagem
fk.count(); // número de itens
fk.isEmpty(); // true se vazioEventos
O componente dispara eventos customizados no container:
javascript
const container = document.querySelector('[data-fk-v2-id="fk_endereco"]');
// Item alterado (adicionado, editado ou removido)
container.addEventListener('fk:change', (e) => {
console.log('Itens:', e.detail.items);
console.log('FK ID:', e.detail.fkId);
});
// Painel aberto
container.addEventListener('fk:panel:open', (e) => {
console.log('Editando:', e.detail.editingOid);
});
// Painel fechado
container.addEventListener('fk:panel:close', (e) => {
console.log('Painel fechado');
});
// Item salvo
container.addEventListener('fk:item:save', (e) => {
console.log('Item:', e.detail.item);
console.log('É novo?', e.detail.isNew);
});
// Item deletado
container.addEventListener('fk:item:delete', (e) => {
console.log('OID removido:', e.detail.oid);
});Exemplos Completos
Exemplo 1: FK de Endereço
Controller:
php
// Controller/Pessoa/Fk.php
trait Fk
{
public function fkEnderecoV2Action()
{
$this->viewModel->dados = \Agrodefesa\Util\Util::arrayToObject(
$this->params()->fromQuery()
);
$this->viewModel->tiposEndereco = $this->negocio->getTiposEndereco();
$this->viewModel->listaMunicipio = $this->negocio->getListaMunicipio();
}
public function fkEnderecoV2ValidarAction()
{
$erros = $this->negocio->validarFkEndereco($this->params()->fromPost());
return $this->fkV2Ok() ?: $this->fkV2Erro($erros);
}
}View:
php
<!-- sidebar.phtml -->
<?php
$fk = new \Agrodefesa\Form\Element\FkV2();
echo $fk->setId('fk_endereco')
->setNome('fk_endereco')
->setTitulo('Endereço(s)')
->setDescricao('Adicione um ou mais endereços')
->setRota($this->url('cadastros-agropecuarios/default', [
'controller' => 'pessoa',
'action' => 'fk-endereco-v2'
]))
->setIcone('mdi-home')
->setLargura(600)
->setTemplateFicha([
'primary' => '{{logradouro}}, {{numero}}',
'secondary' => '{{tipo_endereco}}',
'tertiary' => '{{municipio}}-{{uf}}',
'icon' => 'home'
])
->setDados($this->fkEnderecos ?? [])
->render();
?>Exemplo 2: FK de Contato com Seletor de Tipo
php
<!-- fk-contato-v2.phtml -->
<?php $dados = $this->dados ?? new \stdClass(); ?>
<div class="fk-v2-panel__header">
<h3 class="fk-v2-panel__title">
<?= empty($dados->oid) ? 'Adicionar contato' : 'Editar contato' ?>
</h3>
<button type="button" class="fk-v2-panel__close" data-fk-v2-panel-close>
<i class="mdi mdi-close"></i>
</button>
</div>
<div class="fk-v2-panel__body">
<div data-fk-v2-errors style="display: none;"></div>
<form class="fk-v2-panel__form">
<input type="hidden" name="oid" value="<?= $dados->oid ?? '' ?>">
<div class="fk-v2-panel__form-group">
<label class="fk-v2-panel__label">Tipo de contato</label>
<div class="fk-v2-type-selector">
<?php foreach ($this->tiposContato as $tipo): ?>
<label class="fk-v2-type-card <?= ($dados->id_tipo_contato ?? '') == $tipo->id ? 'is-selected' : '' ?>">
<input type="radio" name="id_tipo_contato" value="<?= $tipo->id ?>"
<?= ($dados->id_tipo_contato ?? '') == $tipo->id ? 'checked' : '' ?>>
<span class="fk-v2-type-card__icon">
<i class="mdi <?= $tipo->icone ?>"></i>
</span>
<span class="fk-v2-type-card__label"><?= $tipo->nome ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<div class="fk-v2-panel__form-group">
<label class="fk-v2-panel__label">Número *</label>
<input type="text" name="numero" class="form-control"
value="<?= $dados->numero ?? '' ?>"
placeholder="(00) 00000-0000"
data-mask="telefone">
</div>
</form>
</div>
<div class="fk-v2-panel__footer">
<button type="button" class="btn btn-outline" data-fk-v2-panel-cancel>
Cancelar
</button>
<button type="button" class="btn btn-primary" data-fk-v2-panel-save>
Salvar
</button>
</div>Migração do FK Legado
Diferenças Principais
| FK Legado | FK V2 |
|---|---|
nti.abrirFK() | Clique automático no trigger |
Delimitadores ; e = | JSON |
| Modal jQuery UI | Painel deslizante |
eval() para parse | JSON.parse() |
Passos de Migração
- Criar action do painel (fk*V2Action)
- Criar action de validação (fk*V2ValidarAction)
- Criar view do painel (fk-*-v2.phtml)
- Substituir na view o FK legado pelo FkV2
- Atualizar Model para validação JSON
Coexistência
O FK V2 pode coexistir com o FK legado indefinidamente. Use FK V2 em novos formulários e migre os existentes gradualmente.
Troubleshooting
Painel não abre
- Verificar se a rota está correta
- Verificar se o JavaScript foi carregado
- Verificar console do navegador
Dados não salvam
- Verificar se o input hidden está dentro do form
- Verificar se a validação está retornando
status: 'ok' - Verificar formato JSON dos dados
Estilos não aplicados
- Verificar se os CSS foram carregados
- Verificar se não há conflitos com CSS legado
- Verificar se as variáveis CSS do design system estão definidas
Documentação completa: /public/doc/architect/fk/evolucao_componente_fk/PROJETO_FK_V2_SIDEBAR.md