Refining the jQuery selection

The code below is my first crack at a jQuery script for animating a three-level ‘Suckerfish’ menu which has run in CSS only form for some time. It can be seen working at http://www.holidaymullandiona.com

This is the entire script:

	$(document).ready(function(){
//		alert ('Welcome to Holiday Mull');

		$('#nav li ul').css({
			display: 'none',
			left: 'auto'
		});
//              Selector for first level <li>
		$('ul.level1 li').hover(function() {
			$(this)
				.children('ul')       // Should select ONLY next (second) level UL
				.stop(true, true)
				.delay(500)
				.slideDown('slow');
		}, function() {
			$(this)
				.children('ul')       // Should select ONLY next (second) level UL, but appears to select third level as well
				.stop(true, true)
				.delay(500)
				.fadeOut('fast');  // This action gets applied to all levels when <li> is un-hovered, but this is NOT what I want
		});

//              Selector for second level <li>
		$('#nav li li').hover(function() {
			$(this)
				.children('ul')       // Should select ONLY next (third) level UL
				.css({'opacity': '0', zIndex: '-1'})
				.animate({
					left: '104px',
					opacity: '1',
					zIndex: '1'
				}, 500);
		}, function() {                     // This should be the 'un-hover' action, but the 'fadeOut' above happens first
			$(this)
				.children('ul')
//				.delay(500)
				.animate({
					left: '200px',
					opacity: '0.25',
//					zIndex: '-1'
				}, 500);
		});
	});

Hovering over the first level <li> gets the drop-down menu as required, then hovering on a second-level <li> gets the third level (where present). The third level <ul> appears from behind the second, in accordance with the animation:

		$('#nav li li').hover(function() {
			$(this)
				.children('ul')
				.css({'opacity': '0', zIndex: '-1'})  // sets a start condition ensuring the third level underlies second
				.animate({
					left: '104px',
					opacity: '1',
					zIndex: '1'
				}, 500);
                                 ...

Up to this point it works as I intend. The problem is that when I move the mouse, either to another <li> or off the menu altogether the third level <ul> does the “fadeOut(‘fast’)” of the second level instead of the intended animation. For illustration purposes only, the intended animation shown would move the <ul> further out to the right and fade it. It is clear that this action does occur (but only after the “fadeOut(‘fast’)”), because when the <li> is next hovered, the <ul> does indeed appear from the right.


// Intended action on UN-hovering (moves <ul> to the right and fades it out)
                ...
		}, function() {
			$(this)
				.children('ul')
//				.delay(500)
				.animate({
					left: '200px',
					opacity: '0.25',
//					zIndex: '-1'
				}, 500);
		});
// For illustration only: ultimately this will be changed to move <ul> back to the left

This can be most easily seen on the linked web page by first hovering on ‘Places to Stay’ and then alternately hovering ‘B&B/Guest Hoses’ and ‘Self-catering’. http://www.holidaymullandiona.com

What I’d like is for the third level <ul> to follow the intended animation when it’s parent <li> is no longer hovered, and NOT fade out as it does now. I think this means making the first level selector more specific so that it doesn’t also select the second level. I thought I’d done that with the “.children(‘ul’)”, but it seems not. I have tried adding a class to the <ul>, as in “.children(‘ul.level2’)” or “.find(‘ul.level2’)” but so far without success. It doesn’t produce a syntax error, just no level 3 action, so presumably it’s now TOO specific !

Can anyone point me in the right direction, please ?

It seems that the main problem you have is that you want to select the immediate children of a certain level. You can do this by adding the immediate-child selector before the selector you want when using .children().

So in your case you could do:


$(this)
    .children('[COLOR=#ff0000] > ul[/COLOR]')
//...

This way you can always be sure that only the next level of <ul>(s) are selected.

Thank you, John, for your response.

It seems that the main problem you have is that you want to select the immediate children of a certain level. You can do this by adding the immediate-child selector before the selector you want when using .children().

I’d have said that was exactly what I wanted to do. In fact, I thought that was what “.chidren(‘e’)” was meant to do on its own, without any ‘>’. I’ve never previously seen the immediate-child selector used without an element in front of it. I’ve tried adding the ‘>’, so the script reads:

		$('ul.level1 li').hover(function() {
			$(this)
				.children('> ul')
				.stop(true, true)
				.delay(500)
				.slideDown('slow');
		}, function() {
			$(this)
				.children('> ul')
				.stop(true, true)
				.delay(500)
				.fadeOut('fast');
		});

but now it doesn’t work at all. No second-level drop down. It doesn’t look as if anything’s getting selected.

However “.find(‘> ul’)” does work, but in exactly the same way as “.children(‘ul’)”.

I was just having a play around and thought I’d make a standalone example to get this to work. I actually just ended up making your main selectors more specific rather than address the .children().

So the first level <li> selector becomes: $(‘#nav > ul > li’)
The second (and subsequent) <li> selector becomes: $(‘#nav > ul ul li’)

I have a live example on JS Fiddle: http://jsfiddle.net/GeekyJohn/C26MJ/

Funnily enough I had the same thought in my shower this morning, though I hadn’t worked out what the main selectors should become. I’ve tried your suggestion, and although $(‘#nav > ul > li’) works at first level (i.e. the second level UL drops down), the third level UL doesn’t fly out to the right when the second level is hovered. Whether that’s due to the change in the first or second main selector I’ve yet to determine.

I’ve looked at your example, which seems to work OK, so it will probably help if I work out what the difference is.

Ah yes, I think in the second hover event handler I had to also add a “display:block” to the .css() call - since you were setting it to “none” to start with, but weren’t setting it back to “block”

i.e.

 .css({'opacity': 0, display: "block", left:0})

Once you add that in together with the updated selector it should show the 3rd level.

Later: Turns out the crucial difference between my script (as amended today) and your demo is in the “display: ‘block’” in the second level hover function. I see now that without that the third level would still be “display:’ none’” in accordance with the first function.
With that and reverting a few other values in my script the third level UL now glides nicely out and back.
http://www.holidaymullandiona.com

Thank you for your help.