views:

143

answers:

4

I have a navigation bar that has two dropdowns (as nested ul's). I'm trying to toggle the subnavs when its parent is clicked. Or hide one subnav when the other parent is clicked. Here's the markup:

<div id="nav-bar">
  <a href="#">A link</a> |
  <ul>
    <li id="feedback">
      <a href="javascript:void(0);">Feedback</a>
      <ul class="subnav">
        <li><a href="#">Give us feedback</a></li>
       </ul>
     </li>
  </ul> |
  <a href="#">Another link</a> |
  <ul>
    <li id="location">
      <a href="javascript:void(0);">Pick your location</a>
      <ul class="subnav">
        <li><a href="#">Los Angeles</a></li>
        <li><a href="#">New York</a></li>
       </ul>
     </li>
  </ul>
</div>

And the code:

//hide the subnavs and give them a little down arrow
$('ul.subnav').hide().parent().append('<small>&#9660;</small>');

// show its subnav when clicked
$('#nav-bar ul li').click(function() {
  var subnav = $(this).children('ul.subnav');
  // hide the other's subnav if it's visible
  if ($(this).attr('id') == 'location') {
    subnav.toggle();
    $('li#feedback').children('ul.subnav').hide();
  } else {
    subnav.toggle();
    $('li#location').children('ul.subnav').hide();
  }
});

Still a novice to JS and jQuery, I'd like to know if there's a less verbose way to accomplish what I'm trying to do above.

Edit: A better question is, is there a way to do this without having to explicitly give the li's an id?

A: 

Use a pluggin like this...

menus

ctrlShiftBryan
I'd prefer not to do something with 600+ lines of code when I could do it in < 10.
farkenfooken
+1  A: 

A better format would be something like this for your HTML:

<ul id="nav-bar">
  <li><a href="#">A link</a> |</li>
  <li>
    <a href="#">Feedback</a> |
    <ul class="subnav">
      <li><a href="#">Give us feedback</a></li>
     </ul>
  </li>
  <li><a href="#">Another link</a> |</li>
  <li>
    <a href="#">Pick your location</a> |
    <ul class="subnav">
      <li><a href="#">Los Angeles</a></li>
      <li><a href="#">New York</a></li>
     </ul>
   </li>
</ul>

Then your jQuery code would look like this:

$(function(){
  var $nav    = $("#nav-bar"),
      $subnav = $nav.find("ul.subnav").hide();

  $nav.children('li:has(ul.subnav)').click(function(e){
    e.preventDefault();
    var $sub = $(this).children('ul.subnav').toggle();
    $subnav.not($sub).hide();
  })
})
Doug Neiner
This is more how I'd like to have done it. Works great.I know this is asking a lot for you to explain but what was your rhyme or reason for doing it this way? And was the way I did it not ideal and why?
farkenfooken
@farkenfooken First, I tried to use semantic markup (which resulted in less markup) by describing the navigation as a "list of links" with some links having "sub lists". Then, on the jQuery, by caching the result sets in variables, I sped up search times (jQuery doesn't have to find the same item over and over). And finally, I used more complex jQuery selectors instead of class names or id's to keep the markup as simple as possible. It could be simplified further if needed.
Doug Neiner
Thanks for taking the time to reply to my questions.
farkenfooken
@farkenfooken Happy to do so! Your respectful comments and questions will take you far on Stack Overflow. I'll be on the lookout for your questions in the future :)
Doug Neiner
@Doug Neiner one thing this method does is make my links in the li's unclickable. Any way around this?
farkenfooken
Edit: it was the e.preventDefault(); that wasn't letting me click the link FYI.
farkenfooken
A: 

Well, there are a few ways that you could make it a little bit easier to maintain your code. Here's a quick example:

//hide the subnavs and give them a little down arrow
$('ul.subnav').hide().parent().append('<small>&#9660;</small>');

// show its subnav when clicked
$('#nav-bar ul li').click(function() {
  var subnav = $(this).children('ul.subnav');
  // hide the other's subnav if it's visible

 $('ul.subnav').hide();
 subnav.toggle();
});

Basically, this will re-hide all of the 'subnav' elements again, and then show the one that was clicked on. But the main difference here is that you can now add a third, fourth, firth, etc dropdown menu, or change the IDs around without having to change the javascript.

The HTML that you're using looks pretty good, and relatively similar to "standard" navigation menus.

Edit: Which is apparently similar to the previous answer :P

Chibu
This did it for me with the least amount of code change.
farkenfooken
I should note this doesn't successfully toggle the li you're clicking, only shows it.
farkenfooken
A: 

I'd get rid of the inline javascript:void(0) code it's unecessary. Have your link link to "#".

You definitely don't want to be matching ID's like that, it doesn't scale very well. Instead, something like this should work (untested):

$('#nav-bar ul li').click(function() {      
  $(this)
    //hide all subnavs
    .closest("#nav-bar").find("ul.subnav").hide().end().end()
    //show subnav under the li clicked
    .find("ul.subnav").show();    
});

Whenever possible, you want to chain your jquery code so that you don't have to grab a new element each time from the dom. From what I understand, it's more efficient to chain as it traverses the already found element, rather than fetching a new one.

the .end() reverts your chain back to the previous found element, so when I say .closest("blah").find("blah2") I'm going two levels deep. .end().end() will bring me back to the original item (this) so that I can then find the specific subnav underneath it.

brad
Thanks, I had no idea about .end() until now.
farkenfooken