Event bound to LI is also triggered by clicks on child elements - javascript

I got the following structure: - nested UL
<ul class="depth-one">
<li>Category 1
<ul class="depth-two">
<li > Category 1.1</li>
<li> Category 1.2</li>
<li> Category 1.3</li>
</ul>
</li>
<li> Category 2
<ul class="depth-two">
<li>Category 2.1</li>
<li>Category 2.2</li>
<li>Category 2.3</li>
</ul>
</li>
<li>Category 3
<ul class="depth-two">
<li>Category 3.1</li>
<li>Category 3.2</li>
<li>Category 3.3</li>
</ul>
</li>
</ul>
I've applied a rule with CSS:
ul{
list-style: none;
padding:0;
margin:0;
}
.depth-one{
display:block;
}
.depth-two{
display:none;
}
which leaves only the MAIN category shown.
$(document).ready(function() {
$(".depth-one > li").click(function() {
selector = $(this).find(' > .depth-two');
if($(selector).css("display") == "none"){
selector.slideDown(800);
} else {
selector.slideUp(800);
}
});
});
this one, toggles the SUB categories when the MAIN category is being clicked.
here is a fiddle to demonstrate: http://jsfiddle.net/NB4bN/1/
Now, as you can see, when I'm clicking on the SUBCATEGORY, the whole category slides up, any idea why?
I'm trying to achieve that only when I click on the MAIN category, the subcategory will slides up, otherwise, nothing happens when I click on the sub <li> items.

Here is a fix for your problem: http://jsfiddle.net/smerny/NB4bN/2/
$(document).ready(function () {
$(".depth-one > li").click(function (e) {
if (e.currentTarget == e.target) {
selector = $(this).find(' > .depth-two');
if ($(selector).css("display") == "none") {
selector.slideDown(800);
} else {
selector.slideUp(800);
}
}
});
});
I added the check to see if currentTarget and target were the same. So you know if the click is on an element within your li or the li itself.

An easy fix is to stop the event bubbling by adding:
$('li li').click(function (e) {
e.stopPropagation();
});
jsFiddle example
.stoppropagation() prevents an event from bubbling up the DOM tree. So what the above chunk does is look for any list items that are children of other list items and whenever it registers a click on one, it stops the click event from bubbling up the DOM.

Related

jQuery or JavaScript menu drop down on click

Right now, I'm trying to build a vertical menu that will have a drop down sub menu below it.
Below is my HTML and the jQuery function I am using:
$(function() {
$('#menusomething > li').click(function(e) {
e.stopPropagation();
var $el = $('ul', this);
$('#menusomething > li > ul').not($el).slideUp();
$el.stop(true, true).slideToggle(400);
});
$('#menusomething > li > ul > li').click(function(e) {
e.stopImmediatePropagation();
});
});
<div id="navmenu">
<ul id="menusomething" style="padding-left:30px">
<li>HOME</li>
<li>ABOUT</li>
<li>CHAPTERS</li>
<ul class="submenu">
<li>Dallas</li>
<li>Los Angeles</li>
<li>New York</li>
<li>Northern California</li>
<li>Orange County</li>
<li>Phoenix</li>
<li>San Diego</li>
<li>Washington DC</li>
</ul>
<li>MEMBER SERVICES</li>
Figured out the answer for anyone who sees this. First had to move the closing li tag from chapters to the end of .submenu Then used this and now it works as wanted.
$(function() {
$('#menusomething li > .submenu').parent().click(function() {
var submenu = $(this).children('.submenu');
if ( $(submenu).is(':hidden') ) {
$(submenu).slideDown(400);
} else {
$(submenu).slideUp(400);
}
});
});
The following code does what I believe you desire: Have a <ul> element that is the nextElementSibling of the first level <li> element slide open and closed when it is clicked. As you mentioned in comments that you desired, it now starts closed due to adding style="display: none;" to the <ul>.
Note: From a user interface perspective, the <li> entries which don't have sub-menus, or are otherwise links, should not have the text enclosed in <a> tags. With the <a> tags the user will think they are clickable, when a click does nothing. This is confusing to a user. It appears that you may have some be sub-menus and some be direct links. If possible, there should be some visual difference between the two types to hint to the user as to what will happen when they click.
Along with other issues, your HTML has nothing that will match either the '#menusomething > li > ul' or the '#menusomething > li > ul > li' selectors. Specifically, you have no <UL> elements that are children of <LI> elements.
$(function() {
$('#menusomething > li').click(function(e) {
e.stopPropagation();
var nextSib = this.nextElementSibling;
if(nextSib && nextSib.nodeName === 'UL') {
//If we get here the nextSib exists and is a <UL> that immediately follows
// the <LI> which was clicked.
$(nextSib).slideToggle(400);
}
});
$('#menusomething > ul > li').click(function(e) {
console.log('Clicked on chapter: ' + this.textContent);
e.stopImmediatePropagation();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="navmenu">
<ul id="menusomething" style="padding-left:30px">
<li>HOME</li>
<li>ABOUT</li>
<li>CHAPTERS</li>
<ul class="submenu" style="display: none;">
<li>Dallas</li>
<li>Los Angeles</li>
<li>New York</li>
<li>Northern California</li>
<li>Orange County</li>
<li>Phoenix</li>
<li>San Diego</li>
<li>Washington DC</li>
</ul>
<li>MEMBER SERVICES</li>
</ul>
</div>

CSS and jQuery universal tree menu

I want to create universal tree menu, with ul li ul. And I've made something like this using just CSS:
CSS
.category-list {
}
.category-list li ul {
display: none;
}
.category-list li:hover > ul {
display: block;
}
HTML
<ul class="category-list">
<li>
Category 1
<ul>
<li>Sub-category 1</li>
<li>Sub-cateagory 1</li>
<li>Sub-category 1</li>
</ul>
</li>
<li>
Category 2
<ul>
<li>Sub-category 2</li>
<li>Sub-category 2</li>
<li>Sub-category 2</li>
</ul>
</li>
</ul>
https://jsfiddle.net/usz9ycmj/1/
--
And I want to make similar effect, but on click, so just current clicked tab displays its parent content.
Even more important for me is the ability to add and remove class on specific action:
.category-list li.current -- while is currently clicked (active)
.category-list li -- removed while different li is clicked (active)
Just, the trigger li has two different states for active and inactive. It changes the colors and arrow from closed to opened to give it a look of a tree menu - I bet You get the point.
I want the simple jquery code, if someone has time to help. feel welcome.
Here is a working code.
Please read the comments and let me know if something not clear.
// listen to the click event
var all_items = $('.category-list>li').click(function(event) {
// stop the propagation - this will abort the function when you click on the child li
event.stopPropagation();
var elm = $(this);
// remove the class from all the items
all_items.not(elm).removeClass('current');
// add class if it's not the current item
elm.toggleClass('current', !elm.is('.current'));
});
.category-list {
}
.category-list li ul {
display: none;
}
.category-list li.current > ul {
display: block;
}
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<ul class="category-list">
<li>
Category 1
<ul>
<li>Sub-category 1</li>
<li>Sub-category 1</li>
<li>Sub-category 1</li>
</ul>
</li>
<li>
Category 2
<ul>
<li>Sub-category 2</li>
<li>Sub-category 2</li>
<li>Sub-category 2</li>
</ul>
</li>
</ul>
http://jsbin.com/tocewe/edit?html,css,js

how to make this slide menu work

Example: https://jsfiddle.net/lmgonzalves/xj6a74jy/1/
Result: I would like to make a slideUp + slideDown menu the has multiple levels.
I'm stuck trying to get this slide menu to work and I'm not sure how about to get it to work. I've tried using "height"0px" on some css when clicked but ultimately I get back to the same problem. I can make it through the first click in making the slide menu work (meaning there is a slideUp and slideDown), but any level after that the slider just slides up and not down leaving me with no visible menu. Here is what I have:
$('.mobile-nav .navigation a').on('click',function(e){
e.preventDefault();
var t = $(this);
var active = t.closest('li.active');
active.children('ul,a, li.back').not(t.closest('ul')).slideUp();
t.next('ul').slideDown();
});
.mobile-nav .navigation {background:#eee; width:250px; position:relative;}
.mobile-nav .navigation ul {margin:0; padding:0;}
.mobile-nav .navigation a {display:block; line-height:30px;}
.mobile-nav .navigation li ul {display:none;}
<div class="mobile-nav">
<div class="navigation">
<ul>
<li class="active">
All
<ul style="display:block;">
<li>
Topic 1
<ul>
<li class="back">Back</li>
<li>
Some Topic
<ul>
<li class="back">Back</li>
<li>
Some Topic1
((( the menu keeps getting repeated here going deeper, using the format of BackTopic 1Topic 1Topic2 with varying number of li's in each ul.
So the first ul looks like this:
<div class="mobile-nav">
<div class="navigation">
<ul>
<li class="active">
All
</li>
</ul>
/* With 3 more ul's and li's in each
<ul></ul>
<ul></ul>
<ul></ul>
</div>
</div>
When I click on one of the a href tag's, the menu slides to the next level showing the ul, which is the 2nd ul. But when I click on any of the li a's within this ul, I can see the menu start to slide down, but at the same time, the entire ul slides up showing nothing. The ul that was opened now is display:none; even though the next ul is now showing block. I can't figure out how to keep the slides going as they were in the first click.
I can redo classes and such if there is a better way to make this happen.
Fiddled something for you: Fiddle
Hope this is what you need. Just changed the way of selecting the elements.
(function ($) {
"use strict";
$('.mobile-nav')
.on('click', 'a', function (e) {
var $cTarget = $(e.currentTarget),
$dropdown = $cTarget.next('ul'),
$parentUl = $cTarget.closest('ul'),
$activeElem = $parentUl.find('ul.active');
$parentUl.children('li').each(function (key, elem) {
var $elem = $(elem);
if(!$cTarget.parent('li').is($elem)) {
$elem.slideUp();
}
});
$activeElem.toggleClass('active').slideUp();
if (!$dropdown.is($activeElem)) {
$dropdown.toggleClass('active').slideDown();
}
})
.on('click', '.back', function (e) {
var $cTarget = $(e.currentTarget),
$dropdown = $cTarget.closest('ul');
$dropdown.toggleClass('active').slideUp();
$cTarget.parents('li').first().siblings().slideDown();
});})(jQuery);
So these answers are going to be pretty close to each other, but I haven't seen one that meets your "only one item can be open at a time criteria." The JQuery is a little verbose if you want to stick with slipeUp and slideDown but here's an example of the code for handling it for the top-level unordered lists:
$('.toplevel > span').click(function () {
if ($(this).parent().hasClass('activeTop')) {
$('.activeTop').removeClass('activeTop');
$(this).parent().children('ul').slideUp();
return;
}
$('.activeTop').children('ul').slideUp();
$('.activeTop').removeClass('activeTop');
$(this).parent().addClass('activeTop');
$('.activeTop').children('ul').slideDown();
});
I replaced the a tags with spans (and cleaned up the HTML a bit) so I didn't have to deal with my demo fiddle navigating away, but here's a demo implementing the behavior for both top- and second-level menu items.
Check out this fiddle, I would make your structure a little simpler like this https://jsfiddle.net/jk90pxgt/1/ and then your jQuery is only a couple of lines. You can obviously add back buttons if you would like and styling is up to you but this is just a much cleaner way to do the slide menu. Also don't use links and prevent the default, it is just extra code. Just do your click function on the LI
Here is the jQuery
$(".mobile-menu li").click(function(e){
e.stopPropagation();
$(this).children(".sub-menu").slideToggle();
});
New HTML Structure
<ul class="mobile-menu">
<li>First Item
<ul class="sub-menu">
<li>First Sub-Item
<ul class="sub-menu">
<li>First Sub-Item</li>
<li>Second Sub-Item</li>
</ul>
</li>
<li>Second Sub-Item</li>
</ul>
</li>
<li>Second Item
<ul class="sub-menu">
<li>First Sub-Item
<ul class="sub-menu">
<li>First Sub-Item
<ul class="sub-menu">
<li>First Sub-Item</li>
<li>Second Sub-Item</li>
</ul>
</li>
<li>Second Sub-Item</li>
</ul>
</li>
<li>Second Sub-Item</li>
</ul>
</li>
<li>Third Item</li>
</ul>
And CSS
.sub-menu {
display:none;
}
li {
cursor:pointer;
}
Here is how I was able to make this work:
$('.mobile-nav .navigation a').on('click',function(e){
var t = $(this), li = t.closest('li'), ul = li.closest('ul'), a = ul.siblings('a');
if(li.hasClass('back')) {
e.preventDefault();
//do back code here
var sib = ul.closest('li').siblings('li');
a = ul.parents('ul').eq(0).siblings('a');
ul.slideUp();
sib.add(a).slideDown();
} else if(t.siblings().length > 0) {
e.preventDefault();
li.siblings('li').add(a).slideUp();
t.next('ul').slideDown();
}
});

If list item has child list, add css style

I have a navigation from a ul, see below:
<nav>
<ul>
<li>Top Link 1</li>
<li>Top Link 2</li>
<ul>
<li><a href="#" >Sub Link 1</a></li>
<li>Sub Link 2</li>
<li>Sub Link 3</li>
</ul>
</li>
<li>Top Link 3</li>
<ul>
<li><a href="#" >Sub Link 1</a></li>
<li>Sub Link 2</li>
<li>Sub Link 3</li>
</ul>
</li>
</ul>
</nav>
I have my css, hiding the the sub link's "ul" unless hovering the parent "li". (Will not show unless asked, as this is not the issues)
I'm trying to use my JQuery to add a css style (margin-bottom:30px;) to the top level "li" only if it has a child "ul" nested in it. My JQuery is as below:
<script>
$(document).ajaxSuccess(function () {
if ($("nav ul >li").children("ul li")) {
$("nav ul >li").hover(function () {
$("nav ul >li").css("margin-bottom", "30");
});
}
});
</script>
This does not appear to be working for me, can anyone point out what I'm doing wrong? Or can they provide a better solution to this approach?
.children will always return an array, check .length
$(document).ajaxSuccess(function () {
if ($("nav ul >li").children("ul li").length > 0) {
$("nav ul >li").hover(function () {
$("nav ul >li").css("margin-bottom", "30");
});
}
});
Try a little something like this :
$('li > ul').each(function(){
$(this).parent().addClass("your class");
});
This will select all uls that are children of li's and apply the class to the respectful li.
you do have an extra </li> in your question which i think is a typo..
<li>Top Link 2</li>
//-------^^^^^here
<ul>
<li><a href="#" >Sub Link 1</a></li>
anyways you check check for length of children if present..
try this
$(document).ajaxSuccess(function () {
if ($("nav ul >li").children().length > 0) {
$("nav ul >li").hover(function () {
$(this).css("margin-bottom", "30");
});
}
});
.children() method returns an object and an object is a truthy value in JavaScript, apart from that if the condition is valuated to true, you are adding a listener to all the li elements not just those that have ul children, You can use .has() method which is a filtering method:
$("nav > ul > li").has('ul').on({
mouseenter: function() {
$(this).css("margin-bottom", "30px");
},
mouseleave: function() {
$(this).css("margin-bottom", "n");
}
});
Or:
li.hovered {
margin-bottom: 30px;
}
$("nav > ul > li").has('ul').on('mouseenter mouseleave', function(e){
$(this).toggleClass('hovered', e.type === 'mouseenter');
});

Jquery selector: whenever a user clicks on the Category 1/2/3, its `<ul>` will be visible

I got the following structure: - nested UL
<ul>
<li>Category 1
<ul>
<li> Category 1.1</li>
<li> Category 1.2</li>
<li> Category 1.3</li>
</ul>
</li>
<li> Category 2
<ul>
<li>Category 2.1</li>
<li>Category 2.2</li>
<li>Category 2.3</li>
</ul>
</li>
<li>Category 3
<ul>
<li>Category 3.1</li>
<li>Category 3.2</li>
<li>Category 3.3</li>
</ul>
</li>
I've applied a rule with CSS:
ul{
list-style: none;
padding:0;
margin:0;
}
ul ul{
display:none;
}
which leaves only the MAIN category shown.
What i was trying to do is, whenever a user clicks on the Category 1/2/3, its <ul> will be visible. I've tried this code:
$(document).ready(function() {
$("ul li").click(function() {
$(this + "ul").Slidedown(800);
});
});
well, basically I was trying to select the <ul element that was inside the main <ul>.
How do I do it?
Try this.
$(document).ready(function() {
$("ul li").click(function() {
$(this).find('ul').Slidedown(800);
});
});
Given your selector, click on Category 1.1 will also call the callback, but it won't do anything since it doesn't have any ul tags. Still, it's better to add a class and bind the event only on those.
this is not a string, it's a DOM element.
Instead of $(this + "ul"), you want $('ul', this).
P.S. .Slidedown should be .slideDown.
$(this).find('ul:hidden').slideDown(800);
something like this should work
$(document).ready(function() {
$("ul > li").click(function() {
$(this).find("> ul").Slidedown(800);
});
});
Although, an even more efficient approach(thanks to Ian) would be:
$(document).ready(function() {
$("ul").children('li').click(function() {
$(this).children("ul").Slidedown(800);
});
});
using the '>' operator tells jquery to only look for direct children, if you don't use '>' the code will apply to the li elements inside the nested ul as well. also, read the other answers info about using 'this' properly.
You're concatenating the DOM element with "ul" to a string - that's not a valid selector. Instead, use .find() to apply the ul selector in the context of the current element.
$(document).ready(function() {
$("ul li").click(function() {
$(this).find('ul').Slidedown(800);
});
});
And maybe make the ul li selector a bit more specific to match only the outer list items.

Categories