Streamlining a nested menu

Hi all,

I’ve put together a nested menu system but at the moment the code isn’t very streamlined or scalable (i.e. if more panels were added on either side you’d have to go through and rename the corresponding IDs).

<!-- CSS -->
<link href="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/bootstrap/ws-responsive-normalize.css" rel="stylesheet" />
<link href="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/bootstrap/ws-bootstrap-framework.css?v=3" rel="stylesheet" />
<link href="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/bootstrap/ws-responsive-fix.css" rel="stylesheet" />
<link href="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/bootstrap/ws-responsive-fonts.css" rel="stylesheet" />
<link href="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/bootstrap/ws-servicepage-template.css?v=1" rel="stylesheet" />

<style type="text/css">
/***** START CSS *****/
	@media (max-width: 768px) {

	}
/*** START OF generic CSS ***/

/*** END OF generic CSS ***/
	
/*** START OF element-specific CSS ***/
ul.ws-gender-selection li.ws-gender-cta.selected{
	background:red;
}

.ws-sg-button{
	color:#f4f4f4;
}

.ws-sg-button.selected{
	color:blue !important;
}

/*** END OF element-specific CSS ***/
	

/***** END CSS *****/
</style>
<script src="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/jquery/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {

	sizeGuideNum = 0;

	$("ul.ws-gender-selection li.ws-gender-cta").click(function() {

		$("ul.ws-gender-selection li.ws-gender-cta").removeClass("selected");
		$(".ws-sg-button").removeClass("selected");
		$(this).toggleClass("selected");

		if($(this).hasClass("selected")) {
			if($(this).attr("id") === "ws-women-cta") {
				$("#ws-men-sg-menu").stop().slideUp();
				$("#ws-women-sg-menu").stop().delay(300).slideDown(
					function(){
		                $('html, body').animate({scrollTop: $(this).offset().top}, '200');
		            }
				);
				
				if($(".ws-sg-panel").hasClass("ws-men-sg-panel")){
					$(".ws-men-sg-panel").css("display", "none");
				};
				

			} else if($(this).attr("id") === "ws-men-cta") {
				$("#ws-women-sg-menu").stop().slideUp(200);
				$("#ws-men-sg-menu").stop().delay(300).slideDown(
					function(){
		                $('html, body').animate({scrollTop: $(this).offset().top}, '200');
		            }
				);

				if($(".ws-sg-panel").hasClass("ws-women-sg-panel")){
					$(".ws-women-sg-panel").css("display", "none");
				};

			};
		}
	});

	$(".ws-sg-button").click(function() {
		$(".ws-sg-panel").css("display", "none");
		$(".ws-sg-button").removeClass("selected");

		sizeGuideNum = this.id.slice(-1);

		$(this).addClass("selected");

		$("#ws-sg-panel-" + sizeGuideNum).stop().slideDown(
			function(){
                $('html, body').animate({scrollTop: $(this).offset().top}, '200');
            }
		);



	});
	
});

</script>
<noscript>

</noscript>
<article id="ws-responsive-article-container">
<!--START Container Fluid-->
    <div class="container-fluid">
        <!-- START Full container -->
        <div class="col-xs-12">
            <!--START Main Row-->
            <div class="row">
                <!--START Main Column-->
                <div class="col-xs-12" id="ws-main-container">
                    <!--START Header-->
                    <section> 
                        <div class="row">
                        	<div class="col-xs-12">
								<h1>Size Guides</h1>
	    						<ul class="ws-gender-selection">
	    							<li id="ws-women-cta" class="ws-gender-cta">Women's</li>
									<li id="ws-men-cta" class="ws-gender-cta">Men's</li>
									<li id="ws-women-sg-menu" style="display:none; width:100%; height:300px; background:pink;">
										<ul>
											<li id="ws-sg-button-1" class="ws-sg-button" style="width:200px; border:1px solid #ffffff; padding:10px">Option 1</li>
											<li id="ws-sg-button-2" class="ws-sg-button" style="width:200px; border:1px solid #ffffff; padding:10px">Option 2</li>
											<li id="ws-sg-button-3" class="ws-sg-button" style="width:200px; border:1px solid #ffffff; padding:10px">Option 3</li>
										</ul>

									</li>
									<li id="ws-men-sg-menu" style="display:none; width:100%; height:300px; background:blue;">Men's SG Menu
										<ul>
											<li id="ws-sg-button-4" class="ws-sg-button" style="width:200px; border:1px solid #ffffff; padding:10px">Option 1</li>
											<li id="ws-sg-button-5" class="ws-sg-button" style="width:200px; border:1px solid #ffffff; padding:10px">Option 2</li>
											<li id="ws-sg-button-6" class="ws-sg-button" style="width:200px; border:1px solid #ffffff; padding:10px">Option 3</li>
										</ul>
									</li>
	    						</ul>
	    						<div id="ws-sg-panel-1" class="ws-sg-panel ws-women-sg-panel" style="display:none; width:100%; height:300px; background:red;">Panel 1</div>
	    						<div id="ws-sg-panel-2" class="ws-sg-panel ws-women-sg-panel" style="display:none; width:100%; height:300px; background:#c31818;">Panel 2</div>
	    						<div id="ws-sg-panel-3" class="ws-sg-panel ws-women-sg-panel" style="display:none; width:100%; height:300px; background:#a46464;">Panel 3</div>


	    						<div id="ws-sg-panel-4" class="ws-sg-panel ws-men-sg-panel" style="display:none; width:100%; height:300px; background:red;">Panel 4</div>
	    						<div id="ws-sg-panel-5" class="ws-sg-panel ws-men-sg-panel" style="display:none; width:100%; height:300px; background:#c31818;">Panel 5</div>
	    						<div id="ws-sg-panel-6" class="ws-sg-panel ws-men-sg-panel" style="display:none; width:100%; height:300px; background:#a46464;">Panel 6</div>
	    						<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque in finibus enim. In molestie, est ut porttitor pellentesque, velit neque lobortis quam, quis mattis augue lectus eget est. Etiam eros sem, fermentum vitae laoreet in, hendrerit sed elit. Fusce volutpat, ex id venenatis gravida, nisl nulla congue mauris, nec tincidunt mauris erat egestas sem. Morbi sem lacus, sagittis id lobortis eu, cursus et elit. Nullam pretium interdum dolor, sed bibendum nulla placerat nec. Duis commodo purus sit amet neque bibendum auctor. Quisque mattis augue vitae nunc viverra, sodales dignissim sapien feugiat. Mauris quis consequat purus, ac porttitor arcu. Nunc blandit a erat sed egestas. Pellentesque sagittis lacus sapien. Ut scelerisque faucibus augue, vel malesuada dolor viverra sed. Curabitur eu leo eget risus lobortis malesuada. Maecenas tristique enim nunc, vel volutpat turpis molestie in. In hac habitasse platea dictumst.</p>
	    						<p>Cras interdum erat ac diam vehicula accumsan. Sed blandit ante nisi. Aliquam consectetur elit sollicitudin, aliquam massa a, posuere quam. Vestibulum odio nisl, convallis et tortor ut, placerat placerat magna. Pellentesque consectetur sapien id neque posuere pulvinar. Ut in ante finibus, dignissim velit at, vestibulum velit. Cras at tempus erat. Curabitur semper magna eget consectetur malesuada. Nunc bibendum elementum lacinia. Sed molestie sed erat in porttitor. Vestibulum luctus massa eu nisl laoreet mollis. Duis eleifend purus ligula, in fringilla mi auctor quis. Proin ex lectus, ultricies sed ante et, vulputate tristique elit. Nulla vestibulum lectus imperdiet velit accumsan, nec convallis turpis aliquam.</p>
	    						<p>In hac habitasse platea dictumst. Nam faucibus nulla sed turpis cursus porttitor. Suspendisse sed congue velit. Maecenas vel blandit est. Aenean pulvinar elit ac viverra sollicitudin. Ut quis massa id justo scelerisque molestie a sed lorem. Sed in ante in libero mollis vehicula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vivamus ultrices commodo luctus.</p>
	    						<p>Fusce turpis nulla, rutrum eget metus ac, commodo ullamcorper tortor. Etiam bibendum, massa in interdum lacinia, augue sem feugiat tellus, sit amet scelerisque ante mauris a eros. Praesent consequat lacus at consequat tempus. Etiam at dolor pretium, pulvinar arcu malesuada, sodales augue. Vivamus ut laoreet neque. Pellentesque pharetra nisi purus, vitae vehicula ligula vestibulum vitae. Nullam viverra sagittis augue blandit convallis. Phasellus mollis, felis id pretium varius, leo felis maximus velit, cursus tristique eros risus et leo.</p>
	    						<p>Nunc feugiat lorem at dapibus commodo. Mauris sagittis, neque eget pulvinar sodales, risus est scelerisque quam, quis gravida elit risus vitae dolor. Vestibulum feugiat tempus eros at elementum. Maecenas auctor magna non neque tincidunt, ac dignissim nisl congue. Mauris enim justo, auctor id egestas non, blandit id metus. Nullam venenatis sed nibh sed tincidunt. Cras massa nunc, tempus sit amet dolor a, tempor convallis turpis. Donec a risus auctor, pellentesque sem at, luctus augue. Donec bibendum nunc in cursus suscipit. Cras ac finibus dolor. Aenean pharetra dolor at mauris ultrices vestibulum.</p>
	                        </div>
                        </div>
                    </section> 
                    <!--END Header-->
                </div>
                <!--END Main Column-->
            </div>
            <!--END Main Row-->
        </div>
        <!--END Full Container-->
    </div>
    <!--END Container Fluid-->
</article>

Please let me know your thoughts.

Cheers,

Andrew

A common approach would be to associate the submenus with the top menu items by actually nesting them inside each other. ;-) Like

CSS

.submenu {
  display: none;
}

.menu-item.selected .submenu {
  display: block;
}

HTML

<ul>
  <li class="menu-item">
    Women
    <ul class="submenu">
      <li>option 1</li>
      <li>option 2</li>
      <li>option 3</li>
    </ul>
  </li>
  <li class="menu-item">
    Men
    <ul class="submenu">
      <li>option 1</li>
      <li>option 2</li>
      <li>option 3</li>
    </ul>
  </li>
</ul>

JS

$menuItems = $('.menu-item')

$menuItems.click(function () {
  $menuItems.removeClass('selected')
  $(this).addClass('selected')
})

fiddle

Thanks for the fiddle.
The only problem is that I have 2 sub-menus, as follows:

  1. The user selects a gender
  2. A further set of options are presented
  3. User selects from these options which keeps the first menu open but also displays a dedicated panel for that further selected option.

Cheers,

Andrew

To nest such menus even further, you have to use a direct-child selector in the CSS so that not all sub menus are getting expanded:

.menu-item.selected > .submenu {
  display: block;
}

And in the JS, you have to make sure not to remove the selected class from the selected ancestor elements. This could be achieved with the .filter() method for instance:

$menuItems.click(function(evt) {
  var target = evt.target

  $menuItems
    .filter(function(_, el) {
      // Filter the menu items to those not being the
      // actual target element clicked, and not containing 
      // the target element
      return el !== target && !$(el).find(target).length
    })
    .removeClass('selected')

  $(target).addClass('selected')
})
1 Like

Hi there,

Thanks for the example.
However, when I create a submenu within the submenu , when I select the menu-item then a first submenu item it closes down to the menu-items again?

<style type="text/css">
.submenu {
  display: none;
}

.submenu .submenu {
  display: none;
}

.menu-item.selected > .submenu {
  display: block;
}

.menu-item.selected > .submenu > .submenu{
  display:block;
}
</style>
<script src="https://4825bb991047015a3f2f-f0eb4387bd2ed49703134ec4683824e8.ssl.cf3.rackcdn.com/jquery/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
  $menuItems = $('.menu-item')

  $menuItems.click(function(evt) {
    var target = evt.target

    $menuItems.filter(function(_, el) {
        // Filter the menu items to those not being the
        // actual target element clicked, and not containing 
        // the target element
        return el !== target && !$(el).find(target).length
      })
      $menuItems.removeClass('selected')

    $(target).addClass('selected')
  })
});
</script>
<ul>
  <li class="menu-item">
    Women
    <ul class="submenu">
      <li>option 1
        <ul class="submenu">
        <li>option 1 > panel 1</li>
        </ul>
      </li>
      <li>option 2</li>
      <li>option 3</li>
    </ul>
  </li>
  <li class="menu-item">
    Men
    <ul class="submenu">
      <li>option 1</li>
      <li>option 2</li>
      <li>option 3</li>
    </ul>
  </li>
</ul>

Cheers,

Andrew

You’re first calling .filter() on $menuItems, discard the result, and then call .removeClass() on $menuItems again. But you’ll want to remove the class from the filtered items only – that’s the whole point of the filtering. :-) Have a closer look at my code above; also, here’s an updated fiddle.

1 Like

Ah after a bit of experimenting with it I get the structure now - I was just missing a class on my inner li element.

So if I wanted to pair the lower options to open individual panels what would be the easiest way to do this? As above I’m doing a lookup on the selected item’s ID, slicing the digit off the end to apply to a variable and using this to pair up with a panel which only opens to that individual selected item - or is this the best way to do it?

The main problem I can see with my approach is that it isn’t scalable (e.g. if more buttons were to be needed in between (i.e. another one in between the current 7 and 8) which would result in both correct naming of any new buttons or panels, and renaming of any subsequent panels in the sequence.

Thank you very much for your help so far!

Cheers,

Andrew

Yes, that would certainly be an appropriate way to do this. A small improvement might be to just store the number of the associated panel in a data-* attribute so that you don’t have to do the slicing, which would be problematic for two-digit numbers. Or you could use hash-links to the panels in the menu, and show them with the :target pseudo-class.

Either way, you somehow have to relate menu item and panel. That’s okay as long you don’t hard-code it into your JS, but just maintain these relations in the markup.

Great, thanks for coming back to me - much appreciated!

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.