quarta-feira, 10 de novembro de 2010

Modal como parte do formulário

Tenho um determinado formulário que pode (ou não) necessitar de mais informações. Então, o botão salvar é:

<a4j:commandButton id="btnSalvar" value="Salvar"
onclick="if(#{mMeuBean.mostrarPopup}){ Richfaces.showModalPanel('meuPopup'); return false; }"
action="#{mMeuBean.salvar}" reRender="meuPopup" />

Este popup, o 'meuPopup' é o rich modalPanel com informações ADICIONAIS. Na verdade, é uma extensão do formulário original, nada mais que isso.

<rich:modalPanel id="popupPublicarSetups" minWidth="400" minHeight="420" resizeable="true">
        <f:facet name="header">
<h:outputText value="Titulo"></h:outputText>
</f:facet>
<f:facet name="controls">
</f:facet>
<h:form id="outroForm" styleClass="form">
<h:outputText value="Info adicional:" />
        <h:inputText value="#{meuBean.info}" />

<h:panelGroup styleClass="divBotoes" layout="block">
<a4j:commandButton value="OK" styleClass="botoes" limitToList="true"
      action="#{mMeuBean.salvar}"
      oncomplete="Richfaces.hideModalPanel('meuPopup');" reRender="msgErros" />
<a4j:commandButton value="Cancelar" styleClass="botoes"
ajaxSingle="true" limitToList="true"
onclick="Richfaces.hideModalPanel('meuPopup'); " />
</h:panelGroup>
    </h:form>
</rich:modalPanel>


O grande problema era: se eu colocasse no mesmo form, o valor dos campos no popup não eram enviados; se colocasse em forms separados, o conteúdo original não era enviado. Pela documentação do modalPanel, não se deveria usar o mesmo form para modal e a página:
http://docs.jboss.org/richfaces/latest_3_3_X/en/devguide/html_single/index.html#rich_modalPanel.

De qualquer jeito eu tentei colocar no mesmo form, tentei fazer a4j:form, tentei ajaxSubmit nele, e nada se comportava como eu desejava. Não me restou muita coisa além de fazer dois submits. Eu poderia enviar antes de mostrar o popup ou fazer dois submits simultâneos no 'OK' do popup; preferi a última.

Inclui um botão sem 'onclick' no formulario original:
<a4j:commandButton id="btnSalvarHidden" value="" style="display:none"
action="#{mMeuBean.salvar}" styleClass="botoes" />

E modifiquei o botão do popup para clicar nele:
<a4j:commandButton value="OK" styleClass="botoes" limitToList="true"
action="#{mMeuBean.salvarPops}"
oncomplete="jQuery('[id$=btnSalvarHidden]').click();Richfaces.hideModalPanel('meuPopup'); "

reRender="msgErros" />

A ação 'salvarPops' simplesmente retorna null.

sexta-feira, 15 de outubro de 2010

Jquery, value e val

Estava buscando todos os inputs de classe 'obrigatorio' que estivessem vazios.

var vaziosInput = jQuery('div[id$=divSetup] input[value=""].obrigatorio').length;

Funciona muito bem no IE. Mas, no chrome e no firefox, sempre acusava que todos os campos estavam vazios.
Acontece que no Chrome e FF, o value continua vazio, mas o que é preenchido é o conteúdo do input.

Foi então que tentei assim:
            
var vaziosInput = 0;
jQuery('div[id$=divSetup]').find('input.obrigatorio').each(function(){
      if (jQuery(this).val() == ""){
           vaziosInput ++;
       }
});
Funciona. Mas não é tão bonito.



Update: http://bugs.jquery.com/ticket/7128
É um bug desta versão do Jquery, deve ser corrigido em breve.

Mas o código abaixo funciona bem:
jQuery('div[id$=divSetup] input.obrigatorio').filter('[value=""]').length;

quinta-feira, 30 de setembro de 2010

Duas instâncias do mesmo tomcat como serviços

Continuando minha saga anterior, agora eu precisava que a mesma versão do tomcat tivesse duas instâncias, uma para homologação e outra para produção.

Novamente, a versão instalada do tomcat tem que ser aquela com vários .jars, .bats no 'bin/', se não for o caso é só copiar os arquivos faltantes.

Primeiro pegue o arquivo tomcat6w.exe e faça uma cópia, por exemplo, tomcat6Homologacaow.exe (lembre-se de não ter nem '_' nem '-'). No prompt:
$service.bat install tomcat6Homologacao
Isso deve ter sucesso. Então, faça uma cópia de todo o diretório do tomcat para qualquer outro lugar. Desta cópia, apague a pasta bin, os conteúdos das pastas logs, temp, webapps, work. Edite seu server.xml para apontar para outras portas. Vá no tomcat6Homologacaow.exe, e nos parâmetros mude o CATALINA_BASE para a cópia recém-feita.

Pronto, vc deve ter dois serviços agora, um para cada tomcat! Ambos com o mesmo binário.

quarta-feira, 29 de setembro de 2010

Diferentes versões de tomcat como serviços

Miguxo @robertsonso me ajudou hoje a instalar um novo tomcat na mesma máquina.

Pegue a versão .zip do tomcat. Provavelmente vc quer a 32-bit Windows zip ou 64-bit Windows zip, que tem os wrappers e algumas DLLs nativas, uns .bat's.

Abra este zip no buraco que você quiser. Vá até o diretório bin e edite o tomcat6w.exe para algo como qualquerCoisaQueVcQueiraw.exe. Pelo prompt, vá até o diretório bin e execute:
            $ service install qualquerCoisaQueVcQueira
Importante notar que 'qualquerCoisaQueVcQueira' não pode ter - ou _, senão acontece um erro como 'Alpha sei lá das quantas'.

No diretório conf, edite as três portas para alguma não utilizada. PRONTO!
Ainda vou descobrir como fazer para rodar várias instâncias do mesmo tomcat.

Group by

Tem uma coisa diferente nessa vida é o group by do sql.

Era uma vez duas entidades:

@Entity
public class Entidade1{
      @OneToMany
       private List<entidade2> lista;
}

@Entity
public class Entidade2{
       private String desc;
    
       @ManyToOne
       private Entidade1 entidade1;
       //outras coisas
}


Não carece de muita imaginação para perceber como ficavam essas duas tabelas, usando JoinColumn para o OneToMany. A pesquisa era: uma lista de 'Entidade1' a partir de um trecho de 'desc'.

A primeira tentativa era com groupBy. O grande problema ali é entender que o group by não retorna nada que não tenha sido agrupado, só podemos retornar o que está no group by ou funções agregadoras.

Então, o sql:
select e2.desc from Entidade2 e2 where e2.desc like '%algumaCoisa%' group by e2.id_entidade1
simplesmente não faz sentido e não funciona. 'Desc' é diferente para cada registro, como o BD vai adivinhar o que ele tem que retornar? Ele não vai concatenar tudo e jogar numa coluna só, não. Ele não vai pegar o primeiro, tem que ter garantias que é igual para todos os caras do grupo.

Por outro lado, uma opção seria (não, eu não gosto de usar join):
select * from Entidade1 e1, Entidade2 e2
where e2.entidade1_id = e1.id
and e1.id in
(
     select e3.entidade1_id from Entidade2 e3
     where e3.desc like '%algumaCoisa%'
) order by e1.id, e2.desc
O ponto é que isto retorna um registro para cada Entidade2 que existem na Entidade1 relacionadas, então na interface isso teria que ser tratado de alguma forma. Chato, mas às vezes necessário.
Importante notar que o SQL acima tem 'in' - o que tem sido bem lento na prática para mim - então seria bom tentar transformá-lo em um 'exists'.

Agora, já que estamos no hibernate e somos gente grande, não custa um HQL de gente:
select distinct e2.entidade1 where e2.desc like  '%algumaCoisa%'.

Se fosse Criteria, teria que fazer aquele DISTINCT ROOT para voltar só uma Entidade1 por grupo de Entidade2's.
 

sábado, 25 de setembro de 2010

Update com subquery

Quando um sistema já está em produção, temos que tomar alguns cuidados no transporte dos dados antigos para que continuem funcionando.

No exemplo, criamos o campo 'placaMae' em pedido, e buscamos a partir de codigoBios.getPlacaMae()

update Pedido set placa_mae_id =
(select pm.id from Codigo_Bios c, Placa_mae pm, Pedido p
where c.id = p.codigo_bios_id
and c.placa_mae_id = pm.id
and p.id_pedido = Pedido.id_pedido)

terça-feira, 14 de setembro de 2010

quinta-feira, 9 de setembro de 2010

JSF e os campos disabled

Meu projeto é JSF (sun) + RichFaces + Jquery para a camada de View. E olha só o que o destino me reservou.

Eu tinha os seguintes campos:
<h:inputText value="#{mBeanMeuBean.atributo1}" style="botoesChatos" id="meuCampo1" disabled="#{mBeanMeuBean.edicao}"/>
<a4j:commandLink id="btnEditarCampo1" rendered="#{mBeanMeuBean.edicao}" oncomplete="liberarEdicao('meuCampo1', this);" value="Habilita Campo 1"/>


<h:inputText value="#{mBeanMeuBean.atributo2}" style="botoesChatos" id="meuCampo2" disabled="#{mBeanMeuBean.edicao}"/>
<a4j:commandLink id="btnEditarCampo2" rendered="#{mBeanMeuBean.edicao}" oncomplete="liberarEdicao('meuCampo2', this);" value="Habilita Campo 2"/>


Isto é, são dois campos inicialmente desabilitados, em que é necessário clicar num link antes de abrí-los para edição. À prova de usuário distraído.
O javascript 'liberarEdicao' se limita a retirar o atributo 'disabled' do campo via jQuery e sumir com o link.

Simplesmente, mesmo clicando no link próprio, mexendo no campo, mandando para o mBean via a4j:commandButton, a modificação NÃO chegava lá por nada. Como se tivesse um 'immediate=true', o que não tinha. É verdade que esse bean tinha keepAlive, mas o que raios ocorria?

Pensei que fosse alguma coisa no meu salvar, que não estivesse habilitando os campos antes de enviar (já tive este problema no Struts). Lá vou eu colocar no onclick do meu salvar:

onclick="jQuery('.botoesChatos').removeAttr('disabled');"

Mas, pasmém, isso NÃO resolveu. Tirei isso do botão, mas retirei o 'disabled' do h:commandButton e passei para o jquery no onload da página.


jQuery(function() {

if(edicao){
jQuery('.botoesChatos').attr('disabled', 'disabled');
}
});

Agora o problema mudou. Os campos que eu realmente editava/habilitava, chegavam no bean. Se eu editasse apenas um, o outro não era enviado. Aí achei que era o problema de habilitar antes de mandar salvar e, de fato, ao recolocar o onclick que citei acima, resolveu.


D'onde tiro que: ao colocar um campo como 'disabled' direto na tag h:inputText, o JSF *NÃO* vai trazer esse cara de volta nem que vc mude-o por javascript. Colocando este 'desabilitar' via jQuery, vc tem que obrigatoriamente habilitá-lo antes de enviar, senão este campo também não será enviado.

Alguma outra sugestão?

sexta-feira, 27 de agosto de 2010

Hexas

Conversão de long para string hexa:
Long.toHexString(long)

Conversão de string hexa para long:
Long.parseLong(String, 16)

sexta-feira, 20 de agosto de 2010

Como atualizar a tela e também fazer um download? Usando JSF/Richfaces

Hoje eu tive um problema relativamente comum: preciso tanto fazer uma ação (ie, salvar no banco, atualizar tela) e disponibilizar um arquivo gerado dinamicamente para o usuário.

O 'a4j:commandButton' não faz download. Quer dizer, até pode fazer, mas euzinha desconheço como.
Tentei fazer um 'h:commandButton' com 'a4j:support' no onComplete, mas não funciona. Então, recebi a sugestão ao contrário, onde o 'a4j:commandButton' chama o 'h:commandButton'. Funciona que é uma beleza.


<a4j:commandButton id="btnAjax" value="Atualiza a tela e faz download" action="#{mBeanWhateva.fazAcao}"  oncomplete="jQuery('[id$=btnDownload]').click()" reRender="o,que,vc,quiser"/>

<h:commandButton id="btnDownload" value="Faz o Download apenas" action="#{mBeanWhateva.fazDownload}" style="display:none"/>



public String finalizarSetup() {
// faz muita coisa
return null;
}



public String fazDownload() throws Exception {
        ByteArrayOutputStream meusDadosdoRelatorio = gerarRelatorio(); // do the trick
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();
response.setContentType("application/force-download");
response.setHeader("Content-Disposition","attachment; filename=nomeDoArquiv.extensao");
OutputStream sos;
sos = response.getOutputStream();
sos.write(meusDadosdoRelatorio.toByteArray());
sos.flush();
sos.close();
context.responseComplete();

return null;
}

quarta-feira, 18 de agosto de 2010

JConsole no Tomcat

Aprendi hoje.

JConsole é um aplicativo para verificar desempenho, memória, essas coisas. Está dentro do bin do JDK.
Coloque no tomcat:

-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=8004 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false
Rode o JConsole, chame pelo 'Remote Process' servidor:8004 e pronto ;)
Por alguma razão, sem o "=true" do primeiro parâmetro era possível ligar o tomcat, mas dava 'connection refused'.

quarta-feira, 11 de agosto de 2010

Url-pattern no tomcat

Tenho uma aplicação cuja autenticação é feita pelo contexto. A princípio, todas as páginas *.jsp e *,jsf estavam protegidas; todos os arquivos (jsp, imagens, css) estavam dentro da pasta 'pages', dentro de WEB-INF.

Precisei de uma página pública. Pensei com meus botões... 'hum, /pages/*.jsf  deve dar conta'. E quem disse que pode, né? Uma hora para entender o porque. Só pode OU definir a pasta OU definir a extensão, nunca os dois juntos

http://stackoverflow.com/questions/2714726/web-xml-are-url-pattern-tags-relative-to-each-other


In the Web application deployment descriptor, the following syntax is used to define mappings:
  • A string beginning with a ‘/’ character and ending with a ‘/*’ suffix is used for path mapping.
  • A string beginning with a ‘*.’ prefix is used as an extension mapping.
  • A string containing only the ’/’ character indicates the "default" servlet of the application. In this case the servlet path is the request URI minus the context path and the path info is null.
  • All other strings are used for exact matches only


Acreditem, eu xinguei muito. No fim, tive que proteger /pages/cada_pasta e cada um dos JSPs que estava dentro de pages direto. Empenho

Oie

Esse blogue é minha área de transferência, guardando para a posteridade. Uma hora eu arranjo um template decente, daí vai até parecer que trabalho com isso!