I'm trying to add a click event listener to a bunch of newly created divs in a for loop. The issue I'm having is that only the last div keeps its event listener. I've read up about closures and read several other posts and questions and their answers, and as far as I can tell I have it set up correctly. But it still isn't working for me. Only the final div to be iterated is receiving the event listener.
function edit_entry(k) {
wrd_input.value = k;
def_input.value = lexicon[k][1];
wrd_input.onkeyup();
delete lexicon[k];
rewrite_entries();
}
function rewrite_entries(keys = null) {
if (keys === null) { keys = []; }
let sorted_keys = sort_lex_keys();
lex_body.style.color = 'rgb(200, 200, 200)';
lex_body.innerHTML = '';
sorted_keys.forEach((key) => {
if (!keys.length || keys.includes(key)) {
lex_body.innerHTML +=
`<div class='lex-entry' id=${key}><i>${key}</i>\n<p class='pronunciation'>${lexicon[key][0]}</p>${lexicon[key][1]}</div>\n`;
let entry = document.getElementById(key)
entry.addEventListener('click', edit_entry.bind(this, key) );
}
});
}
Current state of the relevant code above. If somebody knows the issue, it'd be very helpful.
If it's relevant, this code is running via Electron (17.0.0).
An attempted solution, using an anonymous arrow function instead of a bind:
entry.addEventListener('click', () => edit_entry(key) );
yields the same result.
Update:
Changing the .innerHTML attribute of something apparently strips all event listeners. So the solution was simply to create the element completely in js, add it to the container, and then add the listener. This change to the forEach loop solves the problem:
sorted_keys.forEach((key) => {
if (!keys.length || keys.includes(key)) {
let entry = document.createElement('div');
entry.className = 'lex-entry';
let word = document.createElement('p');
word.appendChild( document.createTextNode(key) );
word.style.fontStyle = 'italic';
let pron = document.createElement('p');
pron.className = 'pronunciation';
pron.appendChild( document.createTextNode(lexicon[key][0]) );
let defn = document.createTextNode(lexicon[key][1]);
entry.append(word, pron, defn);
entry.addEventListener('click', () => edit_entry(key) );
lex_body.appendChild(entry);
So a few things crosses my mind:
Firstly, edit_entry.bind(this, key);: Are you certain this is pointing to what you want? It should be pointing to the window object. Also, is there any reason to bind context for that particular function?
Secondly innerHTML: Generally it's discouraged to use innerHTML directly. I don't know how/when the browser layouts changes to innerHTML, the div might not be on the page when you call getElementById. As an alternative, you could try document.createElement("div"), set its properties accordingly and finally append it to lex_body.
Edit
From mdn:
Please note that using innerHTML to append html elements (e.g. el.innerHTML += "link") will result in the removal of any previously set event listeners. That is, after you append any HTML element that way you won't be able to listen to the previously set event listeners.
I think that binding the function every time with this is overriding the binding every time, that should be the reason that you are only getting the last one's event.
Inside of the forEach lambda, the this keyword refers to the internal class that is calling the forEach not your rewrite_entries function. Try not binding the call, like so:
entry.addEventListener('click', () => edit_entry(key) );
Related
I'm trying to create a simple form validator for a Handlebars template. So, I used addEventListener on blur to validate the inputs but the event is being fired only once. Am I doing this right? Or is there a better way / recommended good practice for this ?
Here is JSFiddle: http://jsfiddle.net/jj4fsn9j/
function addInputValidation(el, event) {
el.addEventListener(event, function() {
var data = {
invalid: {}
};
data.invalid[el.id] = false;
data[el.id] = el.value;
console.log(el.value);
if (typeof el.value === 'undefined' || el.value === '') {
data.invalid[el.id] = true;
}
console.log(data);
updateTemplate(data);
});
Can someone help me with this ?
With each onblur-event you are calling updateTemplate(data) which in turn leads to this line:
document.getElementById('body').innerHTML = html;
Which means that everytime the event is fired, you overwrite the entire document with new content. Therefore, all the event handlers get lost.
See your updated fiddle where i - just as an example - have removed the updateTemplate() function. You will notice that here the event handlers fire accordingly, as many times as needed.
It is not good practice anyway to overwrite your entire document with every firing of an event. Either overwrite only the part of it that requires overwriting, or - if you need to stick to overwriting the entire document - make sure that you run addInputValidations() again after you change the innerHTML of the body. Again, i would like to stress though, that overwriting your body's content over and over is bad practice.
I need to accurately measure the dimensions of text within my web app, which I am achieving by creating an element (with relevant CSS classes), setting its innerHTML then adding it to the container using appendChild.
After doing this, there is a wait before the element has been rendered and its offsetWidth can be read to find out how wide the text is.
Currently, I'm using setTimeout(processText, 100) to wait until the render is complete.
Is there any callback I can listen to, or a more reliable way of telling when an element I have created has been rendered?
The accepted answer is from 2014 and is now outdated. A setTimeout may work, but it's not the cleanest and it doesn't necessarily guarantee that the element has been added to the DOM.
As of 2018, a MutationObserver is what you should use to detect when an element has been added to the DOM. MutationObservers are now widely supported across all modern browsers (Chrome 26+, Firefox 14+, IE11, Edge, Opera 15+, etc).
When an element has been added to the DOM, you will be able to retrieve its actual dimensions.
Here's a simple example of how you can use a MutationObserver to listen for when an element is added to the DOM.
For brevity, I'm using jQuery syntax to build the node and insert it into the DOM.
var myElement = $("<div>hello world</div>")[0];
var observer = new MutationObserver(function(mutations) {
if (document.contains(myElement)) {
console.log("It's in the DOM!");
observer.disconnect();
}
});
observer.observe(document, {attributes: false, childList: true, characterData: false, subtree:true});
$("body").append(myElement); // console.log: It's in the DOM!
The observer event handler will trigger whenever any node is added or removed from the document. Inside the handler, we then perform a contains check to determine if myElement is now in the document.
You don't need to iterate over each MutationRecord stored in mutations because you can perform the document.contains check directly upon myElement.
To improve performance, replace document with the specific element that will contain myElement in the DOM.
There is currently no DOM event indicating that an element has been fully rendered (eg. attached CSS applied and drawn). This can make some DOM manipulation code return wrong or random results (like getting the height of an element).
Using setTimeout to give the browser some overhead for rendering is the simplest way. Using
setTimeout(function(){}, 0)
is perhaps the most practically accurate, as it puts your code at the end of the active browser event queue without any more delay - in other words your code is queued right after the render operation (and all other operations happening at the time).
This blog post By Swizec Teller, suggests using requestAnimationFrame, and checking for the size of the element.
function try_do_some_stuff() {
if (!$("#element").size()) {
window.requestAnimationFrame(try_do_some_stuff);
} else {
$("#element").do_some_stuff();
}
};
in practice it only ever retries once. Because no matter what, by the next render frame, whether it comes in a 60th of a second, or a minute, the element will have been rendered.
You actually need to wait yet a bit after to get the after render time. requestAnimationFrame fires before the next paint. So requestAnimationFrame(()=>setTimeout(onrender, 0)) is right after the element has been rendered.
In my case solutions like setTimeout or MutationObserver weren't totaly realiable.
Instead I used the ResizeObserver. According to MDN:
Implementations should, if they follow the specification, invoke
resize events before paint and after layout.
So basically the observer always fires after layout, thus we should be able to get the correct dimensions of the observed element.
As a bonus the observer already returns the dimensions of the element. Therefore we don't even need to call something like offsetWidth (even though it should work too)
const myElement = document.createElement("div");
myElement.textContent = "test string";
const resizeObserver = new ResizeObserver(entries => {
const lastEntry = entries.pop();
// alternatively use contentBoxSize here
// Note: older versions of Firefox (<= 91) provided a single size object instead of an array of sizes
// https://bugzilla.mozilla.org/show_bug.cgi?id=1689645
const width = lastEntry.borderBoxSize?.inlineSize ?? lastEntry.borderBoxSize[0].inlineSize;
const height = lastEntry.borderBoxSize?.blockSize ?? lastEntry.borderBoxSize[0].blockSize;
resizeObserver.disconnect();
console.log("width:", width, "height:", height);
});
resizeObserver.observe(myElement);
document.body.append(myElement);
This can also we wrapped in a handy async function like this:
function appendAwaitLayout(parent, element) {
return new Promise((resolve, reject) => {
const resizeObserver = new ResizeObserver((entries) => {
resizeObserver.disconnect();
resolve(entries);
});
resizeObserver.observe(element);
parent.append(element);
});
}
// call it like this
appendAwaitLayout(document.body, document.createElement("div")).then((entries) => {
console.log(entries)
// do stuff here ...
});
The MutationObserver is probably the best approach, but here's a simple alternative that may work
I had some javascript that built the HTML for a large table and set the innerHTML of a div to the generated HTML. If I fetched Date() immediately after setting the innerHTML, I found that the timestamp was for a time prior to the table being completely rendered. I wanted to know how long the rendering was taking (meaning I needed to check Date() after the rendering was done). I found I could do this by setting the innerHTML of the div and then (in the same script) calling the click method of some button on the page. The click handler would get executed only after the HTML was fully rendered, not just after the innerHTML property of div got set. I verified this by comparing the Date() value generated by the click handler to the Date() value retrieved by the script that was setting the innerHTML property of the div.
Hope someone finds this useful
suppose your element has classname class="test"
The following function continue test if change has occured
if it does, run the function
function addResizeListener(elem, fun) {
let id;
let style = getComputedStyle(elem);
let wid = style.width;
let hei = style.height;
id = requestAnimationFrame(test)
function test() {
let newStyle = getComputedStyle(elem);
if (wid !== newStyle.width ||
hei !== newStyle.height) {
fun();
wid = newStyle.width;
hei = newStyle.height;
}
id = requestAnimationFrame(test);
}
}
let test = document.querySelector('.test');
addResizeListener(test,function () {
console.log("I changed!!")
});
when you make for example
var clonedForm = $('#empty_form_to_clone').clone(true)[0];
var newForm = $(clonedForm).html().replace(/__prefix__/g, next_index_id_form);
// next_index_id_form is just a integer
What am I doing here?
I clone a element already rendered and change the html to be rendered.
Next i append that text to a container.
$('#container_id').append(newForm);
The problem comes when i want to add a event handler to a button inside newForm, WELL, just use ready event.
$(clonedForm).ready(function(event){
addEventHandlerToFormButton();
})
I hope this help you.
PS: Sorry for my English.
According to #Elliot B.'s answer, I made a plan that suits me.
const callback = () => {
const el = document.querySelector('#a');
if (el) {
observer.disconnect();
el.addEventListener('click', () => {});
}
};
const observer = new MutationObserver(callback);
observer.observe(document.body, { subtree: true, childList: true });
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'm having a problem whereby a loop which is instantiating two objects, which in turn create elements and attach event handlers, only actually attach the event handlers for the last iteration of the loop. While I'm aware of issues with scope inside anonymous functions in a loop, I can't see how that is applicable here, and I'm left scratching my head...
So, first, a setContent method of a view object iterates over an array of content, and creates two instances of the CrowdControl.ui.Button object: (irrelevant code removed for clarity)
setContent: function(content){
if(content.length > 0){
for(var i in content){
var c = content[i];
var container = $('<div></div>');
//content array code removed for clarity
var reject = new CrowdControl.ui.Button({label:'Reject'});
var approve = new CrowdControl.ui.Button({label:'Approve'});
container.append(reject.draw());
container.append(approve.draw());
this.contentlist.addItem(container);
}
}
}
Here is the Button object (again some code removed for clarity)
CrowdControl.ui.Button = function(){
this.uniqueId = Math.random(); //added for debugging
var els = this.elements = {}
els.container = $('<div class="cc-button"></div>');
els.label = $('<div></div>').text('Debug');
els.container.append(els.label);
}
CrowdControl.ui.Button.prototype.draw = function(){
console.log('Drawing element '+this.uniqueId);
var els = this.elements;
els.container.remove();
console.log('Binding click to button');
console.log(els.container);
//attach the click handler - this will only attach during the last iteration
els.container.click( function(){ console.log('wtf'); });
return els.container;
}
The above code logs that all the elements are made, and they're all unique, but only the last two instances of the Button object receive the event handlers and log 'wtf' when clicked.
Any help would be hugely appreciated - if you need any more code or explanation just ask...
I fixed you code: http://jsfiddle.net/maniator/E8XcQ/6/
Click should be done like this:
//attach the click handler
$(els.container).click(function() {
console.log('wtf');
});
You accidentally attached the click to the DOM element and not the jQuery element
Thanks for the help Neal. It actually turned out the problem was elsewhere in my framework, where a view was incorrectly redrawing after each iteration, and calling .remove() each time, which was of course removing the event handlers from child elements.
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