Desenvolvimento

5 jan, 2010

Criando um componente de paginação – Parte 01

Publicidade

Salve, pessoal! Estou voltando a escrever depois de um longo período de jejum nas publicações de novos artigos.

Pois bem, neste novo artigo irei demonstrar como criar um componente de paginação. Sabemos que em grande parte dos projetos é necessário amostrar diversos tipos de informação seguindo os padrões de Master Detail (Grid, List, etc.). Para este tipo de amostragem de dados, a paginação é um componente super importante, pois previne alguns problemas básicos, como:

  1. Problemas de performance na busca e listagem dos dados.
  2. Problemas de usabilidade com relação ao scroll vertical que é criado devido ao montante de dados.

A criação deste componente será dividida em dois artigos, sendo este o primeiro da série. Assim, neste primeiro desenvolveremos um componente que irá apenas paginar um montante de dados estáticos armazenados em um ArrayCollection, ou seja, criaremos toda a lógica para percorrer a lista e paginá-la em um componente do tipo lista (DataGrid, List, etc).

Já na segunda parte, faremos com que o servidor fique responsável pela tarefa de buscar e calcular o total de dados referentes à pesquisa. Desta maneira, nosso componente ficará responsável apenas por enviar qual a quantidade de dados a ser retornada e qual a faixa em que se encontra a sequência.

O principal motivo deste artigo estar dividido em duas partes é fazer com que o desenvolvedor entenda o funcionamento interno da paginação de dados, possibilitando a realização de melhorias durante a utilização do server-side se necessário.

Requisitos:

  • Ter Flex/Flash Builder ou outra IDE.
  • Ter SDK 3 ou superior.
  • Conhecimento em ActionScript e Mxml.

Nivel de dificuldade: 7.5

Agora vamos ao que interessa.

Passo 1

Iremos criar um componente customizado que estenda o Button, este será responsável por referenciar e exibir os dados referentes a uma página. Este componente é muito simples, o que realmente devemos observar são os seguintes pontos:

  1. Propriedade “pagina”, a qual indica qual pagina será exibida.
  2. Propriedade “intervaloInicial”, indica qual o ponto inicial em que teremos que buscar os dados da coleção.
  3. Evento “clickHandler”, realizaremos um override neste método para que seja disparado um evento personalizado que avisará nosso componente principal que o usuário deseja ir para a página x.

Segue o código:

package com.imaster.impl {
import com.imaster.event.PaginacaoEvent;
import flash.events.MouseEvent;
import mx.controls.Button;
/**
*
* @author Fabiel Prestes
*
*/
[Event(name="exibirPagina", type="com.imaster.event.PaginacaoEvent")]
public class BotaoPagina extends Button {

[Bindable]
private var _pagina:int;

[Bindable]
private var _intervaloInicial:int;
private var _evtPaginacao:PaginacaoEvent;
public function BotaoPagina() {
super();
}
override protected function childrenCreated():void{
super.childrenCreated();
this.buttonMode = true;
this.useHandCursor = true;
/* Define a label do botao com o mesmo numero de pagina */
if(this.label == '')
this.label = String(_pagina);
}
/**
* Define a pagina a qual este botao deve amostrar os dados para o usuario.
* @param int Numero da pagina
*/
public function set pagina(value:int):void {
_pagina = value;
}
/**
* Retorna o numero da pagina na qual este botao deve amostrar os dados.
* @return
*/
public function get pagina():int {
return _pagina;
}
/**
* Define o intervalo Inicial que deverá ser amostrado na noav listagem
* @param int Numero da pagina
*/
public function set intervaloInicial(value:int):void {
_intervaloInicial = value;
}
/**
* @return
*/
public function get intervaloInicial():int {
return _intervaloInicial;
}
override protected function clickHandler(event:MouseEvent):void {
super.clickHandler(event);
if(this.selected){
_evtPaginacao = new PaginacaoEvent(PaginacaoEvent.EXIBIR_PAGINA);
_evtPaginacao.botaoPagina = this;
this.dispatchEvent(_evtPaginacao);
}
}
}
}

Passo 2

Agora, com o componente BotaoPagina criado, iremos desenvolver nosso evento personalizado, que irá delegar e orquestrar as requisições feitas entre o BotaoPagina e nosso componente principal.

package com.imaster.event {
    import com.imaster.impl.BotaoPagina;
   
    import flash.events.Event;

    /**
     *
     * @author Fabiel Prestes
     *
     */
    public class PaginacaoEvent extends Event {
       
        public static const EXIBIR_PAGINA:String = "exibirPagina";
       
        public var botaoPagina:BotaoPagina;
       
        public function PaginacaoEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
            super(type, bubbles, cancelable);
        }

    }
}

Passo 3

Abaixo está o código mxml que será a capa do componente de paginação principal, neste teremos os seguintes componentes:

  1. Dois Buttons: um será responsável por avançar e outro para retroceder uma página, sempre baseados na página atual.
  2. Um HBox, este container será responsável por armazenar e identar o componente BotaoPagina.
  3. Por último um ComboBox, que irá definir os intervalos de dados que cada página deverá amostrar.

Segue o código abaixo

<?xml version="1.0" encoding="utf-8"?>
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" horizontalAlign="center" xmlns:impl="com.imaster.impl.*">
<mx:Metadata>
[ExcludeClass]
</mx:Metadata>
<mx:Button id="bpPaginaAnterior" toggle="false" icon="@Embed(source='/assets/anterior.png')" useHandCursor="true" buttonMode="true"/>
<mx:HBox id="containerBpIntermadiarios"/>
<mx:Button id="bpProximaPagina" toggle="false" icon="@Embed(source='/assets/proxima.png')" useHandCursor="true" buttonMode="true"/>
<mx:ComboBox id="cbIntevalo" width="60">
<mx:dataProvider>
<mx:Object label="5" value="5"/>
<mx:Object label="10" value="10"/>
<mx:Object label="15" value="15"/>
</mx:dataProvider>
</mx:ComboBox>
</mx:HBox>

Passo 4

Agora daremos início à criação do componente que orquestrará toda a
tarefa de manipulação da paginação. Este componente é bem simples,
contudo requer atenção em alguns pontos, já que nesta primeira parte do
artigo iremos criar toda a lógica de paginação e, no próximo artigo,
será de responsabilidade do server-side.

Métodos principais:

  1. childrenCreated: Iremos realizar um override para adicionar todos os listener necessários, quando todos os componentes da tela forem criados, para os botões de avançar e retroceder e para o ComboBox de retroceder.
  2. invalidateProperties: Iremos realizar um override para as propridedades quando o commitProperties for chamada.
  3. configurarBotoesPagina: Este é um dos principais métodos, já que é responsável por calcular a quantidade de páginas necessárias, baseado no montante de dados, e depois criar os BotesPagina e adicioná-los no container.
  4. configurarListaNaPagina: Método chamado sempre que uma nova página for requisitada. Assim o método recupera a página selecionada e qual intervalo foi selecionado pelo usuário para, assim, realizar a coleta dos dados na coleção.

Há outros métodos na classe, contudo os citados acima são os mais importantes, deixei todo o código comentado, assim ficará mais fácil tirar as dúvidas.

Segue o código:

package com.imaster.impl {
import com.imaster.PaginacaoUI;
import com.imaster.event.PaginacaoEvent;
import flash.events.MouseEvent;
import mx.collections.ArrayCollection;
import mx.controls.listClasses.ListBase;
import mx.events.ListEvent;
/**
*
* @author Fabiel
*
*/
public class Paginacao extends PaginacaoUI {
[Bindable]
private var _totalPorPagina:int = 5;
[Bindable]
private var _totalDados:int;
[Bindable]
private var _listaBase:ArrayCollection;
[Bindable]
private var _listaBaseAux:ArrayCollection;
private var _bpAux:BotaoPagina;
private var _bpAtual:BotaoPagina;
private var _listaAlvo:ListBase;
private var _botoesPagina:Array = [];
private var totalBotoes:int;
public function Paginacao() {
super();
}
override protected function childrenCreated():void{
super.childrenCreated();
/* Adicionando os Listeners */
this.bpPaginaAnterior.addEventListener(MouseEvent.CLICK, trateExibirPaginaAnterior, false, 0, true);
this.bpProximaPagina.addEventListener(MouseEvent.CLICK, trateExibirProximaPagina, false, 0, true);
this.cbIntevalo.addEventListener(ListEvent.CHANGE, trateTrocaIntervalo, false, 0, true);
}
override public function invalidateProperties():void{
super.invalidateProperties();
/* Inicializa a configuração da paginação */
configurarBotoesPagina();
}
/**
* @private
* Fica escutando quando o usuario trocou o total de interva de dados a ser amostrado na lista
* @param evt
*/
private function trateTrocaIntervalo(evt:ListEvent):void{
_totalPorPagina = cbIntevalo.selectedItem.value;
/* Inicializa a configuração da paginação */
configurarBotoesPagina();
}
/**
* @private
* Responsavel por configurar os botoes de paginação na tela.
*/
private function configurarBotoesPagina():void{
/* Calcula a quantidade de botoes que deverao ser criados baseado no total de dados e total de dados por pagina */
totalBotoes = Math.ceil(totalDados/totalPorPagina);
/* Se houver BotoesPagina ja configurado no container, deve-se removelos para
* recalcular o total de paginas */
if(containerBpIntermadiarios != null && containerBpIntermadiarios.getChildren() != null){
containerBpIntermadiarios.removeAllChildren();
_botoesPagina = [];
}
/* Realiza um loop criando os botoes */
for(var i:int = 1; i <= totalBotoes; i++){
/* Cria e configura o BotaoPagina */
_bpAux = new BotaoPagina();
_bpAux.toggle = true;
_bpAux.pagina = i;
_bpAux.intervaloInicial = (i - 1) * _totalPorPagina;
_bpAux.addEventListener(PaginacaoEvent.EXIBIR_PAGINA, trateExibirPagina);
_botoesPagina[i] = _bpAux;
/* Se for o primeiro BotaoPagina criado deve-se configurar a lista de dados na componente tipo ListBase */
if(i == 1){
_bpAux.selected = true;
_bpAtual = _bpAux;
configurarListaNaPagina(_bpAux);
}
/* Adiciona os botoes no container */
containerBpIntermadiarios.addChild(_bpAux);
}
}
/**
* @private
* Configura e renderiza os dados Base para ser amostrado na tela levendo-se em consideração
* o intervalo passado.
* @param intervloIncial
*
*/
private function configurarListaNaPagina(bpAtual:BotaoPagina):void{
_listaBaseAux = new ArrayCollection();
/* para cada Loop é copiado o objeto que se encontra no intervalo passado como parametro */
for(var j:int = 0; j < _totalPorPagina; j++){
if(bpAtual.intervaloInicial + j < _totalDados)
_listaBaseAux.addItem(_listaBase.getItemAt(bpAtual.intervaloInicial + j));
}
/* Configurando os botoes de avançar e retornar */
if(bpAtual.pagina > 1){
configBpPaginaAnterior();
} else {
configBpPaginaAnterior(false);
}
if(bpAtual.pagina == totalBotoes){
configBpProximaPagina(false);
} else {
configBpProximaPagina();
}
/* Define o provider clonado e define na ListBase */
this._listaAlvo.dataProvider = _listaBaseAux;
}
/**
* @private
* Responsavel por ficar escutando quando o usuario deseja visualizar o conteudo de cada pagina
*/
private function trateExibirPagina(evt:PaginacaoEvent):void{
_bpAtual.selected = false;
_bpAtual = evt.botaoPagina;
configurarListaNaPagina(_bpAtual);
}
/**
* @private
* Responsavel por exibir os dados da paragina anterior.
*/
private function trateExibirPaginaAnterior(evt:MouseEvent):void{
/* Verifica qual a pagina em que esta para pegar a anterior */
var i:int = (_bpAtual.pagina >= 1) ? _bpAtual.pagina - 1 : 0;
_bpAtual.selected = false;
_bpAtual = _botoesPagina[i];
_bpAtual.selected = true;
configurarListaNaPagina(_bpAtual);
}
/**
* @private
* Responsavel por amostrar os dados da proxima pagina
*/
private function trateExibirProximaPagina(evt:MouseEvent):void{
/* Verifica qual a pagina em que esta para pegar a proxima */
var i:int = (_bpAtual.pagina < (_botoesPagina.length - 1)) ? _bpAtual.pagina + 1 : _botoesPagina.length;
if(i < _botoesPagina.length){
/* Desmarca o botao atual */
_bpAtual.selected = false;
/* Recupera o proximo botao e marca o mesmo */
_bpAtual = _botoesPagina[i];
_bpAtual.selected = true;
/* Configura a lista na pagina */
configurarListaNaPagina(_bpAtual);
}
}
/**
* Define o total de dados que serão amostrados em cada pagina
* @param value
*
*/
public function set totalPorPagina(value:int):void{
_totalPorPagina = value;
}
/**
* Retorna o numero de dados que esta/serão amostrados na tela.
* @return
*/
public function get totalPorPagina():int{
return _totalPorPagina;
}
/**
* Define o total de dados que serão amostrados em cada pagina
* @param value
*
*/
public function set totalDados(value:int):void{
_totalDados = value;
}
/**
* Retorna o numero de dados que esta/serão amostrados na tela.
* @return
*/
public function get totalDados():int{
return _totalDados;
}
public function set listaAlvo(value:ListBase):void{
_listaAlvo = value;
}
public function get listaAlvo():ListBase{
return _listaAlvo;
}
/**
* Define a lista que será utilizada para exibir na paginacao
* @param value
*
*/
public function set listaBase(value:ArrayCollection):void{
_listaBase = value;
if(_listaBase){
_totalDados = _listaBase.length;
}
}
public function get listaBase():ArrayCollection{
return _listaBase;
}
private function configBpPaginaAnterior(value:Boolean = true):void {
this.bpPaginaAnterior.enabled = value;
}
private function configBpProximaPagina(value:Boolean = true):void {
this.bpProximaPagina.enabled = value;
}
}
}

E é isso, pessoal, acabamos de criar um componente de paginação que realizará todo o trabalho de manipulação manualmente. Na segunda parte do artigo, vamos alterar e demonstrar como utilizar o server-side para facilitar.

Qualquer dúvida é só comentar ou enviar email.

Source

Até a próxima.