A tabela departamento.produto cresceu: 75 registros já em desenvolvimento, e isso vai escalar em produção. O setPageSize(9999) do CRUD básico despeja tudo de uma vez — renderização lenta, scroll infinito, experiência ruim. A Paginação resolve: setPageSize(50), um navegador de páginas no rodapé, e integração com o QuickSearch pra voltar à página 1 a cada nova busca.
4 mudanças pequenas em um único arquivo (DepartamentoProdutoList). Nada no Bean, nada no DAO, nada no Manager.
package br.xt.app.departamento.produto;
import br.jasap.core.Effect;
import br.jasap.effect.Response;
import br.jasap.gui.JasapPage;
import br.jasap.gui.ListColumn;
import br.jasap.gui.ListLine;
import br.jasap.gui.ListView;
import br.jasap.gui.Bar;
import br.jasap.gui.Button;
import br.jasap.gui.Table;
import br.jasap.gui.Toast;
import br.jasap.gui.form.Text;
import br.jasap.util.JasapFunctions;
import br.jasap.util.Js;
import br.jasap.util.ModalConfig;
import br.jasap.util.exceptions.SQLConstraintException;
import br.xt.acore.view.IconButton;
import br.xt.acore.view.XtPage;
public class DepartamentoProdutoList extends DepartamentoProdutoAction {
// ... execute(), render(), window() (sem alteração)
protected Text qs_produto = null;
public String qs_produto() throws Exception {
qs_produto = ui().text(DepartamentoProdutoBean.QS_PRODUTO)
.setLabel("Consulta")
.setStyle("width:400;height:30px;font-size:14px")
.setMaxlength(300)
.setValue(getFiltro().getQs_produto())
.setOnkeyup(Js.pressEnter(
link(QuickSearch.class)
.putScript(DepartamentoProdutoBean.QS_PRODUTO, Js.SELF_VALUE)
.putInteger(lView().getPAGE(), 0)
.ajax()));
// ... restante da barra de busca (sem alteração)
}
public Bar br() throws Exception {
Button cmd_novo = ui().button(" Novo Registro ").setCss("btn btn-success btn-lg").setNoSize()
.setOnClick(link(DepartamentoProdutoForm.ShowInsert.class)
.modal(new ModalConfig().setWidth("750").setHeight("570")
.setOnCloseURL(url(Sort.class))));
return ui().bar()
.addCenter(lView().nav(lView().getNavForm()))
.addRight(cmd_novo);
}
private ListView lv = null;
public ListView lView() throws Exception {
if (lv == null) {
lv = ui().lView();
String orderBy = getSession().getString(LIST.concat(lv.getORDER_BY()), getInput());
if (orderBy == null) orderBy = DepartamentoProdutoBean.NOME_PRODUTO;
lv.setSortAct(url(Sort.class))
.setPageAction(url(Sort.class))
.setPage(getSession().getInteger(LIST.concat(lv.getPAGE()), getInput()))
.setOrderBy(orderBy)
.setSort(getSession().getString(LIST.concat(lv.getSORT()), getInput()))
.ajax();
lv.setPageSize(50);
lv.setFiltro(getFiltro());
getFactory().departamento().proModel().daoList(lv.getData());
// ... colunas e while (sem alteração)
}
return lv;
}
// ... Sort, QuickSearch, DeleteFromList (sem alteração)
// ... constantes LIST, FILTRO, CONFIRM_LIST (sem alteração)
}
Mudanças nesta sequência: 4 alterações no mesmo arquivo — .putInteger(PAGE, 0) no onkeyup do qs_produto, .addCenter(...) no br(), .setPageAction + .setPage na chain do lv, e setPageSize(9999) virou setPageSize(50). Nenhum import novo.
A paginação é coordenação entre 3 coisas: quantos registros por página, qual página mostrar, e como navegar. A quarta mudança é uma integração com o QuickSearch — sem ela, buscar na página 3 mostraria resultados da página 3 da nova busca em vez de começar do início.
| Mudança | Onde | Papel |
|---|---|---|
setPageSize(50) | lView() | Define tamanho da página — antes era 9999 (uma "página" só) |
setPageAction + setPage | chain do lv | Registra a action que redesenha ao trocar de página e lê a página atual da sessão/input |
nav(getNavForm()) | br() | Renderiza os botões "Anterior / Próxima / N de M" no rodapé |
.putInteger(PAGE, 0) | onkeyup do qs_produto | Zera a página no momento em que o usuário digita a busca |
As 3 primeiras mudanças fazem a paginação existir. A quarta garante que a paginação se comporta bem com a busca rápida.
setPageSize(9999) → setPageSize(50)
lv.setPageSize(50);
Uma linha, um número. Mas é a mudança mais fundamental — define quantos registros o DAO busca de uma vez e quantos o HTML renderiza.
Antes: setPageSize(9999) — truque do CRUD básico pra desligar a paginação. O DAO fazia LIMIT 9999, que na prática trazia tudo. Com 75 registros, trazia os 75. Com 10.000, traria 9.999 e silenciosamente perderia 1 — pior que paginar errado.
Depois: setPageSize(50) — DAO faz LIMIT 50 OFFSET ?. Com 75 registros, o framework calcula 2 páginas (50 + 25). O getNavForm() usa esse cálculo pra mostrar "Página 1 de 2".
Por que 50 e não 10 ou 100? Convenção do XT em produção (ver CpesList, PusuList, LabProdutoList). 50 é um equilíbrio: ocupa a tela bem em resoluções comuns, mantém o usuário rolando no máximo uma página, e não estoura na performance do DAO pra tabelas com índices razoáveis.
setPageAction + setPage na chain do lv
lv.setSortAct(url(Sort.class))
.setPageAction(url(Sort.class))
.setPage(getSession().getInteger(LIST.concat(lv.getPAGE()), getInput()))
.setOrderBy(orderBy)
.setSort(getSession().getString(LIST.concat(lv.getSORT()), getInput()))
.ajax();
Duas linhas adicionadas na chain do lv, logo depois do setSortAct. Ambas são o mínimo pra paginação funcionar.
.setPageAction(url(Sort.class))Registra a action que será chamada ao trocar de página. Quando o usuário clica "Próxima" no nav(), o framework dispara essa action pra redesenhar a lista.
Por que Sort.class? Reaproveitamento. A inner class Sort já atualiza header + body + navegação — exatamente o que precisa acontecer ao trocar de página. Criar uma ChangePage.class separada seria duplicação — o Sort já faz o trabalho, só com input diferente (PAGE em vez de ORDER_BY/SORT).
.setPage(getSession().getInteger(LIST.concat(lv.getPAGE()), getInput()))Lê a página atual em duas fontes: input (prioridade) e sessão (fallback).
lv.getPAGE() retorna a string "PAGE" — o nome do parâmetro que o framework usa.LIST.concat(lv.getPAGE()) gera "XT.PAINEL_CONTROLE.ACESSO_MODULO.DEPARTAMENTO__PRODUTO/__LIST/PAGE" — chave única por lista. Sem o namespace do LIST, duas listas na mesma tela colidiriam.getSession().getInteger(chave, getInput()) — padrão Jasap: primeiro olha o input, depois a sessão. Se o usuário clica "Página 3", o link manda PAGE=3 no input; o framework lê e grava na sessão. Próximo render, a sessão já tem o valor, mesmo sem input.É o mesmo padrão de setOrderBy e setSort. Qualquer estado de navegação da lista segue essa dupla camada.
lView().nav(...) no br()
public Bar br() throws Exception {
Button cmd_novo = ui().button(" Novo Registro ") /* ... */;
return ui().bar()
.addCenter(lView().nav(lView().getNavForm()))
.addRight(cmd_novo);
}
O br() é a barra inferior da lista. Antes só tinha o botão "Novo Registro" à direita. Agora ganha um navegador de páginas no centro.
lView().getNavForm()Retorna o ID do form HTML que o framework usa pra envolver os botões de navegação (<form id="navform_XXX">). Esse ID é preciso pra que os botões "Anterior / Próxima" peguem o valor do PAGE corretamente no submit AJAX.
lView().nav(...)Renderiza o componente visual: botões "Anterior", "Próxima", e texto "Página N de M". Bootstrap, estilo XT. Recebe o ID do form como parâmetro pra amarrar os cliques ao submit certo.
.addCenter(...)O Bar tem 3 regiões: addLeft, addCenter, addRight. O navegador no centro, botão "Novo Registro" na direita — layout clássico de lista paginada.
Sem essa mudança: a paginação até funcionaria no backend (setPageSize(50) cortaria a lista), mas o usuário ficaria preso na página 1 — sem botão pra avançar. O nav() é a UI pra controlar o estado que a Mudança 2 lê.
.putInteger(PAGE, 0) no onkeyup do qs_produto
.setOnkeyup(Js.pressEnter(
link(QuickSearch.class)
.putScript(DepartamentoProdutoBean.QS_PRODUTO, Js.SELF_VALUE)
.putInteger(lView().getPAGE(), 0)
.ajax()));
Uma linha no link do QuickSearch: adiciona o parâmetro PAGE=0 ao submit. Sem isso, a busca rápida teria um bug visual grave.
qs_produto="notebook" mas mantém PAGE=3 (fonte: sessão, que ainda tem o valor antigo)LIMIT 50 OFFSET 100 — offset maior que o total — retorna zero registros.putInteger(lView().getPAGE(), 0) adiciona PAGE=0 ao input do submit. Como o setPage da Mudança 2 prioriza input sobre sessão, o framework usa 0 — volta pra primeira página. A busca mostra os resultados começando do início.
Por que no onkeyup e não no QuickSearch.execute()? Duas abordagens possíveis:
.putInteger(PAGE, 0) no link: explícito, visível no código do form, uma linha só.resetPage() que grava uma flag, consumida no lView(): helper reutilizável, mas adiciona 3 elementos no código (método + constante + bloco consumer).Pra só a paginação, a via input é suficiente e minimalista. A via sessão só compensa quando múltiplas actions precisam resetar página (Tabs de status, filtros avançados) — aí o helper justifica o custo. Ver a seção "O que NÃO faz parte da Paginação" abaixo.
Três coisas parecem fazer parte da paginação, mas são infraestrutura pra features futuras:
| Item | Faz parte? | Quando vem |
|---|---|---|
Método resetPage() | Não | Tabs de status |
Constante RESET_PAGE | Não | Tabs de status |
Bloco consumer if (isSet(RESET_PAGE)) no lView() | Não | Tabs de status |
Inner class ChangePage | Não | Nunca — reaproveitamos Sort |
Registro no Manager | Não | Nunca — Sort já está registrado |
Por que resetPage() não entrou aqui: o .putInteger(PAGE, 0) da Mudança 4 já resolve o único caso da paginação — reset na busca rápida. O resetPage() vira útil quando outras actions precisam resetar página sem conseguir injetar o parâmetro no link: tabs (TabAtivos.execute()), botões "Aplicar Filtros" / "Limpar Filtros", etc.
Adicionar o helper agora, sem ter quem chame, deixaria código morto no projeto — o bloco consumer nunca dispararia, o método nunca seria chamado. Memória do curso: cada episódio só adiciona o que o escopo exige. O que vem a seguir adiciona o que a seguir exige.
Por que ChangePage não existe: nas primeiras versões de listas paginadas do XT, havia uma inner class específica pra trocar de página. Evoluiu pra reaproveitar o Sort — que já atualiza header + body + navegação. ChangePage seria duplicação. O atalho é .setPageAction(url(Sort.class)).
| Arquivo | Tipo | Edição |
|---|---|---|
| DepartamentoProdutoList | editar | 4 mudanças — .putInteger(PAGE, 0) no onkeyup, .addCenter(nav) no br(), .setPageAction + .setPage na chain do lv, e setPageSize(9999) → setPageSize(50) |
| DepartamentoManager | — | Nada — Sort já estava registrado desde a feature de ordenação |
| DepartamentoProdutoBean | — | Nada |
| DepartamentoProdutoDAO | — | Nada — o LIMIT / OFFSET é gerenciado pelo ListView automaticamente via lv.getData() |
1 arquivo editado, nenhuma mudança no banco, nenhum import novo, nenhum arquivo Java novo. Resultado: lista dividida em páginas de 50 registros, navegador "Anterior / Próxima" no rodapé, e busca rápida voltando à página 1 a cada pesquisa.