Turn Nested Lists Into a Collapsible Tree With jQuery

Today I released a new version of File Catalog, a tool to represent directory structures as YAML files. So far, only the indexer and viewer components were available. As of now, a script to turn the YAML files into HTML pages is included.

However, these can get a little uncomfortable to read when containing lots of entries. This can be countered by using JavaScript to turn the nested lists into a collapsible tree. And, while being at it, list items that include other lists should be represented as folders.

HTML

The main part of such a generated HTML file looks like this:

<ul>
  <li>item 1
    <ul>
      <li>item 1.1</li>
      <li>item 1.2</li>
      <li>item 1.3</li>
    </ul>
  </li>
  <li>item 2
    <ul>
      <li>item 2.1
        <ul>
          <li>item 2.1.1</li>
          <li>item 2.2.2</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>item 3</li>
</ul>

CSS

For some eye candy, we'll use a stylesheet:

ul {
    list-style: none;
    margin: 0;
    padding: 0;
}
li {
    background-image: url(page.png);
    background-position: 0 1px;
    background-repeat: no-repeat;
    padding-left: 20px;
}
li.folder {
    background-image: url(folder.png);
}

a {
    color: #000000;
    cursor: pointer;
    text-decoration: none;
}
a:hover {
    text-decoration: underline;
}

It removes the default bullets from the unordered lists and adds a new bullet image for list items. Those using the class li.folder should display another image. The two filenames, page.png and folder.png, refer to those in the excellent Silk Icons set.

Also, style definitions for anchors are included. Note that no anchors appear in the generated markup so far!

JavaScript

I chose the great jQuery JavaScript library to do the heavy lifting here. The comments in the pleasingly short code should explain what is going on pretty well.

// Execute this after the site is loaded.
$(function() {
    // Find list items representing folders and
    // style them accordingly.  Also, turn them
    // into links that can expand/collapse the
    // tree leaf.
    $('li > ul').each(function(i) {
        // Find this list's parent list item.
        var parent_li = $(this).parent('li');

        // Style the list item as folder.
        parent_li.addClass('folder');

        // Temporarily remove the list from the
        // parent list item, wrap the remaining
        // text in an anchor, then reattach it.
        var sub_ul = $(this).remove();
        parent_li.wrapInner('<a/>').find('a').click(function() {
            // Make the anchor toggle the leaf display.
            sub_ul.toggle();
        });
        parent_li.append(sub_ul);
    });

    // Hide all lists except the outermost.
    $('ul ul').hide();
});

Integration

Make sure to add the following lines to the <head> section of the generated HTML file:

<link rel="stylesheet" type="text/css" href="style/style.css"/>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="folding.js"></script>

This assumes that you have downloaded and saved the jQuery library as jquery.js and the given CSS and JavaScript snippets as style.css and folding.js, respectively.

Et voila!

No demo page or complete code download (the exercise of recreating this is left to the reader), but take this screenshot as foretaste:

jQuery collapsible tree

Screenshot

Oh, and the result worked well in Firefox 1.5, Opera 9.20, Internet Explorer 6 and Safari/Win 3.0.2. The credits for this mostly go to jQuery, of course.

Have fun with it! If you have had use for this, please let me know.

Update: As of File Catalog version 0.3, the XHTMLizer adds the folder class to each list item with children itself. Thus, the following JavaScript/jQuery code is sufficient for HTML files created with those newer versions (0.3.1 is also released) to achieve the same behaviour as the code above (the stylesheet and the integration via the <link> and <script> elements stay the same):

// Execute this after the site is loaded.
$(function() {
    // Find list items representing folders and turn them
    // into links that can expand/collapse the tree leaf.
    $('li.folder').each(function(i) {
        // Temporarily decouple the child list, wrap the
        // remaining text in an anchor, then reattach it.
        var sub_ul = $(this).children().remove();
        $(this).wrapInner('<a/>').find('a').click(function() {
            // Make the anchor toggle the leaf display.
            sub_ul.toggle();
        });
        $(this).append(sub_ul);
    });

    // Hide all lists except the outermost.
    $('ul ul').hide();
});