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>
Related
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.
I have the template like this
<template>
<div>
<div id="hiddenElement">
<MyElement v-for='...' #click="...">
</MyElement>
</div>
<div id="appendElementsHere" />
</div<
</template>
The user can append the element into the list, so I have some function like this:
someFunc(){
const hidden = document.querySelector('#hiddenElement')
const target = document.querySelector('#appendElementsHere')
target.innerHtml += hidden.outerHtml
}
The element is cloned can append to the #appendElementsHere successfully,
but the click function is not working. I think that maybe the click function in the vue element, not the html. How can I clone the element as vue-element, not html only? Or any idea to create vue element in the script (method) and then append to the dom ??
What you're doing is technically correct from a javascript perspective, however the click function doesn't work because you're not doing any data bindings after you "clone" the elements. I say "clone", because what you're doing is just passing a bunch of strings containing HTML. With that said, what you will have to do next is to add the event listeners to the cloned elements manually as well.
However, you could try to do it in a more Vue way, so instead of having a hidden component waiting to be cloned, you could create the Vue instance of the component you want (MyElement in your case) with code passing all the props/data you want and then append it to the element where you need it.
Here's an example with how to do it with buttons. If click on "Click to insert" you'll see how a CustomButton component gets appended to the right of the button.
Thanks,
I am trying to figure out how to automatic trigger a click event on certain element after all data are loaded.
the code is like this in html file:
<div *ngFor="let location of locations; let i = index;" class="location-wrapper" (click)="onSelect($event, i); $event.stopPropagation();">
<div class="location">{{ location }}</div>
</div>
the onSelect() method is doing some expansion of something that related to current location.
What I am trying to achieve is that I want the very first element of the *ngFor can be automatically clicked to show the things that related to it every time I get to this page.
Or maybe we can achieve it using other similar approach?
I have tried several ways to do this,
like putting some code in window.on('load', function() { // blablabla });
or using ngAfterViewInit() and ngAfterViewChecked(), both not work well.
You can do this in at least 2 ways. The first one would be old-fashioned javascript click(). The second would be just using component logic, just create an variable like selectedLocation which would hold current index or Object that is currently expanded. Don't forget to add initial value to it to make it after load page visible.
Javascript dispatchEvent (not Angular friendly solution)
Simply we just need to grab our item and use click() function. That's it. To grab an element we can use basic javascript method document.getElementById(elementId)" or with template variable.
<div
[id]="'location_' + i" <!-- For biding with document.getElementById -->
#locationPanel <!-- For biding with template variable -->
*ngFor="let location of locations; let i = index;" class="location-wrapper" (click)="onSelect($event, i); $event.stopPropagation();">
<div class="location">{{ location }}</div>
</div>
With Id it would look like document.getElementById("location_0").click() this gonna dispatch click event on element.
For template variable in your component you need to add
#ViewChildren locationPanel: QueryList<any>;
openFirstLocation() {
if (this.locationPanel && this.locationPanel.first)
this.locationPanel.first.nativeElement.click();
}
And in afterViewInit just call this.openFirstLocation();
Please note that it's not Angular friendly because Angular does not like when you interfere directly with DOM. However as long we don't change anything in structures then everything should be fine, but we should avoid manipulating dom with plain javascript.
Please note that too about using #ViewChild and document.* methods.
Use this API as the last resort when direct access to DOM is needed. Use templating and data-binding provided by Angular instead. Alternatively you can take a look at Renderer2 which provides API that can safely be used even when direct access to native elements is not supported.
Relying on direct DOM access creates tight coupling between your application and rendering layers which will make it impossible to separate the two and deploy your application into a web worker.
From Angular docs link
so i implemented a bit of jQuery that basically toggles content via a slider that was activated by an <a> tag. now thinking about it id rather have the DIV thats holding the link be the link its self.
the jQuery that i am using is sitting in my head looks like this:
<script type="text/javascript">
function slideonlyone(thechosenone) {
$('.systems_detail').each(function(index) {
if ($(this).attr("id") == thechosenone) {
$(this).slideDown(200);
}
else {
$(this).slideUp(600);
}
});
}
</script>
i was using this as a index type box so there are several products when you click on the <a> tag that used to be an image* it would render a bit of content beneath it describing the products details:
<div class="system_box">
<h2>BEE Scorecard database</h2>
<p>________________</p>
</div>
the products details are wrapped in this div.
<div class="systems_detail" id="sms_box">
</div>
so when you click on what used to be a image* it would run the slideonlyone('div_id_name') function. the function above then first closes all the other divs with the class name 'system details' and then opens/slides the div with the id that was passed into the slideonlyone function. that way you can toggle products details and not have them all showing at once.
note i only kept the <a> tag to show you what was in there i will be getting rid of it.
note: i had an idea of just wrapping the whole div in an <a> tag but is that good practice?
So now what i am wondering is since you need JavaScript to run onclick on a div tag how do you write it so that it still runs my slideonlyone function?
Using obtrusive JavaScript (i.e. inline code) as in your example, you can attach the click event handler to the div element with the onclick attribute like so:
<div id="some-id" class="some-class" onclick="slideonlyone('sms_box');">
...
</div>
However, the best practice is unobtrusive JavaScript which you can easily achieve by using jQuery's on() method or its shorthand click(). For example:
$(document).ready( function() {
$('.some-class').on('click', slideonlyone('sms_box'));
// OR //
$('.some-class').click(slideonlyone('sms_box'));
});
Inside your handler function (e.g. slideonlyone() in this case) you can reference the element that triggered the event (e.g. the div in this case) with the $(this) object. For example, if you need its ID, you can access it with $(this).attr('id').
EDIT
After reading your comment to #fmsf below, I see you also need to dynamically reference the target element to be toggled. As #fmsf suggests, you can add this information to the div with a data-attribute like so:
<div id="some-id" class="some-class" data-target="sms_box">
...
</div>
To access the element's data-attribute you can use the attr() method as in #fmsf's example, but the best practice is to use jQuery's data() method like so:
function slideonlyone() {
var trigger_id = $(this).attr('id'); // This would be 'some-id' in our example
var target_id = $(this).data('target'); // This would be 'sms_box'
...
}
Note how data-target is accessed with data('target'), without the data- prefix. Using data-attributes you can attach all sorts of information to an element and jQuery would automatically add them to the element's data object.
Why do you need to attach it to the HTML? Just bind the function with hover
$("div.system_box").hover(function(){ mousin },
function() { mouseout });
If you do insist to have JS references inside the html, which is usualy a bad idea you can use:
onmouseover="yourJavaScriptCode()"
after topic edit:
<div class="system_box" data-target="sms_box">
...
$("div.system_box").click(function(){ slideonlyone($(this).attr("data-target")); });
You can bind the mouseenter and mouseleave events and jQuery will emulate those where they are not native.
$("div.system_box").on('mouseenter', function(){
//enter
})
.on('mouseleave', function(){
//leave
});
fiddle
note: do not use hover as that is deprecated
There's several things you can improve upon here. To start, there's no reason to use an <a> (anchor) tag since you don't have a link.
Every element can be bound to click and hover events... divs, spans, labels, inputs, etc.
I can't really identify what it is you're trying to do, though. You're mixing the goal with your own implementation and, from what I've seen so far, you're not really sure how to do it. Could you better illustrate what it is you're trying to accomplish?
== EDIT ==
The requirements are still very vague. I've implemented a very quick version of what I'm imagining you're saying ... or something close that illustrates how you might be able to do it. Left me know if I'm on the right track.
http://jsfiddle.net/THEtheChad/j9Ump/
I'm trying to work out how to add a class to a child element of a button, specifically to the <i> below:
<button><i class="icon-undo"></i> Undo</button>
Ideally, using plain JavaScript, there'd be a function which can be added to the button (i.e. <button onclick="function()">) which would add the class "icon-spin" to the child <i> of whatever element was clicked (so it could be used in multiple places in the same page).
Maybe the code in this question helps.... posted the answer today itself. Just pass 'this' to the function and you could do wonders to the DOM thereafter
get nearby sibling in javascript