javascript addEventListener without selecting children - javascript

I need to use javascript only for this project. Sorry, no jQuery (I feel ashamed as well).
I am adding an addEventListener to a div. "Problem" is that it applies to all its children, too.
Is there a way to avoid this, and have the listener work only for that div?
Thankd in advance.
my code looks like this:
document.getElementById(myObj.id).addEventListener("mousedown", myObjDown, false);
function myObjDown() {
//do stuff here
}

You can tell which element the event actually fired on by reading event.target in your callback.
var el = ...
el.addEventListener('click', function(event){
if (el !== event.target) return;
// Do your stuff.
}, false);
The other option would be to have handlers bound to the child elements to prevent the event from reaching the parent handler, but that is more work and potentially hides events from things that might actually be listening for them above the parent.
Update
Given your example code, you should be able to do this.
var el = document.getElementById(myObj.id);
el.addEventListener("mousedown", myObjDown, false);
function myObjDown(event) {
if (el !== event.target) return;
//do stuff here
}
Also as a general note, keep in mind that none if this will work on IE < 9 because addEventListener is not supported on those.

You can use the currentTarget Event Property
el.addEventListener('click', function(event) {
if (event.currentTarget !== event.target) {
return;
}
// Do your stuff.
}, false);
More details: https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget

Here's an alternative, which keeps your myObjDown function in line with a typical event handler. (using e.target as reference to the event invoking element)
var CssSelector = "div.className";
var elms = document.querySelectorAll(CssSelector);
for (i = 0; i < elms.length; i++) {
elms[i].addEventListener("mousedown", myObjDown.bind(null, {"target":elms[i]}, false);
}
function myObjDown(e) {
console.log("event: %o - target: %o", e, e.target);
var elm = e.target;
//do stuff here
}
It was suggested that ..
this method could cause memory leaks with versions of some browsers. If anyone experiences this or has any valuable insights. Please comment.
an alternative, in this regard would be
var CssSelector = "div.className";
var elms = document.querySelectorAll(CssSelector);
for (i = 0; i < elms.length; i++) {
elms[i].addEventListener("mousedown", myObjDown.bind(null, elms[i].id}, false);
}
function myObjDown(id) {
console.log("element: %o ", document.getElementById(id));
//do stuff here
}

this work for me:
document.getElementById(myObj.id).addEventListener("mousedown", myObjDown, false);
function myObjDown(e) {
var myTarget= ele.target;
while (myTarget!== this) {
myTarget= myTarget.parentNode; //finding correct tag
}
//do stuff here
}

Related

How to run event listener only once? [duplicate]

I'm trying to remove an event listener inside of a listener definition:
canvas.addEventListener('click', function(event) {
click++;
if(click == 50) {
// remove this event listener here!
}
// More code here ...
How could I do that? this = event...
You need to use named functions.
Also, the click variable needs to be outside the handler to increment.
var click_count = 0;
function myClick(event) {
click_count++;
if(click_count == 50) {
// to remove
canvas.removeEventListener('click', myClick);
}
}
// to add
canvas.addEventListener('click', myClick);
EDIT: You could close around the click_counter variable like this:
var myClick = (function( click_count ) {
var handler = function(event) {
click_count++;
if(click_count == 50) {
// to remove
canvas.removeEventListener('click', handler);
}
};
return handler;
})( 0 );
// to add
canvas.addEventListener('click', myClick);
This way you can increment the counter across several elements.
If you don't want that, and want each one to have its own counter, then do this:
var myClick = function( click_count ) {
var handler = function(event) {
click_count++;
if(click_count == 50) {
// to remove
canvas.removeEventListener('click', handler);
}
};
return handler;
};
// to add
canvas.addEventListener('click', myClick( 0 ));
EDIT: I had forgotten to name the handler being returned in the last two versions. Fixed.
canvas.addEventListener('click', function(event) {
click++;
if(click == 50) {
this.removeEventListener('click',arguments.callee,false);
}
Should do it.
You could use a named function expression (in this case the function is named abc), like so:
let click = 0;
canvas.addEventListener('click', function abc(event) {
click++;
if (click >= 50) {
// remove event listener function `abc`
canvas.removeEventListener('click', abc);
}
// More code here ...
}
Quick and dirty working example: http://jsfiddle.net/8qvdmLz5/2/.
More information about named function expressions: http://kangax.github.io/nfe/.
If #Cybernate's solution doesn't work, try breaking the trigger off in to it's own function so you can reference it.
clickHandler = function(event){
if (click++ == 49)
canvas.removeEventListener('click',clickHandler);
}
canvas.addEventListener('click',clickHandler);
element.querySelector('.addDoor').onEvent('click', function (e) { });
element.querySelector('.addDoor').removeListeners();
HTMLElement.prototype.onEvent = function (eventType, callBack, useCapture) {
this.addEventListener(eventType, callBack, useCapture);
if (!this.myListeners) {
this.myListeners = [];
};
this.myListeners.push({ eType: eventType, callBack: callBack });
return this;
};
HTMLElement.prototype.removeListeners = function () {
if (this.myListeners) {
for (var i = 0; i < this.myListeners.length; i++) {
this.removeEventListener(this.myListeners[i].eType, this.myListeners[i].callBack);
};
delete this.myListeners;
};
};
It looks like no one's covered the part of the current JavaScript DOM specification that gives you a mechanism to remove your event listener without using removeEventListener. If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed to control event listening:
{
type (a string)
callback (null or an EventListener object)
capture (a boolean, initially false)
passive (a boolean, initially false)
once (a boolean, initially false)
signal (null or an AbortSignal object)
removed (a boolean for bookkeeping purposes, initially false)
}
Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us tell the JS engine to remove an event listener by just calling abort() instead of having to bother with removeEventListener:
const canvasListener = (new AbortController()).signal;
canvas.addEventListener('click', () => {
click++;
if (click === 50) {
canvasListener.abort();
} else {
doSomethingWith(click);
}
}, {
signal: canvasListener
});
(Note that this does not use the useCapture flag, because the useCapture flag is essentially completely useless)
And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same properties as we called addEventListener: we just cancel the listener.
I think you may need to define the handler function ahead of time, like so:
var myHandler = function(event) {
click++;
if(click == 50) {
this.removeEventListener('click', myHandler);
}
}
canvas.addEventListener('click', myHandler);
This will allow you to remove the handler by name from within itself.
If someone uses jquery, he can do it like this :
var click_count = 0;
$( "canvas" ).bind( "click", function( event ) {
//do whatever you want
click_count++;
if ( click_count == 50 ) {
//remove the event
$( this ).unbind( event );
}
});
Hope that it can help someone.
Note that the answer given by #user113716 work nicely :)
A way to achieve that is use jquery, so you can use:
canvas.click(yourfunction);
then you can detach all event listener with:
canvas.off();
Try this, it worked for me.
<button id="btn">Click</button>
<script>
console.log(btn)
let f;
btn.addEventListener('click', f=function(event) {
console.log('Click')
console.log(f)
this.removeEventListener('click',f)
console.log('Event removed')
})
</script>

Transitionend triggered on child transitionend [duplicate]

I need to use javascript only for this project. Sorry, no jQuery (I feel ashamed as well).
I am adding an addEventListener to a div. "Problem" is that it applies to all its children, too.
Is there a way to avoid this, and have the listener work only for that div?
Thankd in advance.
my code looks like this:
document.getElementById(myObj.id).addEventListener("mousedown", myObjDown, false);
function myObjDown() {
//do stuff here
}
You can tell which element the event actually fired on by reading event.target in your callback.
var el = ...
el.addEventListener('click', function(event){
if (el !== event.target) return;
// Do your stuff.
}, false);
The other option would be to have handlers bound to the child elements to prevent the event from reaching the parent handler, but that is more work and potentially hides events from things that might actually be listening for them above the parent.
Update
Given your example code, you should be able to do this.
var el = document.getElementById(myObj.id);
el.addEventListener("mousedown", myObjDown, false);
function myObjDown(event) {
if (el !== event.target) return;
//do stuff here
}
Also as a general note, keep in mind that none if this will work on IE < 9 because addEventListener is not supported on those.
You can use the currentTarget Event Property
el.addEventListener('click', function(event) {
if (event.currentTarget !== event.target) {
return;
}
// Do your stuff.
}, false);
More details: https://developer.mozilla.org/en-US/docs/Web/API/Event/currentTarget
Here's an alternative, which keeps your myObjDown function in line with a typical event handler. (using e.target as reference to the event invoking element)
var CssSelector = "div.className";
var elms = document.querySelectorAll(CssSelector);
for (i = 0; i < elms.length; i++) {
elms[i].addEventListener("mousedown", myObjDown.bind(null, {"target":elms[i]}, false);
}
function myObjDown(e) {
console.log("event: %o - target: %o", e, e.target);
var elm = e.target;
//do stuff here
}
It was suggested that ..
this method could cause memory leaks with versions of some browsers. If anyone experiences this or has any valuable insights. Please comment.
an alternative, in this regard would be
var CssSelector = "div.className";
var elms = document.querySelectorAll(CssSelector);
for (i = 0; i < elms.length; i++) {
elms[i].addEventListener("mousedown", myObjDown.bind(null, elms[i].id}, false);
}
function myObjDown(id) {
console.log("element: %o ", document.getElementById(id));
//do stuff here
}
this work for me:
document.getElementById(myObj.id).addEventListener("mousedown", myObjDown, false);
function myObjDown(e) {
var myTarget= ele.target;
while (myTarget!== this) {
myTarget= myTarget.parentNode; //finding correct tag
}
//do stuff here
}

Remove keydown event listener [duplicate]

I'm trying to remove an event listener inside of a listener definition:
canvas.addEventListener('click', function(event) {
click++;
if(click == 50) {
// remove this event listener here!
}
// More code here ...
How could I do that? this = event...
You need to use named functions.
Also, the click variable needs to be outside the handler to increment.
var click_count = 0;
function myClick(event) {
click_count++;
if(click_count == 50) {
// to remove
canvas.removeEventListener('click', myClick);
}
}
// to add
canvas.addEventListener('click', myClick);
EDIT: You could close around the click_counter variable like this:
var myClick = (function( click_count ) {
var handler = function(event) {
click_count++;
if(click_count == 50) {
// to remove
canvas.removeEventListener('click', handler);
}
};
return handler;
})( 0 );
// to add
canvas.addEventListener('click', myClick);
This way you can increment the counter across several elements.
If you don't want that, and want each one to have its own counter, then do this:
var myClick = function( click_count ) {
var handler = function(event) {
click_count++;
if(click_count == 50) {
// to remove
canvas.removeEventListener('click', handler);
}
};
return handler;
};
// to add
canvas.addEventListener('click', myClick( 0 ));
EDIT: I had forgotten to name the handler being returned in the last two versions. Fixed.
canvas.addEventListener('click', function(event) {
click++;
if(click == 50) {
this.removeEventListener('click',arguments.callee,false);
}
Should do it.
You could use a named function expression (in this case the function is named abc), like so:
let click = 0;
canvas.addEventListener('click', function abc(event) {
click++;
if (click >= 50) {
// remove event listener function `abc`
canvas.removeEventListener('click', abc);
}
// More code here ...
}
Quick and dirty working example: http://jsfiddle.net/8qvdmLz5/2/.
More information about named function expressions: http://kangax.github.io/nfe/.
If #Cybernate's solution doesn't work, try breaking the trigger off in to it's own function so you can reference it.
clickHandler = function(event){
if (click++ == 49)
canvas.removeEventListener('click',clickHandler);
}
canvas.addEventListener('click',clickHandler);
element.querySelector('.addDoor').onEvent('click', function (e) { });
element.querySelector('.addDoor').removeListeners();
HTMLElement.prototype.onEvent = function (eventType, callBack, useCapture) {
this.addEventListener(eventType, callBack, useCapture);
if (!this.myListeners) {
this.myListeners = [];
};
this.myListeners.push({ eType: eventType, callBack: callBack });
return this;
};
HTMLElement.prototype.removeListeners = function () {
if (this.myListeners) {
for (var i = 0; i < this.myListeners.length; i++) {
this.removeEventListener(this.myListeners[i].eType, this.myListeners[i].callBack);
};
delete this.myListeners;
};
};
It looks like no one's covered the part of the current JavaScript DOM specification that gives you a mechanism to remove your event listener without using removeEventListener. If we look at https://dom.spec.whatwg.org/#concept-event-listener we see that there are a number of properties that can be passed to control event listening:
{
type (a string)
callback (null or an EventListener object)
capture (a boolean, initially false)
passive (a boolean, initially false)
once (a boolean, initially false)
signal (null or an AbortSignal object)
removed (a boolean for bookkeeping purposes, initially false)
}
Now, there's a lot of useful properties in that list, but for the purposes of removing an event listener it's the signal property that we want to make use of (which was added to the DOM level 3 in late 2020), because it lets us tell the JS engine to remove an event listener by just calling abort() instead of having to bother with removeEventListener:
const canvasListener = (new AbortController()).signal;
canvas.addEventListener('click', () => {
click++;
if (click === 50) {
canvasListener.abort();
} else {
doSomethingWith(click);
}
}, {
signal: canvasListener
});
(Note that this does not use the useCapture flag, because the useCapture flag is essentially completely useless)
And done: the JS engine will abort and clean up our event listener. No keeping a reference to the handling function, no making sure we call removeEventListener with the exact same properties as we called addEventListener: we just cancel the listener.
I think you may need to define the handler function ahead of time, like so:
var myHandler = function(event) {
click++;
if(click == 50) {
this.removeEventListener('click', myHandler);
}
}
canvas.addEventListener('click', myHandler);
This will allow you to remove the handler by name from within itself.
If someone uses jquery, he can do it like this :
var click_count = 0;
$( "canvas" ).bind( "click", function( event ) {
//do whatever you want
click_count++;
if ( click_count == 50 ) {
//remove the event
$( this ).unbind( event );
}
});
Hope that it can help someone.
Note that the answer given by #user113716 work nicely :)
A way to achieve that is use jquery, so you can use:
canvas.click(yourfunction);
then you can detach all event listener with:
canvas.off();
Try this, it worked for me.
<button id="btn">Click</button>
<script>
console.log(btn)
let f;
btn.addEventListener('click', f=function(event) {
console.log('Click')
console.log(f)
this.removeEventListener('click',f)
console.log('Event removed')
})
</script>

Bind hover (mouseenter / mouseleave) event to dynamic content with Javascript (without Jquery)

I wrote an event delegation function in javascript:
function matches(el, selector) {
var test = (el.matches || el.matchesSelector || el.msMatchesSelector || el.mozMatchesSelector || el.webkitMatchesSelector || el.oMatchesSelector);
if (test)
return test.call(el, selector);
return false;
}
function delegation(node, child, evt, fn, limit) {
node.addEventListener(evt, function (e) {
//maximum number of ancestors i'm going to check
limit = limit ? limit : 2;
e = e || event;
var target = e.target || e.srcElement, i = 0, fire = false;
while (target) {
if (matches(target, child)) {
//break out of the loop if i find the matching DOM element, then fire the event
fire = true;
break;
}
if (i > limit) {
break;
}
i++;
//If event.target doesn't has id/class/tag "child", check its ancestors.
target = target.parentNode;
}
if (fire) {
fn(target, e);
}
}, false);
}
Usage: delegation(document, 'class-or-id-or-tagName', 'event-name', function, query-limit);
It works relatively well until I stumbled upon mouse enter and mouse leave events. The problem is that the events are only triggered when my mouse leave or enter document window, not DOM element, I do understand the problem why but I can't seem to fix it. Is there any way to replicate
$(document).on('mouseenter, DOM , function).on('mouseleave', DOM, function);
in pure Javascript.
Edit: Thanks for all the comments, I found out that there's nothing wrong with my code. I just need to use the correct event name when calling the delegation function, mouseenter should be mouseover, mouseleave should be mouseout.
Changing from
delegation(document, '.some-class-name', 'mouseenter', function(){});
to
delegation(document, '.some-class-name', 'mouseover', function(){});
works wonder.

Detect click outside element (vanilla JavaScript)

I have searched for a good solution everywhere, yet I can't find one which does not use jQuery.
Is there a cross-browser, normal way (without weird hacks or easy to break code), to detect a click outside of an element (which may or may not have children)?
Add an event listener to document and use Node.contains() to find whether the target of the event (which is the inner-most clicked element) is inside your specified element. It works even in IE5
const specifiedElement = document.getElementById('a')
// I'm using "click" but it works with any event
document.addEventListener('click', event => {
const isClickInside = specifiedElement.contains(event.target)
if (!isClickInside) {
// The click was OUTSIDE the specifiedElement, do something
}
})
var specifiedElement = document.getElementById('a');
//I'm using "click" but it works with any event
document.addEventListener('click', function(event) {
var isClickInside = specifiedElement.contains(event.target);
if (isClickInside) {
alert('You clicked inside A')
} else {
alert('You clicked outside A')
}
});
div {
margin: auto;
padding: 1em;
max-width: 6em;
background: rgba(0, 0, 0, .2);
text-align: center;
}
Is the click inside A or outside?
<div id="a">A
<div id="b">B
<div id="c">C</div>
</div>
</div>
You need to handle the click event on document level. In the event object, you have a target property, the inner-most DOM element that was clicked. With this you check itself and walk up its parents until the document element, if one of them is your watched element.
See the example on jsFiddle
document.addEventListener("click", function (e) {
var level = 0;
for (var element = e.target; element; element = element.parentNode) {
if (element.id === 'x') {
document.getElementById("out").innerHTML = (level ? "inner " : "") + "x clicked";
return;
}
level++;
}
document.getElementById("out").innerHTML = "not x clicked";
});
As always, this isn't cross-bad-browser compatible because of addEventListener/attachEvent, but it works like this.
A child is clicked, when not event.target, but one of it's parents is the watched element (i'm simply counting level for this). You may also have a boolean var, if the element is found or not, to not return the handler from inside the for clause. My example is limiting to that the handler only finishes, when nothing matches.
Adding cross-browser compatability, I'm usually doing it like this:
var addEvent = function (element, eventName, fn, useCapture) {
if (element.addEventListener) {
element.addEventListener(eventName, fn, useCapture);
}
else if (element.attachEvent) {
element.attachEvent(eventName, function (e) {
fn.apply(element, arguments);
}, useCapture);
}
};
This is cross-browser compatible code for attaching an event listener/handler, inclusive rewriting this in IE, to be the element, as like jQuery does for its event handlers. There are plenty of arguments to have some bits of jQuery in mind ;)
How about this:
jsBin demo
document.onclick = function(event){
var hasParent = false;
for(var node = event.target; node != document.body; node = node.parentNode)
{
if(node.id == 'div1'){
hasParent = true;
break;
}
}
if(hasParent)
alert('inside');
else
alert('outside');
}
you can use composePath() to check if the click happened outside or inside of a target div that may or may not have children:
const targetDiv = document.querySelector('#targetDiv')
document.addEventListener('click', (e) => {
const isClickedInsideDiv = e.composedPath().includes(targetDiv)
if (isClickedInsideDiv) {
console.log('clicked inside of div')
} else {
console.log('clicked outside of div')
}
})
I did a lot of research on it to find a better method. JavaScript method .contains go recursively in DOM to check whether it contains target or not. I used it in one of react project but when react DOM changes on set state, .contains method does not work. SO i came up with this solution
//Basic Html snippet
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="mydiv">
<h2>
click outside this div to test
</h2>
Check click outside
</div>
</body>
</html>
//Implementation in Vanilla javaScript
const node = document.getElementById('mydiv')
//minor css to make div more obvious
node.style.width = '300px'
node.style.height = '100px'
node.style.background = 'red'
let isCursorInside = false
//Attach mouseover event listener and update in variable
node.addEventListener('mouseover', function() {
isCursorInside = true
console.log('cursor inside')
})
/Attach mouseout event listener and update in variable
node.addEventListener('mouseout', function() {
isCursorInside = false
console.log('cursor outside')
})
document.addEventListener('click', function() {
//And if isCursorInside = false it means cursor is outside
if(!isCursorInside) {
alert('Outside div click detected')
}
})
WORKING DEMO jsfiddle
using the js Element.closest() method:
let popup = document.querySelector('.parent-element')
popup.addEventListener('click', (e) => {
if (!e.target.closest('.child-element')) {
// clicked outside
}
});
To hide element by click outside of it I usually apply such simple code:
var bodyTag = document.getElementsByTagName('body');
var element = document.getElementById('element');
function clickedOrNot(e) {
if (e.target !== element) {
// action in the case of click outside
bodyTag[0].removeEventListener('click', clickedOrNot, true);
}
}
bodyTag[0].addEventListener('click', clickedOrNot, true);
Another very simple and quick approach to this problem is to map the array of path into the event object returned by the listener. If the id or class name of your element matches one of those in the array, the click is inside your element.
(This solution can be useful if you don't want to get the element directly (e.g: document.getElementById('...'), for example in a reactjs/nextjs app, in ssr..).
Here is an example:
document.addEventListener('click', e => {
let clickedOutside = true;
e.path.forEach(item => {
if (!clickedOutside)
return;
if (item.className === 'your-element-class')
clickedOutside = false;
});
if (clickedOutside)
// Make an action if it's clicked outside..
});
I hope this answer will help you !
(Let me know if my solution is not a good solution or if you see something to improve.)

Categories