Simplified HTML:
<ul>
<li class="no-link"><a>Should not be clickable</a>
<ul>
<li><a>Should be clickable</a></li>
<li><a>Should be clickable</a></li>
</ul>
</li>
</ul>
Javascript:
jQuery(document).ready(function( $ ) {
$('a').parent().click(function(e) {
if($(this).hasClass('no-link')){
e.preventDefault();
}
});
})
Works fine on the link that should not be clickable, but also affects the two descendant a tags. Why? I thought parent() only traversed up a single step in the DOM.
I'm adding the class programatically via WordPress (as an option in the Appearance > Menus control panel), so targeting the a tag directly via class is not really an option.
What you want is to actually capture the click on a element and then check for parent class inside it.
Just change your code to:
$('a').click(function(e) {
if($(this).parent().hasClass('no-link')){
e.preventDefault();
}
});
$('li > a').click(function(e) {
if($(this).parent().hasClass('no-link')){
console.log('parent has class no-link')
e.preventDefault()
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul>
<li class="no-link">Should not be clickable
<ul>
<li>Should be clickable</li>
<li>Should be clickable</li>
</ul>
</li>
</ul>
"I thought parent() only traversed up a single step in the DOM."
It does. But you are attaching your click handler to the parent, and click events bubble up from the clicked item through their parent, the parent's parent, etc., and can be cancelled anywhere along that chain. So your code cancel all clicks for all anchors within that parent element.
Try this instead:
$('a').click(function(e) {
if($(this).parent().hasClass('no-link')){
e.preventDefault();
}
});
That is, handle the click event at the anchor level, testing the parent of the clicked item.
Simple solution is the best - just stop propagation:
jQuery(document).ready(function( $ ) {
$('a').parent().click(function(e) {
e.stopPropagation(); // Preventing from event bubbling and capturing
if($(this).hasClass('no-link')){
e.preventDefault();
}
});
})
Related
I'm trying to move over to using plain old Javascript (moving away from jQuery, due to speed issues). I need to trigger a click event on something, and then toggle a class on the main element. Here is what I have:
document.querySelector('.wrapper-dropdown').addEventListener("click", function(event) {
event.target.classList.toggle("active");
});
https://jsfiddle.net/h12o073L/17/
The problem I've got, is that I ONLY want the main element to add the class - not the child! Currently if I click on one of the prices, that gets the .active class (whereas I want the .wrapper-dropdown element to have the class added)
I've got to admit - I've become very lazy with jQuery, so I'm probably missing something simple ;)
You want the .active class to only be added to the .wrapper-dropdown You can use the currentTarget this will always refers to the element where you attach the event handler.
document.querySelector('.wrapper-dropdown').addEventListener("click", function(event) {
event.currentTarget.classList.toggle("active");
});
You can use this inside event listener function:
document.querySelector('.wrapper-dropdown').addEventListener("click", function(event) {
this.classList.toggle("active");
});
You should use the .closest() function to select the targeted .wrapper-dropdown.
The Element.closest() method returns the closest ancestor of the
current element (or the current element itself) which matches the
selectors given in parameter. If there isn't such an ancestor, it
returns null.
document.querySelector('.wrapper-dropdown').addEventListener("click", function(event) {
event.target.closest('.wrapper-dropdown').classList.toggle("active");
});
document.querySelector('.wrapper-dropdown').addEventListener("click", function(event) {
event.target.closest('.wrapper-dropdown').classList.toggle("active");
});
<div id="dd-price-from" class="wrapper-dropdown" tabindex="1">
<span>Price From</span>
<ul class="dropdown">
<li data-what="20">€20</li>
<li data-what="50">€50</li>
</ul>
</div>
I've been trying to get a simple vertical navigation to display a list item's children on click.
However when you then click any of those nested, children list items you are in essence 'clicking' the parent list item again. Which triggers my click function to remove '.active' and closes the parent list item right before the user is redirected to their chosen page. This looks bad and is totally annoying.
Any advice on how to get a click function to not affect the children list items?
$('#sidebar > li.parent').click(function(){
if($(this).hasClass('active')){
$(this).removeClass('active');
}else{
$(this).addClass('active');
}
});
HTML
<aside id="tertiary" class="tertiary">
<ul id="sidebar" class="sidebar">
<li class="active">Portal</li>
<li>Start Here</li>
<li>Cards</li>
<li class="parent">Programs
<ul>
<li>LOS</li>
<li>Safety</li>
<li>Retirement</li>
<li>Wellness</li>
<li>Investors</li>
</ul>
</li>
<li>Marketplace</li>
<li>Reporting</li>
</ul>
</aside>
You would need to stop propagation in the children... not the parent
http://jsfiddle.net/TrueBlueAussie/g6a496Lf/
$('#sidebar > li.parent').click(function (e) {
e.preventDefault();
$(this).toggleClass('active');
}).find("ul li").click(function(e){
e.stopPropagation();
});
The e.preventDefault() is just there to stop the link clicking on the parent in the example. e.stopPropagation() stops the child click bubbling up the DOM.
I chained the two handlers together, so that you did not need a separate selector like this:
$('#sidebar > li.parent ul li").click(function(e){
e.stopPropagation();
});
You could add a click handler for the child elements that uses stopPropagationto prevent the click event bubbling to parent elements:
$('#sidebar > li li').click(function(e){
e.stopPropagation();
});
Further reading: MDN docs for Event.
Before you stop propagation of events, take some time to spin up on why you shouldn't do so.
From css-tricks.com, "The Dangers of Stopping Event Propagation", very briefly:
"Modifying a single, fleeting event might seem harmless at first, but it comes with risks. When you alter the behavior that people expect and that other code depends on, you're going to have bugs. It's just a matter of time."
"A much better solution is to have a single event handler whose logic is fully encapsulated and whose sole responsibility is to determine whether or not the menu should be hidden for the given event."
With that being said, you could do something like
$('#sidebar').on('click', function( e ) {
var $target = $(e.target).closest('li');
if ( $target.hasClass('parent') ) {
$target.toggleClass('active');
}
});
Working example:
http://jsfiddle.net/g6a496Lf/1/
I have this code that for now creates a Alert() with the value of that list item.
Now when i click a list item that has another list item as parent. it will alert() them both.
$("#nav li").click(function() {
$LiValue = this.value;
alert($LiValue);
});
Example. here is the HTML
<li value="1">Home</li>
<li value="2">Information
<ul class="subs">
<li value="3">History</li>
<li value="4">Present</li>
<li value="5">Future</li>
</ul>
</li>
Now when i click on list item "Information" it will return with value 2
When i click on list item "Present" it will return value 4 and then 2.
How can i only return the list item i click on and not the parent?
--------->>>> SOLVED!! (can't accept answer yet)
Thank you all for helping me. i will accept the answer as soon as i can. thank you!
Events in JavaScript naturally bubble up the DOM tree, from child elements to their ancestors. You can stop this behavior by stopping the event propagation.
$("#nav li").click(function(e) {
e.stopPropagation();
$LiValue = this.value;
alert($LiValue);
});
The fix to your problem is stopPropagation(). The jQuery documentation tells you that this function "Prevents the event from bubbling up the DOM tree, preventing any parent handlers from being notified of the event."
Basically, the event will not be fired for any of the parent elements. In order to use this method, handle the first parameter that your click function provides you. Then you call the method inside the function. Your method should look like this
$("#nav li").click(function(e) {
e.stopPropagation()
$LiValue = this.value;
alert($LiValue);
});
$("#nav li").click(function(e) {
$LiValue = this.value;
alert($LiValue);
return false;
});
or try to add return false.
Explanation : The difference is that return false; takes things a bit further in that it also prevents that event from propagating (or "bubbling up") the DOM. The you-may-not-know-this bit is that whenever an event happens on an element, that event is triggered on every single parent element as well.
So in other words:
function() {
return false;
}
// IS EQUAL TO
function(e) {
e.preventDefault();
e.stopPropagation();
}
see this link for further explanation
I'm trying to create a global style of dropdowns which toggle between open and closed when their menu icon is clicked, but also close whenever anywhere else on the page is clicked. The way in which I'm opening or closing this dropdowns is by adding or removing a class called "open" to the parent of the dropdown.
The idea of that (to be more clear) is that the normal class of the dropdown has display: none; set on it, but if it's a descendant of something with the class "open", then it has display: block;
So, without further ado, here's what I have so far:
"openable" is a class of 'parent' elements which can be clicked on to add the "open" class.
<script>
$(document).ready(function(){
$('.openable').click(function(){
if($(this).hasClass("open")){
$(this).removeClass("open");
}
else{
$(this).addClass("open");
}
});
});
On it's own that actually works fine - it acts as a decent enough toggle for the dropdowns. Of course, clicking anywhere else won't close the dropdowns.
My apparently non-functioning close script is:
<script>
$(document).ready(function(){
$(document).click(function(event) {
var clicked = $(event.target);
if(clicked.hasClass(".open")){
}
else{
if($(".open").length > 0){
$(".open").each(function(){
$(this).removeClass("open");
});
}
}
});
});
</script>
With that script on the page, dropdowns cease to work altogether, and console isn't throwing up any errors for me to work off of.
Any better way of doing this?
Thanks
edit: html markup is something like
<li class="navItem dropdown openable">
<span><img src="img/settings.png"></span>
<ul class="subNav hubDrop">
<li>Nav item 1</li>
<li>Nav item 2</li>
<li>Nav item 3</li>
<li>Nav item 4</li>
</ul>
</li>
for each one. the li tag there is within another ul (remember, this is for dropdown menu essentially)
jsFiddle Demo - Since you haven't provided any HTML I mocked up some elements...
Update: You don't specify if more than one element can be 'open' at once; in your current solution they can be, so I kept that behavior. However, to limit it to one being open you can add $('.open').not(this).removeClass('open'); inside the .openable click handler.
Part One: Why not just use .toggleClass
$(document).ready( function() {
$('.openable').click( function() {
$(this).toggleClass("open");
});
});
Part Two: No need for a second ready handler; in the first, add this:
$(document).click( function(e) {
var clicked = $(e.target).closest('.openable');
if ( clicked.length == 0 ) {
$(".open").removeClass('open');
}
});
jsBin demo
just played around a bit (don't know your markup.)
<div>
<h2 class="openable">ICON 1</h2>
<div class="cont"></div>
</div>
$('.openable').next('.cont').hide();
$('.openable').click(function(e) {
e.stopPropagation();
$('.opened').removeClass('opened');
var d = $(this).next('.cont');
var visib = (d.is(':visible')) ?
/*yes*/ d.slideUp() :
/*no */ ($('.cont').slideUp()) (d.slideDown().prev('.openable').addClass('opened')) ;
});
$(document).click(function(){
$('.cont:visible').slideUp().prev('.openable').removeClass('opened');
});
I don't believe you can just do $(document).click(), it's not wrong but you never click the document itself, you click children of.
I have a very similar menu system and I capture an event this way:
$('.navTab').mouseover(function (event) { navEvent($(this), event.type); });
Then remove all "open" and reapply "open" to the selected item.
I believe you don't want to capture all document click events. jQuery Event.target
How about making everything but the openable classed elements execute your click method?
var openable = $(".openable");
$("div, h2").not(openable).click(function(){
$('.cont').slideUp().prev('.openable').removeClass('opened');
});
I have a LI with some information, including some links. I would like jQuery to make the LI clickable, but also remain the links in the LI.
The Clickable part works. I just need the links within to work as well.
NOTE: They work if you right click and choose "Open in new tab".
HTML
<ul id="onskeliste">
<li url="http://www.dr.dk">Some info with links Imerco</a>
</ul>
jQuery
$(document).ready(function() {
$("#onskeliste li").click(
function()
{
window.location = $(this).attr("url");
return false;
});
})(jQuery);
I've found a simular question here, but it doesn't seem to solve my problem.
jQuery DIV click, with anchors
Can you help me?? :-)
Thank you in advance...
Use the event target, like:
$("#onskeliste li").bind('click', function(e){
switch(e.target.nodeName){
case 'LI':{
e.preventDefault();
e.stopPropagation();
window.location = $(e.target).attr('url');
break;
}
case 'A':{
// do something
break;
}
}
});
The problem your having is caused by event propagation.
The click on the <a/> tag bubbles up to the <li/> tag, therefore causing the li's click event to "overrule" the link's click.
Essentially, the li's click happens immediately after the clicking on the link. It's like you've clicked on a link to one site, and then clicked a link to a different site before the browser had a chance to change the page.
A solution to this would be to stop the event from bubbling up to the <li/>, thus preventing it from changing the window's location.
I suggest using event.stopPropagation() on the <a/> tag, like this:
$('#onskeliste li a').click(function(e) {
e.stopPropagation();
});
Can you just change the mark up to this and not write js for this?
<ul id="onskeliste">
<li>
Some info with links
Imerco</a>
</li>
</ul>
As #GeReV said, it's event propagation.
$(document).ready(function() {
$('#onskeliste li').click(function(e) {
if ($(e.target).is(':not(a)')) {
window.location = $(this).attr('url');
return false;
}
});
})(jQuery);
This looks at what you clicked and if it's not a link it looks at the url attribute on the list element. If it is a link it does its normal link thing.