I have a Javascript plugin that searches the DOM for any elements starting with the class name "tracking" and adds a click event listener (or another type of listener, if specified) to that element. The idea is that every time that event occurs on that element, that it runs a Javascript function that sends data to our traffic servers. Here's what the code looks like:
// Once the page is completed loaded
window.mmload(function() {
// Get the container object
obj = document.getElementById(name);
if ( obj.length < 0 )
throw ("The Id passed into the tracker does not exist ("+name+")");
// Find all the elements belonging to the tracking class
var trackingClass = new RegExp( /tracking\[[a-zA-Z0-9\.\-_]+\]/g );
var myElements = getElementsByRegex( trackingClass, obj );
//For each of those elements...
for( var i in myElements ) {
var elm = myElements[i];
var method = elm.className.match( /tracking\[[a-zA-Z0-9\.\-_]+\]/ )[0].split('[')[1].replace(']','').split('.')[2];
method = typeof( method ) == 'undefined' ? 'click' : method;
// Add a click event listener
myElements[i].addEventListener( method, function(e){
// Get the element, the link (if any), and the args of the event
var link = elm.getAttribute('href') == null ? "" : elm.getAttribute('href');
var args = elm.className.match( /tracking\[[a-zA-Z0-9\.\-_]+\]/ )[0].split('[')[1].replace(']','').split('.');
// If a link existed, pause it, for now
if ( link != '' )
e.preventDefault();
// Track the event
eventTracker( args[0], args[1], ( method == 'click' ? 'redirect' : 'default' ), link );
return false;
}, true);
}
});
Right now I've got this chuck of code running once the window has completely loaded (window.mmload() is a function I made for appending window.onload events). However, there maybe times when I need to run this function again because I added new elements to the DOM via Javascript with this class name and I want to track them too.
My initial solution was to run this function using setInterval to check the DOM every few milliseconds or second or whatever makes the most sense. However, I was worried if I took this approach that it might slow down the website, especially since this is running on a mobile website for smartphones. I'm not sure what kind of a performance hit I might take if I'm searching to DOM every so often.
The other approach I had in mind was to simply call the function after adding traceable elements to the DOM. This is probably the most efficient way of handling it. However, the people that I'm working with, granted very smart individuals, are Web Designers who don't often think about nor understand very well code. So the simpler I can make this, the better. That's why I liked the setInterval approach because nothing additional would be required of them. But if it noticeably slows down the site, I might have to take the other approach.
You should consider even delegation.
You just add one event listener to the document root and check the class of the element the event originated from (event.target). If you want to include also clicks from descendants, you'd have to traverse the DOM up form the target and check whether any of the ancestors contains the class.
I see two main advantages:
It works for newly generated elements without any extra steps (so the other developers don't have to do anything special).
It adds only one event handler instead of potentially many, which saves memory.
Disadvantages:
If other event handlers are registered along the path and they prevent the event from bubbling up, you cannot register this event.
A bit more information:
An event handler gets an event object as first argument. This object has several properties, among others, which element the event originated form.
E.g. to get the target element:
var element = event.target || event.srcElement;
This will be a DOM element and you can access the classes via element.className.
So your event listener could look like this (note that IE uses another method to attach event listeners and the event object is not passed but available via window.event):
function handler(event) {
event = event || window.event;
var target = event.target || event.srcElement;
if(target.className.match(/tracking\[[a-zA-Z0-9\.\-_]+\]/g) {
// do your stuff
}
}
if(document.addEventListener) {
document.addEventListener('click', handler, false);
}
else {
document.attachEvent('onclick', handler);
}
But as I said, this would miss events that are prevented from bubbling up. At least in the browsers following the W3C model (so not IE), you can handle the events in the capture phase by setting the last parameter to true:
document.addEventListener('click', handler, true);
If you can live without IE, then there is a change event which you can hook into for the window/document/dom element. Simply hook into the event at the document level, and it'd fire anytime something's changed in the page (stuff inserted, deleted, changed). I believe the event's context contains what got changed, so it should be fairly trivial to find any new trackable elements and attach your spy code to it.
A third option would be to write a method for manipulating the innerHTML of an element. At the end of that method simply call your function that refreshes everything.
example:
var setHtml = function(element, newHtml){
element.innerhtml = newHtml;
yourRefreshFunction();
}
So obviously this requires that you have your web developers user this method to update the dom. And you'll have to do it for anything that is more complicated than simple html edits. But that gives you the idea.
Hope that helps!
Related
Is there any way to get the list of all event listeners of an element on the HTML page using JavaScript on that page.
Note: I know we can see them with Chrome dev tools event listeners but I want to log/access see list using the JavaScript of the page.
Also, I know we can get them through jQuery but for that, we also have to apply the events using jQuery, but I want something that would be generic so I could also access the event listeners applied to other elements such as web components or react components.
If you really had to, a general way to do this would be to patch EventTarget.prototype.addEventListener:
const listeners = [];
const orig = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(...args) {
if (this instanceof HTMLElement) {
listeners.push({
type: args[0],
fn: args[1],
target: this,
});
}
return orig.apply(this, args);
};
document.body.addEventListener('click', () => console.log('body clicked'));
console.log(listeners[0].fn);
click this body
To find listeners attached to an element, iterate through the listeners array and look for targets which match the element you're looking for.
To be complete, also patch removeEventListener so that items can be removed from the array when removed.
If you need to watch for listeners attached via on, then you'll have to do something similar to the above to patch the HTMLElement.prototype.onclick getter/setter, and for each listener you want to be able to detect.
That said, although you said you want a generic solution, rather than patching built-in prototypes, it'd be better to add the listeners through jQuery or through your own function.
What I did when I had a similar problem is add a data attribute when the listener was set, so I could identify it later.
At the end of the function that adds the listener:
elm.setAttribute('data-has_mask', true);
At the beginning of that same function:
if("true" == elm.getAttribute('data-has_mask')) {
return;
}
Maybe not exactly what the OP is looking for, but I was having a lot of trouble with this, and this is an obvious solution for a particular use case, and I guess it might help someone out.
I have Javascript that people are including in their page. In my Javascript I have a version of jQuery (1.8 for sake of easy reference) that is sectioned off into its own namespace, and referenced via a global variable (but not one of the two default vars of "$" or "jQuery"). This allows users to have jQuery in their page and have it not interfere with the stuff I'm doing internally in my functions.
So we have one page that has jQuery already (1.4), and everything works fine, except that the user and my code are both listening to "click" events on elements, and theirs is going first, so on the few events they do that return false, jQuery stops propagation and my event never gets triggered. I need my event to go first. The user is expecting my onClick functionality to still work.
Now I know that jQuery keeps its own order of events internally through the _data() object, and through this it is possible to unbind existing events, bind my event, then rebind the existing events, but that only applies to objects bound through that instance of jQuery. I'd rather not just blindly look for the jQuery object in hopes that the conflict was introduced by a user's own version of jQuery. After all what happens when a user binds the event not through jQuery? Trying to manipulate the existing jQuery object in the page isn't a good solution.
I know that, depending on browser, they are using addEventListener/removeEventListener or attachEvent/detachEvent. If only I could get a listing of the already added events, I could rebind them in the order I wanted, but I can't find out how. Looking through the DOM via Chrome inspect I don't see onclick bound anywhere (not on the object, not on window or document either).
I'm having the darndest time trying to figure out just exactly where jQuery binds its listening. To be able to control the order of its own events, jQuery must blanketly listen somewhere and then fire off its own functions right? If I could figure out where that's done I might get some insight into how to ensure my event is always first. Or maybe there's some Javascript API I haven't been able to find on Google.
Any suggestions?
We solved this by just adding a little jQuery extension that inserts events at the head of the event chain:
$.fn.bindFirst = function(name, fn) {
var elem, handlers, i, _len;
this.bind(name, fn);
for (i = 0, _len = this.length; i < _len; i++) {
elem = this[i];
handlers = jQuery._data(elem).events[name.split('.')[0]];
handlers.unshift(handlers.pop());
}
};
Then, to bind your event:
$(".foo").bindFirst("click", function() { /* Your handler */ });
Easy peasy!
As Bergi and Chris Heald said in the comments, it turns out there's no way to get at the existing events from the DOM, and no method to insert events "first". They are fired in the order they were inserted by design, and hidden by design. As a few posters mentioned you have access to the ones added through the same instance of jQuery that you're using via jQuery's data, but that's it.
There is one other case where you can run before an event that was bound before your code ran, and that's if they used the "onclick" HTML attribute. In that case you can write a wrapper function, as nothingisnecessary pointed out in a rather over-the-top toned comment below. While this wouldn't help in the instance of the original question I asked, and it's now very rare for events to be bound this way (most people and frameworks use addEvent or attachEventListener underneath now), it is one scenario in which you can solve the issue of "running first", and since a lot of people visit this question looking for answers now, I thought I'd make sure the answer is complete.
I encounter an opposite situation where I was asked to include a library, which uses event.stopImmediatePropagation() on an element, to our website. So some of my event handlers are skipped. Here is what I do (as answered here):
<span onclick="yourEventHandler(event)">Button</span>
Warning: this is not the recommended way to bind events, other developers may murder you for this.
Its not a proper solution, but ... You can add event handler to parent node in capture phase. Not on target element itself!
<div>
<div id="target"></div>
</div>
target.parentNode.addEventListener('click',()=>{console.log('parent capture phase handler')},true)
Third argument in addEventListener means:
true - capture phase
false - bubble phase
Helpful links:
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
https://javascript.info/bubbling-and-capturing
Found it easiest to add addListener and removeListener methods to document (as that's only where I need them - I suppose you can use Element.prototype and this instead). Only one "real" listener is added per type, and it's just a func to call the actual listeners in order. The eventListeners dictionary is added to document (so can mess with the handler or order).
[edit]
I think the correct answer for most cases is to use the 3rd argument of addEventListener: https://stackoverflow.com/a/29923421. The answer below ignores the argument (on purpose).
[edit] Updated code to only add one extra property: document.eventHandlers + modified naming.
// Storage.
document.eventListeners = {}; // { type: [ handlerFunc, listenerFuncs ] }
// Add event listener - returns index.
document.addListener = (type, listener, atIndex) => {
// Get info.
const listening = document.eventListeners[type];
// Add to existing.
if (listening) {
// Clean up.
atIndex = atIndex || 0;
const listeners = listening[1]; // Array of funcs.
// Already has.
const iExists = listeners.indexOf(listener);
if (iExists !== -1) {
// Nothing to do.
if (iExists === atIndex)
return atIndex;
// Remove from old position.
listeners.splice(atIndex, 1);
}
// Add (supporting one cycle of negatives).
const nListeners = listeners.length;
if (atIndex > nListeners)
atIndex = nListeners;
else if (atIndex < 0)
atIndex = Math.max(0, atIndex + nListeners + 1);
listeners.splice(atIndex, 0, listener);
}
// New one.
else {
// Handler func.
const handler = (...args) => {
const listening = document.eventListeners[type];
if (listening) {
const listeners = listening[1]; // Array of funcs.
for (const listener of listeners)
listener(...args);
}
};
// Update dictionary.
document.eventListeners[type] = [ handler, [ listener ] ];
// Add listener.
document.addEventListener(type, handler);
// First one.
atIndex = 0;
}
// Return index.
return atIndex;
};
// Remove event listener - returns index (-1 if not found).
document.removeListener = (type, listener) => {
// Get info.
const listening = document.eventListeners[type];
if (!listening)
return -1;
// Check if exists.
const listeners = listening[1];
const iExists = listeners.indexOf(listener);
if (iExists !== -1) {
// Remove listener.
listeners.splice(iExists, 1);
// If last one.
if (!listeners.length) {
// Remove listener.
const handlerFunc = listening[0];
document.removeEventListener(type, handlerFunc);
// Update dictionary.
delete document.eventListeners[type];
}
}
// Return index.
return iExists;
}
Aliaksei Pavlenkos suggestion about useCapture can be used. His allegation that it must be attached to the parent node is wrong: MDN
Event listeners in the “capturing” phase are called before event listeners in any non-capturing phases
target.addEventListener(type, listener, useCapture);
Just so it's said, I think this might be possible if you override the native implementations of these functions. This is BAD practice - very bad practice when developing a library to alter native implementations, because it can easily conflict with other libraries.
However, for completeness, here's one possibility (completely untested, just demonstrating the general concept):
// override createElement()
var temp = document.createElement;
document.createElement = function() {
// create element
var el = document.createElement.original.apply(document, arguments);
// override addEventListener()
el.addEventListenerOriginal = el.addEventListener;
el._my_stored_events = [];
// add custom functions
el.addEventListener = addEventListenerCustom;
el.addEventListenerFirst = addEventListenerFirst;
// ...
};
document.createElement.original = temp;
// define main event listeners
function myMainEventListeners(type) {
if (myMainEventListeners.all[type] === undefined) {
myMainEventListeners.all[type] = function() {
for (var i = 0; i < this._my_stored_events.length; i++) {
var event = this._my_stored_events[i];
if (event.type == type) {
event.listener.apply(this, arguments);
}
}
}
}
return myMainEventListeners.all[type];
}
myMainEventListeners.all = {};
// define functions to mess with the event list
function addEventListenerCustom(type, listener, useCapture, wantsUntrusted) {
// register handler in personal storage list
this._my_stored_events.push({
'type' : type,
'listener' : listener
});
// register custom event handler
if (this.type === undefined) {
this.type = myMainEventListeners(type);
}
}
function addEventListenerFirst(type, listener) {
// register handler in personal storage list
this._my_stored_events.push({
'type' : type,
'listener' : listener
});
// register custom event handler
if (this.type === undefined) {
this.type = myMainEventListeners(type);
}
}
// ...
A lot more work would need to be done in this regard to truly lock this down, and again, it's best not to modify native libraries. But it's a useful mental exercise that helps to demonstrate the flexibility JavaScript provides in solving problems like this.
I want to add an listener exactly once for beforeunload. This is my pseudocode:
if(window.hasEventListener('beforeunload') === false) {
window.addEventListener('beforeunload', function() { ... }, false);
}
But hasEventListener does not exist obviously. How can I achieve this? Thanks.
In fact there is no need to check if an listener was added to a target:
If multiple identical EventListeners are registered on the same EventTarget with the same parameters, the duplicate instances are discarded. They do not cause the EventListener to be called twice, and since the duplicates are discarded, they do not need to be removed manually with the removeEventListener method.
Source:https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener#Multiple_identical_event_listeners
Using jquery you can do use data("events") on any object (here the window) :
var hasbeforeunload = $(window).data("events") && $(window).data("events").['beforeunload'];
But this works only for jquery added events.
In a more general case, you should simply store the information that you add a listener somewhere :
var addedListeners = {};
function addWindowListenerIfNone(eventType, fun) {
if (addedListeners[eventType]) return;
addedListeners[eventType] = fun;
window.addEventListener(eventType, fun);
}
I think there is no standard way in javascript to get the existing event handlers. At best you could surcharge the addEventListener function of Node to intercept and store the listeners but I don't recommend it...
EDIT :
From jQuery 1.8, event data are available in $._data(element, "events"). The change log has a warning that should be taken into account :
Note that this is not a supported public interface; the actual data
structures may change incompatibly from version to version.
In Chrome Dev tool, you can check all events attached to an element (For debugging)-
// print all events attached to document
var eventObjectAttachedToDocument = getEventListeners(document);
for (var event in eventObjectAttachedToDocument) {
console.log(event);
}
I have already looked at these questions:
How to find event listeners on a DOM node when debugging or from the JavaScript code?
can I programmatically examine and modify Javascript event handlers on html elements?
How to debug JavaScript/jQuery event bindings with Firebug (or similar tool)
however none of them answers how to get a list of event listeners attached to a node using addEventListener, without modifying the addEventListener prototype before the event listeners are created.
VisualEvent doesn't display all event listener (iphone specific ones) and I want to do this (somewhat) programmatically.
Chrome DevTools, Safari Inspector and Firebug support getEventListeners(node).
You can't.
The only way to get a list of all event listeners attached to a node is to intercept the listener attachment call.
DOM4 addEventListener
Says
Append an event listener to the associated list of event listeners with type set to type, listener set to listener, and capture set to capture, unless there already is an event listener in that list with the same type, listener, and capture.
Meaning that an event listener is added to the "list of event listeners". That's all. There is no notion of what this list should be nor how you should access it.
Since there is no native way to do this ,Here is the less intrusive solution i found (dont add any 'old' prototype methods):
var ListenerTracker=new function(){
var targets=[];
// listener tracking datas
var _elements_ =[];
var _listeners_ =[];
this.init=function(){
this.listen(Element,window);
};
this.listen=function(){
for(var i=0;i<arguments.length;i++){
if(targets.indexOf(arguments[i])===-1){
targets.push(arguments[i]);//avoid duplicate call
intercep_events_listeners(arguments[i]);
}
}
};
// register individual element an returns its corresponding listeners
var register_element=function(element){
if(_elements_.indexOf(element)==-1){
// NB : split by useCapture to make listener easier to find when removing
var elt_listeners=[{/*useCapture=false*/},{/*useCapture=true*/}];
_elements_.push(element);
_listeners_.push(elt_listeners);
}
return _listeners_[_elements_.indexOf(element)];
};
var intercep_events_listeners = function(target){
var _target=target;
if(target.prototype)_target=target.prototype;
if(_target.getEventListeners)return;
if(typeof(_target.addEventListener)!=='function'||typeof(_target.removeEventListener)!=='function'){
console.log('target=',target);
throw('\nListenerTracker Error:\nUnwrappable target.');
}
// backup overrided methods
var _super_={
"addEventListener" : _target.addEventListener,
"removeEventListener" : _target.removeEventListener
};
_target["addEventListener"]=function(type, listener, useCapture){
var listeners=register_element(this);
// add event before to avoid registering if an error is thrown
_super_["addEventListener"].apply(this,arguments);
// adapt to 'elt_listeners' index
var uc=(typeof(useCapture)==='object'?useCapture.useCapture:useCapture)?1:0;
if(!listeners[uc][type])listeners[uc][type]=[];
listeners[uc][type].push({cb:listener,args:arguments});
};
_target["removeEventListener"]=function(type, listener, useCapture){
var listeners=register_element(this);
// add event before to avoid registering if an error is thrown
_super_["removeEventListener"].apply(this,arguments);
// adapt to 'elt_listeners' index
useCapture=(typeof(useCapture)==='object'?useCapture.useCapture:useCapture)?1:0;
if(!listeners[useCapture][type])return;
var lid = listeners[useCapture][type].findIndex(obj=>obj.cb===listener);
if(lid>-1)listeners[useCapture][type].splice(lid,1);
};
_target["getEventListeners"]=function(type){
var listeners=register_element(this);
// convert to listener datas list
var result=[];
for(var useCapture=0,list;list=listeners[useCapture];useCapture++){
if(typeof(type)=="string"){// filtered by type
if(list[type]){
for(var id in list[type]){
result.push({
"type":type,
"listener":list[type][id].cb,
"args":list[type][id].args,
"useCapture":!!useCapture
});
}
}
}else{// all
for(var _type in list){
for(var id in list[_type]){
result.push({
"type":_type,
"listener":list[_type][id].cb,
"args":list[_type][id].args,
"useCapture":!!useCapture
});
}
}
}
}
return result;
};
};
}();
ListenerTracker.init();
EDIT
Suggestion from #mplungjan: modified to listen to wrappable targets (singleton|constructor). 'init' tracks Element and window .
exemple with other wrappable target:
ListenerTracker.listen(XMLHttpRequest);
Suggestion from #kodfire : You may get optionals arguments with the args property.
I can't find a way to do this with code, but in stock Firefox 64, events are listed next to each HTML entity in the Developer Tools Inspector as noted on MDN's Examine Event Listeners page and as demonstrated in this image:
You can obtain all jQuery events using $._data($('[selector]')[0],'events'); change [selector] to what you need.
There is a plugin that gather all events attached by jQuery called eventsReport.
Also i write my own plugin that do this with better formatting.
But anyway it seems we can't gather events added by addEventListener method. May be we can wrap addEventListener call to store events added after our wrap call.
It seems the best way to see events added to an element with dev tools.
But you will not see delegated events there. So there we need jQuery eventsReport.
UPDATE: NOW We CAN see events added by addEventListener method SEE RIGHT ANSWER TO THIS QUESTION.
I have a predicament: I want to send some data with an event listener but also be able to remove the listener. Here's the standard closure approach...
var fn = function(e){method(e,data)};
el.addEventListener('click',fn,false);
el.removeEventListener('click',fn,false);
and you could remove the event, just fine. But say, the element was removed from the DOM? Then, you'd be left with the fn function sitting around. After removing a couple thousand DOM elements, it will result in something of a memory leak.
I've considered attaching a DOMNodeRemoved event handler, that would remove any left over functions/data along with the removed node. But apparently, that event isn't cross-browser compatible.
The only other option I've come up with would be modifying the element's DOM. Consider...
el.MyEventData = function(e){method(e,data)};
el.addEventListener('click',el.MyEventData,false);
el.removeEventListener('click',el.MyEventData,false);
Is modifying the DOM acceptable in this situation? The only sticky part of that solution is when you try to add more than one event listener. Let's say we made a custom function to parse the adding/removing of events...
function makeEvent(fn,data){
var dataFn = function(e){fn(e,data)};
//create object to hold all added events
el.myEvents = {};
//make ID for this specific event
var eventID = ranString();
//add the event to the events object
el.myEvents[eventID] = [fn,dataFn];
//finally add the listener
el.addEventListener('click',dataFn,false);
}
function destroyEvent(fn){
//find all fn references
for(var id in el.myEvents){
if (el.myEvents[id][0] == fn){
el.removeEventListener('click',el.myEvents[id][1],false);
el.myEvents[id] = null;
}
}
}
It still modifies the DOM, as before, and certainly isn't a very elegant solution either. Does anyone know of any alternative, better method for passing data?
EDIT: So, I've looked into a little of jQuery's data/event scripts. I don't completely understand the code, so if someone would clarify, it would be helpful. But it seems as though they use a similar method, by making some type of el.cache property, that holds event data.
Considering that you use addEventListener this is not an issue as all modern garbage collectors can take care of such situations. The problem with event listeners only exists in IE's implementation (7-).
Test - 10 000 addEventListener and remove element (see Windows Task Manager)
When a DOM object contains a reference
to a JavaScript object (such an event
handling function), and when that
JavaScript object contains a reference
to that DOM object, then a cyclic
structure is formed. This is not in
itself a problem. At such time as
there are no other references to the
DOM object and the event handler, then
the garbage collector (an automatic
memory resource manager) will reclaim
them both, allowing their space to be
reallocated. The JavaScript garbage
collector understands about cycles and
is not confused by them.
http://www.crockford.com/javascript/memory/leak.html
Did you consider .delegate()?
According to your jQuery question:
Each jQ object has a data property. It does not stored inside the element itself - it's very important. jQ use general storage for all elements - jQuery.cache. So when you add anything to the element like this:
$('#myEl').data('someValue', 1);
jQ do the following:
jQuery.cache[elementUniqId]['someValue'] = 1;
So element does not contain its data object. It only have an uniq id that is allows it to access to the data recorde at the global storage. (elementUniqId is autogenerated)
jQ events are stored into the element data as well:
$('#myEl').click(function() { first listener });
$('#myEl').mouseenter(function() { one more listener });
$('#myEl').click(function() { anotheer listener });
Will be stored:
jQuery.cache[elementUniqId]['events'] = {
click: [function() { first listener }, function() { anotheer listene }],
mouseenter: [function() { one more listener }]
};
It allows jQ to store the order of execution for all listeners attached to each event. And later, when you delete dom element, using jQuery - .remove(), jQuery loops through the jQuery.cache[elementUniqId]['events'] and remove each listener from the element, and after removes element cache record. It allows jQ to preven memory leaks
A possible solution to maybe take you in a different direction: add the function as an inline sibling of the element.
<span id='element12345'>Test</span><script
type='text/javascript'>function fn12345() { /* ... */ }</script>
Then, when you remove all the event listeners that you want, you can also remove the "nextSibling()" of the element you're working with.
how about a setup like this? (using IE syntax since that's what I have available right now)
<div id="clickbox" style="width: 100px; height: 100px; border: 1px solid orange;">
click here to test</div>
<input id="Button1" type="button" value="clear handler" />
<script>
var data = "derp1";
var el = document.getElementById('clickbox');
var btn = document.getElementById('Button1');
// methods
var method = function (e, dat2) { alert(dat2); };
var fn = function (e) { method(e, data) };
var remover = null;
// attachment
el.attachEvent('onclick', fn, false);
(function (id, handler) {
// handler variable is local but points to the right function
remover = function (e) {
if (document.getElementById(id)) {
// remove the original listener (handler is fn)
document.getElementById(id).detachEvent('onclick', handler, false);
alert('detached');
}
// remove last reference to the original method
handler = null;
alert('method nulled');
// clean up the remover method
e.srcElement.detachEvent('onclick', remover);
remover = null;
};
btn.attachEvent('onclick', remover);
})('clickbox', fn);
// clear the original variable but the method still exists as an event listener
fn = null;
// you should be able to remove the div element and any references to it
// without leaving any stray bits around.
setTimeout( function() {
var d = document.getElementById('clickbox');
if (d){ d.parentNode.removeChild(d) ; }
} , 6000 );
el = null;
btn = null;
</script>
I'm assuming you don't want the listener removed immediately after adding it but rather want to be able to remove it at a later time. to deal with this, the cleanup routine is given its own scope by creating an anonymous function which is immediately invoked with fn as a parameter. the anon function then has its own reference to fn maintained in the handler variable. after that, fn can be cleaned up and the remaining references to the original method exist in the listener list for your element and in the scope of the anonymous function.
within the anonymous function scope, the function remover has access to the handler variable and can use it to detach the listener. remover then detaches and clears itself so there should be nothing left with access to any version of fn/handler.
I don't have any way to verify all this right now but I think it makes sense and should hold up in modern browsers.
why not take a look at this
Binding Events To Non-DOM Objects With jQuery
http://www.bennadel.com/blog/1520-Binding-Events-To-Non-DOM-Objects-With-jQuery.htm