DepartamentoProdutoForm novo arquivo

0:00 / 0:00

Arquivo central e mais extenso da sequência CREATE. O DepartamentoProdutoForm é o formulário de cadastro de produtos — a primeira tela do módulo que RECEBE dados do usuário (até agora só exibíamos dados na List). Formulário completo com 4 campos, botões Salvar e Cancelar, e 3 inner classes: ShowInsert, Insert (com TransactionFilter) e Cancelar.

CÓDIGO COMPLETO
package br.xt.app.departamento.produto;

import br.jasap.core.Effect;
import br.jasap.effect.Response;
import br.jasap.gui.Bar;
import br.jasap.gui.Button;
import br.jasap.gui.JasapPage;
import br.jasap.gui.Table;
import br.jasap.gui.form.Form;
import br.jasap.gui.form.Text;
import br.jasap.gui.form.Textarea;
import br.jasap.util.Js;
import br.jasap.util.filters.TransactionFilter;
import br.xt.acore.view.XtPage;

public class DepartamentoProdutoForm extends DepartamentoProdutoAction {

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

    public static class ShowInsert extends DepartamentoProdutoForm {
        @Override
        public Effect execute() throws Exception {
            setInsert(FORM);
            getSession().remove(ROOT.concat(DepartamentoProdutoBean.ID_PRODUTO));
            render();
            return new Response();
        }
    }

    public static class Insert extends DepartamentoProdutoAction {
        public Insert() { super.getFilters().add(new TransactionFilter()); }
        @Override
        public Effect execute() throws Exception {
            getFactory().departamento().proModel().insert(proBean());
            evalParent(Js.CLOSE_SUB_WINDOWS);
            return new Response();
        }
    }

    public static class Cancelar extends DepartamentoProdutoForm {
        @Override
        public Effect execute() throws Exception {
            evalParent(Js.CLOSE_SUB_WINDOWS);
            return new Response();
        }
    }

    public void render() throws Exception {
        XtPage page = new XtPage(getManager());
        if (isAjaxCall()) {
            update(JasapPage.DIV_WINDOW, page.content(window().toHtml()));
            eval(Js.CLOSE_SUB_WINDOWS);
        } else {
            page.getTable().setBorder(4).rowC("100%")
                    .setContent(page.content(window().toHtml()))
                    .setStyle(ui().modalBorder());
            page.setWinTitle("Produto");
            getOutput().write(this, page);
        }
    }

    public Table window() throws Exception {
        Table w = new Table(getManager()).setSize("100%", "100%");
        w.rowC("1%",  JasapPage.DIV_TITLE, ui().title("CADASTRO DE PRODUTO"));
        w.rowC("1%",  null, ui().line());
        w.rowC("auto").setId(JasapPage.DIV_MASTER).setContent(form()).table();
        w.rowC("1%",  null, ui().line());
        w.rowC("1%",  JasapPage.DIV_BOTTOM, br());
        return w;
    }

    protected Bar br() throws Exception {
        Button salvar = ui().button("  Salvar  ")
                .setCss("btn btn-success btn-lg").setNoSize()
                .setOnClick(submit(Insert.class).validate().toHtml())
                .setVisible(isInsert(FORM));

        Button cancelar = ui().button("  Cancelar  ")
                .setCss("btn btn-primary btn-lg").setNoSize()
                .setOnClick(link(Cancelar.class).ajax());

        Bar bar = ui().bar();
        bar.addRight(cancelar);
        bar.addRight(" ");
        bar.addRight(salvar);
        return bar;
    }

    public String form() throws Exception {
        Form frm = ui().form();
        frm.addHidden(DepartamentoProdutoBean.ID_PRODUTO, proBean().getId_produto());

        frm.line().add(nome_produto(), "150");
        frm.line().add(vl_produto(),   "150");
        frm.line().add(qtd_produto(),  "150");
        frm.line().add(obs_produto(),  "150");

        return frm.getTable().toHtml();
    }

    protected Text nome_produto = null;
    public Text nome_produto() throws Exception {
        if (nome_produto == null) {
            nome_produto = ui().text(DepartamentoProdutoBean.NOME_PRODUTO)
                    .setLabel("Nome")
                    .setStyle("width:350;height:25")
                    .setMaxlength(100)
                    .setRequired(true)
                    .setValue(proBean().getNome_produto());
        }
        return nome_produto;
    }

    protected Text vl_produto = null;
    public Text vl_produto() throws Exception {
        if (vl_produto == null) {
            vl_produto = ui().text(DepartamentoProdutoBean.VL_PRODUTO)
                    .setLabel("Valor")
                    .setStyle("width:120;text-align:right;height:25")
                    .setValue(proBean().getVl_produto());
        }
        return vl_produto;
    }

    protected Text qtd_produto = null;
    public Text qtd_produto() throws Exception {
        if (qtd_produto == null) {
            qtd_produto = ui().text(DepartamentoProdutoBean.QTD_PRODUTO)
                    .setLabel("Quantidade")
                    .setStyle("width:80;text-align:right;height:25")
                    .setValue(proBean().getQtd_produto());
        }
        return qtd_produto;
    }

    protected Textarea obs_produto = null;
    public Textarea obs_produto() throws Exception {
        if (obs_produto == null) {
            obs_produto = ui().textArea(DepartamentoProdutoBean.OBS_PRODUTO, "width:530;height:80")
                    .setLabel("Observações")
                    .setValue(proBean().getObs_produto());
        }
        return obs_produto;
    }

    public static String FORM = ROOT.concat("__FORM");

}

Arquivo novo — todo o código é desta sequência (CRUD CREATE).

Estrutura visual — render, window, form, br, campos
0:00 / 0:00

render() — dois caminhos de entrega

O render() do Form tem dois branches — o mesmo padrão da List, mas com diferenças importantes:

Branch Ajax (isAjaxCall)Branch primeira carga
QuandoForm já está aberto, atualiza conteúdoForm abrindo pela primeira vez
Fazupdate(DIV_WINDOW, ...) + eval(CLOSE_SUB_WINDOWS)getOutput().write(this, page)
BordamodalBorder() (modal flutuante)

Comparação com a List: a List usa stretchBorder() (tela cheia), o Form usa modalBorder() (janela flutuante). É isso que faz o formulário abrir como popup dentro do iframe da lista.

O eval(Js.CLOSE_SUB_WINDOWS) no branch Ajax fecha qualquer sub-janela que estivesse aberta antes de atualizar — evita janelas órfãs empilhadas.

window() — a montagem vertical

O window() monta a estrutura vertical do formulário, de cima pra baixo:

w.rowC("1%",  JasapPage.DIV_TITLE,  ui().title("CADASTRO DE PRODUTO"));  // título escuro
w.rowC("1%",  null,                 ui().line());                         // separador
w.rowC("auto").setId(JasapPage.DIV_MASTER).setContent(form()).table();    // campos
w.rowC("1%",  null,                 ui().line());                         // separador
w.rowC("1%",  JasapPage.DIV_BOTTOM, br());                               // botões

Cada rowC é uma linha da tabela vertical. O "auto" na linha do form() faz ela ocupar o espaço restante — os campos ficam centralizados entre o título e os botões.

Os IDs (DIV_TITLE, DIV_MASTER, DIV_BOTTOM) são constantes do JasapPage. São usados quando o framework precisa atualizar parcialmente uma dessas áreas via Ajax.

form() — campos e hidden

O método form() monta o formulário HTML usando a API ui().form():

Form frm = ui().form();
frm.addHidden(DepartamentoProdutoBean.ID_PRODUTO, proBean().getId_produto());

frm.line().add(nome_produto(), "150");
frm.line().add(vl_produto(),   "150");
frm.line().add(qtd_produto(),  "150");
frm.line().add(obs_produto(),  "150");

addHidden — campo invisível que viaja com o submit. Na inserção, o ID é null (o banco gera via serial). Na edição futura (UPDATE), terá o ID do registro sendo editado.

frm.line().add(campo, "150") — cada chamada cria uma linha no form com o label na largura indicada (150px) e o campo do lado. O "150" é a largura da coluna do label, não do campo.

Campos — Text e Textarea

Cada campo segue o padrão lazy (cria uma vez, reutiliza). Exemplo do nome_produto:

nome_produto = ui().text(DepartamentoProdutoBean.NOME_PRODUTO)  // name HTML = "nome_produto"
        .setLabel("Nome")                                       // label visível
        .setStyle("width:350;height:25")                        // dimensões do input
        .setMaxlength(100)                                      // limite de caracteres
        .setRequired(true)                                      // validação obrigatória
        .setValue(proBean().getNome_produto());                  // valor atual do Bean

O name do campo (NOME_PRODUTO = "nome_produto") é o que conecta o HTML ao Java. Quando o formulário é submetido, o framework faz match: nome_produto do input → setNome_produto() do Bean.

setRequired(true) marca o campo como obrigatório — o submit().validate() do botão Salvar valida no browser antes de enviar. Se estiver vazio, mostra aviso e NÃO envia o request.

CampoComponenteCaracterísticas
NomeText350px, obrigatório, max 100 chars
ValorText120px, alinhado à direita (numérico)
QuantidadeText80px, alinhado à direita (numérico)
ObservaçõesTextarea530×80px, multilinha

br() — barra de botões

submit(Insert.class).validate() — ao clicar Salvar, o browser primeiro valida os campos obrigatórios (setRequired). Se passar, faz submit dos dados do formulário pro setor Insert. Diferente do link() que só manda uma URL, o submit() coleta TODOS os campos do form e manda junto.

isInsert(FORM) — o Salvar só aparece quando o form está em modo inserção. Na sequência UPDATE, vai ter um segundo botão Salvar visível só no modo edição.

link(Cancelar.class).ajax() — cancelar não precisa de submit (não coleta dados), só manda a URL do setor Cancelar.

Inner classes — ShowInsert, Insert, Cancelar
0:00 / 0:00

ShowInsert — preparar o terreno

Executada quando o usuário clica "Novo Registro" na lista. Faz duas coisas antes de montar a tela:

setInsert(FORM);
getSession().remove(ROOT.concat(DepartamentoProdutoBean.ID_PRODUTO));

setInsert(FORM) — liga a flag "modo inserção" na sessão. É essa flag que o isInsert(FORM) verifica pra decidir quais botões mostrar. Sem ela, o botão Salvar não aparece.

getSession().remove(...) — limpa qualquer ID de produto que estivesse na sessão de uma operação anterior. Garante que o form abre limpo, sem resquícios.

Depois chama render() — que monta o formulário com campos em branco (porque proBean() cria um Bean novo, sem dados).

Herda de DepartamentoProdutoForm — por isso tem acesso a render(), window(), form(), br(), e todos os campos. É como um "modo de operação" do form.

Insert — gravar com transação

Executada quando o usuário clica "Salvar". Duas coisas críticas:

1. TransactionFilter no construtor:

public Insert() { super.getFilters().add(new TransactionFilter()); }

O TransactionFilter envolve o execute() numa transação de banco. Se tudo der certo → COMMIT. Se qualquer Exception acontecer → ROLLBACK automático. É obrigatório em qualquer action que altera dados no banco.

O filter é adicionado no construtor (não no execute). Isso porque o framework monta a cadeia de filtros ANTES de chamar execute() — os filtros precisam estar registrados na hora da construção.

2. O execute() em si:

getFactory().departamento().proModel().insert(proBean());
evalParent(Js.CLOSE_SUB_WINDOWS);

proBean() já vem preenchido com os dados do formulário (via getInstance que fez o match automático). O insert(proBean()) desce pelas camadas: Model → DAO → banco.

evalParent(Js.CLOSE_SUB_WINDOWS) — manda ordem pro browser fechar o modal. O Parent é a janela da lista que está por trás. Ao fechar, o setOnCloseURL que a List configurou dispara automaticamente e a lista atualiza.

Herda de DepartamentoProdutoAction (não de Form) — porque não precisa de render(), window(), nem de nenhum campo visual. Só precisa do proBean() e da conexão com o banco.

Cancelar — fechar sem gravar

A mais simples das três. Só fecha o modal — não grava nada, não altera sessão. O evalParent manda a ordem pra janela pai (lista) fechar todas as sub-janelas.

submit vs link — quando usar cada um

submit(Class)link(Class)
Coleta dados?SIM — pega todos os campos do formNÃO — só manda a URL
Valida?SIM (com .validate())NÃO
Usado emSalvar (precisa dos dados)Cancelar, navegação, refresh
No HTMLGera <form> submit com POSTGera chamada Ajax GET

Regra prática: se a action precisa dos dados do formulário (insert, update), usa submit. Se não precisa (cancelar, abrir modal, refresh), usa link.

Fluxo do usuário — do clique ao banco

O ciclo completo de criar um produto envolve 3 requests (3 agentes distintos):

  1. Clique em "Novo Registro" na List → request chega → agente vai pro setor ShowInsert → seta modo inserção → monta o formulário vazio → response volta pro browser → modal abre com campos em branco
  2. Usuário preenche e clica "Salvar" → request chega com os dados dos campos → agente vai pro setor InsertTransactionFilter abre transação → proBean() preenche o Bean automaticamente → model.insert(bean) grava → transação fecha → response manda fechar o modal
  3. Modal fechasetOnCloseURL dispara request automático → agente re-renderiza a List → usuário vê o produto novo na tabela

Cada um desses requests é um agente novo, cartão novo, tudo do zero. O encadeamento lógico vive no código (ShowInsert prepara, Insert grava, List atualiza), não na memória de nenhum agente.

O que o Form NÃO tem nesta sequência

Este é o Form mínimo — só o necessário pro INSERT funcionar. Funcionalidades que virão em sequências futuras:

  • ShowUpdate / Update / Delete — editar e excluir pelo formulário (sequência CRUD UPDATE)
  • insert_chk — checkbox "Continuar inserindo" (episódio próprio)
  • Quick Search / Sort / Tabs / Paginação — na List (episódios próprios)

Cada feature ganha seu próprio episódio com foco pedagógico claro. Nesta sequência, o objetivo é um: abrir o form, preencher, salvar, ver na lista.