JS-0505 - JavaScript Completo ES6 - Dropdown Menu e Event Bubbling
Utilizando a fase de Bubbling para controlar eventos
Para os exemplos será utilizado o conteúdo do projeto. Caso queira o montar em seu ambiente de desenvolvimento, leia o conteúdo do JS-0302 - JavaScript Completo ES6 - Início do Projeto Prático.
Preparando:
Inicialmente, mantendo o padrão de modules, será criado o arquivo dropdownMenu.js já importando e ativando no script.js com o nome de dropdownMenu para a função opcionalmente.
Para o estilo, será utilizado o mesmo padrão (organização e reaproveitamento), criando um arquivo dropdown-menu.css e importando no style.css utilizando @import 'dropdown-menu.css';.
No menu do projeto será colocado um novo elemento, chamado de "Sobre", que será utilizado para apresentar elementos de uma lista abaixo dele, que seriam links para diferentes páginas. Estes links serão fictícios, apenas para exemplificar. No "ul" da lista será colocada uma classe para o estilo (dropdown-menu) e no "li" pai dos links será colocado o dataset para manipulação do JS, chamado de data-dropdown:
Um submenu é facilmente implementado via CSS, contudo, não responde bem a eventos de toque em dispositivos móveis, pois alguns tipos não executam o "hover". Nesse caso, a implementação via JavaScript oferece uma interface melhor para subitens do menu.
Estilizando:
No css, a classe dropdown-menu ficou com o position absolute em relação ao menu (selecionado pelo dataset). As variáveis são do base.css do projeto (o link para o código deste exemplo está disponível no final do post). Um z-index foi aplicado para manter a caixa acima dos demais.
O toptem que ser calculado com cuidado, de modo que não haja área vazia entre a área de clique do link e o dropdown, pois um movimento sobre essa zona vazia desativa o menu (seria identificado com um evento fora da área clicável do menu para ativar submenu).
O triângulo foi gerado com o pseudo-elemento before. Por padrão, o submenu ficará oculto com um display none e aparecerá no hover sobre o link pai ("SOBRE"). A classe active também aplicará o display block que será adicionada via JS pelo evento de click ou toque no menu.
Também foi adicionada uma animação de subida do submenu para não ficar "seco".
Vamos salvar com o JS!
No JS, quando houver o clique ou toque no menu, o submenu ficará ativo e quando houver o toque fora da área, o submenu será desativado.
O primeiro passo é selecionar os submenus (sempre prepare para mais que um) com o querySelectorAll sobre o dataset "data-dropdown". Para cada item deste elemento uma função de callback (handleEvent) foi atribuída em um evento de clique (click) ou toque (touchstart). O event.preventDefault foi usado para brecar a ação padrão do elemento. O callback adciona a classe "active" do CSS, para aplicar o display block do submenu. Até o momento, o script está da seguinte forma:
O porque de entender o evento Bubbling:
O JavaScript segue um padrão e há quatro áreas em que as instruções permanecem para a execução dos eventos. Estas áreas são as seguintes:
- Memory:inicialização de valores, utilização e liberação da memória;
- Call stack: organiza o funcionamento do script;
- Callback Queue: execução de subtarefas enfileiradas em eventos assíncronos; e
- Web Api: interpretação do código.
O primeiro evento que ocorre, quando o script é iniciado é o Hoisting, que coloca os objetos na memória. As variáveis declaradas como const e let vão para a chamada "temporal dead zone" por não existir ainda um valor atribuído. Variáveis declaradas como var não entram nesta zona por receberem por padrão o valor "undefined". Todos os objetos (funções também são objetos) vão para a memória.
Depois inicia a fase de call stack, que é a execução propriamente dita do script. Nesta fase ocorrem as seleções de elementos, a atribuição a estes das funções em que eles estão relacionados, atualizando os valores da memória. As constantes saem da dead zone, pois nesta faze já recebem seus valores atribuídos. Os eventos que estão aguardando a sua execução permanecem nessa área até a ativação.
Quando um evento é realizado, as suas funções que estão na memória vão para a call stack para a execução. Nesta fase que ocorre o BUBBLING que pode gerar alguns bugs caso não seja considerado devido a PROPAGAÇÃO DE EVENTOS.
Na fase de bubblingocorre uma varredura, procurando eventos linkados ao elemento que executa a chamada em seus elementos ascendentes do pai ao window (pai de todos). Caso não haja, outra execução entra para a call stack. Por isso o nome bubbling, que significa borbulhamento.
Nessa verificação, se houver algum evento que possa ser ativado em um dos elementos filhos, ele vai ocorrer!
Então, será DELEGADO ao htlmElement a verificação do clique fora do submenu aproveitando a fase de bubbling do evento de clique.
Retornando para o script!
Agora vamos selecionar o html para verificar a ocorrência de um clique nele que não seja dentro do menu adicionando a este um evento de click. Este evento só será adicionado ao html quando for disparada a função handleClick, que será chamado através de uma função chamada outsideHandleClick que será executado na fase de bubbling.
Para prevenir a ocorrência desse evento repetidas vezes em caso de vários clique no menu e submenu, será setado um atributo no menu, para sinalizar que ele o evento já foi adicionado (uma flag), chamada de data-outside. Caso o menu não tenha o atributo, no momento do clique será acionado todos os eventos, colocando um ouvinte no html que aguardará o clique na área fora. Se o menu já tiver o atributo, então o html já estará aguardando. Quando ocorrer o clique fora e o html executar o seu evento, esse atributo deverá ser removido para permitir um eventual próximo acionamento do menu.
A função outsideHandleClick receberá como parâmetros o elemento e um callback, passados pela função handleClick, onde this será o elemento clicado e callback uma função. O callback removerá a classe "active" do submenu, mas removerá apenas se houver o evento não conter o target deste evento, que é a constante dropdownMenus.
Nesse momento, a "olhos nus" o evento está ocorrendo como esperado, contudo, a cada clique um evento seria adicionado ao html. Então, no momento de execução da função de callback do outsideHandleClick será realizada a remoção do evento com o removeEventListener, passando e evento (click) e a função (handleOutsideClick).
O código já está 100% neste momento:
Mas, tudo pode ser melhorado e principalmente COMPARTILHADO (o cóooodigo!!!). Sendo assim, a função outsideClickserá separada em módulo e importada para este arquivo. Ela será utilizada a frente para o menu mobile do projeto.
Os arquivos finais ficaram da seguinte forma:
Resultado final:
Para visualizar o projeto em andamento, clique aqui.
Para baixar o projeto, clique aqui.