Always select a parent element when clicking on a child - javascript

I have a div (a tab) with 3 span inside, like this:
<div class="chat-tabs">
<div class="chat-tabs-cont">
<div id="chat-tab-1" class="chat-tab chat-tab-sel">
<span class="chat-tab-n">1</span>
<span class="chat-tab-t">Tab text 1</span>
<span class="chat-tab-c">11:00</span>
</div>
<div id="chat-tab-2" class="chat-tab">
<span class="chat-tab-n">2</span>
<span class="chat-tab-t">Tab text 2</span>
<span class="chat-tab-c">11:30</span>
</div>
</div>
</div>
These are tabs, so when I click on one tab, I have a click event in Meteor to give the new tab a class of chat-tab-sel and remove this class from old tab (standard tab behaviour).
My problem is that depending where the user clicks, my event.target is not allways the parent div chat-tab, but one of child span. And I need to add/remove classes to the parent div.
I think if the parent has display: block it may work, but in this case I need it to be display: flex because it makes sense to have flexible width on childs.
So: Is it possible to ensure that the parent div is targeted when user clicks on a child?

If you use a normal Meteor event handler in combination with #Brian Shamblen's tip it should just work.
Template.myTemplate.events({
'click .chatTab': function(ev){
$(".chat-tab").removeClass("chat-tab-sel"); // remove from all
$(ev.target).closest(".chat-tab").addClass("chat-tab-sel"); // set the one you're on
}
});

Events bubble. This means that you can listen for events on the parent. In an event listener this is bound to the element that you bind the event listener to. So we can listen for any clicks inside that element, then set the current active tab to inactive, and the clicked tab to active.
Not sure about Meteor specifically, but this is how I would accomplish this using vanilla JavaScript.
var tabs = document.querySelectorAll('.chat-tab');
for(var i in Object.keys(tabs)) tabs[i].onclick = function() {
document.querySelector('.chat-tab.chat-tab-sel').className = 'chat-tab';
this.className += ' chat-tab-sel'
}
.chat-tab-sel {
border: 1px solid #012450;
}
<div class="chat-tabs">
<div class="chat-tabs-cont">
<div id="chat-tab-1" class="chat-tab chat-tab-sel">
<span class="chat-tab-n">1</span>
<span class="chat-tab-t">Tab text 1</span>
<span class="chat-tab-c">11:00</span>
</div>
<div id="chat-tab-2" class="chat-tab">
<span class="chat-tab-n">2</span>
<span class="chat-tab-t">Tab text 2</span>
<span class="chat-tab-c">11:30</span>
</div>
</div>
</div>

Building on Tiny Giant's answer on events bubbling, here is to do this in meteor:
Template.theTemplate.events({
'click .chat-tab': function(ev) {
$('.chat-tab').removeClass('chat-tab-sel');
$(ev.currentTarget).addClass('chat-tab-sel');
}
});
Here is a interesting info.meteor.com blogpost that goes into some detail on this:
Browser events: bubbling, capturing, and delegation
Suppose you have this HTML structure:
<body>
<p>
<a><span>Hello</span></a>
</p>
</body>
Event delegation is not a browser feature, but a popular technique built into libraries like jQuery. Many blogs get confused talking about it or equate it with bubbling, but I hope the following description is clear.
Suppose you want to respond to a click on any A tag on the page, even if the set of A tags changes over time. In particular, you don't want to visit every A tag and add an event listener. So, taking advantage of bubbling, you bind a single event handler to the BODY, and from this handler you look at event.target to see if an A was clicked, and if so, which one. Be careful, though, because event.target may be the SPAN! You need to not just check if the event's target is an A tag, but also walk up the DOM tree in a simple simulation of bubbling.
This is event delegation. The BODY element is the delegate that handles events on behalf of the A tags. Conceptually, we'd like to think of the event handler as being on the A tags, so we create that illusion as much as we can. To that end, the final step in event delegation (at least in jQuery and Meteor) is to set event.currentTarget to the A tag. Further code that handles the event then sees an A tag as currentTarget and a SPAN tag as target. The BODY element is not really important, so it is nowhere to be found.

Related

Vue3: How to receive #click event on parent DOM?

I have following structure in Vue3 template:
template:
/*html*/
`<div class='element1' #click='onClick'>
<img :src='image_url' />
<p>{{desc_text}}</p>
</div>`,
data() {
return {
image_url: "path/to/image",
desc_text: "Some text goes here"
}
},
methods: {
onClick(e) {
console.log(e);
}
}
Depending on where I click, console outputs e.target to be either <img>, <p> or <div class='element1'>, and it will have e.path set to array of elements, from the element that was topmost under the pointer to the bottom-most, while currentTarget is set to null.
I am looking for a solution where event handler would be called with e.target that points to the element that has #click listener added, in this case, <div class='element1'>. Not to point to any of it's children.
I've been looking at Vue3 documentation on the event bubbles, but so far no modifiers fit my need.
For example #click.self will only fire if e.target is the element that has the event listener, however, if pointer was above any of it's children, it will not fire.
Right now, I use pointer-events: none; styling on all children elements I don't want to have their own pointer event handling, which appears to have sufficient coverage. However, I'm surprised if Vue does not have any option on it's own to handle this case, maybe I am missing it?
I've read through Vue3 Event Modifiers document, but no option listed there does this.
In some cases, I have dozen of sub-elements in an element (eg. a card showing image, title, paragraph etc.), and I'd like entire element to be one big button to click on, with just one #click event handler set.
You can achieve same result (maybe better) without using event target but by passing arguments to the onClick handler.
What I usually do when rendering clickable list is that I pass the entire item
<div v-for="element in elements" :key="'element' + item.id" class="'element' + element.id" #click="onClick(element)">
<img :src='element.image_url' />
<p>{{element.desc_text}}</p>
</div>
It then become easy to handle the onclick as you already have the element you want at your disposal
onClick(element) {
console.log(element);
}

How to differentiate between multiple spans on mouseover?

I'm making a chrome extension where I need to get the email-id when I mouseover to any name i.e. of sender and receivers.
I'm getting the email-id of the sender by mouseover using the div class name:var exp = $(".acZ").find(".gD"), where 'gD' is the div class which contains emai-id.
The same is to be done for receivers, but the div of receivers has multiple span tags, each span tag for a receiver. So the issue is that I'm not able to separate the ids when I mouseover as they are all under the same div class: var exp1 = $(".xD").find(".g2"). Every receiver is under 'g2' class with span tags.
So how can I differentiate between the span tags?
In mouseover event, you can use event.target to get the reference to the DOM element on which the event is triggered.
<div class="xD">
<span class="g2" id="r1">Test1</span>
<span class="g2" id="r2">Test2</span>
<span class="g2" id="r3">Test3</span>
<span class="g2" id="r4">Test4</span>
</div>
<script language="javascript">
$(".xD").find(".g2").mouseover(function(event)
{
console.log("Concerned id " + $(event.target).attr("id"));
})
</script>
Please enable the Developer Console and see the output to verify.
Hope i was able to clarify your requirement...

When a Div class is clicked, alert it's inner content's Div Class

How do i even put these, let me try. In the following sets of codes, i want to click 'parentclass' and have an alert value of 'child1' and when i click the class below it which is 'Parent 2' have an alert fire with a value of 'child2'
So this must alert the content of that class only and not the entire class.
Here's some Javascript in Jquery.
var childclass = $('.childclass').html();
$('.parentclass').click(function(e) {
alert (childclass)
});
$('.childclass').click(function(e) {
e.stopPropagation()
e.preventDefault()
});
And HTML
<a href="" onClick="return false">
<div class='parentclass'>
Parent 1
<div style="display:none" class="childclass">child1</div>
</div>
</a>
<a href="" onClick="return false">
<div class='parentclass'>
Parent 2
<div style="display:none" class="childclass">child2</div>
</div>
</a>
This line var childclass = $('.childclass').html(); doesnt make sense as it doesn't know which element in particular you mean. The result of that will just be child1child2 which is just a concatenation of the .html() of all the elements with class childclass. This is obviously not what you want.
Therefore you should dynamically find the child with a class of childclass upon receiving the click event.
$('.parentclass').click(function(e) {
alert($(this).find('.childclass').html())
});
Also, you should know that your child class event handler is useless as we don't care if the event gets propogated downwards. If you DID care, then your e.stopPropagation() and e.preventDefault() should be in the event handler of the parent class.
You need to fetch the html of the clicked parent element within the click handler
$('.parentclass').click(function (e) {
alert($(this).find('.childclass').html())
});
$('.childclass').click(function (e) {
e.stopPropagation()
e.preventDefault()
});
Demo: Fiddle
Several ways you can go about this.
First, if your HTML will not be dynamic (elements already exist when page loads), then you can select elements by the parent class name and assign click event as so:
$('.parentclass').click(function(e) {
// the first variable here is selecting the inner elements having class 'childclass'
// keep in mind, if more than one child having that class is present within this parent, it will select all of them
var child = $(this).find('.childclass');
// here we alert the text of the inner child found
// if it is more than one, you will have undesired results. you may want to specify `.first()`
alert(child.text())
})
For newer jQuery you can also use $('.parentclass').on('click', function(e) {.
If you expect any pieces of parentclass to be dynamic, then you'll want to delegate the event based on either a static parent to the parents or document. This can be like so:
$(document).on('click', '.parentclass', function(e) {
alert($(this).find('.childclass').text())
})
Or, if you have a static (already there when page loads) wrapping element, give it an ID like `parentClassWrapper' and assign the click event dynamically as:
$('#parentClassWrapper').on('click', '.parentclass', function(e) {
alert($(this).find('.childclass').text())
})
Some helpful links:
jQuery API
jQuery Selectors
.click()
.on()
Some info on Event Delegation
jquery on vs click methods
jQuery .on('click') vs. .click() and .delegate('click')
jquery .live('click') vs .click()
I made several adjustments to your html that are worth noting. There's no need for the <a> tag. Don't use inline js - onlick in your html. Note that I wrapped the text inside of the div in the <a> tag instead. This markup is more semantic. Also, move your styles to css rather than in the html.
<div class="parent">
<a>Parent 1</a>
<a class="child">child of parent 1 contents</a>
</div>
<div class="parent">
<a>Parent 2</a>
<a class="child">child of parent 2 contents</a>
</div>
css:
.parent > .child { /* good practice: only select an immediate child of the parent */
display: none;
}
The other answers here are using find() to select the child, but I recommend children() instead. For example, if you had additional nested .childs, find() will select them all, but children() will only select direct .childs of the parent, so it is better in this case. I also recommend using the console for debugging rather than alert.
Live demo here (click).
$('.parent').click(function() {
var $child = $(this).children('.child');
var cls = $child.attr('class');
console.log(cls);
$child.show(); //added so that you can click the child
});
$('.child').click(function() {
var html = $(this).html();
console.log(html);
//if you just want the text, use this instead:
var text = $(this).text();
console.log(text);
});

Use JQuery to listen for all clicks on the html body Except a specific div and its children

I am trying to use a jQuery listener to listen for a users clicks on the html body and perform a specific function if anywhere on the body has been clicked except for a specific div and the children within that div.
The idea is that the div is a popup type element and instead of having to have a close button that the user can click, they should just be able to click anywhere on the page besides that div and it will automatically close.
I have been using this listener:
var initialClick = false;
$('body').on('click.addPhoneListeners', function(event) {
var target = EventUtility.getTarget(event);
if(initialClick) {
if(target.parentNode.id != clone.id && target.id != '') {
finish();
};
}
initialClick = true;
});
Which listens for all clicks on the body and unless the click comes from within the element I want the user to be able to interact with, it closes. Unfortunately this only works with a div that has only one level of children. As soon as I start getting multiple hierarchies such as this:
<div id="addressContainer">
<div id="address" class="hidden row">
<div class="row">
<div id="address.primary" class="hidden">P</div>
<div id="address.type"></div>
<div id="address.street"></div>
<div id="address.editButton" class="hidden"><a id="addressEditButton">Edit</a></div>
<div id="address.deleteButton" class="hidden"><a id="addressDeleteButton">Delete</a></div>
</div>
<div class="row">
<div id="address.city"></div>
<div id="address.state"></div>
<div id="address.zip"></div>
</div>
<input type="hidden" id="address.id"></input>
</div>
</div>
The target.parentNode.id gives me the objects parent element as opposed to the addressContainer id and thus does not work. Is use the top level parent from within nested elements? Other elements will be using this same code, so it has to work on both divs with just one level and div's with multiple.
UPDATE: Found a few excellent solutions, thanks guys. I do however have one other question. Refer to my code above where I set an initialClick boolean to false, then set it to true. I am doing this because for some reason if I don't, when I go to add the popup div, the initial click from the button used to set that popup fires the listener and closes the popup before I have a chance to do anything. This has been my solution around the problem, but is that the only way? Or am I just setting the listener slightly incorrect?
I usually do something like this:
$(document).click(function(e) {
// hide popup
});
$('#popup_div').click(function(e) {
e.stopPropagation();
});
That way the clicks from your popup never propagate to the document, so the close function never fires.
Replace
if(target.parentNode.id != clone.id)
with
if ($(target).closest("#" + clone.id).length === 0)
(I left the second clause alone since it didn't seem related to your question.)
This tries to find the closest ancestor with ID equal to clone.id. If none is found, an empty jQuery object is returned (i.e. one with length === 0), which is what we test for.
Incidentally: jQuery normalizes event.target, so you can just use that instead of whatever custom monstrosity EventUtility.getTarget(event) embodies.

One click handler for multiple buttons and stop propagating event up to parent?

I'm generating a bunch of "li" items as a result of an ajax call. They look like:
<li>
<p>Hello result 1</p>
<button>remove</button>
</li>
<li>
<p>Hello result 2</p>
<button>remove</button>
</li>
...
can I create a single click handler that will get called when any one of the "li" item "button" elements gets clicked, and will also stop propagating the click event up to the "li" parent? Something like:
function btnClicked() {
var liItem = this.parent; // ideally is the <li> whose button was clicked.
return true; // don't let click event go up to <li> parent?
}
----------------- Edit ---------------------
Also, I'm not sure if I'm going to use the button element, might use a clickable div as a button instead - the same techniques should work for both though, right?
Thank you
$('li').click(function(event) {
//do something
event.stopPropagation()
});
This will work in jQuery. You will want to change the li to select only the li items you want.

Categories