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:

Bruno Macedo disse...

Muito bom o post.

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

Armin Bachmann disse...

parabens mesmo!

Ricardo Oliveira disse...

Parabpens Professor.. sua didatica foi ótima.

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

Anônimo disse...

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

Anônimo disse...

ò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!

debian pt disse...

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

Anônimo disse...

Boa Tarde,

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

Grato,
Lucas

Douglas Hamilton de Oliveira disse...

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

Douglas Hamilton de Oliveira disse...

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.

Lucas Soares disse...

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

Lucas disse...

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

Lucas Soares disse...

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

Douglas Hamilton de Oliveira disse...

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

Ricardo Vieira disse...

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 ?

REGINALDO G A SILVA disse...

ai garoto, realmente bem clara a explicativa, parabens.

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

Douglas Hamilton de Oliveira disse...

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,...)

Douglas Hamilton de Oliveira disse...

Valeu Reginaldo

Douglas Hamilton de Oliveira disse...

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.

Anônimo disse...

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

Douglas Hamilton de Oliveira disse...

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

Anônimo disse...

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.

Douglas C disse...

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

Douglas Hamilton de Oliveira disse...

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

Douglas Hamilton de Oliveira disse...

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

Leônidas disse...

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

Douglas Hamilton de Oliveira disse...

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

Eduardo A Lima (Petroluz) disse...

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!

Eduardo A Lima (Petroluz) disse...

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()"

Douglas Hamilton de Oliveira disse...

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.

Samuel disse...

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 !

Douglas Hamilton de Oliveira disse...

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

Samuel disse...

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

Samuel disse...

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 !

Douglas Hamilton de Oliveira disse...

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

Douglas Hamilton de Oliveira disse...

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

Empresas.Link(65)

Eita Lance disse...

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;

Douglas Oliveira disse...

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.