Ao desenvolver para a web, é comum encontrar a necessidade de lidar com eventos do DOM de forma que funcione em diferentes navegadores e versões. A API de eventos do JavaScript possui algumas particularidades entre os ambientes, especialmente entre navegadores baseados em WebKit/Gecko e o Internet Explorer mais antigo.
Implementando Funções de Associação de Eventos Robustas
Uma função utilitária para adicionar eventos de forma compatível pode verificar a existência de métodos específicos do navegador.
Abordagem Direta
function adicionarOuvinte(elemento, tipo, acao) {
if (elemento.addEventListener) {
// Para navegadores modernos (Chrome, Firefox, Safari, etc.)
elemento.addEventListener(tipo, acao, false);
} else if (elemento.attachEvent) {
// Para versões mais antigas do Internet Explorer
elemento.attachEvent('on' + tipo, acao);
}
}
window.onload = function() {
var listaPai = document.getElementById('listaItens');
adicionarOuvinte(listaPai, 'click', function(evento) {
var eventoReal = evento || window.event;
var alvo = eventoReal.target || eventoReal.srcElement;
if (alvo.tagName.toLowerCase() === 'li') {
alert(alvo.id);
}
});
};
Utilizando Padrão de IIFE (Immediately Invoked Function Expression)
Essa técnica permite retornar a função de associação de evento correta uma única vez, otimizando a chamada.
var associarOuvinte = (function() {
if (document.addEventListener) {
return function(elemento, tipo, acao) {
elemento.addEventListener(tipo, acao, false);
};
} else {
return function(elemento, tipo, acao) {
elemento.attachEvent('on' + tipo, acao);
};
}
})();
window.onload = function() {
associarOuvinte(document.getElementById('listaItens'), 'click', function(evento) {
var eventoReal = evento || window.event;
var alvo = eventoReal.target || eventoReal.srcElement;
if (alvo.tagName.toLowerCase() === 'li') {
alert(alvo.id);
}
});
};
Implmeentando Funções de Remoção de Eventos Compatíveis
A remoção de eventos é igualmente importante. Funções como removeEventListener e detachEvent são as contrapartes para addEventListener e attachEvent, respectivamente.
Observação: Para que a remoção funcione corretamente, especialmente com detachEvent, a função de callback deve ser uma referência nomeada (declarada externamente) e não uma função anônima inline.
var associarOuvinte = (function() {
if (document.addEventListener) {
return function(elemento, tipo, acao) {
elemento.addEventListener(tipo, acao, false);
};
} else {
return function(elemento, tipo, acao) {
elemento.attachEvent('on' + tipo, acao);
};
}
})();
function removerOuvinte(elemento, tipo, acao) {
if (elemento.removeEventListener) {
elemento.removeEventListener(tipo, acao, false);
} else if (elemento.detachEvent) {
elemento.detachEvent('on' + tipo, acao);
}
}
window.onload = function() {
// É necessário usar uma função nomeada para poder removê-la depois
var meuCallback = function(evento) {
var eventoReal = evento || window.event;
var alvo = eventoReal.target || eventoReal.srcElement;
if (alvo.tagName.toLowerCase() === 'li') {
alert(alvo.id);
}
};
associarOuvinte(document.getElementById('listaItens'), 'click', meuCallback);
// Exemplo de remoção do listener
// removerOuvinte(document.getElementById('listaItens'), 'click', meuCallback);
};
Obtendo o Evento e o Elemento de Origem de Forma Universal
Para acessar informações consistentes sobre o evento e o elemento que o disparou, funções auxiliares são úteis.
function obterEvento(evento) {
return evento || window.event;
}
function obterAlvo(evento) {
var eventoReal = obterEvento(evento);
return eventoReal.target || eventoReal.srcElement;
}
Controlando o Fluxo de Eventos: Prevenção de Comportamentos
Duas operações comuns são interromper a propagação de um evento e prevenir sua ação padrão.
Interromper Propagação (Stop Propagation)
Impede que o evento suba na árvore DOM.
function pararPropagacao(evento) {
var eventoReal = obterEvento(evento);
if (eventoReal.stopPropagation) {
eventoReal.stopPropagation();
} else {
eventoReal.cancelBubble = true; // Compatibilidade com IE
}
}
Prevenir Ação Padrão (Prevent Default)
Impede que o navegador execute a ação padrão associada ao evento (ex: seguir um link, subemter um formulário).
function prevenirPadrao(evento) {
var eventoReal = obterEvento(evento);
if (eventoReal.preventDefault) {
eventoReal.preventDefault();
} else {
eventoReal.returnValue = false; // Compatibilidade com IE
}
}
Um Objeto Utilitário para Eventos
Consolidar todas essas funcionalidades em um objeto pode simplificar o gerenciamento de eventos.
var GerenciadorEventos = {
adicionar: function(elemento, tipo, acao) {
if (elemento.addEventListener) {
elemento.addEventListener(tipo, acao, false);
} else if (elemento.attachEvent) {
elemento.attachEvent('on' + tipo, acao);
}
},
remover: function(elemento, tipo, acao) {
if (elemento.removeEventListener) {
elemento.removeEventListener(tipo, acao, false);
} else {
elemento.detachEvent('on' + tipo, acao);
}
},
obterEvento: function(evento) {
return evento || window.event;
},
obterAlvo: function(evento) {
var eventoReal = this.obterEvento(evento);
return eventoReal.target || eventoReal.srcElement;
},
pararPropagacao: function(evento) {
var eventoReal = this.obterEvento(evento);
if (eventoReal.stopPropagation) {
eventoReal.stopPropagation();
} else {
eventoReal.cancelBubble = true;
}
},
prevenirPadrao: function(evento) {
var eventoReal = this.obterEvento(evento);
if (eventoReal.preventDefault) {
eventoReal.preventDefault();
} else {
eventoReal.returnValue = false;
}
}
};
Criando uma Função de Associação Genérica (Sem Suporte a IE)
Para cenários onde a compatibilidade com versões antigas do IE não é um requisito, podemos simplificar a lógica.
Este exemplo implementa um bindEvent que suporta tanto a associação direta quanto a delegação de eventos, utilizando a API moderna.
/**
* Função genérica para associar eventos.
* @param {Element} elemento - O elemento DOM ao qual o evento será associado.
* @param {string} tipo - O tipo de evento (ex: 'click', 'mouseover').
* @param {string|Function} seletorOuFuncao - Um seletor CSS para delegação de eventos, ou a função de callback se não houver delegação.
* @param {Function} [funcaoCallback] - A função a ser executada quando o evento ocorrer (usada apenas com delegação).
*/
function bindEvent(elemento, tipo, seletorOuFuncao, funcaoCallback) {
let callbackFn = funcaoCallback;
let selector = null;
// Verifica se é delegação de evento ou associação direta
if (typeof seletorOuFuncao === 'function') {
// Associação direta: seletorOuFuncao é o callback
callbackFn = seletorOuFuncao;
} else {
// Delegação de evento: seletorOuFuncao é o seletor
selector = seletorOuFuncao;
}
elemento.addEventListener(tipo, function(event) {
const target = event.target;
if (selector) {
// Delegação: verifica se o elemento alvo corresponde ao seletor
if (target.matches(selector)) {
// Executa o callback com o contexto (this) definido para o elemento alvo
callbackFn.call(target, event);
}
} else {
// Associação direta: executa o callback
callbackFn.call(target, event);
}
});
}
Exemplos de Uso
Associação direta de evento em um botão:
const meuBotao = document.getElementById('botaoExemplo');
bindEvent(meuBotao, 'click', function(event) {
console.log('Botão clicado!', event);
event.preventDefault(); // Exemplo: previne ação padrão
event.stopPropagation(); // Exemplo: para propagação
});
Delegação de evento em uma lista:
const minhaLista = document.getElementById('listaItens');
// Associa um ouvinte de clique na lista, que executará a função apenas se o alvo for um item com a classe 'item-especifico'
bindEvent(minhaLista, 'click', '.item-especifico', function(event) {
console.log('Item específico da lista clicado:', event);
event.preventDefault();
event.stopPropagation();
});
- Item 1
- Item 2 (Específico)
- Item 3
- Item 4 (Específico)
Clique Aqui