Nested navigation click logic issue - javascript

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/

Related

Prevent default on link if parent hasClass()

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();
}
});
})

jQuery toggle: Close all open li when another one is clicked

I've got an accordion menu which toggles on click.
This is the code :
$('ul.internal-nav-list li ').on('click', function () {
$(this).find('.internal-sub-list li ').toggle();
});
And the markup looks like this:
<div id="internal-nav">
<ul class="internal-nav-list">
<li><a>products</a>
<ul class="internal-sub-list">
<li>product1</li>
<li><a href="product2.aspx" >product2</a></li>
<li>product3</li>
</ul>
</li>
</ul>
</div>
Now I'm trying to enable that when an li element from the menu is open and the user clicks on another li, the open one will automatically close. Can anybody give me a suggestion on how to do this?
I'v I'm interpreting what you want correctly, try this:
var mainlis = $('.internal.nav.list > li'); // cache selector
mainlis.on('click', function(e) {
e.preventDefault();
var me = $(this);
mainlis.hide();
me.show();
});
The other existing answers come close, but it seems like what you want to do is hide the children of other menu items when the main menu items are clicked. If that is the case, the following will do:
$('.internal-nav-list > li > a').on('click', function () {
var $thisLi = $(this).parents('li');
$thisLi.siblings().find('.internal-sub-list').hide();
$thisLi.find('.internal-sub-list').show();
});
Note the first selector: this restricts the click handler to just the anchor, not the entire li. That means if they click on a child of the currently displayed main menu item, the function will not be called. That way you don't risk having a flicker as the click the submenu items...
In the handler itself, it traverses back to the parent li, finds its siblings and hides their children. Then is shows the submenu for the currently selected main menu.
Note that I took the liberty of hiding the entire ul of the non-selected menus; this should be faster than hiding each child. Perhaps not significantly, but I find it's best practice to perform these kinds of actions on the container rather than all children of the container.
The simplest solution is to close all lielements and open only the one clicked
$('ul.internal-nav-list > li').on('click', function () {
$(this).siblings('li').slideUp();
$(this).slideDown();
});
EDIT As Morfie pointed out, only the immediate children li of the internal-nav-list should be clickable, thus the > operator is used.
Thanks for the suggestions- I got it working this way in the end (in case it helps anyone)
$('ul.internal-nav-list li').on('click', function () {
$close = $(this).find('.internal-sub-list li ').toggle();
$('.internal-sub-list li').not($close).hide()
});

Prevent a tag to redirect on URL

I am stuck at very normal scnerio. I have HTML code generated by YII CLINK PAGER Pagination widget :
<ul class="yiiPager" id="yw0">
<li class="first hidden"><< First</li>
<li class="previous hidden">< Previous</li>
<li class="page selected">1</li>
<li class="page">2</li>
</ul>
and I want AJAX pagination for my requirement, so that I have wrote Jquery code :
$("ul.yiiPager li.page a").on('click',function (e){
e.preventDefault();
alert($(this).attr('href'));
return false;
loadlistData($(this).attr('href'));
});
But by clicking on any of the <a> tag it is redirecting to the LINK given in href for preventing that i have used e.preventDefault(); but still it is not coming in JQuery code and alert not showing.
Please help. Thanks in advance.
Since your anchor tags are created dynamically. You need to use event delegation. Because the elements should be present on the dom at the time of event binding. In the case of event delegation events are binded to the document or parent element which is presented on the dom
$(document).on('click', "ul.yiiPager li.page a", function(e) {
e.preventDefault();
alert($(this).attr('href'));
loadlistData($(this).attr('href'));
});
Case 1 (direct):
$("ul.yiiPager li.page a").on("click", function() {...});
I want every ul.yiiPager li.page a inside ul.yiiPager li.page to listen up: when you get clicked on, do something.
Case 2 (delegated):
$("ul.yiiPager li.page").on("click", "a", function() {...});
ul.yiiPager li.page When any of your child elements which are "a" get clicked, do something with them.
Summary
In case 1, each of those spans has been individually given instructions. If new spans get created, they won't have heard the instruction and won't respond to clicks. Each a is directly responsible for its own events.
In case 2, only the container has been given the instruction; it is responsible for noticing clicks on behalf of its child elements. The work of catching events has been delegated.

Jquery get list value without parent

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

stopPropagation not working in this instance

Okay so a simple thing that I don't know how to search for, wasn't able to get an answer anywhere...
Basically, I'm using XML to populate a list. What happens is when clicking on the first tier li, it adds all the different suburbs to the child ul. This generates then the structure below:
<li class="province-active" style="background-image: url("img/minus-icon-storefinder.png");">
<p>Gauteng</p>
<ul class="province-sub-menu">
<li class="province-sub-nav-item" data-suburb="Strijdom Park">Strijdom Park,Randburg</li>
<li class="province-sub-nav-item" data-suburb="Edenvale">Edenvale,Eastrand</li>
<li class="province-sub-nav-item" data-suburb="Strubens Valley">Strubens Valley,Roodepoort</li>
<li class="province-sub-nav-item" data-suburb="Centurion">Centurion,Pretoria</li>
</ul>
</li>
Okay the problem is, using the JQuery below, it first triggers the click event for "province-sub-nav-item" and then triggers the click event for "province-active" (the parent li). This is the JS I'm using for the children li.
$(document).on("click", ".province-sub-nav-item", function(e){
e.stopPropagation();
$shop = $(this).attr("data-suburb");
loadShop($shop);
});
And this is the function for the parent li.
$(".province-active").click(function(e) {
$li = $(this);
$province = $li.children("p").text();
$(".store-locations").css("width", 375);
getMap($li, $province, 15);
});
So basically, I just need to not fire the click event for "province-active" without firing the click event for "province-active" and stopPropagation(); isn't working.
Thanks guys.
It is because you have used used event delegation and bounded the handler to teh document object.
So when event bubbles up the province-active elements handler will be triggered before the document objects handler is triggered so your stop propagation does not have any effect.
Demo: Fiddle - see the order of logging in the console
One possible solution is to use event delegation for the province-active element also
$(document).on("click", ".province-active", function (e) {
var $li = $(this);
var $province = $li.children("p").text();
$(".store-locations").css("width", 375);
getMap($li, $province, 15);
});
Demo: Fiddle - see the order of logging in the console
Alternatively this works too:
$(".province-sub-menu").on("click", ".province-sub-nav-item", function(e){
e.stopPropagation();
$shop = $(this).attr("data-suburb");
loadShop($shop);
});

Categories