JavaScript list of links with sub lists, hide and show

Hey guys, I am trying to set up a list of links for a list of products in my sidebar, where a user clicks on one category then a sub set of links will appear under the main list link, clicking on any other category closes the previous set of links and opens the new sub set. I tried to do this with PHP but due to using PHP include for my sidebar I ended up with multiple PHP files, not good.I also think that creating this function should be client side, I hope I am right. I do not even know if this could be done in JavaScript, any suggestion on this would be appreciated!

I don’t know how to write JavaScript to work with the sidebar.php file which I am including for every page in the sidebar div in the HTML, its really confusing me.

Many thanks!!

It would be relatively easy to do this in JavaScript and even quicker if we use the jQuery library along with it.

A working example of the below code can be seen at: http://jsfiddle.net/GeekyJohn/9kaQQ/

If you had some basic markup like an unordered list for the navigation:


<ul id="nav">    
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
</ul>

Together with some very very basic CSS, you can of course expand on this to make it look nicer.


body {
    padding:20px;
}
.open {
    display:block;
}
ul ul {
    margin-left:20px;
}

Now we can get to the good part. The jQuery that will take care of it.

This snippet emulates the basic functionality of the jQuery Accordion (which is pretty much the functionality you described ;), of course this snippet is a little shorter than the jQuery Accordion script)

Hopefully the comments will explain enough, let me know if any of it isn’t clear.


//get a reference to the nav <ul>
$nav = $("#nav");


//hide all but the first sub list
$nav.find("ul:not(:first)").hide();


//mark the first sub list as open
$nav.find("ul:first").addClass("open");


//receive clicks on the section links
$nav.on("click", "#nav>li>a", function(e){


    //get a reference to the currently clicked link and its list item
    $this = $(this);
    $currentLi = $this.closest("li");
    
    //if our current section is open, just return
    if ($currentLi.find(".open").is("ul")) {
        //if you want a section link to be able to collapse itself, uncomment this line:
        //$currentLi.find("ul.open").slideUp().removeClass("open")
        
        return; 
    }


    //close all open lists
    $nav.find("ul.open").slideUp().removeClass("open");
    
    //open the submenu that belongs to this section
    $currentLi.find("ul").slideDown().addClass("open");


});

Aussie John, many thanks for that example code, I am this minute about to test it all out and see if I can use it to do what I want! Thanks a lot for this, I shall let you know how I get on!

Cheers!

Am I missing a step? I am using the exact example Aussie John provided. I am trying to replicate deucalion0’s process as I have a similar request. Thank you.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Untitled Document</title>
<link href="test.css" rel="stylesheet" type="text/css">
</head>

<body>
<script type="text/javascript" src="test.js">
<ul id="nav">
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
</ul></script>
</body>
</html>

I think the trouble here might be that you’ve put all your markup inside a script tag. Try placing the closing script tag at the end of the opening one and then moving the entire script tag to just before the closing body tag.


<ul id="nav">
...
</ul>

<script type="text/javascript" src="test.js"></script>
</body>

Thank you for your response!! Hmm… So I followed your directions, I even added the css and js into the page, still nothing happening…?

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Untitled Document</title>
<style type="text/css">
body {
    padding:20px;
}
.open {
    display:block;
}
ul ul {
    margin-left:20px;
}
</style>
</head>

<body>
<ul id="nav">
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
</ul><script type="text/javascript">
//get a reference to the nav <ul>
$nav = $("#nav");

//hide all but the first sub list
$nav.find("ul:not(:first)").hide();

//mark the first sub list as open
$nav.find("ul:first").addClass("open");

//receive clicks on the section links
$nav.on("click", "#nav>li>a", function(e) {

    //get a reference to the currently clicked link and its list item
    $this = $(this);
    $currentLi = $this.closest("li");

    //if our current section is open, just return
    if ($currentLi.find(".open").is("ul")) {
        //if you want a section link to be able to collapse itself, uncomment this line:
        $currentLi.find("ul.open").slideUp().removeClass("open")
        return;
    }

    //close all open lists
    $nav.find("ul.open").slideUp().removeClass("open");

    //open the submenu that belongs to this section
    $currentLi.find("ul").slideDown().addClass("open");

});// JavaScript Document</script>
</body>
</html>

Ah, right, you’ll also need to include jQuery.
Put this line above your first <script> section and things should start working :slight_smile:

<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

Still nothing…

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Untitled Document</title>
<style type="text/css">
body {
    padding:20px;
}
.open {
    display:block;
}
ul ul {
    margin-left:20px;
}
</style>
</head>

<body>
<ul id="nav">
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
</ul>
<script src="jquery.js"></script>
<script type="text/javascript">
//get a reference to the nav <ul>
$nav = $("#nav");

//hide all but the first sub list
$nav.find("ul:not(:first)").hide();

//mark the first sub list as open
$nav.find("ul:first").addClass("open");

//receive clicks on the section links
$nav.on("click", "#nav>li>a", function(e) {

    //get a reference to the currently clicked link and its list item
    $this = $(this);
    $currentLi = $this.closest("li");

    //if our current section is open, just return
    if ($currentLi.find(".open").is("ul")) {
        //if you want a section link to be able to collapse itself, uncomment this line:
        $currentLi.find("ul.open").slideUp().removeClass("open")
        return;
    }

    //close all open lists
    $nav.find("ul.open").slideUp().removeClass("open");

    //open the submenu that belongs to this section
    $currentLi.find("ul").slideDown().addClass("open");

});// JavaScript Document</script>
</body>
</html>

Ok so this was kind of interesting :wink:

I took your code and tried to run it, and indeed nothing happened when clicking on the links.

I had a look at my example on JSFiddle and confirmed everything was working.

While looking there, I noticed that it was using an older version of jQuery (1.7.1) and of course locally I did a quick test with the latest version, as you undoubtedly did. Alas, things didn’t work in the latest version.

The issue lies around how the event delegate method .on() interprets its second parameter, the child selector which should be monitored for the events. In jQuery 1.7.1 you could still reference the parent element that the event was bound on, but somewhere along the way to 1.9.1 that must have been fixed.

Long story short, if you update the following line:


//receive clicks on the section links
$nav.on("click", "#nav>li>a", function(e) {

And change it to:


//receive clicks on the section links
$nav.on("click", ">li>a", function(e) {

Now things should start working :slight_smile:

(The “>” at the start of the selector is intentional)

The Fiddle has been updated as well: http://jsfiddle.net/GeekyJohn/9kaQQ/

still nothing… :\

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Untitled Document</title>
<style type="text/css">
body {
    padding:20px;
}
.open {
    display:block;
}
ul ul {
    margin-left:20px;
}
</style>
</head>

<body>
<ul id="nav">
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
    <li>
        <a href="#">Section</a>
        <ul>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
            <li><a href="#">Product</a></li>
        </ul>
    </li>
</ul>
<script src="jquery.js" type="text/javascript"></script>
<script type="text/javascript">
//get a reference to the nav <ul>
$nav = $("#nav");

//hide all but the first sub list
$nav.find("ul:not(:first)").hide();

//mark the first sub list as open
$nav.find("ul:first").addClass("open");

//receive clicks on the section links
$nav.on("click", ">li>a", function(e) {

    //get a reference to the currently clicked link and its list item
    $this = $(this);
    $currentLi = $this.closest("li");

    //if our current section is open, just return
    if ($currentLi.find(".open").is("ul")) {
        //if you want a section link to be able to collapse itself, uncomment this line:
        $currentLi.find("ul.open").slideUp().removeClass("open")
        return;
    }

    //close all open lists
    $nav.find("ul.open").slideUp().removeClass("open");

    //open the submenu that belongs to this section
    $currentLi.find("ul").slideDown().addClass("open");

});// JavaScript Document</script>
</body>
</html>

If I copy that code verbatim and put it in a HTML file, everything seems to work just fine. Can you double check that it is actually loading the jQuery script in?

If in doubt, replace your reference to jQuery with the following:


<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>