Sort — Ordenação por título de coluna editar arquivo

0:00 / 0:00

Clicar no título de uma coluna ordena a lista por aquele campo. Clicar de novo inverte a ordem (ASC ↔ DESC). Três peças: a inner class Sort que atualiza o body da lista, o setSortAct que liga o mecanismo e o setOrderBy em cada coluna que indica qual campo SQL aquela coluna representa.

Dois arquivos editados, nenhum arquivo novo. Nenhum import novo — tudo o que o Sort precisa já existia desde o CRUD READ.

CÓDIGO COMPLETO — DepartamentoProdutoList
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.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 {

    @Override
    public Effect execute() throws Exception {
        render();
        return new Response();
    }

    public void render() throws Exception {
        // ... (sem alteração)
        XtPage page = new XtPage(getManager());
        if (isAjaxCall()) {
            update(JasapPage.DIV_WINDOW, page.content(window().toHtml()));
        } else {
            page.getTable()
                    .setBorder(4)
                    .rowC("100%")
                    .setContent(page.content(window().toHtml()))
                    .setStyle(ui().stretchBorder());
            page.setWinTitle("Produtos");
            getOutput().write(this, page);
        }
    }

    public Table window() throws Exception {
        // ... (sem alteração)
        Table w = new Table(getManager()).setSize("100%", "100%");
        w.rowC("99%", JasapPage.DIV_WSPACE, lView());
        w.rowC("1%",  null, ui().line());
        w.rowC("1%",  JasapPage.DIV_BOTTOM, br());
        w.rowC("1%",  null, ui().line());
        return w;
    }

    public Bar br() throws Exception {
        // ... (sem alteração)
        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(DepartamentoProdutoList.class))));

        return ui().bar()
                .addRight(cmd_novo);
    }

    private ListView lv = null;
    public ListView lView() throws Exception {
        if (lv == null) {
            lv = ui().lView();

            // ADICIONAR
            String orderBy = getSession().getString(LIST.concat(lv.getORDER_BY()), getInput());
            if (orderBy == null) orderBy = DepartamentoProdutoBean.NOME_PRODUTO;
            lv.setSortAct(url(Sort.class))
              .setOrderBy(orderBy)
              .setSort(getSession().getString(LIST.concat(lv.getSORT()), getInput()))
              .ajax();
            // FIM

            lv.setPageSize(9999);

            lv.setFiltro(getFiltro());
            getFactory().departamento().proModel().daoList(lv.getData());

            // ADICIONAR
            ListColumn col_nome = lv.newColumn("Nome").setOrderBy(DepartamentoProdutoBean.NOME_PRODUTO).setWidth(220).setPadding(";padding:10 8 10 8;");
            ListColumn col_vl   = lv.newColumn("Valor").setOrderBy(DepartamentoProdutoBean.VL_PRODUTO).setWidth(100).setPadding(";padding:10 8 10 8;").alignCenter();
            ListColumn col_qtd  = lv.newColumn("Qtd").setOrderBy(DepartamentoProdutoBean.QTD_PRODUTO).setWidth(80).setPadding(";padding:10 8 10 8;").alignCenter();
            ListColumn col_obs  = lv.newColumn("Observação").setOrderBy(DepartamentoProdutoBean.OBS_PRODUTO).setPadding(";padding:10 8 10 8;");
            // FIM
            ListColumn col_del  = lv.newColumn("").setWidth(50).setPadding(";padding:6 4 6 4;").alignCenter();

            while (lv.hasNext()) {
                DepartamentoProdutoBean bean = (DepartamentoProdutoBean) lv.next();
                ListLine line = lv.createLine();
                line.setOnclick(link(DepartamentoProdutoForm.ShowUpdate.class)
                        .putInteger(DepartamentoProdutoBean.ID_PRODUTO, bean.getId_produto())
                        .modal(new ModalConfig().setWidth("750").setHeight("570")
                                .setOnCloseURL(url(DepartamentoProdutoList.class))));
                col_nome.setContent(bean.getNome_produto());
                col_vl.setContent(bean.getVl_produto());
                col_qtd.setContent(bean.getQtd_produto());
                col_obs.setContent(bean.getObs_produto());
                col_del.setHtmlData(new IconButton("trash")
                        .setColor("#d9534f")
                        .setTitle("Excluir")
                        .setOnclick(link(DeleteFromList.class).putInteger(DepartamentoProdutoBean.ID_PRODUTO, bean.getId_produto()).ajax())
                        .toHtml());
                lv.addLine(line);
            }
        }
        return lv;
    }

    // ... getFiltro() (sem alteração)

    // ADICIONAR
    public static class Sort extends DepartamentoProdutoList {
        @Override
        public Effect execute() throws Exception {
            update(lView().getDIV_HEADER(), lView().getHeader());
            update(lView().getDIV_BODY(), lView().getBody());
            update(lView().getDIV_NAVIGATE(), lView().getNavForm());
            return new Response();
        }
    }
    // FIM

    // ... DeleteFromList (sem alteração)

    public static final String LIST         = ROOT.concat("__LIST/");
    public static final String FILTRO       = LIST.concat("__FILTRO");
    public static final String CONFIRM_LIST = LIST.concat("__CONFIRM_LIST");

}

Mudanças nesta sequência: bloco setSortAct (6 linhas), .setOrderBy() nas 4 colunas de dados, e a inner class Sort (9 linhas). Nenhum import novo. As linhas verdes são as adições.

CÓDIGO COMPLETO — DepartamentoManager
package br.xt.app.departamento;

import br.xt.app.departamento.produto.DepartamentoProdutoForm;
import br.xt.app.departamento.produto.DepartamentoProdutoList;
import br.xt.app.painel.PnlManager;

public class DepartamentoManager extends PnlManager {

    public static final String F_ACESSO_MODULO = "XT.PAINEL_CONTROLE.ACESSO_MODULO.DEPARTAMENTO";

    @Override
    public void config() throws Exception {

        regFun("PAINEL DE CONTROLE", "Acesso ao Módulo", "DEPARTAMENTO", F_ACESSO_MODULO);

        regAction(DepartamentoHome.class);
        regAction(DepartamentoHome.Title.class);
        regAction(DepartamentoHome.MenuItem.class);
        regAction(DepartamentoHome.MenuInicial.class);

        regAction(DepartamentoProdutoList.class);
        // ADICIONAR
        regAction(DepartamentoProdutoList.Sort.class);
        // FIM
        regAction(DepartamentoProdutoList.DeleteFromList.class);

        regAction(DepartamentoProdutoForm.class);
        regAction(DepartamentoProdutoForm.ShowInsert.class);
        regAction(DepartamentoProdutoForm.ShowUpdate.class);
        regAction(DepartamentoProdutoForm.Insert.class);
        regAction(DepartamentoProdutoForm.Update.class);
        regAction(DepartamentoProdutoForm.Cancelar.class);

    }
}

Mudança nesta sequência: 1 linha regAction nova (verde). Nenhum import novo — o import do DepartamentoProdutoList já existia.

O mecanismo — 3 peças que fazem o Sort funcionar

Ordenar por coluna exige a cooperação de três partes. Se qualquer uma faltar, nada acontece:

PeçaOndePapel
setSortActlView()Diz ao ListView qual action chamar quando um header for clicado
setOrderBycada ListColumnIndica qual campo SQL aquela coluna representa — habilita o header clicável
Inner class Sortfim da classeA action de destino — re-renderiza header + body + navegação com a nova ordem

O framework faz o resto: gera o HTML do header como <a> clicável, monta a URL com o campo e a direção (ASC/DESC), e usa a sessão pra lembrar a última escolha do usuário.

Peça 1 — setSortAct + leitura da sessão
String orderBy = getSession().getString(LIST.concat(lv.getORDER_BY()), getInput());
if (orderBy == null) orderBy = DepartamentoProdutoBean.NOME_PRODUTO;
lv.setSortAct(url(Sort.class))
  .setOrderBy(orderBy)
  .setSort(getSession().getString(LIST.concat(lv.getSORT()), getInput()))
  .ajax();

Decompondo linha por linha:

  • getSession().getString(LIST.concat(lv.getORDER_BY()), getInput()) — lê da sessão qual campo está ordenado no momento. Na primeira vez é null (ninguém clicou ainda).
  • if (orderBy == null) orderBy = DepartamentoProdutoBean.NOME_PRODUTO — se for a primeira vez, ordena por nome como padrão.
  • setSortAct(url(Sort.class)) — registra a action Sort como destino dos cliques nos headers. Sem isso, os headers são texto puro, não links.
  • .setOrderBy(orderBy) — informa ao ListView qual campo está ativo agora. O framework usa isso pra colocar a setinha (▲/▼) no header correto.
  • .setSort(...) — lê da sessão a direção atual (ASC ou DESC).
  • .ajax() — os cliques nos headers serão requisições AJAX (sem recarregar a página inteira).
Peça 2 — setOrderBy nas colunas
ListColumn col_nome = lv.newColumn("Nome").setOrderBy(DepartamentoProdutoBean.NOME_PRODUTO).setWidth(220)...;
ListColumn col_vl   = lv.newColumn("Valor").setOrderBy(DepartamentoProdutoBean.VL_PRODUTO).setWidth(100)...;
ListColumn col_qtd  = lv.newColumn("Qtd").setOrderBy(DepartamentoProdutoBean.QTD_PRODUTO).setWidth(80)...;
ListColumn col_obs  = lv.newColumn("Observação").setOrderBy(DepartamentoProdutoBean.OBS_PRODUTO)...;

Cada .setOrderBy() recebe a constante do Bean que corresponde ao nome da coluna no banco (ex: NOME_PRODUTO = "nome_produto"). O framework usa essa constante pra montar o ORDER BY do SQL.

A coluna da lixeirinha (col_del) não tem setOrderBy — ela não representa nenhum campo do banco. Só colunas com dados ordenáveis recebem esse método.

ColunasetOrderByConstante do Bean
NomesimNOME_PRODUTO
ValorsimVL_PRODUTO
QtdsimQTD_PRODUTO
ObservaçãosimOBS_PRODUTO
(lixeirinha)não
Peça 3 — Inner class Sort
public static class Sort extends DepartamentoProdutoList {
    @Override
    public Effect execute() throws Exception {
        update(lView().getDIV_HEADER(), lView().getHeader());
        update(lView().getDIV_BODY(), lView().getBody());
        update(lView().getDIV_NAVIGATE(), lView().getNavForm());
        return new Response();
    }
}

A action chamada quando o usuário clica num header. Faz 3 updates parciais via AJAX — troca só o que mudou, sem recarregar a página:

  • update(getDIV_HEADER(), getHeader()) — re-renderiza os títulos das colunas. Necessário porque a setinha (▲/▼) muda de coluna e/ou de direção.
  • update(getDIV_BODY(), getBody()) — re-renderiza as linhas da tabela na nova ordem.
  • update(getDIV_NAVIGATE(), getNavForm()) — re-renderiza a área de navegação (paginação). Mesmo sem paginação ativa, o framework espera esse update pra manter o estado consistente.

Como Sort estende DepartamentoProdutoList, ela herda tudo: lView(), getFiltro(), constantes. Quando lView() roda dentro da Sort, a sessão já contém o campo e direção novos (o framework gravou antes de chamar o execute()), então o ORDER BY do SQL vem atualizado automaticamente.

regAction — registrar no Manager
regAction(DepartamentoProdutoList.Sort.class);

Uma linha no DepartamentoManager.config(). Toda inner class que é uma action precisa de regAction — sem isso, o framework não sabe que ela existe e o clique no header retorna "setor não encontrado".

A linha fica logo depois do regAction(DepartamentoProdutoList.class), antes do DeleteFromList. Organização lógica: List → Sort → DeleteFromList.

O que NÃO faz parte do Sort

É comum confundir o Sort com features que usam a mesma action Sort como refresh. Mas o exercício de ordenação é só isso — 3 peças pra headers clicáveis:

FeatureFaz parte do Sort?Porquê
Trocar setOnCloseURLurl(Sort.class)NãoOtimização: usa Sort como refresh geral, mas Sort funciona sem isso
setPageAction / setPageNãoPaginação — feature separada
QuickSearchNãoBusca rápida — feature separada (mas também usa Sort pra recarregar)
insert_chkNãoContinuar inserindo — usa Sort como refresh, mas é independente

Essas features vão ser adicionadas nos próximos episódios. O Sort que implementamos aqui é a base — ele faz uma coisa só e faz bem: headers clicáveis com ordenação ASC/DESC.

Resumo — o que mudou
ArquivoTipoEdição
DepartamentoProdutoListeditarBloco setSortAct (6 linhas) + .setOrderBy() em 4 colunas + inner class Sort (9 linhas)
DepartamentoManagereditar1 regAction(Sort.class)

2 arquivos editados, nenhum arquivo novo, nenhum import novo. Resultado: clicar no título de qualquer coluna ordena a lista por aquele campo, clicar de novo inverte a direção.