DOM CustomEvent listeners - javascript

I think this is best asked with code:
var event = new CustomEvent("custom", {bubbles: true}); // Create Event
// Create and attach element
var element = document.createElement("div");
document.body.appendChild(element);
element.addEventListener("custom", function(e) {
console.log("listening");
});
// At this point I thought that if the "custom" event were fired that my
// element listing would be triggered with the listener callback.
// But....
document.body.dispatchEvent(event);
// check the console... nothing :(
// the only way I am able to get my element event listener to fire is if
// the element calling the dispatchEvent(event) is the element itself.
element.dispatchEvent(event);
What am I doing wrong? Is this the way events are meant to work?
My question is how do I listen to a custom event when the event is not dispatched
dispatchEvent(event)
from the element containing the listener callback
element.addEventListener("custom", function(){});

You have to dispatch the event on the element that is actually listening for it, or an element lower in the chain if it's supposed to bubble.
You can't listen for the event on a DIV, and then dispatch the event on the BODY tag, and expect it to bubble downwards
element.dispatchEvent(event);
Is the proper way to fire the event, it won't bubble from the body to a child, events only bubble upwards the DOM chain.

Related

Accessing data attribute of clicked element with javascript [duplicate]

Can anyone please tell me the exact difference between currentTarget and target property in JavaScript events with example and which property is used in which scenario?
Events bubble by default. So the difference between the two is:
target is the element that triggered the event (e.g., the user clicked on)
currentTarget is the element that the event listener is attached to.
target = element that triggered event.
currentTarget = element that has the event listener.
Minimal runnable example
window.onload = function() {
var resultElem = document.getElementById('result')
document.getElementById('1').addEventListener(
'click',
function(event) {
resultElem.innerHTML += ('<div>target: ' + event.target.id + '</div>')
resultElem.innerHTML += ('<div>currentTarget: ' + event.currentTarget.id + '</div>')
},
false
)
document.getElementById('2').dispatchEvent(
new Event('click', { bubbles:true }))
}
<div id="1">1 click me
<div id="2">2 click me as well</div>
</div>
<div id="result">
<div>result:</div>
</div>
If you click on:
2 click me as well
then 1 listens to it, and appends to the result:
target: 2
currentTarget: 1
because in that case:
2 is the element that originated the event
1 is the element that listened to the event
If you click on:
1 click me
instead, the result is:
target: 1
currentTarget: 1
Tested on Chromium 71.
For events whose bubbles property is true, they bubble.
Most events do bubble, except several, namely focus, blur, mouseenter, mouseleave, ...
If an event evt bubbles, the evt.currentTarget is changed to the current target in its bubbling path, while the evt.target keeps the same value as the original target which triggered the event.
It is worth noting that if your event handler (of an event that bubbles) is asynchronous and the handler uses evt.currentTarget. currentTarget should be cached locally because the event object is reused in the bubbling chain (codepen).
const clickHandler = evt => {
const {currentTarget} = evt // cache property locally
setTimeout(() => {
console.log('evt.currentTarget changed', evt.currentTarget !== currentTarget)
}, 3000)
}
If you use React, from v17, react drops the Event Pooling.
Therefore, the event object is refreshed in the handler and can be safe to use in asynchronous calls (codepen).
↑is not always true. onClick event's currentTarget is undefined after the event handler finishes. In conclusion, always cache the event's properties locally if you are going to use them after a synchronous call.
From react docs
Note:
As of v17, e.persist() doesn’t do anything because the SyntheticEvent
is no longer pooled.
And many other things that are too long to be pasted in an answer, so I summarized and made a blog post here.
If this isn't sticking, try this:
current in currentTarget refers to the present. It's the most recent target that caught the event that bubbled up from elsewhere.
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
If click on the P tag in above code then you will get three alert,and if you click on the div tag you will get two alert and a single alert on clicking the form tag.
And now see the following code,
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<script>
function fun(event){
alert(event.target+" "+event.currentTarget);
}
</script>
<form>FORM
<div onclick="fun(event)">DIV
<p>P</p>
</div>
</form>
We just removed onclick from the P and form tag and now when we click we on P tag we get only one alert:
[object HTMLParagraphElement] [object HTMLDivElement]
Here event.target is [object HTMLParagraphElement],and event.curentTarget is [object HTMLDivElement]:
So
event.target is the node from which the event originated,
and
event.currentTarget, on the opposite, refers to the node on which current-event listener was attached.To know more see bubbling
Here we clicked on P tag but we don't have listener on P but on its parent element div.
Event.currentTarget is the element to which the event handler has been
attached, as opposed to Event.target, which identifies the element on
which the event occurred and which may be its descendant.
Source: MDN
target always refers to the element in front of addEventListener - it's the element on which the event originated.
currentTarget tells you - if this is an event that's bubbling - the element that currently has the event listener attached (which will fire the event handler if the event occurs).
See this CodePen for an example. If you open up developer tools and click the square, you'll see that first the div is the target and the currentTarget, but the event bubbles up to the main element - then main element becomes the currentTarget, while the div is still the target. Note the event listener needs to be attached to both elements for the bubbling to occur.
event.target is the node from which the event originated, ie. wherever you place your event listener (on paragraph or span), event.target refers to node (where user clicked).
event.currentTarget, on the opposite, refers to the node on which current-event listener was attached. Ie. if we attached our event listener on paragraph node, then event.currentTarget refers to paragraph while event.target still refers to span.
Note: that if we also have an event listener on body, then for this event-listener, event.currentTarget refers to body (ie. event provided as input to event-listerners is updated each time event is bubbling one node up).
Here's a simple scenario to explain why it's needed. Let's say there are some messages that you show to the user with the format below, but you also want to give them the freedom to close them (unless you have a special mental disorder), so here are some message panes:
[ A message will be in this pane [x] ]
[ A message will be in this pane [x] ]
[ A message will be in this pane [x] ]
and when the user clicks on the [x] button on each, the whole corresponding pane must be removed.
Here's the HTML code for the pane:
<div class="pane">
A message will be here
<span class="remove-button">[x]</span>
</div>
Now where do you want to add the click event listener? The user clicks on [x], but you want to remove the pane, so:
If you add the click event listener to the [x], then you will have to find its parent on DOM and remove it... which is possible but ugly and "DOM dependent".
And if you add the click event listener to the pane, clicking "everywhere on the pane" will remove it, and not just clicking on its [x] button.
So what can we do? We can use the "Bubbles Up" feature of the event system:
"Events are raised and bubble up the DOM tree regardless of the existence of any event handlers."
In our example, this means that even if we add the event handlers to the panes, we will be able to catch the events raised specifically by the [x] button clicks (because events bubble up). So there can be difference between where an event is raised, and where we catch and handle it.
Where it's raised will be in the event.target, and where it's caught will be in the event.currentTarget (where we're currently handling it). So:
let panes = document.getElementsByClassName("pane");
for(let pane of panes){
pane.addEventListener('click', hndlr);
}
function hndlr(e){
if(e.target.classList.contains('remove-button')){
e.currentTarget.remove();
}
}
(The credit of this example goes to the website JavaScript.info)
An experiment:
document.addEventListener("click", (e) => {
console.log(e.target, e.currentTarget);
});
document.querySelector("p").click();
output:
<p></p>
#document
The target (<p></p>) seems to be the element clicked, while the currentTarget (#document) is the element that is listening for the click event.

jQuery - Register / unregister listener for element "document"

I'll try to explain my problem. I'm using mouseup event listener so I can check whenever a click is performed and the target is not the desired element. This is the code I'm using:
function clickOutListener(element, callbackFunction){
$(document).mouseup(function(e){
if(!$(element).is(e.target) && $(element).has(e.target).length === 0) callbackFunction.call(this, null);
});
}
As you can see, the event listener is bound to the global document element and the way to unbind the listener would be:
$(document).off("mouseup");
Here comes what I need to achieve. If I unbind mouseup listener it will affect the other elements which use this listener (dropdowns and other features). I must guess that everytime I'm registering a listener it's not overriding the previous defined listener but adding the defined target function.
How can I access the different defined target functions for the same listeners?
$(document).mouseup(funct1);
$(document).mouseup(funct2);
$(document).mouseup(funct3);
How would you unregister the registered listener just for "funct2"?
Thank you in advance.
You can namespace your events when using the .on() syntax.
$(document).on('mouseup.myNamespace', function(e){ ... }
This allows you to remove events by namespace whilst leaving others in place.
$(document).off('mouseup.myNamespace');

Javascript prevent event propagation through element

Working with google maps which has infowindows that popup. These infowindows have images which are click-able. As a result I need to be able to propagate events in the infowindow. However I am also able to click THROUGH the infowindow to other markers which causes the current infowindow to close and it to open a new infowindow.
I dont think this issue is specific to google maps. Is there a way to stop events from propagating through an element?
Thought the below code would help but it didnt.
$(document).on('touchstart', '.infoWindow', function(e){
if ($(e.currentTarget) == $(this)) e.stopPropagation();
});
if ($(e.currentTarget) == $(this))
This is never true. It creates two distinct jQuery instances, which - even if they contain the same element - are not the same object. You might have wanted to do
if (e.currentTarget == this)
but this is always true - by definition of the event dispatch algorithm, the this value of an event listener and the currentTarget of the event object always refer to the same thing. Hell, even jQuery does this.
So you should just write
$(document).on('touchstart', '.infoWindow', function(e){
e.stopPropagation();
});
to prevent touchstart events from bubbling through your infoWindows.
You cannot stop the event propagation when you use event delegation. Event delegation relies on the bubbleing event, so by the time the delegated handler gets the event, the event already bubbled through every other element. You need to attach your event listener directly to the element.
$('.infoWindow').on('touchstart', function(e){
e.stopPropagation();
});
Event delegation works by adding the event listener to the document, then every time the document receives that touchstart event, jQuery checks the source element and all of its parent if they match the given selector. If one matches, the handler gets invoked. This is why you don't need to add the listener every time you add a new element that would match the given selector.
Setting a background color of the .infoWindow element might be a solution.
If it should be transparent, you can use as follows:
background-color: rgba(255, 0, 0, 0);

Programmatically call "mousemove" event in JS

Is there a possibility to programmatically call the mousemove event in jQuery?
Obviously, I'm not going to change the actual position of the cursor - it's impossible. All I want is re-call this event so all other scripts that have attached their handers to it will also be called.
To trigger event handlers bound to the mousemove event you can use trigger()
$('#elementID').on('mousemove', function() {
// do stuff
});
$('#elementID').trigger('mousemove'); // triggers above event handler

Event delegation - jQuery

I though it is possible to listen all the events on document but following code is not working. Am I doing anything wrong here?
$(document).on('test', function() {
console.log('document is listening for test event');
});
and then fire test event from an object:
var obj = {};
$(obj).trigger('test'); //nothing happens, why?
Shouldn't above code fire function associated to test event on document?
To start with, what you have isn't event delegation, that's just a regular event binding to the document. In order for an event handler on something - such as the document - to fire, that event has to be triggered on that element - either by triggering it on that element directly or by triggering an event that propagates ("bubbles") up the DOM to that element.
Your example doesn't work because your object obj isn't part of the document, so the test event is never triggered on the document.
This will fire your event:
$(document).trigger('test');
Or any child of document:
$('body').trigger('test');
Your empty object obj is not document or a child of document so will not fire the event bound on document

Categories

Resources