Uma das principais dificuldades de criar aplicações Web flexíveis e amigáveis é a de criar uma interface de usuário intuitiva. Muitas aplicações tendem a crescer rapidamente em tamanho e complexidade e tanto designers como programadores acabam tendo que lidar com o problema de mostrar centenas ou milhares de registros. Refatoração leva tempo e o desempenho e a satisfação do usuário podem ser afetados.
Mostrar um número razoável de registros por página tem sido uma parte crítica das aplicações e geralmente causam muitas dores de cabeça para os desenvolvedores. CakePHP facilita o trabalho do desenvolvedor provendo uma forma rápida e fácil para paginar os dados.
O PaginatorHelper oferece uma ótima solução porque é muito fácil de usar. Além da paginação ele também traz consigo algumas funcionalidades de ordenação. Por último, mas não menos importante, ordenação e paginação Ajax também são suportadas.
No controller, começamos definindo os padrões de paginação na variável $paginate do controller. É importante notar aqui que a entrada «order» deve estar presente na estrutura dada do array.
class RecipesController extends AppController {
var $paginate = array(
'limit' => 25,
'order' => array(
'Post.title' => 'asc'
)
);
}
Você também pode incluir outras opções do find(), como, p.ex., fields:
class RecipesController extends AppController {
var $paginate = array(
'fields' => array('Post.id', 'Post.created'),
'limit' => 25,
'order' => array(
'Post.title' => 'asc'
)
);
}
Outras chaves que podem ser incluídas no array $paginate são semelhantes aos parâmetros do método Model->find(“all”), quais sejam: conditions, fields, order, limit, page, contain, e recursive. De fato, você pode definir mais do que um conjunto de padrões de paginação no controller, você apenas denomina as peças do array depois do model que você deseja configurar:
class RecipesController extends AppController {
var $paginate = array(
'Recipe' => array (...),
'Author' => array (...)
);
}
Exemplo de sintaxe usando o ContainableBehavior:
class RecipesController extends AppController {
var $paginate = array(
'limit' => 25,
'contain' => array('Article')
);
}
Uma vez a variável $paginate tenha sido definida, podemos chamar o método paginate() nas actions do controller. Este método retorna resultados de um find() paginados a partir do model e obtém estatísticas adicionais de paginação que são passadas para a View por debaixo dos panos. Este método também adiciona o PaginatorHelper à lista de helpers em seu controller, se ainda não tiver sido adicionado.
function list_recipes() {
// semelhante a find('all'), mas com resultados paginados
$data = $this->paginate('Recipe');
$this->set('data', $data);
}
Você pode filtrar os registros passando condições como segundo parâmetro
para o método paginate()
.
$data = $this->paginate('Recipe', array('Recipe.title LIKE' => 'a%'));
Ou você também pode definir a chave conditions no array $paginate
.
Fica a seu critério decidir como exibir os registros para o usuário, mas na maioria das vezes isso normalmente é feito com tabelas HTML. Os exemplos abaixo assumem um layout tabular, mas o PaginatorHelper disponível nas views não se restringe em absoluto a nenhum formato de exibição.
Veja os detalhes sobre o PaginatorHelper na API.
Como mencionado, o PaginatorHelper também fornece recursos de ordenação que podem ser facilmente integrados às colunas de suas tabelas de dados:
// app/views/recipes/list_recipes.ctp
<table>
<tr>
<th><?php echo $paginator->sort('ID', 'id'); ?></th>
<th><?php echo $paginator->sort('Title', 'title'); ?></th>
</tr>
<?php foreach($data as $recipe): ?>
<tr>
<td><?php echo $recipe['Recipe']['id']; ?> </td>
<td><?php echo $recipe['Recipe']['title']; ?> </td>
</tr>
<?php endforeach; ?>
</table>
Os links exibidos a partir do método sort() do PaginatorHelper permite que os usuários cliquem nos cabeçalhos da tabela para trocar a ordenação de dados para o campo dado.
Também é possível ordenar uma coluna baseada em associações:
<table>
<tr>
<th><?php echo $paginator->sort('Title', 'title'); ?></th>
<th><?php echo $paginator->sort('Author', 'Author.name'); ?></th>
</tr>
<?php foreach($data as $recipe): ?>
<tr>
<td><?php echo $recipe['Recipe']['title']; ?> </td>
<td><?php echo $recipe['Author']['name']; ?> </td>
</tr>
<?php endforeach; ?>
</table>
O ingrediente final para a exibição da paginação nas views é a adição do navegador de páginas, também disponível no PaginationHelper.
<!-- Mostra os números das páginas -->
<?php echo $paginator->numbers(); ?>
<!-- Mostra os links próximo e anterior -->
<?php
echo $paginator->prev('« Anterior ', null, null, array('class' => 'disabled'));
echo $paginator->next(' Próximo »', null, null, array('class' => 'disabled'));
?>
<!-- exibe X de Y, sendo X a página atual e Y o total de páginas -->
<?php echo $paginator->counter(); ?>
Os dizeres exibidos pelo método counter() também podem ser personalizados com o uso de marcadores especiais:
<?php
echo $paginator->counter(array(
'format' => 'Página %page% de %pages%, exibindo %current% registros de um total de %count%, exibindo do registro %start% até o %end%'
));
?>
Para passar todos os argumentos URL para funções do paginador, adicione o seguinte à sua view:
$paginator->options(array('url' => $this->passedArgs));
Elementos de rota que não forem argumentos parametrizados (named
arguments) devem ser mesclados manualmente com $this->passedArgs
:
// para urls como http://www.example.com/en/controller/action
// que são roteadas como Router::connect('/:lang/:controller/:action/*', array(),array('lang'=>'ta|en'));
$paginator->options(array('url'=>array_merge(array('lang'=>$lang),$this->passedArgs)));
Ou você pode especificar quais parâmetros serão passados manualmente:
$paginator->options(array('url' => array("0", "1")));
É muito simples incorporar funcionalidades Ajax na paginação. O único código extra requerido é a inclusão da biblioteca Javascript Prototype, e a especificação de uma DIV para ser atualizada com o conteúdo de paginação (ao invés de reler a página). Pode-se também definir o parâmetro indicator contendo o id de uma DIV que será mostrada quando o conteúdo da paginação estiver sendo lido e ocultado ao término de sua leitura.
Não se esqueça de adicionar o componente RequestHandler para detectar as chamadas Ajax em seu controller.
var $components = array('RequestHandler');
Primeiramente, nós vamos incluir a biblioteca Prototype no cabeçalho, definir nossa imagem indicadora de status (spinner.gif) e definir o DIV principal que conterá nossos dados, «content».
Aqui você vê como layout que inclui estes elementos se parece (parcialmente):
<head>
<title><?php echo $title_for_layout; ?></title>
<?php echo $javascript->link(array('prototype')); ?>
<style type="text/css">
div.disabled {
display: inline;
float: none;
clear: none;
color: #C0C0C0;
}
</style>
</head>
<body>
<div id="main">
<div id="spinner" style="display: none; float: right;">
<?php echo $html->image('spinner.gif'); ?>
</div>
<div id="content">
<?php echo $content_for_layout; ?>
</div>
</div>
</body>
</html>
A única configuração extra para a paginação Ajax é feita usando-se o método options() do PaginationHelper, a partir do qual se especificam os parâmetros Ajax requeridos. Neste caso, estamos definindo que todos os links de paginação devem atualizar o elemento com o ID “content” com os dados resultantes e queremos que “spinner” apareça como indicador de carregamento.
Se a chave “update” não for especificada, o PaginationHelper irá exibir links não-Ajax para ordenação e paginação.
<?php
// Define os elementos a ser atualizados (update) e indicador de carga (indicator) através de seu DOM ID
$paginator->options(array('update' => 'content', 'indicator' => 'spinner'));
echo $paginator->prev('<< Anterior', null, null, array('class' => 'disabled'));
echo $paginator->next('Próximo >>', null, null, array('class' => 'disabled'));
?>
<!-- exibe X de Y, sendo X a página atual e Y o total de páginas -->
<?php echo $paginator->counter(); ?>
FIXME: Por favor, incluir um exemplo que demonstre a necessidade de se personalizar as consultas de paginação.
Se você precisar criar consultas personalizadas para gerar os dados que
quer paginar, você pode sobrescrever os métodos paginate()
e
paginateCount()
do model, usados pela lógica de paginação do
controller.
Antes de prosseguir, verifique se você realmente não consegue fazer o que você deseja com os métodos padrão do core model.
O método paginate()
utiliza os mesmos parâmetros que o
Model::find()
. Para usar sua própria versão/sua própria lógica,
sobrescreva este método no model a partir do qual você quer obter os
dados.
/**
* Método paginate sobrescrito - agrupa pelos campos "week", "away_team_id" e "home_team_id"
*/
function paginate($conditions, $fields, $order, $limit, $page = 1, $recursive = null, $extra = array()) {
$recursive = -1;
$group = $fields = array('week', 'away_team_id', 'home_team_id');
return $this->find('all', compact('conditions', 'fields', 'order', 'limit', 'page', 'recursive', 'group'));
}
Você também precisa sobrescrever o método paginateCount()
, que é um
método que utiliza os mesmos argumentos que o Model::find('count')
.
O exemplo abaixo utiliza alguns recursos específicos para banco
Postgres, então atente para fazer ajustes para o banco de dados que você
estiver utilizando.
/**
* Método paginateCount sobrescrito.
*/
function paginateCount($conditions = null, $recursive = 0, $extra = array()) {
$sql = "SELECT DISTINCT ON(week, home_team_id, away_team_id) week, home_team_id, away_team_id FROM games";
$this->recursive = $recursive;
$results = $this->query($sql);
return count($results);
}
O leitor mais atento vai perceber que o método paginate que definimos
não era realmente necessário - tudo que você precisaria fazer seria
adicionar a palavra-chave na variável de classe $paginate
do
controller.
/**
* Inclui a cláusula GROUP BY.
*/
var $paginate = array(
'MyModel' => array('limit' => 20,
'order' => array('week' => 'desc'),
'group' => array('week', 'home_team_id', 'away_team_id'))
);
/**
* Ou, fazendo "on-the-fly" dentro da própria action.
*/
function index() {
$this->paginate = array(
'MyModel' => array('limit' => 20,
'order' => array('week' => 'desc'),
'group' => array('week', 'home_team_id', 'away_team_id'))
);
No entanto, ainda seria necessário sobrescrever o método
paginateCount()
para obter um valor correto.