Consider a basic addEventListener as
window.onload=function(){
document.getElementById("alert")
.addEventListener('click', function(){
alert("OK");
}, false);
}
where <div id="alert">ALERT</div> does not exist in the original document and we call it from an external source by AJAX. How we can force addEventListener to work for newly added elements to the documents (after initial scan of DOM elements by window.onload)?
In jQuery, we do this by live() or delegate(); but how we can do this with addEventListener in pure Javascript? As a matter of fact, I am looking for the equivalent to delegate(), as live() attaches the event to the root document; I wish to make a fresh event listening at the level of parent.
Overly simplified and is very far away from jQuery's event system but the basic idea is there.
http://jsfiddle.net/fJzBL/
var div = document.createElement("div"),
prefix = ["moz","webkit","ms","o"].filter(function(prefix){
return prefix+"MatchesSelector" in div;
})[0] + "MatchesSelector";
Element.prototype.addDelegateListener = function( type, selector, fn ) {
this.addEventListener( type, function(e){
var target = e.target;
while( target && target !== this && !target[prefix](selector) ) {
target = target.parentNode;
}
if( target && target !== this ) {
return fn.call( target, e );
}
}, false );
};
What you are missing on with this:
Performance optimizations, every delegate listener will run a full loop so if you add many on a single element, you will run all these loops
Writable event object. So you cannot fix e.currentTarget which is very important since this is usually used as a reference to some instance
There is no data store implementation so there is no good way to remove the handlers unless you make the functions manually everytime
Only bubbling events are supported, so no "change" or "submit" etc which you took for granted with jQuery
Many others which I'm simply forgetting about for now
document.addEventListener("DOMNodeInserted", evtNewElement, false);
function evtNewElement(e) {
try {
switch(e.target.id) {
case 'alert': /* addEventListener stuff */ ; break;
default: /**/
}
} catch(ex) {}
}
Note: according to the comment of #hemlock, it seems this family of events is deprecated. We have to head towards mutation observers instead.
Related
I'm currently attempting to test some code that uses drag-and-drop. I found some other questions that were kinda related to this, but they were way too specific to help me, or not related enough.
This being a test, I'm struggling on trying to automatically execute code inside a .on('drop',function(e){....} event. The main issue is not that I can't run the code inside, but it's that I can't transfer the dataTransfer property, and I can't seem to fake it because it's read-only. Is there anyway to fake the dataTransfer property or otherwise get around it?
I came up with this JSFiddle that serves as a template of what I'm trying to do: https://jsfiddle.net/gnq50hsp/53/
Essentially if you are able to explain to me (if this is at all possible) how I can possibly fake the dataTransfer property, I should be all set.
Side notes:
I'm totally open to other ways of somehow getting inside that code, like for example, maybe its possible to trigger the event and pass in a fake event object with a fake dataTransfer object.
To see the drag-drop behavior, change the JavaScript load type from no-wrap head to on-Load, then you should see what I'm trying to simulate.
Important to note that I cannot modify any of the code inside the event handlers, only inside the outside function
Using Karma/Jasmine so use of those tools are also possible like spies
Also, I'm using Chrome.
Thanks in advance, and let me know for any questions/clarifications!
You should be able to override pretty much everything you want using Object.defineProperty. Depending on what you want to test it can be very simple or very complex. Faking the dataTransfer can be a bit tricky, since there's a lot of restrictions and behaviors linked to it, but if you simply want to test the drop function, it's fairly easy.
Here's a way, this should give you some ideas as to how to fake some events and data:
//Event stuff
var target = $('#target');
var test = $('#test');
test.on('dragstart', function(e) {
e.originalEvent.dataTransfer.setData("text/plain", "test");
});
target.on('dragover', function(e) {
//e.dataTransfer.setData('test');
e.preventDefault();
e.stopPropagation();
});
target.on('dragenter', function(e) {
e.preventDefault();
e.stopPropagation();
});
//What I want to simulate:
target.on('drop', function(e) {
console.log(e)
//Issue is that I can't properly override the dataTransfer property, since its read-only
document.getElementById('dataTransferDisplay').innerHTML = e.originalEvent.dataTransfer.getData("text");
});
function simulateDrop() {
// You'll need the original event
var fakeOriginalEvent = new DragEvent('drop');
// Using defineProperty you can override dataTransfer property.
// The original property works with a getter and a setter,
// so assigning it won't work. You need Object.defineProperty.
Object.defineProperty(fakeOriginalEvent.constructor.prototype, 'dataTransfer', {
value: {}
});
// Once dataTransfer is overridden, you can define getData.
fakeOriginalEvent.dataTransfer.getData = function() {
return 'test'
};
// TO have the same behavior, you need a jquery Event with an original event
var fakeJqueryEvent = $.Event('drop', {
originalEvent: fakeOriginalEvent
});
target.trigger(fakeJqueryEvent)
}
https://jsfiddle.net/0tbp4wmk/1/
As per jsfiddel link you want to achieve drag and drop feature. jQuery Draggable UI already provides this feature why you can not use that?
For create custom event on your way you have to follow two alternative ways
$('your selector').on( "myCustomEvent", {
foo: "bar"
}, function( event, arg1, arg2 ) {
console.log( event.data.foo ); // "bar"
console.log( arg1 ); // "bim"
console.log( arg2 ); // "baz"
});
$( document ).trigger( "myCustomEvent", [ "bim", "baz" ] );
On above example
In the world of custom events, there are two important jQuery methods: .on() and .trigger(). In the Events chapter, we saw how to use these methods for working with user events; for this chapter, it's important to remember two things:
.on() method takes an event type and an event handling function as arguments. Optionally, it can also receive event-related data as its second argument, pushing the event handling function to the third argument. Any data that is passed will be available to the event handling function in the data property of the event object. The event handling function always receives the event object as its first argument.
.trigger() method takes an event type as its argument. Optionally, it can also take an array of values. These values will be passed to the event handling function as arguments after the event object.
Here is an example of the usage of .on() and .trigger() that uses custom data in both cases:
OR
jQuery.event.special.multiclick = {
delegateType: "click",
bindType: "click",
handle: function( event ) {
var handleObj = event.handleObj;
var targetData = jQuery.data( event.target );
var ret = null;
// If a multiple of the click count, run the handler
targetData.clicks = ( targetData.clicks || 0 ) + 1;
if ( targetData.clicks % event.data.clicks === 0 ) {
event.type = handleObj.origType;
ret = handleObj.handler.apply( this, arguments );
event.type = handleObj.type;
return ret;
}
}
};
// Sample usage
$( "p" ).on( "multiclick", {
clicks: 3
}, function( event ) {
alert( "clicked 3 times" );
});
On above example
This multiclick special event maps itself into a standard click event, but uses a handle hook so that it can monitor the event and only deliver it when the user clicks on the element a multiple of the number of times specified during event binding.
The hook stores the current click count in the data object, so multiclick handlers on different elements don't interfere with each other. It changes the event type to the original multiclick type before calling the handler and restores it to the mapped "click" type before returning:
Is there anyway to remove an event listener added like this:
element.addEventListener(event, function(){/* do work here */}, false);
Without replacing the element?
There is no way to cleanly remove an event handler unless you stored a reference to the event handler at creation.
I will generally add these to the main object on that page, then you can iterate and cleanly dispose of them when done with that object.
You could remove the event listener like this:
element.addEventListener("click", function clicked() {
element.removeEventListener("click", clicked, false);
}, false);
Anonymous bound event listeners
The easiest way to remove all event listeners for an element is to assign its outerHTML to itself. What this does is send a string representation of the HTML through the HTML parser and assign the parsed HTML to the element. Because no JavaScript is passed, there will be no bound event listeners.
document.getElementById('demo').addEventListener('click', function(){
alert('Clickrd');
this.outerHTML = this.outerHTML;
}, false);
<a id="demo" href="javascript:void(0)">Click Me</a>
Anonymous delegated event listeners
The one caveat is delegated event listeners, or event listeners on a parent element that watch for every event matching a set of criteria on its children. The only way to get past that is to alter the element to not meet the criteria of the delegated event listener.
document.body.addEventListener('click', function(e){
if(e.target.id === 'demo') {
alert('Clickrd');
e.target.id = 'omed';
}
}, false);
<a id="demo" href="javascript:void(0)">Click Me</a>
Old Question, but here is a solution.
Strictly speaking you can’t remove an anonymous event listener unless you store a reference to the function. Since the goal of using an anonymous function is presumably not to create a new variable, you could instead store the reference in the element itself:
element.addEventListener('click',element.fn=function fn() {
// Event Code
}, false);
Later, when you want to remove it, you can do the following:
element.removeEventListener('click',element.fn, false);
Remember, the third parameter (false) must have the same value as for adding the Event Listener.
However, the question itself begs another: why?
There are two reasons to use .addEventListener() rather than the simpler .onsomething() method:
First, it allows multiple event listeners to be added. This becomes a problem when it comes to removing them selectively: you will probably end up naming them. If you want to remove them all, then #tidy-giant’s outerHTML solution is excellent.
Second, you do have the option of choosing to capture rather than bubble the event.
If neither reason is important, you may well decide to use the simpler onsomething method.
Yes you can remove an anonymous event listener:
const controller = new AbortController();
document.addEventListener(
"click",
() => {
// do function stuff
},
{ signal: controller.signal }
);
You then remove the event listener like this:
controller.abort();
You may try to overwrite element.addEventListener and do whatever you want.Something like:
var orig = element.addEventListener;
element.addEventListener = function (type, listener) {
if (/dontwant/.test(listener.toSource())) { // listener has something i dont want
// do nothing
} else {
orig.apply(this, Array.prototype.slice.apply(arguments));
}
};
ps.: it is not recommended, but it will do the trick (haven't tested it)
Assigning event handlers with literal functions is tricky- not only is there no way to remove them, without cloning the node and replacing it with the clone- you also can inadvertantly assign the same handler multiple times, which can't happen if you use a reference to a handler. Two functions are always treated as two different objects, even if they are character identical.
Edit: As Manngo suggested per comment, you should use .off() instead of .unbind() as .unbind() is deprecated as of jQuery 3.0 and superseded since jQuery 1.7.
Even though this an old question and it does not mention jQuery I will post my answer here as it is the first result for the searchterm 'jquery remove anonymous event handler'.
You could try removing it using the .off() function.
$('#button1').click(function() {
alert('This is a test');
});
$('#btnRemoveListener').click(function() {
$('#button1').off('click');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button1">Click me</button>
<hr/>
<button id="btnRemoveListener">Remove listener</button>
However this only works if you've added the listener using jQuery - not .addEventListener
Found this here.
If you're using jQuery try off method
$("element").off("event");
Jquery .off() method removes event handlers that were attached with .on()
With ECMAScript2015 (ES2015, ES6) language specification, it is possible to do with this nameAndSelfBind function that magically turns an anonymous callback into a named one and even binds its body to itself, allowing the event listener to remove itself from within as well as it to be removed from an outer scope (JSFiddle):
(function()
{
// an optional constant to store references to all named and bound functions:
const arrayOfFormerlyAnonymousFunctions = [],
removeEventListenerAfterDelay = 3000; // an auxiliary variable for setTimeout
// this function both names argument function and makes it self-aware,
// binding it to itself; useful e.g. for event listeners which then will be able
// self-remove from within an anonymous functions they use as callbacks:
function nameAndSelfBind(functionToNameAndSelfBind,
name = 'namedAndBoundFunction', // optional
outerScopeReference) // optional
{
const functionAsObject = {
[name]()
{
return binder(...arguments);
}
},
namedAndBoundFunction = functionAsObject[name];
// if no arbitrary-naming functionality is required, then the constants above are
// not needed, and the following function should be just "var namedAndBoundFunction = ":
var binder = function()
{
return functionToNameAndSelfBind.bind(namedAndBoundFunction, ...arguments)();
}
// this optional functionality allows to assign the function to a outer scope variable
// if can not be done otherwise; useful for example for the ability to remove event
// listeners from the outer scope:
if (typeof outerScopeReference !== 'undefined')
{
if (outerScopeReference instanceof Array)
{
outerScopeReference.push(namedAndBoundFunction);
}
else
{
outerScopeReference = namedAndBoundFunction;
}
}
return namedAndBoundFunction;
}
// removeEventListener callback can not remove the listener if the callback is an anonymous
// function, but thanks to the nameAndSelfBind function it is now possible; this listener
// removes itself right after the first time being triggered:
document.addEventListener("visibilitychange", nameAndSelfBind(function(e)
{
e.target.removeEventListener('visibilitychange', this, false);
console.log('\nEvent listener 1 triggered:', e, '\nthis: ', this,
'\n\nremoveEventListener 1 was called; if "this" value was correct, "'
+ e.type + '"" event will not listened to any more');
}, undefined, arrayOfFormerlyAnonymousFunctions), false);
// to prove that deanonymized functions -- even when they have the same 'namedAndBoundFunction'
// name -- belong to different scopes and hence removing one does not mean removing another,
// a different event listener is added:
document.addEventListener("visibilitychange", nameAndSelfBind(function(e)
{
console.log('\nEvent listener 2 triggered:', e, '\nthis: ', this);
}, undefined, arrayOfFormerlyAnonymousFunctions), false);
// to check that arrayOfFormerlyAnonymousFunctions constant does keep a valid reference to
// formerly anonymous callback function of one of the event listeners, an attempt to remove
// it is made:
setTimeout(function(delay)
{
document.removeEventListener('visibilitychange',
arrayOfFormerlyAnonymousFunctions[arrayOfFormerlyAnonymousFunctions.length - 1],
false);
console.log('\nAfter ' + delay + 'ms, an event listener 2 was removed; if reference in '
+ 'arrayOfFormerlyAnonymousFunctions value was correct, the event will not '
+ 'be listened to any more', arrayOfFormerlyAnonymousFunctions);
}, removeEventListenerAfterDelay, removeEventListenerAfterDelay);
})();
//get Event
let obj = window; //for example
let eventStr= "blur"; //for example
let index= 0; //you can console.log(getEventListeners(obj)[eventStr]) and check index
let e = getEventListeners(obj)[eventStr][index];
//remove this event
obj .removeEventListener(eventStr,e.listener,e.useCapture);
THE END :)
i test in chrome 92, worked
How I used options parameter for my customEvent
options Optional
An object that specifies characteristics about the event listener. The available options are:
...
**once**
A boolean value indicating that the listener should be invoked at most once after being added. If true, the listener would be automatically removed when invoked.
for my custom function that I created, it worked quite nicely.
const addItemOpenEventListener = (item, openItem) => {
document.addEventListener('order:open', ({detail}) => {
if(detail.id === item.id) {
openItem();
}
}, {once: true})
};
el.addItemOpenEventListener(item, () => dispatch(itemOpen)()));
checked my console, seems like it worked (any feedback appreciated!)
The following worked well enough for me. The code handles the case where another event triggers the listener's removal from the element. No need for function declarations beforehand.
myElem.addEventListener("click", myFunc = function() { /*do stuff*/ });
/*things happen*/
myElem.removeEventListener("click", myFunc);
I'm sure we've all seen the site for vanilla-js (the fastest framework for JavaScript) ;D and I was just curious, exactly how much faster plain JavaScript was than jQuery at adding an event handler for a click. So I headed on over to jsPerf to test it out and I was quite surprised by the results.
jQuery outperformed plain JavaScript by over 2500%.
My test code:
//jQuery
$('#test').click(function(){
console.log('hi');
});
//Plain JavaScript
document.getElementById('test').addEventListener('click', function(){
console.log('hi');
});
I just can't understand how this would happen because it seems that eventually jQuery would end up having to use the exact same function that plain JavaScript uses. Can someone please explain why this happens to me?
As you can see in this snippet from jQuery.event.add it does only create the eventHandle once.
See more: http://james.padolsey.com/jquery/#v=1.7.2&fn=jQuery.event.add
// Init the element's event structure and main handler, if this is the first
events = elemData.events;
if (!events) {
elemData.events = events = {};
}
eventHandle = elemData.handle;
if (!eventHandle) {
elemData.handle = eventHandle = function (e) {
// Discard the second event of a jQuery.event.trigger() and
// when an event is called after a page has unloaded
return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) : undefined;
};
// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
eventHandle.elem = elem;
}
And here we have the addEventListener:
// Init the event handler queue if we're the first
handlers = events[type];
if (!handlers) {
handlers = events[type] = [];
handlers.delegateCount = 0;
// Only use addEventListener/attachEvent if the special events handler returns false
if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
// Bind the global event handler to the element
if (elem.addEventListener) {
elem.addEventListener(type, eventHandle, false);
} else if (elem.attachEvent) {
elem.attachEvent("on" + type, eventHandle);
}
}
}
I think it's because internally jQuery really only has to call addEventListener() once, for its own internal handler. Once that's set up, it just has to add your callback to a simple list. Thus most of the calls to .click() just do some bookkeeping and a .push() (or something like that).
Is is possible to pass an object instead of a selector as the first argument for jQuery delegate?
var ancestor = $('ancestor'),
children = ancestor.find('a');
ancestor.delegate(children, eventType, handler);
Instead of the regular:
ancestor.delegate('a', eventType, handler);
EDIT
Motivation:
var children = $('a[href^="#"]').map($.proxy(function(i, current) {
var href = $(current).attr('href');
if(href.length > 1 && givenElement.find(href).length === 1) return $(current);
},
this));
$(document).delegate(children, eventType, handler);
I want to delegate only the anchor elements that are hash linked to any element as a child of a given element. Basically I want to do something you can't do with just a selector only.
You could always just set up the delegation and then do your predicate inside the event handler:
ancestor.delegate('a[href^="#"]', 'click', function(ev) {
if (someElement.find($(ev.target).attr('href')).length > 0) {
// do whatever with ev.target
}
});
If you wanted to avoid the runtime price of doing that jQuery DOM search inside the handler, you could pre-tag all the "good" tags:
$('a[href^="#"]').each(function() {
if (someElement.find($(this).attr('href')).length > 0)
$(this).addClass("special");
});
Then your delegated event handler can just check
if ($(ev.target).hasClass('special')) {
// do stuff
}
which will perform well enough to not be a problem under any circumstances.
The reason you have to start with a selector for ".delegate()" to work is that that's the way it's implemented. The event handler always does something like:
function genericDelegateHandler(ev) {
if ($(ev.target).is(theSelector)) {
userHandler.call(this, ev);
}
}
Now, clearly it could also try and compare the actual elements in the case that you set up a delegate without a selector, but it just doesn't.
edit — #DADU (the OP) correctly points out that if you go to the trouble to mark everything with a class name, then you don't even need a fancy event handler that tests; an ordinary ".delegate()" will do it. :-)
What would be the best way to implement a mouseenter/mouseleave like event in Javascript without jQuery? What's the best strategy for cross browser use? I'm thinking some kind of checking on the event.relatedTarget/event.toElement property in the mouseover/mouseout event handlers?
Like to hear your thoughts.
(Totally changed my terrible answer. Let's try again.)
Let's assume you have the following base, cross-browser event methods:
var addEvent = window.addEventListener ? function (elem, type, method) {
elem.addEventListener(type, method, false);
} : function (elem, type, method) {
elem.attachEvent('on' + type, method);
};
var removeEvent = window.removeEventListener ? function (elem, type, method) {
elem.removeEventListener(type, method, false);
} : function (elem, type, method) {
elem.detachEvent('on' + type, method);
};
(Pretty simple, I know.)
Whenever you implement mouseenter/mouseleave, you just attach events to the
normal mouseover/mouseout events, but then check for two important particulars:
The event's target is the right element (or a child of the right element)
The event's relatedTarget is not a child of the target
So we also need a function that checks whether one element is a child of
another:
function contains(container, maybe) {
return container.contains ? container.contains(maybe) :
!!(container.compareDocumentPosition(maybe) & 16);
}
The last "gotcha" is how we would remove the event listener. The quickest way
to implement it is by just returning the new function that we're adding.
So we end up with something like this:
function mouseEnterLeave(elem, type, method) {
var mouseEnter = type === 'mouseenter',
ie = mouseEnter ? 'fromElement' : 'toElement',
method2 = function (e) {
e = e || window.event;
var target = e.target || e.srcElement,
related = e.relatedTarget || e[ie];
if ((elem === target || contains(elem, target)) &&
!contains(elem, related)) {
method();
}
};
type = mouseEnter ? 'mouseover' : 'mouseout';
addEvent(elem, type, method2);
return method2;
}
Adding a mouseenter event would look like this:
var div = document.getElementById('someID'),
listener = function () {
alert('do whatever');
};
mouseEnterLeave(div, 'mouseenter', listener);
In order to remove the event, you'd have to do something like this:
var newListener = mouseEnterLeave(div, 'mouseenter', listener);
// removing...
removeEvent(div, 'mouseover', newListener);
It's hardly ideal, but all that's left is just implementation details. The
important part was the if clause: mouseenter/mouseleave is just
mouseover/mouseout, but checking if you're targeting the right element, and if
the related target is a child of the target.
The best way, imho, is to craft your own event system.
Dean Edwards wrote one some years ago that I've taken cues from in the past. His solution does work out of the box however.
http://dean.edwards.name/weblog/2005/10/add-event/
John Resig submitted his entry to a contest, in which his was judged the best (Note: Dean Edwards was one of the jury). So, I would say, check this one out too.
Also its doesn't hurt to go thru jQuery, DOJO source once in a while, to actually see the best practices they r using to make it work cross-browser.
another option is to distinguish true mouseout events from fake (child-generated) events using hit-testing. Like so:
elt['onmouseout']=function(evt){
if (!mouse_inside_bounding_box(evt,elt)) console.debug('synthetic mouseleave');
}
I've used something like this on chrome and, caveat emptor, it seemed to do the trick. Once you have a reliable mouseleave event mouseenter is trivial.