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
self
pois assim podemos acessar suas propriedades independente do escopo. - Linha 26: Através do serviço
$http
fazemos 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 adiv
for criada chamaremos o métodoconsultar
definido 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
consultar
definido 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
consultando
for 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
consultando
for 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.