terça-feira, 1 de março de 2011

ZK 5.0.6

A nova versão do ZK já está disponível para download aqui.
Como toda versão nova do ZK há sempre introdução de novas funcionalidades. Entre esses recursos novos estão:
  1. Geração de conteúdo customizado para SEO(Search Engine Optimization) onde agora é possível gerar um conteúdo específico que pode ser usado pelos indexadores de busca e transparentes ao usuários.
  2. Efeitos customizados quando mostrar oun esconder um widget atrávez de uma maneira simples sem uso de JavaScript.
Voce pode encontrar mais novidades aqui.

Construção de interfaces

Neste artigo vou continuar a falar sobre alguns fundamentais do ZK, como é feita a construção dos componentes, de que forma é possível fazê-lo e como anexar os componentes a uma página e será abordado também o conceito de Desktop e Page.
Mas antes gostaria de pautar alguns conceitos importantes:
  1.  JSON(JavaScript Object Notation) - Um formato leve para intercâmbio de dados computacionais. Outro link.
  2. JSP(Java Server Pages) - Uma tecnologia similar ao ASP da Microsoft ou PHP utilizada para desenvolvimento de aplicações web baseada em Java. Outro link.
Esse artigo foi baseado nos textos do site do ZK:
http://books.zkoss.org/wiki/ZK_Essentials/Introduction_to_ZK/Component_Based_UI http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/UI_Composing/Component-based_UI

Visão Geral

Cada objeto de interface é representado como um componente (Component). Assim, compor componentes trata-se da montagem de componentes. Para alterar a interface basta modificar os estados e a relação dos componentes.

Por exemplo, como mostrado abaixo, declaramos um componente Window, permitindo borda(border = "normal") e definindo sua largura em 250 pixels. Foram adicionados 2 componentes Button à Window.


Os componentes são declarados usando ZUML, em um arquivo ZUL, os quais são analisados por um poderoso parser XML do ZK. Os componentes declarados são criados como POJO(Plain Old Java Objects) na JVM no servidor. Suponha que temos uma página ZUL que descreve uma árvore de componentes como o seguinte:


    <button label="Hello" />
    <button label="Good-bye" />


A página com base ZUL renderiza uma janela com dois botões, como mostrado na imagem abaixo:
A marcação em ZUL é equivalente a seguinte declaração POJO em Java:

Window win = new Window();
    win.setTitle("ZKBrasil");
    win.setBorder("normal");
    win.setWidth("250px");
         
Button helloBtn = new Button();
    helloBtn.setLabel("Hello");
    helloBtn.setParent(win);
         
Button byeBtn = new Button();
    byeBtn.setLabel("Good-bye");
    byeBtn.setParent(win);

Os componentes no servidor são depois traduzidos para instruções (em JSON) necessárias para a criação de widget(objetos JavaScript) e enviado então para o cliente.



Como você pode notar, há duas maneiras de declarar um componente, através de XML ou Java puro:
  1. XML
  2. Os componentes são declaradas em arquivos com a extensão ".zul". Uma página ZUL é interpretada dinamicamente no servidor, podemos imaginar como um JSP capacitado com recursos Ajax. Por exemplo, aqui nós criamos um novo arquivo ajax.zul, e vamos implementar um pequeno exemplo de que a entrada do usuário no Textbox é refletido no label abaixo instantaneamente quando o Textbox perde o foco:
        
        <textbox id="txtbx" onChange="lbl.value = txtbx.value"/>
        <label id="lbl"/>
        
    
    
    A marcação declarada acima renderiza o simples programa abaixo:
  3. Java
  4. Os componentes poderiam ser declarados totalmente em Java também:
    package org.zkoss.zkdemo;
     
    import org.zkoss.zk.ui.Page;
    import org.zkoss.zk.ui.GenericRichlet;
    import org.zkoss.zul.*;
     
    public class TestRichlet extends GenericRichlet {
        //Richlet//
        public void service(Page page) {
            
            final Window win = new Window("ZKBrasil", "normal", false);
            win.setWidth("250px");
     
            Vlayout vl = new Vlayout();
            vl.setParent(win);
            
            final Textbox txtbx = new Textbox();
            txtbx.setParent(vl);
            
            final Label lbl = new Label();
            lbl.setParent(vl);
            
            txtbx.addEventListener("onChange", new EventListener(){
               @Override
               public void onEvent(Event event) throws Exception {
                   lbl.setValue(txtbx.getValue());
               }
           });
            
     
            win.setPage(page);
        }
    }
    
    Mais informações sobre Richlets podem ser encontradas aqui.
Além disso, voce pode misturá-las se quiser.

Estruturação dos Componentes


Como uma estrutura de árvore, um componente tem no máximo um pai, enquanto ele pode ter vários filhos.
Alguns componentes aceitam apenas tipos específicos de componentes como filhos, como por exemplo, o componente Grid aceita apenas os componentes Columns, Rows e Foot.
Outros não permitem nenhum tipo de componente, como por exemplo, o componente Textobox.

Um componente sem nehum pai é chamado de componente raiz (root componente). Múltiplos componentes raízes são permitidos em cada página, embora não seja comum.

Note que, se voce estiver usando ZUML, há uma limitação imposta pelo próprio XML: Exatamente uma raiz no documento é permitida. Para especificar várias raízes, é necessário inserir os componentes utilizando tag zk como raiz a qual é uma tag especial que não cria componentes. Por exemplo:


     <!-- O primeiro componente raiz -->
    <div/> <!-- O segundo componente raiz -->


Componentes Filhos


A maioria das coleções retornado por um componente, como Component.getChildren(), são de componentes "vivos", ou seja, existe algum componente adicionado na página. Significa que voce pode adicionar um componente, remover um componente ou remover todos. Por exemplo, para retirar todas os filhos, voce pode fazê-lo através da seguinte declaração:

comp.getChildren().clear();

A qual é equivalente a:

for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
    it.next();
    it.remove();

No entanto, isso também significa que o seguinte código não irá funcionar, pois a exceção ConcurrentModificationExceptionserá lançada.

for (Iterator it = comp.getChildren().iterator(); it.hasNext();)
    ((Component)it.next()).detach();

Ordenação


Um exemplo menos sutil: ordenação de componentes filhos. A seguinte declaração falha porque a lista contém todos os componentes "vivos" e um componente será retirado quando nós movermos ele para outro local.

Collections.sort(comp.getChildren());

Mais precisamente, um componente tem no máximo um pai e ele tem apenas um ponto na lista dos filhos. Isso significa que a lista é na verdade um conjunto (nenhum elemento duplicado é permitido). Por outro lado, Collections.sort() não consegue tratar um conjunto corretamente. Assim, temos que copiar a lista para outra lista ou vetor, e ordená-la. Components.sort(List, Comparator) é apenas uma maneira simples de reduzir o trabalho.

Desktop, Página e Componente


Imagine que os componentes são atores em um jogo, então a página é a fase onde os componentes desempenham os seus papéis. Uma página (Page) é um suporte de espaço em uma janela do navegador, onde os componentes ZK podem ser anexados e desanexados. A página não é um componente, não implementa a interface Component, mas é uma coleção de componentes e apenas os componentes ligados a uma página estão disponíveis no cliente. Uma página é criada automaticamente quando usuário requisita um recurso como uma página ZUL.

Um desktop (Desktop) é uma coleção de páginas. Ela representa a janela do navegador (ou um tab ou um frame do navegador). Você pode imaginar que um desktop representa uma requisição HTTP independente.


Um desktop é também o escopo lógico que uma aplicação pode acessar em uma requisição. Cada vez que uma requisição é enviada a partir do cliente, ela é associado ao desktop a que ele pertence. O pedido é passado para DesktopCtrl.service(AuRequest, boolean) e, em seguida encaminhado para ComponentCtrl.service(AuRequest, boolean). Significa também que a aplicação não pode acessar componentes em vários desktops ao mesmo tempo.

Um desktop e uma página são criados automaticamente quando o ZK Loader carrega uma página ZUML ou chama um richlet(Richlet.service(Page)). A segunda página é criada quando o componente Include inclui outra página com o modo defer. Por exemplo, duas páginas serão criadas se o seguinte for visitado.

<!-- Página principal -->

  <include src="another.zul" mode="defer"/> <!-- Cria outra página -->


Repare que se o modo não for especificado (ou seja, o modo instant), o incluide não irá criar uma nova página. Em vez disso, ele anexará todos os componentes criados por another.zul como seus componentes filhos próprios.


  <include src="another.zul"/> <!-- default: mode instant -->


É equivalente a seguinte declaração (exceto div não é um proprietário do espaço, veja abaixo)


  <div>
    <zscript>
      execution.createComponents("another.zul", self, null);
    </zscript>
  </div>


Anexar um componente a uma página


Um componente está disponível no cliente somente se ele está anexado a uma página. Por exemplo, a janela criada a seguir não estará disponível no cliente.

Window win = new Window();
win.appendChild(new Label("foo"));

Um componente é um objeto POJO. Se você não tem qualquer referência a ele, ele vai ser reciclado quando a JVM iniciar o Garbage Collection.

Há duas maneiras de anexar um componente de uma página:
  1. Acrescentá-o como um filho de outro componente que já está conectado a uma página (Component.appendChild(Componente), Component.insertBefore(Componente, Componente), ou Component.setParent(Componente)).
  2. Chamar Component.setPage(Page) para anexá-lo a uma página diretamente. É também a maneira de fazer um componente se tornar um componente raiz.

Uma vez que um componente pode ter no máximo um pai e ser anexado no máximo a uma página, é desanexado automaticamente a partir do pai ou da página anterior quando ele é anexado a outro componente ou página. Por exemplo, b será um filho de win2 e win1 não terá nenhum filho no final.

Window win1 = new Window;
Button b = new Button();
win1.appendChild(b);
win2.appendChild(b); //implica desanexar b de win1

Desanexar um componente a uma página


Para desanexar da página, você poderia chamar comp.setParent(null), se não for um componente raiz, ou comp.setPage(null), se for um componente raiz. Component.detach() é um atalho para desanexar um componente sem saber se ele é um componente raiz.

Invalidar um componente


Quando um componente é anexado a uma página, o componente e todos os seus descendentes serão renderizados. Por outro lado, quando um estado de um componente anexado é alterado, apenas o estado alterado é enviado ao cliente para atualização (para melhor desempenho). Embora seja raro, você poderia chamar Component.invalidate() para forçar o componente e seus descendentes a serem renderizados novamente(O ZK Update Engine irá enfileirar os comandos update e Invalidate, e otimizá-los antes de enviar de volta ao cliente para uma melhor performace).

Existem apenas algumas razões para invalidar um componente:

  1. Se você estiver adicionando vários componentes filhos, pelo menos mais de 20, o desempenho será melhor se você invalidar o componente pai. Embora o resultado da resposta Ajax possa ser grande, o navegador é mais eficiente em substituir uma árvore DOM do que a adicionar elementos DOM.
  2. Se um componente tem um bug que uma atualização não atualiza a árvore DOM corretamente, você pode invalidar o seu componente pai, que geralmente resolve o problema (Qualquer tipo de bug pode ser relatado no forum da comunidade. Se confirmado, ele será resolvido na próxima versão).

Não guarde referências de componentes adicionados a uma Página em campos estáticos


Como descrito acima, um desktop é um escopo lógico que a aplicação pode acessar ao atender a uma requisição. Em outras palavras, a aplicação não pode desanexar um componente de um desktop para outro desktop. Isso normalmente acontece quando é referenciado um componente acidentalmente.

Por exemplo, o código a seguir lançará exceção se ele for carregado várias vezes:

 <!--  Instancia e executa foo.Foo -->

e foo.Foo é definido a seguir:

package foo;
import org.zkoss.zk.ui.*;
import org.zkoss.zul.*;
 
public class Foo implements org.zkoss.zk.ui.util.Composer {
   private static Window main; //Errado! Não guarde em um campo estático
   public void doAfterCompose(Component comp) {
       if (main == null)
           main = new Window();
       comp.appendChild(main);
   }
}

A exceção é parecida com essa:

org.zkoss.zk.ui.UiException: The parent and child must be in the same desktop: 
    org.zkoss.zk.ui.AbstractComponent.checkParentChild(AbstractComponent.java:1057)
    org.zkoss.zk.ui.AbstractComponent.insertBefore(AbstractComponent.java:1074)
    org.zkoss.zul.Window.insertBefore(Window.java:833)
    org.zkoss.zk.ui.AbstractComponent.appendChild(AbstractComponent.java:1232)
    foo.Foo.doAfterCompose(Foo.java:10)

Clonando Componentes


Todos os componentes são clonáveis (java.lang.Cloneable). É simples replicar componentes invocando Component.clone().

main.appendChild(listbox.clone());

Note:
  • É um clone de profundidade. Ou seja, todos componentes filhos e descendentes são clonados, também.
  • O componente retornado por Component.clone() não pertence a nenhuma página e ele não tem um pai também. Você deve anexá-lo manualmente, se necessário.

  • O ID será preservado, se houver. Assim, você não pode anexar o componente retornado para o mesmo espaço de ID, sem modificar o ID, se houver.

Da mesma forma, todos os componentes são serializáveis (java.io.Serializable). Como a clonagem, todos os filhos e descendentes são serializados. Se você serializa um componente e, em seguida, deserializá-lo de volta, o resultado é o mesmo que chamar Component.clone()(Mas a performace de executar Component.clone() é muito maior).