Jquery: toggling the sibling

Hi,

I want to toggle the next sibling of a link in a list like this,

<ul>
	<li><a href="#">1</a></li>
	<li><a href="#">2</a>
		<ul class="selected">
			<li><a href="#">2.1</a></li>
			<li><a href="#">2.2</a></li>
			<li><a href="#">2.3</a></li>
		</ul>
	</li>
	<li><a href="#">3</a></li>
	<li><a href="#">4</a>
		<ul class="selected">
			<li><a href="#">4.1</a></li>
			<li><a href="#">4.2</a></li>
			<li><a href="#">4.3</a></li>
		</ul>
	</li>
	<li><a href="#">5</a></li>
</ul>

and this is my jquery,

$(document).ready(function(){

	$("a").each(function () {
		if ( $(this).siblings().size() > 0)
		{
			$(this).append("<span class='has-child'>has child</span>");

			$(this).toggle(
				function (){
					$("ul").removeClass("showme");
					$(this).siblings(".selected").addClass("showme");
					//$(this).next().css({display: "block"});
				},
				function (){
					$(this).siblings(".selected").removeClass("showme");
					//$(this).next().css({display: "none"});
			});

		}
	});
	
	$('ul > li > ul > li:last-child > a').css('background','yellow');
	
});

the css,

ul > li > ul {
		display:none;
	}
	
	ul > li:hover ul{
		display: block;
	}
	.showme {
		display: block;
	}

first of all, it seems fine - I can toggle the targeted sibling, but why after the first toggle at any parent, I have to click twice sometime to hide other siblings when I click/ toggle at the current parent?

here is the live site if I am not explaining it clearly,
http://lauthiamkok.net/tmp/jquery/toggle/

Many thanks,
Lau

$(document).ready(function(){
	// Change the first <ul> of the HTML to <ul id="tree"> in order for the next line to work
	// I changed it because it's bit overkill to walk ALL <a>'s on the page when you're just interested in
	// the <a>'s in the tree
	$("#tree a").each(function () {
		// add .selected in .siblings() to be more specific
		if ($(this).siblings('.selected').size() > 0) {
			// Please, no single quotes for HTML attributes...
			$(this).append("<span class=\\"has-child\\">has child</span>");

			// The toggle here was causing the problem of needing to click twice.
			// .toggle() does not take into account if an element is currently visible, but merely
			// lets you assign two onclick functions where the first function is fired every even click
			// and the second one if fired every odd click
			// Because a click of an element in your case can cause *another* element to show/hide, the
			// normal principle of .show()/.hide() doesn't work here anymore
			$(this).click( function (){
					// check if the next element (the "drop down") is currently visible
					if ($(this).siblings('.selected').is(':visible')) {
						$('.selected').removeClass('showme'); // hide all drop downs
						$(this).siblings('.selected').addClass('showme'); // and show the drop down connected to this link
					}
			});
		}
	});
	$('ul > li > ul > li:last-child > a').css('background','yellow');
});

:slight_smile:

Hi, thanks so much for explaining about toggle(). I have tried your suggestion, however, the current parent does not toggle anymore, it only toggle when I click on other parents… have a look here,

http://lauthiamkok.net/tmp/jquery/toggle/index_2.html

for instance, when I click on the link 2 only, I was hoping to show its sibling too. and hide it when I click on this parent again.

is it possible…?

thanks!

It sure is.

Replace

$(this).click( function (){
		// check if the next element (the "drop down") is currently visible
		if ($(this).siblings('.selected').is(':visible')) {
			$('.selected').removeClass('showme'); // hide all drop downs
			$(this).siblings('.selected').addClass('showme'); // and show the drop down connected to this link
		}
});
}

with


$(this).click( function (){
	// check if the next element (the "drop down") is currently visible
	var sibling = $(this).siblings('.selected');
	$('.selected').not( sibling ).removeClass('showme'); // hide all visible drop downs, but not the sibling of the clicked <a>
	if (!sibling.hasClass('showme')) {
		sibling.addClass('showme'); // show the drop down connected to this <a>
	} else {
		sibling.removeClass('showme'); // hide the drop down connected to this <a>
	}
});

:slight_smile:

hi thanks again! I have modified the code a little, so that I can toggle the parent in the way I want to achieve,

$(this).click( function (){
				// check if the next element (the "drop down") is currently visible
				var sibling = $(this).siblings('.selected');
				$('.selected').not( sibling ).removeClass('showme'); // hide all visible drop downs, but not the sibling of the clicked <a>
				if (!sibling.hasClass('showme')) {
					sibling.css({display: 'block'});
					sibling.addClass('showme'); // show the drop down connected to this <a>
				} else {
					sibling.css({display: 'none'});
					sibling.removeClass('showme'); // hide the drop down connected to this <a>
				}
				return false;
			});

but then, the problem is the css hover is not working anymore!

is there any way I can have both toggle and hover at the same time, or I only can have one of them??

thanks so much!

Why did you use sibling.css()? Isn’t that what you created the “showme” CSS class for? Does sibling.css() add anything to the script it didn’t do before (besides breaking the :hover CSS) ?

PS. Instead of node.css({display: "block";}); you can use node.show(); and instead of node.css({display: "none";}); you can use node.hide();. Shorter and better readable :slight_smile:

sorry, i got things complicated! i created addClass to fix this problem in the first place actually,

http://lauthiamkok.net/tmp/jquery/toggle/index_3.html

I can do the hover with usual css’s tricks, and I want the next sibling/ children to stay open when I click on the parent, the problem is that when I close/ hide the next sibling/ children, I cannot open/ show the the next sibling/ children again by hovering. so i thought addClass could help me on this… guess I was wrong.

this is the initial idea…

$(document).ready(function(){

	$("a").each(function () {
		if ( $(this).siblings().size() > 0) 
		{
			$(this).append("<span class='has-child'>has child</span>");
			
			$(this).toggle(
				function (){
					$(this).next().css({display: "block"});
					//$(this).parent().css({background: "#000"});
				},
				function (){
					$(this).next().css({display: "none"});
			});	

		}
	});	
	
	$('ul > li > ul > li:last-child > a').css('background','yellow');
	
});

it seems that there’s no way to re-open/ show the sibling/ children again after hiding them with toggle() - doesn’t it?

thanks!

The css :hover doesn’t work because .show() and .hide() – and their .css nephews as well – set an inline style on the elements, which by definition take precedence over styles defined in the CSS.

Hence, if you drop the .hide() and .show() and use .addClass() and .removeClass() --as I’ve already provided for you-- it will work, because ul > li:hover ul takes precedence over .show.

Am I missing something or are you seeing a problem where there is none? :shifty:

nope, you are not missing anything! :slight_smile: it is just that I am rubbish in explaining this, without demonstrating the problem on screen for u. will get back to this thread when I can explain it better or when i get the solution :smiley:

thanks for your help and time! :stuck_out_tongue:

I came out with this solution, in which I made two functions,

$(document).ready(function(){

	$("a").each(function () {
		if ( $(this).siblings().size() > 0) 
		{
			$(this).append("<span class='has-child'>has child</span>");
			
			var $this = $(this);
			hoverme($this);
		}
	});	
	
	$('ul > li > ul > li:last-child > a').css('background','yellow');
	
});


function hoverme($this) {

	var parent_li = $this.parent();
	var sibling_ul = $this.next();
			
	parent_li.hover(
		function (){
			sibling_ul.css({display:'block'});
				parent_li.click( function (){
						
					sibling_ul.css({display:'block'});
					parent_li.unbind('mouseenter mouseleave');
						
					toggleme($this,parent_li,sibling_ul);
					return false;
				});
					
			},
		function (){
		sibling_ul.css({display:'none'});
		//sibling_ul.fadeOut();
	});
}	

function toggleme($this,parent_li,sibling_ul){
	
	parent_li.addClass('hideme');
	
	$('.hideme').click(function (){
							
		sibling_ul.css({display:'none'});
		parent_li.removeClass('hideme');
		parent_li.addClass('showme');
		
		$('.showme').click(function (){
			sibling_ul.css({display:'block'});
			parent_li.removeClass('showme');
								
			toggleme($this,parent_li,sibling_ul);
		});
							
	hoverme($this);
	});					
}

you can have a look at the link below for what I am trying to achieve here,
http://lauthiamkok.net/tmp/jquery/toggle/index_3.html

  1. you can hover the parent, and you can stop the hover when you click on the parent.
  2. you can toggle the parent without hovering away from the parent.
  3. when you close/ hide the sibling/ children, you can start hovering the parent again.

but the flaw of this solution is, the more I click on the same parent without hovering away from it, the script seems to run oddly and getting ‘tired’…

what have I done wrong?:sick:

thanks.

No offense, but if you need 41 lines of jQuery (not counting black lines and comment-only lines) to achieve the three relative simple points you outlined above you’re doing something wrong :stuck_out_tongue:
My advise is to delete all jQuery code you have so far and go back to the drawing board. The code you have now is way over-complicated.

Furthermore, I’m not a usability expert, but the workings of your menu are utterly confusing me.

Am I correct to assume you want to toggle the menu items with jQuery but want to offer an alternative to users who don’t have javascript enabled through the use of CSS hover?

If so, there’s a better solution for that.

  1. In your master stylesheet, leave ul > li:hover > ul { display: block; }
  2. Create an external stylesheet with the rule ul > li:hover > ul { display: block; }
  3. Load the external stylesheet through jQuery

$('<link>').attr({'rel':'stylesheet','type':'text/css','href':'/path/to/my/stylesheet.css'}).appendTo('head');

  1. Create the jQuery for toggling the menu items

That way, if a user doesn’t have javascript enabled the functionality will be available using CSS hover, but if they do have javascript enabled, javascript cancels the CSS behavior through the loading of the external stylesheet and replaces it with javascript functionality.

:slight_smile:

nope, no offence at all! :goof: thanks for pointing out the mistake I am doing indeed :blush:

actually is the other way round - I would like to offer the use of CSS hover as the first choice, but also to offer Javascript’s toggling as an alternative.

the reason doing is that sometime the CSS hover doesn’t work properly on Apple’s machine and IE browser (whether on PC or Apple). so, I thought Javascript’s toggling can provide this solution when the browser and machine fail the CSS :smiley:

this is an interesting idea. I will think about it. again, thanks so much :stuck_out_tongue:

In that case get rid of the child-selectors in your CSS (which are not properly supported in all major browsers), ie change ul > li:hover > ul to ul li:hover ul (which is supported in all major browser) and use Whatever:hover to enable :hover for all elements in IE < 7

The website of Whatever:hover suggests


body {
  behavior: url("csshover3.htc");
}

but I find that a bit over-kill and suggest you only apply it for elements and classes where you actually need it. In your case that would be


ul li {
  behavior: url("csshover3.htc");
}

In addition, if you store that rule in a stylesheet called ie6.css and serve it using conditional comments by putting the following in the <head> of your HTML


<!--[if lt IE 7]>
   <link rel="stylesheet" type="text/css" href="/styles/ie6.css" />
<![endif]-->

only IE versions lower than 7 will download and apply it while other browsers don’t bother :slight_smile:

PS. Come to think of it, you could also rewrite ul > li:hover > ul to li:hover ul because the first ul is superfluous and li:hover ul is a better (faster) rule than ul li:hover ul :slight_smile:

oh, i use lots of child-selectors in my CSS, loads of changes to work on now! lol :injured:

I should avoid child-selectors as much as possible in the futures. :smiley:

thank you. :slight_smile:

Well, for the near future at least. Let’s hope that at some point in the future all major browsers will support them :cool:

PS. Futures? Do you subscribe to the many worlds theory? :wink:

I hope so! :smiley:

it’s my bad english! :blush:

(out of topic) but in science and philosophy, I think many worlds should exist! string theory! :cool: