Neste artigo pretendo mostrar como fazer uma consulta de registros completamente dinâmica, ou seja, sem a necessidade de recarregar a página para filtrar ou paginar os registros. Utilizaremos como exemplo uma simples consulta de clientes onde o usuário poderá filtrá-los e navegar entre as páginas.
No lado cliente iremos utilizar o AngularJS para facilitar a comunicação com o servidor, além de manter um código mais organizado e reutilizável. Atualmente já temos o Angular 2, porém ainda gosto da simplicidade do AngularJS e acredito que se encaixa melhor no propósito deste artigo.
No lado servidor iremos apenas utilizar uma biblioteca de Query Builder para montarmos nossa query de consulta com o banco de dados. Hoje em dia é muito comum a utilização de um Query Builder em frameworks e no nosso caso vai facilitar muito a paginação dos registros.
Confira o exemplo em funcionamento, clicando aqui.
O banco de dados
Vamos criar nossa tabela no banco de dados e inserir alguns registros para teste. No nosso exemplo, iremos utilizar MySQL.
CREATE TABLE `clientes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `nome` varchar(60) NOT NULL, `telefone` varchar(15), `email` varchar(80), PRIMARY KEY (`id`) ) ENGINE=InnoDB; INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (1, 'Kaua Ribeiro Cardoso', '(74) 8414-6200', 'KauaRibeiroCardoso@armyspy.com'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (2, 'Guilherme Ferreira Barros', '(12) 8865-4022', 'GuilhermeFerreiraBarros@teleworm.us'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (3, 'Murilo Azevedo Martins', '(96) 9572-6402', 'MuriloAzevedoMartins@jourrapide.com'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (4, 'Gabriel Melo Castro', '(11) 6048-4953', 'GabrielMeloCastro@rhyta.com'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (5, 'Luan Castro Goncalves', '(21) 8790-5998', 'LuanCastroGoncalves@rhyta.com'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (6, 'Sophia Sousa Almeida', '(62) 4566-3895', 'SophiaSousaAlmeida@teleworm.us'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (7, 'Fernanda Carvalho Pinto', '(44) 5505-7193', 'FernandaCarvalhoPinto@dayrep.com'); INSERT INTO `clientes` (`id`, `nome`, `telefone`, `email`) VALUES (8, 'Rebeca Sousa Cunha', '(14) 5928-5119', 'RebecaSousaCunha@rhyta.com');
O lado cliente
Javascript
Primeiramente, vamos criar nosso module.
angular.module('appModule', ['bw.paging']);
No segundo parâmetro, definimos que que nosso module depende de um outro module chamado bw.paging. Esse module será útil para fazermos a paginação mais adiante.
Vamos então criar o nosso controller responsável por gerenciar a página de consulta.
angular.module('appModule')
.controller('consultaController', function($http) {
// Armazenando o objeto do controller para evitar conflitos
var self = this;
// Filtros da consulta
self.filtros = [];
// Lista de clientes
self.clientes = [];
// Controla a exibição da barra de progresso da pesquisa
self.consultando = false;
// Método responsável pela consulta dos registros
self.consultar = function(pagina) {
// Se nenhuma página definida, define como primeira página
if (!pagina) pagina = 1;
// Habilita a barra de progresso
self.consultando = true;
// Fazendo requisição AJAX com o servidor e passando os filtros por parâmetro
$http.get('api/consulta.php?pagina=' + pagina, {
params: self.filtros
}).then(function(response) {
// Armazena os clientes retornados
self.clientes = response.data.registros;
// Definindo dados para a paginação
self.paginacao = response.data.paginacao;
}).finally(function() {
// Quando terminar a requisição, desabilita a barra de progresso
self.consultando = false;
});
};
});
- Linha 2: Declaramos o nosso controller e injetamos o $http que é um serviço do AngularJS para fazer requisições AJAX.
- Linha 5: O controller é nada mais que uma função em Javascript, portanto o this é referente a esta função. Porém, se declaramos uma função dentro do controller, o this dentro desta função é referente à ela. Portanto, apenas armazenamos o objeto do controller na variável
selfpois assim podemos acessar suas propriedades independente do escopo. - Linha 26: Através do serviço
$httpfazemos uma requisição AJAX com o servidor utilizando o método GET. Enviamos a página atual e os filtros para recuperarmos no servidor. Se você utiliza jQuery, isso é equivalente ao $.get.
HTML
Vamos incluir em nossa página as dependências.
<!DOCTYPE html>
<html lang="pt-br">
<head>
<meta charset="UTF-8">
<title>Consulta de clientes</title>
<link rel="stylesheet" href="css/materialize.min.css">
<link rel="stylesheet" href="css/icon.css">
<script type="text/javascript" src="js/lib/angular.min.js"></script>
<script type="text/javascript" src="js/lib/paging.min.js"></script>
<script type="text/javascript" src="js/app/app.js"></script>
<script type="text/javascript" src="js/app/controllers/consulta.js"></script>
</head>
Para nossa página ter um visual mais moderno, incluímos o Materialize. Além disso, incluímos a biblioteca do AngularJS, o componente de paginação e os arquivos que criamos anteriormente.
<!-- Definindo aplicativo que será utilizado -->
<div class="container" ng-app="appModule">
<h3>Consulta de clientes</h3>
<!-- Definindo controller que será utilizado e fazendo pesquisa inicial -->
<div ng-controller="consultaController as ctrl" ng-init="ctrl.consultar()">
<!-- Formulário para pesquisar com os filtros -->
<form ng-submit="ctrl.consultar()">
<button class="btn" type="submit" name="action">Pesquisar
<i class="material-icons left">search</i>
</button>
<div class="row">
<div class="input-field col s4">
<input placeholder="Nome" type="text" ng-model="ctrl.filtros.nome">
</div>
<div class="input-field col s4">
<input placeholder="Telefone" type="text" ng-model="ctrl.filtros.telefone">
</div>
<div class="input-field col s4">
<input placeholder="Email" type="text" ng-model="ctrl.filtros.email">
</div>
</div>
</form>
<!-- Se barra de progresso habilitada, exibe ela -->
<div class="progress" ng-show="ctrl.consultando">
<div class="indeterminate"></div>
</div>
<!-- Se barra de progresso desabilitada, exibe a tabela com os registros -->
<div ng-show="!ctrl.consultando">
<table class="bordered striped">
<thead>
<tr>
<th>Nome</th>
<th>Telefone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
<!-- Construindo linhas e colunas de acordo com a lista de clientes -->
<tr ng-repeat="cliente in ctrl.clientes">
<td ng-bind="cliente.nome"></td>
<td ng-bind="cliente.telefone"></td>
<td ng-bind="cliente.email"></td>
</tr>
<!-- Se não houver nenhum cliente na lista -->
<tr ng-if="ctrl.clientes.length == 0">
<td colspan="3" class="center-align">Nenhum cliente encontrado</td>
</tr>
</tbody>
</table>
<!-- Definindo informações de paginação do componente -->
<paging class="center-align" page="ctrl.paginacao.pagina_atual"
page-size="ctrl.paginacao.registros_por_pagina" total="ctrl.paginacao.total_registros"
paging-action="ctrl.consultar(page)" show-prev-next="true" ></paging>
</div>
</div>
</div>
Um dos recursos que eu mais gosto no AngularJS é o Data Binding. Esse recurso nos permite a separação da lógica (controller) com a página (view), ou seja, nós definimos uma propriedade no controller, vinculamos na view e o framework se encarrega de atualizar a view quando a propriedade for alterada e vice-versa.
- Linha 2: Através do ng-app definimos que esta
div
é a raiz do nosso module e dentro dela poderemos fazer a chamada dos controllers. - Linha 7: Através do ng-controller definimos que esta
div
é a raiz do nosso controller e dentro dela poderemos acessar suas propriedades e métodos. O ng-init é sempre chamado na criação do elemento, ou seja, quando adivfor criada chamaremos o métodoconsultardefinido no controller, fazendo assim a consulta inicial. - Linha 10: Criamos um formulário onde colocaremos os filtros e através do ng-submit definimos que quando esse formulário for enviado, seja clicando no botão de submit ou através do enter, chamaremos método
consultardefinido no controller. - Linha 19, 22, 25: O ng-model nos permite vincular um campo de formulário com uma propriedade no controller, sendo assim sempre que o campo for atualizado, a propriedade será atualizada e vice-versa.
- Linha 33: O ng-show nos permite definir se o elemento será mostrado de acordo com a condição. No caso, se a propriedade
consultandofor true será exibido a barra de progresso. - Linha 38: Aqui utilizamos o ng-show, porém negando a condição, ou seja, se a propriedade
consultandofor false será exibido tabela de clientes. - Linha 53 até 57: O ng-repeat nos permite repetir um elemento de acordo com uma collection. No caso, iremos repetir o elemento
tr(que cria uma linha na tabela) para cada item da propriedadeclientes. Para cada linha que for criada criaremos as colunas e através do ng-bind vinculamos a coluna com sua respectiva propriedade do objetocliente. - Linha 60: O ng-if nos permite definir se um elemento será criado ou não através de uma condição. Neste caso, se não houver nenhum item em nossa collection de clientes, essa linha será exibida.
- Linha 69 até 71: Utilizamos a directive de paginação que adicionamos ao nosso module no inicio. Nos atributos definimos as informações necessárias para a geração do elemento. Você pode ver a documentação completa da directive aqui.
O lado servidor
Precisamos agora desenvolver o código para consultar os registros no banco de dados. Porém, antes de qualquer coisa vamos instalar a biblioteca de Query Builder utilizando o Composer. Para isto vamos criar nosso composer.json.
{
"require": {
"usmanhalalit/pixie": "2.*@dev"
}
}
Agora basta rodar composer install no terminal e podemos começar. Primeiramente, vamos criar um arquivo para configuração e conexão com o banco de dados.
<?php
// Definindo dados para conexão com o banco de dados
$config = [
'driver' => 'mysql',
'host' => 'localhost',
'database' => 'rafaelcouto',
'username' => 'root',
'password' => '',
];
// Iniciando objeto de conexão da biblioteca
// A partir de agora podemos instanciar uma query utilizando QB
new \Pixie\Connection('mysql', $config, 'QB');
Simples assim. A biblioteca já faz a conexão com base nas informações que passamos e disponibiliza o Query Builder que pode ser acessado através do alias QB, definido no terceiro parâmetro.
<?php
// Incluindo biblioteca e configuração do banco de dados
require 'vendor/autoload.php';
require 'config/database.php';
// Quantidade de registros que serão exibidos por página
$registrosPorPagina = 3;
// Recupera a página atual, caso não tenha sida definida, define como a primeira página
$paginaAtual = (isset($_GET['pagina']) ? intval($_GET['pagina']) : 1);
// Inicia a query na tabela de clientes
$query = QB::table('clientes');
// Definindo filtros
// Nome
if (!empty($_GET['nome'])) {
$query->where('nome', 'LIKE', '%' . $_GET['nome'] . '%');
}
// Telefone
if (!empty($_GET['telefone'])) {
$query->where('telefone', 'LIKE', '%' . $_GET['telefone'] . '%');
}
// Email
if (!empty($_GET['email'])) {
$query->where('email', 'LIKE', '%' . $_GET['email'] . '%');
}
// Ordenando
$query->orderBy('nome');
// Aplicando paginação
$query->limit($registrosPorPagina)->offset(($paginaAtual - 1) * $registrosPorPagina);
// Executando query e armazenando os registros retornados
$registros = $query->get();
// Definindo quantidade total de registros
$totalRegistros = $query->count();
// Retornando os dados no formato JSON
echo json_encode([
'registros' => $registros,
'paginacao' => [
'registros_por_pagina' => $registrosPorPagina,
'pagina_atual' => $paginaAtual,
'total_registros' => $totalRegistros,
],
]);
- Linha 18 até 20: No controller do lado cliente passamos os filtros por parâmetro. Portanto, verificamos se o filtro existe e aplicamos um where em nossa query. A vantagem do Query Builder neste caso é que a query é um objeto, portanto apenas chamamos o método where, muito mais simples e organizado do que fazer o SQL manualmente.
- Linha 36: É aqui que a paginação acontece. Através do limit definimos a quantidade de registros que queremos, no caso 3. E através do offset a partir de qual registro vamos começar. Por exemplo:
- Página 1: (1 – 1 * 3 = 0) – Começando do primeiro registro;
- Página 2: (2 – 1 * 3 = 3) – Começando depois do terceiro registro;
- Linha 39: O Query Builder irá construir e executar o comando SQL. Por exemplo, se tivermos na primeira página sem nenhum filtro, ele irá executar o SQL abaixo.
SELECT * FROM `clientes` ORDER BY `nome` ASC LIMIT 3 OFFSET 0
- Linha 42: Para saber a quantidade de páginas, precisamos saber a quantidade total de registros, portanto precisamos fazer duas consultas no banco de dados; uma para buscar os dados e a outra para contar os registros. Com o Query Builder isso fica muito simples, pois basta reutilizar o objeto da query para contar a quantidade de registros.
Conclusão
Neste artigo, vimos como fazer uma consulta com a possibilidade de filtrar e paginar os registros, sem a necessidade de recarregar toda a página.
No lado cliente, utilizamos AngularJS para manter o código mais simples e organizado. Você poderia fazer a mesma coisa utilizando jQuery ou Javascript puro, porém teria que escrever muito mais linhas de código e provavelmente não teria a separação das camadas de lógica e visualização.
No lado servidor, utilizamos uma biblioteca de Query Builder para evitar duplicação e manter um código mais limpo do que utilizar SQL puro. Você poderia muito bem utilizar as funções do próprio PHP para fazer a consulta, porém seria um pouco mais trabalhoso.
Enfim, espero que este artigo tenha sido útil de alguma forma para você. Até a próxima.
Código fonte disponível em: https://github.com/rafaelcouto/consulta-dinamica-com-paginacao-utilizando-angularjs-e-php/
Referências
- Angular Paging <https://github.com/brantwills/Angular-Paging>. Acesso em 07 de abril de 2017.
- Pixie Query Builder <https://github.com/usmanhalalit/pixie>. Acesso em 07 de abril de 2017.