Scriptcaser

Gerador de Menu Dinâmico Para Scriptcase

Voltando ao meu diário de desenvolvimento do sistema de rastreamento veicular utilizando o scriptcase, eu necessitava que o administrador do sistema pudesse criar dinamicamente e alterar os menus de grupos de usuários — assim como as permissões de uso das aplicações — de maneira dinâmica.

Seguindo a mesma lógica do script gerado automaticamente pelo módulo de segurança quando selecionado por grupos, eu desenvolvi dentro da interface do scriptcase um gerador de menu de dinâmico.

O primeiro passo é modelar o banco de dados; e como utilizo o MySQL como meu SGBD eu utilizei o MySQL Workbench para montar o meu diagrama entidade-relacional, como pode ver abaixo.

Código SQL da tabela “sec_group_menu” para importar ao seu projeto:

CREATE TABLE IF NOT EXISTS `sec_group_menu` (
  `group_id` INT(11) NOT NULL,
  `menu_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `menu_active` ENUM('Y', 'N') NOT NULL DEFAULT 'Y' COMMENT 'Y = Sim, N = Não',
  `menu_name` VARCHAR(255) NULL,
  `menu_link` VARCHAR(255) NULL,
  `menu_icon` VARCHAR(32) NULL,
  `menu_parent_id` INT UNSIGNED NULL,
  `menu_position` TINYINT NULL,
  `menu_options` VARCHAR(255) NULL,
  `ts_insert` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP(),
  `ts_update` TIMESTAMP NULL ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`menu_id`),
  INDEX `sec_group_menu_ibfk_1_idx` (`group_id` ASC),
  INDEX `sec_group_menu_ibfk_2_idx` (`menu_parent_id` ASC),
  CONSTRAINT `sec_group_menu_ibfk_1`
    FOREIGN KEY (`group_id`)
    REFERENCES `sec_groups` (`group_id`)
    ON DELETE CASCADE
    ON UPDATE RESTRICT,
  CONSTRAINT `sec_group_menu_ibfk_2`
    FOREIGN KEY (`menu_parent_id`)
    REFERENCES `sec_group_menu` (`menu_parent_id`)
    ON DELETE RESTRICT
    ON UPDATE RESTRICT)
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_0900_ai_ci

Será necessário criar duas stored procedures que usaremos para retornar os itens de menu para a aplicação que renderiza o menu, sendo a primeiro sp_get_menu e a segunda sp_get_submenu.

CREATE PROCEDURE `sp_get_menu`(IN _group_id INT(10))
BEGIN
SELECT menu_id as id,
	   menu_name as label,
	   menu_link as url,
	   menu_icon as icon,
	   menu_options as opt
	 FROM sec_group_menu
	 WHERE group_id = _group_id
	 AND menu_parent_id IS NULL AND menu_active LIKE 'Y'
	 ORDER BY menu_position ASC;
END;

CREATE PROCEDURE `sp_get_submenu`(IN _menu_parent_id INT(10))
BEGIN
 SELECT menu_id as id,
	   menu_name as label,
       menu_link as url,
	   menu_icon as icon,
	   menu_options as opt
	 FROM sec_group_menu
	 WHERE menu_parent_id = _menu_parent_id AND menu_active LIKE 'Y'
	 ORDER BY menu_position ASC;
END;

Precisaremos também criar duas aplicações: a primeira do tipo search com um campo para selecionar os grupos, e para isso basta fazer o lookup; a segunda do tipo form editável para criar os menus que serão vinculados ao grupo selecionado.

No evento onValidate adicione o código:

[group] = {group_id};

Adicione uma ligação para a aplicação app_sec_group_menu passando a variável global [group] como parâmetro e em Aplicação > Variável Global marque [group] como saída.

Agora crie a segunda aplicação do tipo form editável app_sec_group_menu que receberá o grupo selecionado na aplicação app_search_sec_group_menu.

No evento onApplicationInit da aplicação app_sec_grou_menu adicione o seguinte código:

$str_sql = "SELECT description FROM sec_groups WHERE group_id = " . [group];
sc_lookup(rs, $str_sql);
[group_desc] = {rs}[0][0];

Esse algoritmo cria a variável global [group_desc]. Apesar de ser algo estético, é importante mostrar ao usuário qual grupo ele está manipulando. Insira essa variável no cabeçalho da aplicação.

Importantíssimo! O campo menu_parent_id é uma chave estrangeira da própria tabela com cardinalidade 0:n, ou seja, ele pode receber valor nulo.

Vá em editar campos e faça como na imagem abaixo, defina como valor padrão a variável global [group] no campo group_id e habilite o menu_parent_id para receber valor nulo.

Altere o campo menu_parent_id para select e adicione o seguinte código no lookup:

SELECT 
    menu_id, 
    CONCAT(menu_id,' - ', menu_name) 
FROM 
    sec_group_menu
WHERE
    group_id = [group] 
ORDER BY 
    menu_id, menu_name

Se você chegou até aqui sua aplicação sua rotina de criação de menu — à exceção do layout — ficará parecido com o gif abaixo:

O que significa cada campo no gif animado acima?

  1. ID: chave primária do tipo auto-increment;
  2. Ativo: chave do tipo ENUM que define se o item de menu está ativo ou não;
  3. Nome: nome que aparecerá no item de menu na visualização;
  4. Ligação: nome da aplicação que deseja gerar o link;
  5. Ícone: ícone da biblioteca font awesome que será exibido à esquerda do nome do menu, você pode pesquisar aqui o ícone que deseja definir.
  6. Posição: posição do item de menu dentro da estrutura criada.
  7. Opções: argumentos que serão passados via $_GET para a aplicação, utilizados para modificar o comportamento inicial ou passar um valor para um campo.

Para você aninhar os menus a uma estrutura de submenus é só selecionar como menu pai o menu que você criou sem link.

Agora que já preparamos as aplicações para criar os menus, iremos criar a aplicação do tipo blank que irá exibir o resultado dos menus que você cadastrar.

Lembrando que a minha aplicação utiliza o bootstrap como css base, portanto para implementar da forma que eu colocarei neste post você deverá saber como funciona o bootstrap.

Primeiro criaremos os dois métodos php que utilizaremos selecionar os itens de menu do grupo de usuário no banco de dados. Os métodos são getMenu($tree_menu_id) e getSubmenu($parent_id).

Método getMenu($tree_menu_id)

$sql = "call sp_get_menu({$tree_menu_id})";
sc_lookup(rs, $sql);
if(isset({rs[0][0]}) && !empty({rs[0][0]})) {
	return {rs};
}

Método getSubmenu($parent_id)

$sql = "call sp_get_submenu({$parent_id})";
sc_lookup(rs, $sql);
if(isset({rs[0][0]}) && !empty({rs[0][0]})) {
	return {rs};
}

Agora vamos ao que importa, o algoritmo responsável de renderizar o menu:

<?php foreach (getMenu($_SESSION['usr_group']) as $item) { ?>
    <li class="nav-item">
        <?php if ($submenu = getSubmenu($item[0])) { ?> 
        <a href="#" data-target="#collapse<?php echo $item[1]; ?>" class="nav-link collapsed" data-toggle="collapse" aria-expanded="true" aria-controls="collapse<?php echo $item[1]; ?>">
	    <i class="<?php echo $item[3]; ?>"></i>
	    <span><?php echo $item[1]; ?></span>
	</a>
	<div class="collapse" id="collapse<?php echo $item[1]; ?>" aria-labelledby="heading<?php echo $item[1]; ?>" data-parent="#accordionSidebar">
	    <div class="bg-white py-2 collapse-inner rounded">
	    <?php foreach($submenu as $subitem) { ?>
	        <a href="//<?php echo get_base_dir() . $subitem[2] . '/' . (($subitem[4]) ? $subitem[2] . '.php?' . $subitem[4] : $subitem[2] . '.php'); ?>" target="main" class="collapse-item">
		    <?php echo $subitem[1]; ?>
		</a>
	    <?php } ?>
	    </div>
        </div>
	<?php } else { ?>
	<a href="//<?php echo get_base_dir() . $item[2] . '/' . (($item[4]) ? $item[2] . '.php?' . $item[4] : $item[2] . '.php'); ?>" class="nav-link" target="main">
	    <i class="<?php echo $item[3]; ?>"></i>
	    <span><?php echo $item[1]; ?></span>
	</a>
	<?php } ?>
    </li>
<?php } ?>

Acho que ficou confuso para entender, com um print da tela com os elementos coloridos por tipo ficará mais claro.

Me acompanhe aqui! Vou te explicar passo a passo como esse algoritmo funciona. A ideia central é utilizar um laço de repetição foreach dentro do outro.

O primeiro laço captura todos os itens de menu to tipo raiz, ou seja, que não há nenhum item de menu como pai, e havendo algum item de menu vinculado a ele são carregados todos os itens dentro dele como submenus; caso contrário é gerado um link de menu e renderizado na tela.

Existe também um método php dentro do algoritmo chamado get_base_dir, sua função é retornar o endereço relativo da pasta raiz do seu projeto. Salve o código abaixo em suas bibliotecas internas, ele provavelmente servirá para outras aplicações.

 <?php
 function get_base_dir() {
    $url = $_SERVER['REQUEST_URI']; //returns the current URL
    $parts = explode('/',$url);
    $dir = $_SERVER['SERVER_NAME'];
    
    if ($_SERVER['SERVER_PORT'] != '80' || $_SERVER['SERVER_PORT'] != '443')
        $dir .= ":" . $_SERVER['SERVER_PORT'];
    for ($i = 0; $i < count($parts) - 2; $i++) {
        $dir .= $parts[$i] . "/";
    }
    return $dir;    
}
?>

Agora é com você! Baixe o seu template, insira o algoritmo dentro da sua estrutura de menu e poste aqui o resultado. Se você tiver dificuldade pode vim aqui comentar que eu e você faremos funcionar juntos o gerador de menu dinâmico para scriptcase no seu projeto.

Observação: Eu utilizei o template gratuito SB Admin 2 que você pode baixar aqui.

scriptcaser

Add comment

Newsletter

Se inscreva na nossa newsletter
Se increva hoje na nossa lista de e-mail para receber atualizações, tutoriais e ofertas especiais!

Respeitarei sua privacidade. Seu e-mail nunca será compartilhado.