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.
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).
render() — dois caminhos de entregaO render() do Form tem dois branches — o mesmo padrão da List, mas com diferenças importantes:
Branch Ajax (isAjaxCall) | Branch primeira carga | |
|---|---|---|
| Quando | Form já está aberto, atualiza conteúdo | Form abrindo pela primeira vez |
| Faz | update(DIV_WINDOW, ...) + eval(CLOSE_SUB_WINDOWS) | getOutput().write(this, page) |
| Borda | — | modalBorder() (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 verticalO 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 hiddenO 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.
Text e TextareaCada 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.
| Campo | Componente | Características |
|---|---|---|
| Nome | Text | 350px, obrigatório, max 100 chars |
| Valor | Text | 120px, alinhado à direita (numérico) |
| Quantidade | Text | 80px, alinhado à direita (numérico) |
| Observações | Textarea | 530×80px, multilinha |
br() — barra de botõessubmit(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.
ShowInsert — preparar o terrenoExecutada 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çãoExecutada 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 gravarA 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 umsubmit(Class) | link(Class) | |
|---|---|---|
| Coleta dados? | SIM — pega todos os campos do form | NÃO — só manda a URL |
| Valida? | SIM (com .validate()) | NÃO |
| Usado em | Salvar (precisa dos dados) | Cancelar, navegação, refresh |
| No HTML | Gera <form> submit com POST | Gera 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.
O ciclo completo de criar um produto envolve 3 requests (3 agentes distintos):
ShowInsert → seta modo inserção → monta o formulário vazio → response volta pro browser → modal abre com campos em brancoInsert → TransactionFilter abre transação → proBean() preenche o Bean automaticamente → model.insert(bean) grava → transação fecha → response manda fechar o modalsetOnCloseURL dispara request automático → agente re-renderiza a List → usuário vê o produto novo na tabelaCada 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.
Este é o Form mínimo — só o necessário pro INSERT funcionar. Funcionalidades que virão em sequências futuras:
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.