segunda-feira, 30 de julho de 2012

Paginação de registros PHP Mysql MVC

Olá, tomando como base o post que mostrei um esquema simples p/ implementar MVC em programação PHP, estendendo essa explicação mostrarei neste post como fazer paginação de registros.
(vale a pena conferir o post sobre autenticação, para restringir o acesso a esses boletos {ou qualquer outra informação}) 


Quando isso é utilizado? ao se criar uma view(página,tabela ou tela) que mostrará uma grande quantidade de dados.
Imagine carregar 2.000 linhas de uma consulta e mostrar em uma página ? Além da página ficar grande e pesada(em kbs), está sujeito o usuário ter que usar o recurso "Localizar" do navegador para encontrar o que procura.


P/ evitar isso (que é muito feio), o que sugiro é uma técnica bastante utilizada de dividir a consulta em blocos ou páginas. Exemplo os 2.000 ficariam :
Registros por página : 100 --> Nº de páginas : 20
Registros por página : 50 --> Nº de páginas : 40


Além do mecanismo de paginação, pode-se implementar filtros para restringir os resultados. Logo, precisamos manter os mesmos filtros durante a navegação dos registros. A busca do Google é um bom exemplo.

O exemplo apresentado será com uma tabela p/ armazenar boletos bancários. Não vou entrar nos detalhes da geração do boleto.Basta saber o que o boleto foi gerado usando BoletoPhp , que na minha opinião é uma das maneiras mais simples e completas p/ gerar boletos com PHP.


Este exemplo ainda utiliza o variáveis tipo $_GET e mantém os valores na URL.
Esse não é o melhor jeito.A melhor maneira de fazer é com formulários usando $_POST, e os links postarem os valores necessários, e um ajax trocar o conteúdo da tabela de dados dentro de uma tag <tbody>, o que mostrarei num posso próximo post, pois tive q fazer isso p/ uma base ORACLE.

Então o que precisamos? (além do que já foi citado nos dois posts anteriores)
  • Banco de Dados
    -Tabela p/ armazenar os boletos: boleto
  • MVC
    -Model-> Boleto.php
    -View-> /pages/*(seção pública que monta a tela),/app/view/Boleto/{index,add}.php
    -Controller-> BoletoControler.php, PostController.php (para tratar os formulários postados que não sejam de consulta)
  • DAO
    -BoletoDAO.php
  • Arquivos de função, costumo chamar de lib ou /lib
    -Conexao.php
    -Utils.php
  • EXTRA: /ajax/clientes.php, /app/model/Cliente.php /app/controller/ClienteController, /app/dao/ClienteDAO.php

Estrutura de arquivos : 
Figura 1 - Estrutura de arquivos no Projeto do Netbeans


Diagrama do Banco
Figura 2 - Diagrama do Banco, a tabela boleto_seq serve apenas p/ controlar o campo nosso_num dentro da aplicação.


Codigo da tabela do Mysql :
CREATE  TABLE IF NOT EXISTS `mydb`.`boleto` (
  `id` INT(11) NOT NULL AUTO_INCREMENT ,
  `nosso_num` VARCHAR(16) NOT NULL ,
  `data_vencimento` DATE NOT NULL ,
  `data_documento` DATETIME NOT NULL ,
  `valor` DECIMAL(10,2) NOT NULL ,
  `nota_fiscal` VARCHAR(30) NULL DEFAULT NULL ,
  `discriminacao` TEXT NOT NULL ,
  `id_cliente` INT(11) NOT NULL ,
  `sacado` VARCHAR(225) NULL DEFAULT NULL ,
  `linha_digitavel` VARCHAR(54) NULL DEFAULT NULL ,
  `pago` TINYINT(1) NULL DEFAULT '0' , -- ao ser criado não pode ter sido pago, certo (0_o)?
  PRIMARY KEY (`id`) ,
  INDEX `boleto_cliente_FK` (`id_cliente` ASC) ,
  CONSTRAINT `boleto_cliente_FK`
    FOREIGN KEY (`id_cliente` )
    REFERENCES `mydb`.`cliente` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci

-- table cliente 
CREATE  TABLE IF NOT EXISTS `mydb`.`cliente` (
  `id` INT(11) NOT NULL AUTO_INCREMENT ,
  `nome` VARCHAR(255) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL ,
  `email` VARCHAR(130) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL ,
  `ddd` VARCHAR(3) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `fone` VARCHAR(15) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL ,
  `ramal` VARCHAR(6) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `endereco` VARCHAR(255) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL ,
  `numero` VARCHAR(7) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL ,
  `complemento` VARCHAR(30) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `bairro` VARCHAR(150) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `cidade_id` INT(11) NOT NULL ,
  `estado_id` INT(11) NOT NULL ,
  `cep` VARCHAR(9) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL ,
  `tipo_end` ENUM('Res','Com','Cor') CHARACTER SET 'utf8' COLLATE 'utf8_bin' NOT NULL DEFAULT 'Res' ,
  `tipo_fone` ENUM('Res','Com','Cel') CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT 'Res' ,
  `tipo_cliente` ENUM('pf','pj') CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `cnpj` VARCHAR(20) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `cpf` VARCHAR(15) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `inscr_estadual` VARCHAR(15) CHARACTER SET 'utf8' COLLATE 'utf8_bin' NULL DEFAULT NULL ,
  `senha` VARCHAR(8) NULL DEFAULT NULL ,
  `ativo` TINYINT(1) NOT NULL DEFAULT '1' ,
  `contato` VARCHAR(150) NULL DEFAULT NULL ,
  PRIMARY KEY (`id`) ,
  INDEX `cidade_cliente_FK` (`cidade_id` ASC) ,
  INDEX `estado_cliente_FK` (`estado_id` ASC) ,
  CONSTRAINT `cidade_cliente_FK`
    FOREIGN KEY (`cidade_id` )
    REFERENCES `mydb`.`cidade` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION,
  CONSTRAINT `estado_cliente_FK`
    FOREIGN KEY (`estado_id` )
    REFERENCES `mydb`.`estado` (`id` )
    ON DELETE NO ACTION
    ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_general_ci


Veja a tela como ficará após o login :
Lista dos boletos com paginação e pesquisa

Explicação das seções da página

Vamos aos arquivos. obs: as datas ficam no formato brasileiro dia/mes/ano
Modelos
<?php
//@arquivo /app/model/Boleto.php
class Boleto {

    var $id;
    var $nossoNum;
    var $dataVencimento;
    var $dataDocumento;
    var $valor;
    var $notaFiscal;
    var $discriminacao;
    var $idCliente;
    var $linhaDigitavel;
    var $sacado;
    var $pago;

    public function Boleto() { // contrutor
        $this->pago=false; //ao ser criado ele não pode ter sido pago, certo ?(0_o)
        $this->nossoNum=NULL;
        $this->valor=NULL;
        $this->dataDocumento=NULL;
        $this->dataVencimento=NULL;
        $this->idCliente=NULL;
        
    }

    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getNossoNum() {
        return $this->nossoNum;
    }

    public function setNossoNum($nossoNum) {
        $this->nossoNum = $nossoNum;
    }

    public function getDataVencimento() {
        return $this->dataVencimento;
    }

    public function setDataVencimento($dataVencimento) {
        $this->dataVencimento = $dataVencimento;
    }

    public function getDataDocumento() {
        return $this->dataDocumento;
    }

    public function setDataDocumento($dataDocumento) {
        $this->dataDocumento = $dataDocumento;
    }

    public function getValor() {
        return $this->valor;
    }

    public function setValor($valor) {
        $this->valor = $valor;
    }

    public function getNotaFiscal() {
        return $this->notaFiscal;
    }

    public function setNotaFiscal($notaFiscal) {
        $this->notaFiscal = $notaFiscal;
    }

    public function getDiscriminacao() {
        return $this->discriminacao;
    }

    public function setDiscriminacao($discriminacao) {
        $this->discriminacao = $discriminacao;
    }

    public function getIdCliente() {
        return $this->idCliente;
    }

    public function setIdCliente($idCliente) {
        $this->idCliente = $idCliente;
    }

    public function getLinhaDigitavel() {
        return $this->linhaDigitavel;
    }

    public function setLinhaDigitavel($linhaDigitavel) {
        $this->linhaDigitavel = $linhaDigitavel;
    }
    public function getSacado() {
        return $this->sacado;
    }

    public function setSacado($sacado) {
        $this->sacado = $sacado;
    }

    public function isPago() {
        return $this->pago;
    }

    public function setPago($pago) {
        $this->pago = $pago;
    }


}

?>
Controller
<?php
//@arquivo /app/controller/BoletoController.php
// observe que o controler nao faz include do conexao. quem faz isso é o DAO
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Boleto.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/dao/BoletoDAO.php');

class BoletoController {

    // funcao p/ gerar o proximo nosso numero que é sequencia e de controle da empresa
    public function getNextNossoNumero() {
        $boleto = new BoletoDAO();
        $numRetorno = $boleto->getNextNossoNum();
        return $numRetorno;
    }
    // veja como passar um TIPO de objeto p/ uma função. isso sim é moderno :)
    public function salvarBoleto(Boleto $Boleto) {
        $boletoC = new BoletoDAO();
        $salvarBoleto = $boletoC->salvarBoleto($Boleto);
        return $salvarBoleto;
    }

    public function listarBoletos() {
        $boletoD = new BoletoDAO();
        $boletoArr = $boletoD->getBoletos();
        return $boletoArr; // retorna um array de Objetos Boletos
    }

    // aqui que é feita a divisão dos registros em blocos.
    // Observe que passaremos um objeto Boleto para utilizar na busca
    public function getDadosPaginados($primeiroRegistro, $numPorPagina, Boleto $buscaBoleto) {
        $boletoD = new BoletoDAO();
        $boletoArr = $boletoD->getClientesPaginados($primeiroRegistro, $numPorPagina, $buscaBoleto);
        return $boletoArr; // retorna um array de Objetos Boletos
    }
    
    // essa funcao é usada pegar o total de registros p/ fazer a paginacao.
    // é o mesmo objeto,  passado p/ a função getDadosPaginados
    public function getTotalBoletos(Boleto $buscaBoleto) {
        $boletoD = new BoletoDAO();
        $total = $boletoD->contaTotalBoletos($buscaBoleto);
        return $total;
    }

}
?>
DAO
<?php
//@arquivo /app/dao/BoletoDAO.php
require_once($_SERVER['DOCUMENT_ROOT'] . '/lib/Conexao.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Boleto.php');

class BoletoDAO {

    public function BoletoDAO() { }
    
    // essa funcao sera utilizada qdo for criado um novo boleto
    public function getNextNossoNum() {
        $numRetorno = 1000001; // nº inicial
        $sqlQuery = "select max(valor) + 1 as valor from boleto_seq ";
        $conexao = new Conexao();
        $conexao->conecta();
        $rs = $conexao->executeQuery($sqlQuery);
        if ($row = mysql_fetch_array($rs)) { // parece estranho. mas só preciso de 1 registro 
            $numRetorno = $row['valor'];
        }
        $conexao->desconecta();
        return $numRetorno;
    }

    public function salvarBoleto(Boleto $Boleto) {

        $sqlQueryInsert = "INSERT INTO `mydb`.`boleto`
            (
            `nosso_num`,
            `data_vencimento`,
            `data_documento`,
            `valor`,
            `nota_fiscal`,
            `discriminacao`,
            `id_cliente`,
            `sacado`,
            `linha_digitavel`)
            VALUES
            (
            '" . $Boleto->getNossoNum() . "',
            STR_TO_DATE('" . $Boleto->getDataVencimento() . "','%d/%m/%Y') ,
            STR_TO_DATE('" . $Boleto->getDataDocumento() . "','%d/%m/%Y') ,
            " . $Boleto->getValor() . ",
            '" . $Boleto->getNotaFiscal() . "',
            '" . $Boleto->getDiscriminacao() . "',
            " . $Boleto->getIdCliente() . ",
            '" . $Boleto->getSacado() . "',
            '" . $Boleto->getLinhaDigitavel() . "'
            );";
//        die($sqlQueryInsert);
        $conexao = new Conexao();
        $conexao->conecta();
        $rsInsert = $conexao->executeUpdate($sqlQueryInsert);


        // se inseriu, precisa incrementar  a tabela que controla os NossoNumº
        // poderia fazer uma trigger, mas nao vem ao caso
        if ($rsInsert) {  

            $sqlQuery = "select max(valor) + 1 as valor from boleto_seq ";
            $rs = $conexao->executeQuery($sqlQuery);

            while ($row = mysql_fetch_array($rs)) {
                $numRetorno = $row['valor'];
            }

            $sqlIncrement = "INSERT into boleto_seq (valor) values($numRetorno) ";
//            die($sqlIncrement);
            $rsInsert2 = $conexao->executeUpdate($sqlIncrement);
        }
        // apos realizar 2 inserts e uma consulta, pode fechar a conexao
        $conexao->desconecta();
        return true;
    }
    //lista simples dos boletos
    public function getBoletos() {
//        $sqlQuery = "Select * from boleto"; // apenas p/ ilustrar , nunca use SELECT * 
        $sqlQuery = "SELECT
            `boleto`.`id`,
            `boleto`.`nosso_num`,
            `boleto`.`data_vencimento`,
            `boleto`.`data_documento`,
            `boleto`.`valor`,
            `boleto`.`nota_fiscal`,
            `boleto`.`discriminacao`,
            `boleto`.`id_cliente`,
            `boleto`.`sacado`,
            `boleto`.`linha_digitavel`,
            `boleto`.`pago`
            FROM `mydb`.`boleto`"; // 
        $conexao = new Conexao();
        $conexao->conecta();
        $boletoArr = array(); // esse é o Array de boletos
        $cnt = 0; // criei p/ controlar o indice, poderia usar array_push($boletoArr, $boleto)
        $rsInsert = $conexao->executeQuery($sqlQuery);

        while ($row = mysql_fetch_array($rsInsert)) {
            $boleto = new Boleto();
            $boleto->setId($row['id']);
            $boleto->setNossoNum($row['nosso_num']);
            $boleto->setDataVencimento($row['data_vencimento']);
            $boleto->setDataDocumento($row['data_documento']);
            $boleto->setValor($row['valor']);
            $boleto->setNotaFiscal($row['nota_fiscal']);
            $boleto->setDiscriminacao($row['discriminacao']);
            $boleto->setIdCliente($row['id_cliente']);
            $boleto->setSacado($row['sacado']);
            $boleto->setLinhaDigitavel($row['linha_digitavel']);
            $boleto->setPago($row['pago']);

            //array_push($boletoArr, $boleto); // se eu nao estivesse usando um indice
            $boletoArr[$cnt] = $boleto;// armazena um objeto Boleto no array
            $cnt++;
        }

        $conexao->desconecta();
        return $boletoArr; // retorna o array de boletos
    }
    /*
     * Em vez de ficar verificando se ja existe WHERE na consulta
     * inicio as duas consultas com o parametro : "WHERE id is not null" que sempre
     * será verdadeiro, ae sim acrescento os filtros usando AND e como o filtro das duas 
     * consultas será o mesmo criei a funcao montaFiltroPaginacao que recebe um boleto
     * como parametro, e retorna o SQL complementar as consultas base
     */
    
    // essa funcao é usada pegar o total de registros p/ fazer a paginacao.
    public function contaTotalBoletos(Boleto $boleto) {
        $conexao = new Conexao();
        $conexao->conecta();
        $sqlQuery = "SELECT COUNT(*) as total FROM mydb.boleto B WHERE id is not null ";
        $sqlQuery .= $this->montaFiltroPaginacao($boleto);

        $rs = $conexao->executeQuery($sqlQuery);
        $total = 0;
        while ($row = mysql_fetch_array($rs)) {
            $total = $row['total'];
        }

        $conexao->desconecta();
        return $total;
        
    }
    // essa funcao é usada p/ fazer a paginacao.
    public function getClientesPaginados($primeiroRegistro, $numPorPagina, Boleto $boleto) {
        $sqlQuery = "Select B.*, 
            DATE_FORMAT(B.data_documento,'%d/%m/%Y') as data_documento,  
            DATE_FORMAT(B.data_vencimento,'%d/%m/%Y') as data_vencimento   
            from boleto B WHERE id is not null ";
        $sqlQuery .= $this->montaFiltroPaginacao($boleto);
        
        $sqlQuery.= " LIMIT " . $primeiroRegistro . "," . $numPorPagina; // esse é segredo da divisao
        $conexao = new Conexao();
        $conexao->conecta();
        $rs = $conexao->executeQuery($sqlQuery);

        $boletoArr = array();
        $cnt = 0;
        while ($row = mysql_fetch_array($rs)) {
            $B = new Boleto();
            $B->setId($row['id']);
            $B->setNossoNum($row['nosso_num']);
            $B->setDataVencimento($row['data_vencimento']);
            $B->setDataDocumento($row['data_documento']);
            $B->setValor($row['valor']);
            $B->setNotaFiscal($row['nota_fiscal']);
            $B->setDiscriminacao($row['discriminacao']);
            $B->setIdCliente($row['id_cliente']);
            $B->setSacado($row['sacado']);
            $B->setLinhaDigitavel($row['linha_digitavel']);
            $B->setPago($row['pago']);

            $boletoArr[$cnt] = $B;
            $cnt++;
            
        }
        $conexao->desconecta();
        return $boletoArr; //retorna o array de boletos de acordo com a pagina que sera controlada na view
    }

    //funcao utilizada para aplicar o filtro na pesquisa
    public function montaFiltroPaginacao(Boleto $boleto){
        $sqlQuery="";
        
        if ($boleto->getNossoNum() != NULL) {
            $sqlQuery.=" AND  B.nosso_num  like '%" . $boleto->getNossoNum() . "%' ";
        }

        if ($boleto->getValor() != NULL) {
            $sqlQuery.=" AND  B.valor =" . $boleto->getValor() . " ";
        }
        
        if ($boleto->getDataDocumento() != NULL) {
            $sqlQuery.=" AND  B.data_documento  = STR_TO_DATE('" . $boleto->getDataDocumento() . "','%d/%m/%Y') ";
        }

        if ($boleto->getDataVencimento() != NULL) {
            $sqlQuery.=" AND  B.data_vencimento  = STR_TO_DATE('" . $boleto->getDataVencimento() . "','%d/%m/%Y') ";
        }

        if ($boleto->getIdCliente() != NULL) {
            $sqlQuery.=" AND  B.id_cliente  = " . $boleto->getIdCliente() . " ";
        }
        
        if ($boleto->isPago()) {
            $sqlQuery.=" AND  B.pago  = 1 ";
        }
        
        
        return $sqlQuery;
    }
    
    
}

?>
VIEW
Assumindo que você já viu os 2 posts anteriores, e o cliente já está logado, com a estrutura acima explicada, precisamos apenas definir na view
o nº de Registros por página, a página em que está, e objeto Boleto que será mantido na busca durante a paginacao.
Lembre-se que essa página privado.php já fez os includes necessários de todas as classes que utilizaremos abaixo.
codigo necessário para fazer a paginação: obs: não vou incluir a parte da pesquisa para focar na paginação e no final eu mostro o arquivo inteiro

<?
//@arquivo=/app/view/Boleto/index.php
/* arquivo q ja estao sendo incluidos
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Boleto.php'); 
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/controller/BoletoController.php');
*/
session_start();
error_reporting('E_ALL');
// define registros por pagina
$numPorPagina = 5;
//base p/ gerar os links
$basePage = $_SESSION['user']['view'] . "?section=boletos&action=index";

// acrescenta o parametro pagina no link, o valor sera gerado ao carregar
$basePage .="&pagina=";

if (isset($_GET['pagina'])) {
    $pagina = $_GET['pagina'];
} else {
    $pagina = 1;
}
//trava palhaçadas, como tentar passar numeros negativos na url
if ($pagina <= 0) {
    header("Location:" . $basePage . "1");
}

// 1º registro do Bloco
$primeiroRegistro = ($pagina * $numPorPagina) - $numPorPagina;

// observe o objeto boletoBusca usado nas duas funcoes 
$BoletoC = new BoletoController();
$cache = $BoletoC->getDadosPaginados($primeiroRegistro, $numPorPagina, $boletoBusca); // carrega os registros do array 
$totalRegistros = $BoletoC->getTotalBoletos($boletoBusca); // pega o total de boletos 
$totalPaginas = ceil($totalRegistros / $numPorPagina); // ceil arredonda p/ maior , exemplo 1.65 sao 2 paginas

$anterior = $pagina - 1;
$proximo = $pagina + 1;

//trava palhaçadas , tipo colocar um nº maior do que o total de paginas
if ($pagina > $totalPaginas) {
    header("Location:" . $basePage . $totalPaginas);
}
?>

<!-- SECAO DADOS-->
    <table width="85%" border="0" cellspacing="2" cellpadding="0">
        <tr>
            <th>ID</th>
            <th>SACADO</th>
            <th>NOSSO NUM</th>
            <th>DATA DOC</th>
            <th>DATA VENC</th>
            <th>VALOR</th>
            <th>a&ccedil;&otilde;es</th>
        </tr>
        <? if (count($cache) > 0) { 
            for ($i = 0; $i < count($cache); $i++) {
                $B = new Boleto();
                $B = $cache[$i];
                $idBoleto = $B->getId();
                ?>
                <tr>
                    <td><? echo $idBoleto; ?></td>
                    <td><? echo $B->getSacado(); ?></td>
                    <td><? echo $B->getNossoNum(); ?></td>
                    <td><? echo $B->getDataDocumento(); ?></td>
                    <td><? echo $B->getDataVencimento(); ?></td>
                    <td><? echo $B->getValor(); ?></td>
                    <td class="actions">
                        <a href="<? echo $_SESSION['user']['view']; ?>?section=boletos&action=view&id=<? echo $idBoleto; ?>"><img src="/images/search-menor.png"></a>| 
                    </td>
                </tr>
        <? } ?>
            <tr> 
                <td colspan="7"><hr width="100%" size="1" /></td>
            </tr>
            <tr>
                <td colspan="7"><? echo $primeiro = ($primeiroRegistro <= 0) ? "1" : $primeiroRegistro; ?> até <?
                echo $ultimo = ($totalRegistros <= ($primeiroRegistro + $numPorPagina) ? $totalRegistros : ($primeiroRegistro + $numPorPagina));
                ?> de <? echo $totalRegistros; ?></td>
            </tr>
        </table>
    
        <!-- SECAO BARRA DE NAVEGACAO -->
        <div class="paging">

            <? if ($pagina <= 1) { ?>
                <span class="prev disabled">&lt; anterior</span>
            <? } else { ?>
                <span class="prev"><a href=" <? echo $basePage . $anterior; ?>">&lt;anterior</a></span>
            <? } ?>
            <?
            for ($z = 1; $z <= $totalPaginas; $z++) {
                if ($z != $pagina) {
                    ?> <a href="<? echo $basePage . $z; ?>"><? echo $z; ?></a>
                <? } else { ?>
                    <? echo $z; ?>
                <? } ?>

            <? } ?>
            <? if ($pagina == $totalPaginas) { ?>
                <span class="next disabled">pr&oacute;xima &gt;</span>
            <? } else { ?>
                <span class="next"><a href="<? echo $basePage . $proximo; ?>">pr&oacute;xima &gt;</a></span>
    <? } ?>
        </div>
        <!-- FIM SECAO BARRA DE NAVEGACAO -->
    </div>
<? } else {// se  a consulta retornar 0 registros  ?>
    <tr> 
        <td colspan="5">Boleto não econtrado.</td>
    </tr>
    </table>
    </div>
<? } ?>
    <!-- FIM SECAO DADOS-->

Simples hein ? agora vamos adicionar a parte php da carga do boletoBusca e a parte   html da busca em cima da seção DADOS.
PHP
<?
// apenas usado p/ realizar a busca, caso seja utilizada
$boletoBusca = new Boleto();
if ((isset($_GET['nosso_num'])) && ($_GET['nosso_num'] != "")) {
    $boletoBusca->setNossoNum($_GET['nosso_num']);
    $basePage.="&nosso_num=" . $_GET['nosso_num'];
}
if ((isset($_GET['valor'])) && ($_GET['valor'] != "")) {
    $boletoBusca->setValor($_GET['valor']);
    $basePage.="&valor=" . $_GET['valor'];
}
if ((isset($_GET['data_doc'])) && ($_GET['data_doc'] != "")) {
    $boletoBusca->setDataDocumento($_GET['data_doc']);
    $basePage.="&data_doc=" . $_GET['data_doc'];
}
if ((isset($_GET['data_venc'])) && ($_GET['data_venc'] != "")) {
    $boletoBusca->setDataVencimento($_GET['data_venc']);
    $basePage.="&data_venc=" . $_GET['data_venc'];
}

if ((isset($_GET['clienteId'])) && ($_GET['clienteId'] != 'todos')) {
    $boletoBusca->setIdCliente($_GET['clienteId']);
    $basePage.="&clienteId=" . $_GET['clienteId'];
}
if ((isset($_GET['pago']))) {
    $boletoBusca->setPago(1);
    $basePage.="&pago=1";
}
?>
HTML, observe que os campos já vem preenchidos com os parâmetros da busca
<!-- SECAO BUSCA  -->
<div id="busca">
<fieldset>
<form id="formBusca" name="formBusca" method="GET" action="<? echo $_SESSION['user']['view']; ?>">
<table style="width: auto;">
    <tr>
        <th colspan="2">Localizar Boleto:<th>
    </tr>
    <tr>
        <td>Nosso Nº:</td><td><input type="text" name="nosso_num" id="nossoNum" value="<? echo $boletoBusca->getNossoNum(); ?>"></td>
    </tr> 
    <tr>
        <td>
            Valor:</td><td><input type="text" name="valor" id="valor" value="<? echo $boletoBusca->getValor(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Data Doc:</td><td><input type="text" name="data_doc" id="dataDoc" size="16" value="<? echo $boletoBusca->getDataDocumento(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Pago:</td><td><input type="checkbox" name="pago" id="pago" <? if ($boletoBusca->isPago()) {
echo "checked=\"checked\"";
} ?> value="<? echo $boletoBusca->isPago(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Data Venc:</td><td><input type="text" name="data_venc" id="dataVenc" size="16" value="<? echo $boletoBusca->getDataVencimento(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Cliente:</td><td>
            <input name="data[OS][busca_cliente]" maxlength="30" type="text" id="buscaCliente" />
            <span class="actions">
                <input  type="button" value="Buscar Cliente" id="busca2"/>
            </span>
        </td>
    </tr> 
    <tr>
        <td>Cliente Selecionado</td>
        <td>
            <select name="clienteId" id="clienteId">
                <option value="todos">todos</option>
                <?  // essa parte apenas enche o combo com os clientes retornados da consulta
                if ($boletoBusca->getIdCliente() != NULL) {
                    $clienteC = new ClienteController();
                    $clienteC->loadOptions($boletoBusca->getIdCliente());
                }
                ?>
            </select>
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="hidden" name="section" id="section" value="boletos">
            <input type="hidden" name="action" id="action" value="index">
            <input type="hidden" name="pagina" id="pagina" value="1">
<!-- ao clicar no botao pesquisar sempre iniciará uma nova pesquisa por isso 
pagina=1 -->
            <div class="submit">
                <input type="submit" value="Buscar">
            </div>
        </td>
    </tr>
</table>
</form>
</fieldset>    
</div>
<!-- FIM SECAO BUSCA-->

E está feita a paginação de registros. Porém existe uma particularidade nessa busca. Não faz sentido nessa busca, pesquisar nomes de clientes que não existam, Então ao preencher o campo busca_cliente ou clicar no botão "buscar cliente" um ajax busca uma lista de clientes e enche o combo com os resultados, realizando o objetivo de filtrar os boletos por Cliente.
Porque isso? imagine se tivesse 3000 clientes o tamanho q essa lista ficaria. ... então é melhor filtrar ;) 


cuidado ao utilizar ajax p/ não expor dados sigilosos. pesquise no google "cross-site scripting ajax" ou "Cross-site request forgery"
parte do ajax 
<?
session_start();
require_once($_SERVER['DOCUMENT_ROOT'] . '/lib/block.php');
/*
  @arquivo = /ajax/clientes.php
  MVC : model
  objeto : Cliente
  tabela : clientes
  obs : responde a requisições ajax e monta as options do combo clientes
 */
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Cliente.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/controller/ClienteController.php');

//jeito inseguro
//
//if (isset($_GET['busca'])) {
//    $busca = $_GET['busca'];
//    $cliente = new ClienteController();
//    if (isset($_GET['incluirOpcao']) && $_GET['incluirOpcao'] == 'todos') {
//        echo "<option value=\"todos\" selected=\"selected\" >todos</option>";
//    }
//    if ($busca != "") {
//        $cliente->buscaOptions($busca);
//    }
//}

//jeito mais seguro
if (isset($_POST['busca'])) {
    $busca = $_POST['busca'];
    $clienteC = new ClienteController();
    $todos = false;
    if (isset($_POST['incluirOpcao']) && $_POST['incluirOpcao'] == 'todos') {
//        echo "<option value=\"todos\" selected=\"selected\" >todos</option>";
        $todos = true;
        $primeiroCliente= new Cliente();
        $primeiroCliente->setId('todos');
        $primeiroCliente->setNome('todos');
        
    }
    if ($busca != "") {
        $arrClientes= $clienteC->getListaClientesAjax($busca);
    }
//    ternary way
//    $increment=$todos==true?array_shift($arrClientes,$primeiroCliente):null;
//    if ($todos){array_shift($arrClientes,$primeiroCliente);}
    $conta=0;
    if ($todos){
        $arrtmp[$conta]=
        (array(
        "id"=>$primeiroCliente->getId(),
        "nome"=>$primeiroCliente->getNome()
        ));
        $conta++;
    }
    foreach ($arrClientes as $cliente) {
     
        $arrtmp[$conta]=
        (array(
        "id"=>$cliente->getId(),
        "nome"=>$cliente->getNome()
        ));
        $conta++;
}
    echo json_encode($arrtmp);
}

// like a beam , but not java
if (isset($_POST['cliente_id'])){
    $clienteId=$_POST['cliente_id'];
    $clienteC = new ClienteController();
    $cliente = new Cliente();
   
    $cliente = $clienteC->loadDados($clienteId);
    echo json_encode(array(
        "id"=>$cliente->getId(),
        "nome"=>$cliente->getNome(),
        "tipo_cliente"=>$cliente->getTipoCliente(),
        "cpf"=>$cliente->getCpf(),
        "cnpj"=>$cliente->getCnpj()
        ));
    
}
?>
//complementar ao ajax :o clienteCOntroller
<?
/*
  @arquivo = /app/controller/ClienteController.php
  MVC : controller
  objeto : Cliente
 */
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Cliente.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/dao/ClienteDAO.php');

Class ClienteController {
public function buscaOptions($busca) {
        $clienteDAO = new ClienteDAO();
        $clientes = $clienteDAO->getClientesBusca($busca);
        $conta = 0;


        foreach ($clientes as $cliente) {
            echo "<option value=\"" . $cliente->getId() . "\" >" . $cliente->getNome() . "</option> \n";
            $conta++;
        }

        if ($conta == 0) {
            echo "<option  value=\"0\" selected=\"selected\" > cliente n&atilde;o encontrado </option> \n";
        }
    }
 public function getListaClientesAjax($busca) {
        $clienteDAO = new ClienteDAO();
        $arrClientes = $clienteDAO->getClientesBusca($busca);
        if (count($arrClientes)==0){
            return NULL;
        }else {
        return $arrClientes;
        }
}?>
e o clienteDAO
<?php

//  @arquivo = /app/dao/ClienteDAO.php
//  MVC : controller
//  objeto : Cliente

require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Cliente.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/lib/Conexao.php');

class ClienteDAO {

    public function ClienteDAO() {
        
    }

    public function getClientesBusca($busca) {
        $arrClientes = array();
        $indice = 0;
        $sqlQuery = "Select id,nome from cliente where nome like '%" . $busca . "%' order by nome";

        $conexao = new Conexao();
        $conexao->conecta();
        $rs = $conexao->executeQuery($sqlQuery);

        while ($row = mysql_fetch_array($rs)) {
            $cliente = new Cliente();
            $cliente->setId($row['id']);
            $cliente->setNome($row['nome']);
            $arrClientes[$indice] = $cliente;
            $indice++;
        }

        $conexao->desconecta();
        return $arrClientes;
    }
}?>



Agora vejamos pagina completa :
<?
session_start();
error_reporting('E_ALL');

require_once($_SERVER['DOCUMENT_ROOT'] . '/app/controller/ClienteController.php');

// define registros por pagina
$numPorPagina = 5;
//base p/ gerar os links
$basePage = $_SESSION['user']['view'] . "?section=boletos&action=index";

// apenas usado p/ realizar a busca, caso seja utilizada
$boletoBusca = new Boleto();
if ((isset($_GET['nosso_num'])) && ($_GET['nosso_num'] != "")) {
    $boletoBusca->setNossoNum($_GET['nosso_num']);
    $basePage.="&nosso_num=" . $_GET['nosso_num'];
}
if ((isset($_GET['valor'])) && ($_GET['valor'] != "")) {
    $boletoBusca->setValor($_GET['valor']);
    $basePage.="&valor=" . $_GET['valor'];
}
if ((isset($_GET['data_doc'])) && ($_GET['data_doc'] != "")) {
    $boletoBusca->setDataDocumento($_GET['data_doc']);
    $basePage.="&data_doc=" . $_GET['data_doc'];
}
if ((isset($_GET['data_venc'])) && ($_GET['data_venc'] != "")) {
    $boletoBusca->setDataVencimento($_GET['data_venc']);
    $basePage.="&data_venc=" . $_GET['data_venc'];
}

if ((isset($_GET['clienteId'])) && ($_GET['clienteId'] != 'todos')) {
    $boletoBusca->setIdCliente($_GET['clienteId']);
    $basePage.="&clienteId=" . $_GET['clienteId'];
}
if ((isset($_GET['pago']))) {
    $boletoBusca->setPago(1);
    $basePage.="&pago=1";
}
// acrescenta o parametro pagina no link, o valor sera gerado ao carregar
$basePage .="&pagina=";



if (isset($_GET['pagina'])) {
    $pagina = $_GET['pagina'];
} else {
    $pagina = 1;
}
//trava palhaçadas, como tentar passar numeros negativos na url
if ($pagina <= 0) {
    header("Location:" . $basePage . "1");
}

// 1º registro do Bloco
$primeiroRegistro = ($pagina * $numPorPagina) - $numPorPagina;

// observe o objeto boletoBusca usado nas duas funcoes 
$BoletoC = new BoletoController();
$cache = $BoletoC->getDadosPaginados($primeiroRegistro, $numPorPagina, $boletoBusca); // carrega os registros do array 
$totalRegistros = $BoletoC->getTotalBoletos($boletoBusca); // pega o total de boletos 
$totalPaginas = ceil($totalRegistros / $numPorPagina); // ceil arredonda p/ maior , exemplo 1.65 sao 2 paginas

$anterior = $pagina - 1;
$proximo = $pagina + 1;

//trava palhaçadas , tipo colocar um nº maior do que o total de paginas
if ($pagina > $totalPaginas) {
    header("Location:" . $basePage . $totalPaginas);
}
?>        

<div class="index">
    <h3>Seção : boletos:</h3> 
<!-- SECAO BUSCA  -->
<div id="busca">
<fieldset>
<form id="formBusca" name="formBusca" method="GET" action="<? echo $_SESSION['user']['view']; ?>">
<table style="width: auto;">
    <tr>
        <th colspan="2">Localizar Boleto:<th>
    </tr>
    <tr>
        <td>Nosso Nº:</td><td><input type="text" name="nosso_num" id="nossoNum" value="<? echo $boletoBusca->getNossoNum(); ?>"></td>
    </tr> 
    <tr>
        <td>
            Valor:</td><td><input type="text" name="valor" id="valor" value="<? echo $boletoBusca->getValor(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Data Doc:</td><td><input type="text" name="data_doc" id="dataDoc" size="16" value="<? echo $boletoBusca->getDataDocumento(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Pago:</td><td><input type="checkbox" name="pago" id="pago" <? if ($boletoBusca->isPago()) {
echo "checked=\"checked\"";
} ?> value="<? echo $boletoBusca->isPago(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Data Venc:</td><td><input type="text" name="data_venc" id="dataVenc" size="16" value="<? echo $boletoBusca->getDataVencimento(); ?>">
        </td>
    </tr> 
    <tr>
        <td>
            Cliente:</td><td>
            <input name="data[OS][busca_cliente]" maxlength="30" type="text" id="buscaCliente" />
            <span class="actions">
                <input  type="button" value="Buscar Cliente" id="busca2"/>
            </span>
        </td>
    </tr> 
    <tr>
        <td>Cliente Selecionado</td>
        <td>
            <select name="clienteId" id="clienteId">

                <option value="todos">todos</option>
                <?  // essa parte apenas enche o combo com os clientes retornados da consulta
                if ($boletoBusca->getIdCliente() != NULL) {
                    $clienteC = new ClienteController();
                    $clienteC->loadOptions($boletoBusca->getIdCliente());
                }
                ?>

            </select>
        </td>
    </tr>
    <tr>
        <td colspan="2">
            <input type="hidden" name="section" id="section" value="boletos">
            <input type="hidden" name="action" id="action" value="index">
            <input type="hidden" name="pagina" id="pagina" value="1">
            <div class="submit">
                <input type="submit" value="Buscar">
            </div>
        </td>
    </tr>
</table>
</form>
</fieldset>    
</div>
<!-- FIM SECAO BUSCA-->
    <!-- SECAO DADOS-->
    <table width="85%" border="0" cellspacing="2" cellpadding="0">
        <tr>
            <th>ID</th>
            <th>SACADO</th>
            <th>NOSSO NUM</th>
            <th>DATA DOC</th>
            <th>DATA VENC</th>
            <th>VALOR</th>
            <th>a&ccedil;&otilde;es</th>
        </tr>
        <? if (count($cache) > 0) { 
            for ($i = 0; $i < count($cache); $i++) {
                $B = new Boleto();
                $B = $cache[$i];
                $idBoleto = $B->getId();
                ?>
                <tr>
                    <td><? echo $idBoleto; ?></td>
                    <td><? echo $B->getSacado(); ?></td>
                    <td><? echo $B->getNossoNum(); ?></td>
                    <td><? echo $B->getDataDocumento(); ?></td>
                    <td><? echo $B->getDataVencimento(); ?></td>
                    <td><? echo $B->getValor(); ?></td>
                    <td class="actions">
                        <a href="<? echo $_SESSION['user']['view']; ?>?section=boletos&action=view&id=<? echo $idBoleto; ?>"><img src="/images/search-menor.png"></a>| 
                    </td>
                </tr>
        <? } ?>
            <tr> 
                <td colspan="7"><hr width="100%" size="1" /></td>
            </tr>
            <tr>
                <td colspan="7"><? echo $primeiro = ($primeiroRegistro <= 0) ? "1" : $primeiroRegistro; ?> até <?
                echo $ultimo = ($totalRegistros <= ($primeiroRegistro + $numPorPagina) ? $totalRegistros : ($primeiroRegistro + $numPorPagina));
                ?> de <? echo $totalRegistros; ?></td>
            </tr>
        </table>
    
        <!-- SECAO BARRA DE NAVEGACAO -->
        <div class="paging">

            <? if ($pagina <= 1) { ?>
                <span class="prev disabled">&lt; anterior</span>
            <? } else { ?>
                <span class="prev"><a href=" <? echo $basePage . $anterior; ?>">&lt;anterior</a></span>
            <? } ?>
            <?
            for ($z = 1; $z <= $totalPaginas; $z++) {
                if ($z != $pagina) {
                    ?> <a href="<? echo $basePage . $z; ?>"><? echo $z; ?></a>
                <? } else { ?>
                    <? echo $z; ?>
                <? } ?>

            <? } ?>
            <? if ($pagina == $totalPaginas) { ?>
                <span class="next disabled">pr&oacute;xima &gt;</span>
            <? } else { ?>
                <span class="next"><a href="<? echo $basePage . $proximo; ?>">pr&oacute;xima &gt;</a></span>
    <? } ?>
        </div>
        <!-- FIM SECAO BARRA DE NAVEGACAO -->
    </div>
<? } else {// se  a consulta retornar 0 registros  ?>
    <tr> 
        <td colspan="5">Boleto não econtrado.</td>
    </tr>
    </table>
    </div>
<? } ?>
    <!-- FIM SECAO DADOS-->
    <!-- SECAO NOVO -->
<div class="actions">
    <h3>A&ccedil;&otilde;es</h3>
    <ul>
        <li><a style="width: 80px;"  href="<? echo $_SESSION['user']['view']; ?>?section=boletos&action=add">Novo Boleto</a></li>


    </ul>
</div>
    <!-- FIM SECAO NOVO -->
e no final da pagina
<script type="text/javascript">
    $(document).ready(function() {
        $(function(){  
            $("#dataDoc").mask("99/99/9999");  
            $("#dataVenc").mask("99/99/9999");  
            
        }); 

        $('#buscaCliente').change(function(){
//            JEITO INSEGURO              
//            $('#clienteId').load('/ajax/clientes.php?incluirOpcao=todos&busca='+$('#buscaCliente').val(),function(){
//                $('#clienteId').each(function(){
//                    if (this.value==""){
//                        alert('cliente nao econtrado');
//                    }
//                });
//
//            }) ;

//            JEITO SEGURO
            if ( $('#buscaCliente').val()!='') {
            var dataString='incluirOpcao=todos&busca='+$('#buscaCliente').val();
            $('#clienteId').empty(); // limpa o select
            $.post("/ajax/clientes.php", dataString,
            function(data) {
              var conta=0;
              
              $.each(data,function(){
                $('#clienteId').append("<option value="+this['id']+">"+this['nome']+"</option>");
//                alert(data);
//                alert(this);
                conta++;
              })
                if (conta==1){ // o 1º é o todos
                alert('nenhum cliente econtrado');
            }
            },"json");
            

            }
        
        });
        
    });
</script>



E assim está concluída nossa paginação registros com opções de filtragem/busca. Para complementar vou mostrar os principais trechos das duas paginas que salvam os boletos. Eis o formulário após a geração do boleto.

<br>
<form action="/app/controller/PostController.php" method="post">
<input type="hidden" name="data[boleto][operacao]" value="novo"/>
<input type="hidden" name="data[boleto][id_cliente]" value="<? echo $_POST['id_cliente'];?>"/>
<input type="hidden" name="data[boleto][nossoNum]" value="<? echo $dadosboleto["nosso_numero"]; ?>"/>
<input type="hidden" name="data[boleto][dataVencimento]" value="<? echo $dadosboleto["data_vencimento"] ;?>"/>
<input type="hidden" name="data[boleto][dataDocumento]" value="<? echo $dadosboleto["data_documento"];?>"/>
<input type="hidden" name="data[boleto][valorBoleto]" value="<? echo number_format($dadosboleto["valor_boleto"],2,'.','') ;?>"/>
<input type="hidden" name="data[boleto][sacado]" value="<? echo $dadosboleto["sacado"] ;?>"/>
<input type="hidden" name="data[boleto][nf]" value="<? echo $dadosboleto["nu_doc"] ;?>"/>
<input type="hidden" name="data[boleto][discriminacao]" value="<? echo $dadosboleto["demonstrativo1"] ;?>"/>
<input type="hidden" name="data[boleto][doc_cliente]" value="<? echo $dadosboleto["cnpj_sacado"];?>"/>
<input type="hidden" name="data[boleto][linha_digitavel]" value="<? echo $dadosboleto["linha_digitavel"];?>"/>

<button>Salvar</button>
</form>

e o PostController que recebe e trata os formularios, esta seção abaixo salva o boleto.

<?php
session_start();
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/model/Boleto.php');
require_once($_SERVER['DOCUMENT_ROOT'] . '/app/controller/BoletoController.php');

//echo "<pre>";
//print_r($_POST);
//echo  "</pre>";



if (isset($_POST['data']['boleto']['operacao'])) {
    
    $nossoNum=$_POST['data']['boleto']['nossoNum'];
    $dataVencimento=$_POST['data']['boleto']['dataVencimento'];
    $dataDocumento=$_POST['data']['boleto']['dataDocumento'];
    $valor=$_POST['data']['boleto']['valorBoleto'];
    $notaFiscal=$_POST['data']['boleto']['nf'];
    $discriminacao=$_POST['data']['boleto']['discriminacao'];
    $idCliente =$_POST['data']['boleto']['id_cliente'];
    $sacado = $_POST['data']['boleto']['sacado'];
    $linhaDigitavel = $_POST['data']['boleto']['linha_digitavel'];
    $B= new Boleto();
    $B->setNossoNum($nossoNum);
    $B->setDataVencimento($dataVencimento);
    $B->setDataDocumento($dataDocumento);
    $B->setValor($valor);
    $B->setNotaFiscal($notaFiscal);
    $B->setDiscriminacao($discriminacao);
    $B->setIdCliente($idCliente);
    $B->setSacado($sacado);
    $B->setLinhaDigitavel($linhaDigitavel);
    
    $boletoC = new BoletoController();
    $boletoC->salvarBoleto($B);
    $urlToGo = $_SESSION['user']['view']."?section=boletos&action=index&flash=boleto&resultado=3";
    header("Location: $urlToGo");
    
}


?>

Até a próxima...

4 comentários:

Unknown disse...

Como sempre, otimo material professor.
Marcello Nicollette

Unknown disse...
Este comentário foi removido pelo autor.
Empreender com a Net disse...

Otimo material, tem como enviar ele completo para o meu email para que possa estudar, inclusive as partes de crud num pagina php, na hora de inserir, remover e edita e listar
marcela@gmail.com

Marcos Paulo disse...

Pesquisando sobre o MVC encontrei os seus artigos e achei muito interessante. Você poderia enviar o material completo para facilitar o entendimento, visualizar melhor a estrutura de diretório, etc. marcospmsantos@gmail.com