Já havia me deparado com esse problema algumas vezes e sempre contornava, sem pensar em uma solução. Acontece que estou no meio da organização de uma biblioteca js - algo que me evite a criação de um novo arquivo js com as mesmas funções a cada novo projeto. Bem, o fato é que novamente encontrei a mesma dificuldade... vou criar um exemplo simples para explicar. Supondo que temos as seguintes funções:
-
function toRed() {
-
this.style.color = "red";
-
}
-
function toBlue() {
-
this.style.color = "blue";
-
}
Então pego um div e copio as funções para os eventos mouseover e mouseout:
-
var div = document.getElementById('meudiv');
-
div.onmouseover = toRed;
-
div.onmouseout = toBlue;
Bacana, isso vai funcionar que é uma beleza. Mas durante a navegação, meu visitante executou uma ação que precisou adicionar mais um comando ao evento mouseover do mesmo div, mas não quero perder as ações que já estão lá. Se usar novamente comandos como os que usei acima, a função será substituída pela nova... Aí lembrei daquela famosa função addEvent(), que usa os métodos addEventListener ou attachEvent, isso tanto para objetos tipo Object quanto para elementos HTML, "pegos" via DOM. Bem, funcionou, as funções foram executadas, mas aí eu fiz a incrível descoberta de que esses métodos não criam uma propriedade no elemento com a função enviada. Ao invés disso, ela é executada "externamente ao objeto", portanto a minha palavra this não aponta para o meu elemento, mas para o objeto window.
Bolei então um modo de resolver essa situação criando uma propriedade chamada events no elemento. É um objeto que vai armazenando as funções registradas para os eventos e para gerenciar isso vamos inserir alguns novos métodos no elemento: addEvent, removeEvent, clearEvent, hasEvent e fireEvent. Para que tudo aconteça, criamos a função elementEvents, que aplica os novos métodos ao elemento enviado - e função extendAll apenas para aplicar a primeira a todos os elementos da página. Veja o código:
-
function elementEvents(el) {
-
el.addEvent = function addEvent(evname, func) {
-
evname = /^on/.test(evname) ? evname : "on"+evname;
-
var fire = new Function("e", "this.fireEvent(\""+evname+
-
"\", e || window.event);");
-
if(typeof this.events == "undefined") this.events = {};
-
if(typeof this.events[evname] == "undefined")
-
this.events[evname] = [];
-
if(typeof this[evname] == 'function' &&
-
this[evname].toString() != fire.toString())
-
this.events[evname].push(this[evname]);
-
if(!this.hasEvent(evname, func)) this.events[evname].push(func);
-
this[evname] = fire;
-
return this;
-
};
-
-
el.removeEvent = function (evname, func) {
-
evname = /^on/.test(evname) ? evname : "on"+evname;
-
if(typeof this.events == "undefined" ||
-
typeof this.events[evname] == "undefined" ||
-
!this.events[evname].length) return this;
-
var ret = [];
-
for(var i = 0; i < this.events[evname].length; i++)
-
if(this.events[evname][i].toString() != func.toString())
-
ret.push(this.events[evname][i]);
-
this.events[evname] = ret;
-
return this;
-
};
-
-
el.clearEvent = function (evname) {
-
evname = /^on/.test(evname) ? evname : "on"+evname;
-
if(typeof this.events == "object" &&
-
typeof this.events[evname] != "undefined" &&
-
this.events[evname].length) delete(this.events[evname]);
-
this[evname] = null;
-
return this;
-
};
-
-
el.hasEvent = function (evname, func) {
-
evname = /^on/.test(evname) ? evname : "on"+evname;
-
if(typeof this.events == "undefined" ||
-
typeof this.events[evname] == "undefined" ||
-
!this.events[evname].length) return false;
-
for(var i = 0; i < this.events[evname].length; i++)
-
if(this.events[evname][i].toString() == func.toString())
-
return true;
-
return false;
-
};
-
-
el.fireEvent = function (evname, ev) {
-
evname = /^on/.test(evname) ? evname : "on"+evname;
-
if(typeof this.events[evname] == "undefined") return true;
-
for(var i = 0; i < this.events[evname].length; i++) {
-
if(typeof this.events[evname][i] == "function") {
-
this.tempFunction = this.events[evname][i];
-
this.tempFunction(ev);
-
}
-
}
-
};
-
return el;
-
}
-
-
function extendAll() {
-
var els = document.getElementsByTagName("*");
-
for(var i = 0; i < els.length; i++) elementEvents(els[i]);
-
}
-
-
// window.onload = extendAll;
-
// ou...
-
// addEvent(window, 'load', extendAll);
As últimas linhas mostram duas maneiras diferentes de aplicar a função extendAll ao evento onload do objeto window, fazendo com que os mátodos sejam aplicados a todos os elementos da página. Mas podemos usar o sistema em um elemento específico... veja:
-
var div = document.getElementById('meudiv');
-
elementEvents(div);
-
div.addEvent('mouseover', toRed);
-
div.addEvent('mouseout', toBlue);
-
div.addEvent('mouseover',
-
function() {
-
this.style.border = "2px solid #666";
-
});
-
div.addEvent('mouseout',
-
function() {
-
this.style.borderWidth = "0px";
-
});
Após esses comandos, a propriedade events do div estará com a seguinte anatomia:
-
{
-
'mouseover': [
-
function() { this.style.color = "red"; },
-
function() { this.style.border = "2px solid #666"; }
-
],
-
'mouseout': [
-
function() { this.style.color = "blue"; },
-
function() { this.style.borderWidth = "0px"; }
-
]
-
}
-
As funções de cada evento serão executadas na seqüência do array de funções, mas até aí nada... se simplesmente executarmos as funções num loop, o this ainda não estará apontando para o elemento. A mágica está no método tempFunction, criado em fireEvent, que recebe a função e a executa como parte do objeto. Isso faz com que o evento seja repassado corretamente (inclusive para IE) e que a palavra this aponte para o próprio elemento.
Um comentário
Opa, isso aqui é uma ótima solução visto que trabalhar com o addEventListener não nos dá um controle total (também não permite clonagem do evento caso clonemos o objeto html).
Mas você também poderia ter pegado o target lá dentro da função, veja isto: http://elcio.com.br/crossbrowser/#7