For a research project, I am building a web tool to track users' interactions with specific elements that I need for later analysis.
Often, I have interactable containers that contain graphics or text elements, sometimes both. For instance:
<div class="interaction-field trackable" id="specific-interaction-id"> <!-- This id is what I want to track -->
<img id="img-id" src="path/to/img.png"/> <!-- But i am triggering either this... -->
<p> Some Text </p> <!--...or this -->
</div>
I style the container by its interaction-field class and have a javascript function that logs all interactions based on the trackable class. Now, I got two issues.
For the tracking, I want to store the container's id for simplicity, as in such cases the <img> and <p> belong together. However, most click events, for example, are only recognised on child elements.
Because of this, the parents' targetable class is not triggering, denying any logging. Since I want the parents' id as a compound trackable element, I would like to avoid giving the children the targetable class to avoid ambiguity and redundancy.
I do get the general layering problem and it is logical that I rather hit the children than their parents. But is there an elegant way to always pass the parents' classes and id's to make the logging easier? Or is there an even simpler solution that I am not seeing?
Thanks in advance!
You can delegate the click handler to the document. Then grab the clicked element and use closest to find the parent element's ID if it has the trackable class.
document.addEventListener("click",(e)=>{
let trackedEl = e.target.closest(".trackable");
if(trackedEl){
console.log(trackedEl.id)
}
});
<div class="interaction-field trackable" id="specific-interaction-id"> <!-- This id is what I want to track -->
<img id="img-id" src="path/to/img.png"/> <!-- But i am triggering either this... -->
<p> Some Text </p> <!--...or this -->
</div>
First of all, if your question is on javascript you should have showed us the corresponding code.
Now, if you have event listeners on parent and child elements you can handle this through propagation.
When an event is triggered there are three phases.
Capturing - It first goes up the tree of elements starting from the root up to the targeted element. This by default doesn't trigger anything
Target phase - It triggers the targeted element's event.
Bubbling - It goes back down the tree again all the way to the root. This phase does trigger the events of the parents.
Your parent's event should be triggering in the bubbling phase. First make sure
that you set it correctly. Make it trigger an alert or something that makes you know if its happening.
You can also decide to trigger it in the capturing phase but I don't you need that.
Once you know for sure the event works it should be triggered when you click on its children. From there it is only a matter of adding the id to your list.
You can check this page to learn more about event propagation:
https://javascript.info/bubbling-and-capturing
I think you might find exactly what you need in there.
Related
I've been trying to use MutationObserver. I can get it to report if any element has been added to the DOM, but I can't reference these elements. I need to apply certain functions to them.
Let's say I have this HTML:
<div id="main">
Some Links
Link 1
Link 2
Link 3
</div>
New links may be added to this DIV element when I scroll. What I need is to get these new link elements and reference them in Javascript.
Note that it's not the exact tree. It's possibly much more nested, not so basic. But the myLink classes are there.
Performance is a slight worry, compatibility is not.
I am still new to Angular and I'm struggling to get the DOM Element of an Angular Click Listener.
What I have is the following component HTML:
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top" (click)="itemClicked($event, !!menuItem.submenu)">
<!-- Additional divs inside... -->
</div>
</div>
I would like to toggle a class of "menuItem__top" when it is clicked. My approach was to use a click event listener but I can't get the source Element to apply the class on.
itemClicked(event, hasSubmenu){
console.log(this) //is the component
let target = event.target || event.srcElement || event.currentTarget;
if(hasSubmenu){
console.log(target);
}
}
It could be done by getting the target of the $event but this way I would need to check the target and move with closest(".menuItem__top") up to the correct source element.
Is there an easy way in Angular to pass the source element of the click listener to the click function or a way to get it inside the click function?
In vanilla JS it would be as easy as using "this" inside the click function, but in Angular this is bind to the component. (In this case, it would be ok to loose the binding to the component if this is the only way.)
I thought about two ways:
Assigning a dynamic reference containing some string and the itemId, passing the itemId and retrieving the reference object based on the itemId in the listener.
Using a #HostListener to listen on every "menuItem__top" click and toggling the class every time.
What do you think is the best way? I feel like I am missing something simple here.
Go the other way around. People are used to jQuery and the way it works (selecting elements already present in the DOM, then adding them a class). So in Angular, they try to do the same thing and grab the element that was clicked, to apply it a class. Angular (and React and others) work the other way around. They construct the DOM from data. If you want to modify an element, start with modifying the data it was generated from.
This <div class="menuItem__top"> that you click on is constructed from let menuItem. All you have to do is add a property, say "isClicked" to menuItem.
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top"
[class.isClicked]="menuItem.isClicked"
(click)="menuItem.isClicked = true">
<!-- Additional divs inside... -->
</div>
</div>
My code is this:
document.addEventListener('click', function(ev){
if(ev.path[0].className == 'linkTogether'){//do something}
if(ev.path[0].id == "createNewPage"){//do something}
});
Which has actually been working well for dynamically created buttons and nodes, but something just feels off about it. So I'm wondering if this is best practice or if there is a better way to add event listeners to dynamically created elements.
Thanks,
Jack
In specific cases where you have huge amount of objects that behave in the same way you can use this technique (adding event listener to their parent) to improve the performance of your script.
In a general page however you just have to many different objects and iterating trough all of them to check which one you've clicked is not faster.
Here is an article for the technique you are referring to - event delegation.
This is the proper pattern for creating event listeners that will work for dynamically-added elements. It's essentially the same approach as used by jQuery's event delegation methods (e.g. .on).
However, it does have performance implications. Every time you click anywhere in the document, the code will run, and have to go through the entire list of event bindings that you need to check. You can improve this by adding your event listener to a more specific element. If the dynamic elements are always added inside a specific DIV, add your listener to that DIV rather than document.
This also avoids another pitfall of event delegation. Event delegation depends on the event bubbling up from an inner element to all its containers. But if there are any handlers along the way that call event.stopPropagation, the event won't make it out to document. If you add the listener to a lower element, you're less likely to have a conflict like that.
And I would like to add, if you create elements dynamically, better you include the listeners inline and filter in the function what you want to do.
<div class="form_ID1" onclick="myfunction(this, event);">
... children bubbling
... Use if statements in caught js myFunction function.
In myfunction function you will capture the child element by event.target and the element that contains the listener by this.className.
There are scenarios however that you need a universal ( document ) click event. e.g You need to close a pop up when you click outside of a pop up !! It is like: e.g.
if (clicked.className != popup.className) popup.remove()
Even in this case there is a workaround by inserting an onblur="myfunction(this); in the parent DIV of the popup.
I remember when I was playing with Flash around 10 years ago. I could create a clip, than duplicate it, and whatever I would do with clip 1 would auto-instantly change duplicated item - so in essence these where two windows of the same thing.
I wonder if same is possible with dom elements. I would like to clone element, and whatever happens to original should happen to clone too. In whatever I mean, click events and classes mostly.
This is html:
<li id="67" data-word-id="2" data-order-id="1" class="ui-state-default">
<article>
Some content...
<div class="invisible active" data-invisi-status"1"="" title="Do you want to keep this item private and invisible to anyone except for you?">Make word invisible</div>
</article>
</li>
I want to keep invisible icon active class and status synced between copied elements.
I achieve copying from 1 list to the other by:
$(this).removeClass("ui-sortable-helper").css({"height":"auto"}).clone().appendTo($list).show("slow");
How I would keep this synced?
Using classes affects all DOM elements of that class. So I guess you could think of a flash clip as a DOM element with a specific class.
$('.my-clip').removeClass('ui-sortable-helper');
Would remove the class ui-sortable-helper from all elements with the class .my-clip
However, there are certain methods which only work on one element, and it's generally the first in the selection.
If you bind an event handler to something inside .my-clip, say a link for instance, and then refer to it using the this variable, you're only updating the element that triggered the event.
For example:
$('.my-clip').on('click', 'a', function(e){
$(this).addClass('link-clicked');
});
This would only add the class link-clicked to the link that triggered the event, however, if you did:
$('.my-clip').on('click', 'a', function(e){
$('.my-clip').find('a').addClass('link-clicked');
});
It would add the class link-clicked to all a elements within all instances of .my-clip
My goal is the following: create a listener that will be bound to a div and it will fire up if there are no children left in that div.
I keep seeing how to bind a listener to say onClick etc.. but I cannot seem to find anyone that deals with actual states of the elements (empty, at least one child, etc... ). I have not started coding anything yet because I am not sure what kind of approach I need to take, since I am pretty new to JavaScript development. I am not necessarily looking for an answer with code in it but more of an advise on what approach to take.
One of the approaches that I was thinking of was to simply have a function call every single time I make a change to the div such as deleting a child but that seems too trivial. I want to create some kind of automation in that process of checking for no children.
jQuery has remove event fired when element is removed
$(el).on("remove", function () {
alert("Element was removed");
});
You could use live (if they are appended dynamically to parent container) or on (if statically) method to bind this event to child nodes of particular container and on every remove event check if parent container has any child nodes. If not then do some actions.
You cannot assign a listener to a div element to check if the element has no children(done automatically without knowing which function is removing the children, but only knows that there was a child removed). Granted you could do a function to check every so many seconds but that is not what I wanted. Anyways, where I remove the children, I simply added a function that checks if the parent is left with no children and it handles it there.