terça-feira, 19 de outubro de 2010

Criando um Menu Recursivo (revisado)

Às vezes o mais complexo é resolvido com coisas simples, na verdade me parece que essa é uma regra da natureza, pois quanto mais complexa é a tentativa, mais distante se está da solução. Claro que quando falamos de programação de computadores, essa regra é quebrada de vez em quando, principalmente quando se depara com algoritmos com certo grau de dificuldade.

E na classe dos algoritmos mais estranhos, e que faz com que os programadores pensem duas vezes, encontram-se os recursivos. Ou seja, aqueles que por alguma razão precisam chamar a si próprios.

Acontece que uma das aplicações mais interessantes desses algoritmos, se encontram exatamente os menus de opções, e planejar um bom menu para o usuário é uma das tarefas importantes do desenvolvimento, para dar acessibilidade, organização e clareza nas operações do sistema.

A boa notícia é que Genexus é 10! e alguns programadores muito bem intencionados nos deram todos os recursos para programar um menu recursivo, sem muito esforço, portanto, cabe aqui apenas o direcionamento para os recursos corretos.
Entao, maos na massa e um pouco de paciencia para entender os elementos do menu.
 

1. JSCookMenu:

Esse é um controle muito pratico, que possibilita que se construam menus na interface web de forma muito interessante com controle sobre as classes CSS, ou seja, estilos, imagens, ...

Para carregar o JSCookMenu é necessário utilizar uma SDT, que já é recursiva por natureza, pois os Itens (Item) são na verdade ramos da própria SDT, como pode ser visto abaixo (veja o Type).

O menu possui uma variável do tipo coleção do SDT JsCookMenuItem que ao ser carregado apresenta as opções no controle, se olhar nas Variables verá que foi incluida uma variavel chamada &MenuDataCollection para esta finalidade. Portanto, para se carregar um item no menu teriamos que programar algo como:

 

&JSCookMenuItem = new()
&JSCookMenuItem.Title = 'item'

&JSCookMenuItem.UTL = 'http://www.gotoitem.com/itemfolder/item'
&MenuDataCollection.Add(&JSCookMenuItem)

 
Desta forma para cada item a ser incluido no menu teriamos que gerar um conjunto igual a este de codigo.Se não entendeu este código melhor voce estudar um pouquinho a respeito de Structured Data Type e também Collections.

 


2. Data Provider:

Para programarmos o menu recursivo usando a programação apresentada anteriormente seria bastante complexo, com for each, procedure recursiva, enfim, vamos aproveitar o exemplo para falar de mais um recurso interessante que temos a nosso favor.
 
A Artech comemorou muito o lançamento deste recurso no Genexus, é não é pra menos, com poucas linhas de ‘código’, se carrega uma SDT complexa como a apresentada, bastando declarar o conteúdo que desejamos em cada elemento da estrutura.

Para ser mais claro, o DP abaixo dá conta do recado e substitui a programação da procedure mais complexa, apenas usando o recurso de declarar o conteudo que queremos.



Os elementos incluidos nesta programação são atributos que estão em uma tabela Menu, e prontos para irem para nosso controle na interface (MenuTitulo, MenuTarget, FatherMenuId, ...)


Um detalhe que nao explicamos da primeira vez aqui foi a regra PARM necessária para este Data Provider funcionar, que deve ser:


Parm(&FatherMenuId, &FatherMenuItemId);


Falando em recursividade, se observar bem verá que existe um elemento na estrutura (childs) que chama o próprio Data Provider (JsCookMenuProvider1), ou seja, o SDT foi criado para funcionar de forma recursiva.


Quando este artigo foi escrito pela primeira vez, a idéia era uma tabela mais complexa, agora analisando com calma, a tabela Menu poderia ter sido criada mais simples apenas com os atributos:
  • MenuId
  • MenuTitulo
  • MenuUrl
  • MenuPaiId (subtipo apontando para MenuId)
 

3. Web Panel:

Aqui a coisa é simples, somente inclua um user control JSCookMenu na interface, e em seguida carregue a variável &MenuDataCollection com o nosso DP recursivo. Passe o parâmetro 1,1 na ‘primeira chamada’, e no evento Start do Webpanel chame o MenuSample.





4. O Resultado:

O resultado alcançado com esse Data Provider é bem legal, com as opções principais, sub menus e por ai vai, nao existe limite para organizar os niveis no Menu, pois, lembre que é recursivo.

Temos ai um recurso de árvore!! Fala sério, ficou legal, hein!!



5. A Árvore:

Observe que a parte programável acabou! E não gastamos muito para chegar aqui (Uma transação Menu, Um DataProvider carregando uma coleção do tipo SDT JSCookMenuItem), e você deve estar se perguntando cadê a procedure, onde esta o codigo fonte necessário para se carregar a coisa?. A resposta é que o Data Provider faz tudo, é uma 'procedure declarativa', e pode funcionar ainda como caracteristica recursiva.


Se não temos muito o que fazer com o programa então vamos explicar à parte mais difícil que consiste em alimentar o monstro, ou seja, como armazenar e carregar informação recursiva que alimente o DP e o JSCookMenu.



Para isso vou recorrer a um desenho de árvore mesmo, que talvez simplifique um pouco a visão para você. Veja que pendurado no nó 2 temos os itens (5, 6 e 7), e no nó 6 os itens (8 e 9). Outro exemplo, pendurado no nó 1, temos os nós (2, 3 e 4), e assim por diante. Entenda que o Menu pode ser organizado desta forma, bastando voce escolher onde vai pendurar as coisas.

Para os ‘puristas’ e mais exigentes, pode-se dizer que esta árvore não é binária, portanto, para cada nó, temos vários que podem estar conectados.



Em termos de dados podemos dizer que precisamos apenas de um numero para definir o nó e outro para indicar a qual nó este pertence. Ou seja, nó=7, pertence ao nó=2. E por ai vai.
]

Portanto, simplificando a coisa, precisaremos de uma tabela com os seguintes dados, para produzir o exemplo anterior:




E o que a tabela tem de especial? Apenas um campo recursivo chamado FatherMenuId e FatherMenuItemId que aponta para MenuId e MenuItemId, respectivamente. Observe que utilizei dois números para indicar um nó. Entenda o primeiro como grupo e o segundo como item do grupo, tipo uma idéia para organizar menus por grupos de usuários.


Aqui também faltou na explicação que para apontar FatherMenuId e FatherMenuItemId para MenuId e MenuItemId é necessário um objeto chamado Subtipo no Genexus.



6. Transação:

Claro que estamos no Genexus, e para que tudo se complete é necessária uma transação que grave os dados. Para isso temos uma estrutura simples:




Sendo que FatherMenuId e FatherMenuItemId são subtipos que apontam para MenuId e MenuItemId.



Agradecimentos e Conclusão

Portanto, para se criar um menu siga os passos:
  1. Crie uma transação Menu
  2. Crie um WebPanel e inclua nele o controle JSCookMenu
  3. Crie um DataProvider para carregar o SDT JsCookMenuItem
  4. Chame o DataProvider no WebPanel
Se voce usar sua imaginação poderá entender que este mesmo conceito pode ser usado para tratar de coisas diferentes, como por exemplo uma árvove hierárquica de familia, controle genetico de animais, ...


A conclusão aqui é simples, Genexus proporciona uma programação em árvore, complexa, com poucas linhas de código no Data Provider, então, reclamar do que? Genexus é 10!



Já o agradecimento é complexo, porque tantas pessoas participaram da criação do JSCookMenu, do Genexus, Transação, Subtipo, Data Provider, treinamento da Artech, ... que fica impossível identificar todos, e para não deixar ninguém de fora desta, então vamos apenas dizer:
OBRIGADAO!!!



Se quiser saber um pouco mais sobre recursividade em Data Provider, pode dar uma olhada no link: http://wiki.gxtechnical.com/commwiki/servlet/hwiki?Recursive+Data+Providers,

37 comentários:

  1. Muito bom o post.

    Boa didática aliada a uma pitada de humor, resultado:post com estilo.

    ResponderExcluir
  2. Parabpens Professor.. sua didatica foi ótima.

    E realmente entendendo estes recursos fica mais fácil ainda mexer com GX X .

    ResponderExcluir
  3. Excelente ejemplo que explica un método para implementar jerarquías recursivas e reutilizando código existente en Genexus.

    Estas típicas para resolver BOMs (listas de materiales) de multi-nivel, organizaciones (ejemplo en clientes: corporativos, regionales, sucursales, etc), estructura en la empresa (CEO, VP, dirección, gerencia... etc... el organigrama) o como fundamento para crear jerarquías en reportes contables que requieren subtotales.

    Se agradece...

    Luis GomezDelCampo

    ResponderExcluir
  4. òtimo post!

    Caro colega você por acaso podia me enviar um exemplo passo a passo para mim aplicar!
    Baixei o Trial do Genexus X e gostaria de aprender a fazer um menu com este!
    obrigado!

    ResponderExcluir
  5. Hamilton , muito bom , vc tem alguma coisa sobre segurança de usuario para acesso ao menu , por exemplo o usuario fulano1 so pode acessar cadastro de cliente e etc.. e ful2 pode tudo e assim por diante regras de acesso

    valeu
    marcelo.farma@gmail.com

    ResponderExcluir
  6. Boa Tarde,

    Como faço para chamar o programa?
    Sendo que a URL está dentro de uma tabela...

    Grato,
    Lucas

    ResponderExcluir
  7. Lucas,
    A propriedade URL do componente JSCookMenu, é o local onde deverá colocar o nome do programa a ser chamado. Se for um link externo pode utilizar o link() para gerar.
    ab
    Douglas

    ResponderExcluir
  8. Quanto a seguraça, podem utilizar uma tabela de Funcoes, e associar a cada item do menu a funcao que pode acessá-la. Isso permite que se crie diversos menus para cada tipo de função. Não se esqueça de incluir a FuncaoId na clausula where do Data Provider.

    ResponderExcluir
  9. Bom dia Douglas, quanto, como funcionaria essa tabela de funções, não entendi muito bem, poderia me passar um exemplo simples.

    ab
    Lucas Soares
    lucas.soares@vikamtech.com.br

    ResponderExcluir
  10. Bom dia Douglas,

    Gostaria de saber se esse tipo de permissão que você diz, é uma permissão embutida pelo programador, ou uma onde o Administrador do sistema possa cadastrar um tipo de permissão para cada usuário?

    Grato,

    Lucas

    ResponderExcluir
  11. Bom dia Douglas, no caso de embutir no campo url o nome do programa, como eu faria para enviar parametros ao chamar a tela ?

    Grato
    Lucas Soares
    lucas.soares@vikamtech.com.br

    ResponderExcluir
  12. Caro Lucas,
    Postei um texto que trata de embutir a função do usuário no menu (http://profdouglasoliveira.blogspot.com/2011/05/treeview-com-controle-de-acesso.html).
    Quanto ao campo URL, se o acesso é interno basta fornecer ai o nome do programa, por exemplo, para uma transação Cliente basta Cliente. Se o programa é externo e acessado via URL, então pode programar um Link() ao mesmo.
    ab
    Douglas

    ResponderExcluir
  13. Douglas, boa tarde.
    Existe forma de pegar uma variável que está na WEBPANEL e utilizá-la no JSCOOK como um parâmetro para chamar outra WEBPANEL ?

    ResponderExcluir
  14. ai garoto, realmente bem clara a explicativa, parabens.

    Pessoal pra quem quiser postei o menu dinâmico desenvolvido no Gxopen.

    ResponderExcluir
  15. Ricardo,
    Voce pode incluir qualquer variável na carga do Menu, só precisa acrescentá-la no do Data Provider, que no caso se chama JSCookMenuProviderxis1.Udp(1,1, &variavel,...)

    ResponderExcluir
  16. Pessoal,

    Uma dica a respeito da estrutura do Menu, pois a mesma pode ser simplificada (no exemplo foi proposto uma chave composta, mas na pràtica essa estrutura não é necessária).

    Substitua a estrutura do exemplo por:

    MenuId*
    MenuTitulo
    MenuTarget
    MenuUrl
    PaiMenuId (fk)

    O atributo PaiMenuId (fk) deve ser definido como um subtipo para o MenuId.

    ResponderExcluir
  17. Olá Douglas,

    sou novato na utilização do Genexus e achei o exemplo aqui postado muito didático. Arrisquei implementar em um projeto de teste. Criei todas as estruturas necessárias a partir de tuas instruções mas ao compilar ocorrem 2 erros, relacionados ao mesmo problema:

    menuteste.cs(479,42): error CS0030: NÆo ‚ poss¡vel converter o tipo 'int' em 'GeneXus.Utils.IGxCollection'
    jscookmenuproviderx1.cs(117,45): error CS0030: NÆo ‚ poss¡vel converter o tipo 'short' em 'GeneXus.Utils.IGxCollection'

    Você teria alguma dica para contornar este erro? procurei no Google e encontrei posts relativos ao erro CS0030 mas nenhum respondeu ao que procuro (pelo menos entendi assim :)

    Utilizo a versão X ev1 com W7 professional.

    um abraço

    Vitor
    Florianópolis

    ResponderExcluir
  18. Vitor,

    Esse erro pode estar relacionado com a passagem de parametros para o objeto DataProvider, entao confira se existe uma regra PARM programada com as variaveis do mesmo tipo utilizadas na chamada.

    Recomendo que simplifique a estrutura da transacao para o modelo definido no comentario acima, e em seguida:

    1) Renomeie o objeto JSCookMenuProviderXis1 para algo mais simples como por exemplo, MenuDP

    2) No DataProvider (MenuDP) programe uma regra PARM com Parm(&PaiMenuId);, onde a variavel &PaiMenuId deve ser do mesmo tipo do atributo.

    2) Na chamada do DataProvider, no WebPanel, programe como &MenuDataCollection = MenuDP(1)

    Creio que faltou vc programar a regra Parm.

    ab

    ResponderExcluir
  19. Douglas,

    você acertou na mosca!
    O erro ocorria exatamente pela falta do Parm - coisas de novato, estou estudando o Genexus há apenas uma semana. É uma ótima ferramenta de desenvolvimento.


    Muito obrigado pela pronta resposta e parabéns pelo seu excelente Blog.

    abraço

    Vitor
    Florianópolis.

    ResponderExcluir
  20. Caro Douglas,

    Executei os procedimentos do post, porém meu menu não aparece.
    Como devo proceder neste caso?

    Abraços e parabéns pelo blog, está ótimo!

    Douglas
    SP

    ResponderExcluir
  21. Ola,
    Vc me passou pouca informacao, nao sei se apareceu ai algum erro ou nao. Recomendo que revise a estrutura da Transacao Menu e considere tambem as dicas apresentadas nos comentarios para simplificar um pouco mais.
    Tem tambem o POST do Tree View que apresenta a estrutura simples em funcionamento.
    ab
    Douglas

    ResponderExcluir
  22. Pessoal,
    Publiquei a KB no GxServer, ver detalhes em (http://profdouglasoliveira.blogspot.com/2011/09/menu-recursivo-no-gxserver.html)

    ResponderExcluir
  23. Douglas, sou novato ainda em Genexus e achei excelente este exemplo.

    Gostaria de saber como implementar para que ao acessar o sistema eu chame uma tela de acesso onde o usuário digite o id e a senha. Depois, conforme o usuário (pelo Grupo ou pelo Usuário mesmo), seja direcionado para este menu, ativando somente as opções permitidas para ele acessar.

    Grato, desde já,

    Leônidas

    ResponderExcluir
  24. Caro Leonidas,
    Me desculpe o atraso em responder.
    Voce vai encontrar informações a respeito desses assuntos em dois outros posts referentes a menu:

    http://www.genexando.com/2011/09/menu-recursivo-no-gxserver.html

    http://www.genexando.com/2011/05/treeview-com-controle-de-acesso.html

    ab

    ResponderExcluir
  25. Douglas , sou novato tambem na ferramenta do Genexus e esta o erro que esta aparecendo pra mim e o mesmo do Vitor , porem segui os passos e ainda esta dando o erro:


    menudp.cs(123,45): error CS0030: NÆo ‚ poss¡vel converter o tipo 'short' em 'GeneXus.Utils.IGxCollection'

    O Erro de conversao do Int nao aparece mais. apenas este agora!

    ResponderExcluir
  26. Como faco para chamar 1 Form utilizando o JSCookMenu , tenho o menu cadastro-Veiculos quando clico em Veiculos quero chamar o form de cadastro de veiculos , como fazer?

    Coloquei assim e nada:

    Url = "WcadastroVeiculos()"

    ResponderExcluir
  27. Eduardo,

    Seria algo como Url = WcadastroVeiculos.Link(),e se precisar de parametros pode coloca-los entre parenteses.

    Quanto ao erro, voce precisa declarar &MenuDataCollection como tipo collection.

    ResponderExcluir
  28. Parabéns pela matéria !
    Tenho um problema: a exibição do meu meu está perfeita, com os itens baseados na transação Menu simplificada; contudo, a url não funciona e retorna este erro: "Não é possível encontrar o recurso. Descrição: HTTP 404 ... URL solicitada: /KB1.NetEnvironment/Empresas". Tentei tbem com Empresas.Link() como usado no DP padrão, mas nada. O que pode ser ? Abraço !

    ResponderExcluir
  29. Esse erro ocorre porque o link nao esta apontando para uma URL correta, acho que esta faltando a extensao do programa (.aspx, .html, ....)

    ResponderExcluir
  30. Hum, entendi Douglas ...
    Coloquei Empresas.aspx e funcionou perfeitamente !
    Obrigado pela pronta resposta amigo !

    ResponderExcluir
  31. Como vai Douglas !
    Antes estava usando a URL Empresas.aspx e tudo ia bem; mas agora preciso mandar um parametro ! O que faço ? tentei Empresas.Call(65) mas deu o erro HTTP 404 que mencionei no 1o post. Há como fazer a chamada no menu passando parametro usando C# ? Abraço !

    ResponderExcluir
  32. Samuel,
    Para passar parametros pela URL voce deve colocar um ? e os valores em seguida. Por exemplo:

    http://www.algumacoisa.com.br/empresas.aspx?65

    ResponderExcluir
  33. Mais um detalhe, se deseja programar o link (que me parece ser seu problema) entao pode usar a chamada com link:

    Empresas.Link(65)

    ResponderExcluir
  34. Boa noite, eu fiz o processo mais não conseguir obter sucesso, faço uso do Genexus V1, conforme explicado no meu caso não gerou nenhum erro mais portanto não aparece nada;

    ResponderExcluir
  35. Faltou informação pra mim também, rs...
    Vocë leu os comentários, tem várias idéias legais que simplificam o processo.
    Preciso de mais informações para dizer com certeza o que está faltando.

    ResponderExcluir