65.9K
CodeProject is changing. Read more.
Home

Current Tree-Node Opener

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (9 votes)

May 24, 2003

5 min read

viewsIcon

72463

downloadIcon

1322

Simple DOM exploiting code to display the currently selected node in a HTML list tree

Introduction

This article describes a simple method of using JavaScript and the DOM to display the selected node in a HTML tree list. Most methods rely on the tree constructor to "follow the path" down the node tree to the selected item, setting each parent node to a visible mode.

This method however does the reverse; The current node is tagged and the path from it up to the root node is followed making each parent visible on the way. This method is more efficient as no searches or iterations on node collections are needed to find the next node to make visible.

This is not an article on how to make HTML tree lists, I assume you already know how to do this. You can of course view the sample code which includes HTML tree lists. That topic has been covered very well in other articles here on Code Project.

Requirements

Knowledge of HTML, CSS and JavaScript are essential. Knowledge of the Document Object Model (DOM) will help you as well.

No server-side environments, ASP/PHP/etc., are required to run the examples or use this  method. It is all client-side.

The Problem

You have this...
Sample screenshot
and you want this on-load of your page:
Sample screenshot

What you have here is a HTML tree list coded like this:

<ul id="menu">
    <li><a href="">Section 1</a></li>
    <li><a href="">Section 2</a>
        <ul>
            <li><a href="">Section 2.1</a></li>
            <li><a href="">Section 2.2</a></li>
            <li><a href="">Section 2.3</a>
                <ul>
                    <li><a href="">Section 2.3.1</a></li>
                    <li><a href="">Section 2.3.2</a></li>
                </ul>
            </li>
            <li><a href="">Section 2.4</a></li>
        </ul>
    </li>
    <li><a href="">Section 3</a></li>
</ul>

Each UL within a LI will have it's display style attribute set to none in a CSS file. e.g. ul#menu li ul { display:none; }. This is a great setup allowing simple JavaScript to open and close nodes as the user needs.

However when it comes to loading a page and having a sub-node displayed in a visible/open mode by default this structure makes it less than easy. You cannot very well expect users to click back down through the tree structure every time they visit a new page. The tree structure should remain open to the node they clicked on.

The conventional way of sorting this problem out is with code like so:

<ul id="menu">
    <li><a href="">Section 1</a></li>
    <li><a href="">Section 2</a>
        <ul class="open">
            <li><a href="">Section 2.1</a></li>
            <li><a href="">Section 2.2</a></li>
            <li><a href="">Section 2.3</a>
                <ul class="open">
                    <li><a href="">Section 2.3.1</a></li>
                    <li><a href="">Section 2.3.2</a></li>
                </ul>
            </li>
            <li><a href="">Section 2.4</a></li>
        </ul>
    </li>
    <li><a href="">Section 3</a></li>
</ul>

Each parent of the node you want to display needs an assigned class of open. In your CSS you then have ul#menu li ul.open { display: block }.

It works but the main problem is that you have to concoct code which assigns that class to each node that needs it. Not only is the code for this not easy but it is also inefficient. Another really big problem is that you cannot easily cache your tree code as with each navigation event the code changes. It basically becomes per-user code, rendering caching a bit pointless. There are ways around that but the method I will explain below needs none of this.

Looking Up, Another Way

The DOM element property of parentElement is the key. Basically ever element excluding HTML must have a parent element. The tree view code of above is especially dependant on this child/parent structure.

To kick this off here is what I propose; Walk up the tree. Start with the selected node and then using parentElement you walk up the tree to the "root" node of the menu. Everytime you hit a UL element you simply assign block to it's display style attribute. Damned simple IMO. No looping through other elements on the same level is required. You don't need to check if the node is on the right path because it automatically is, by virtue of it being a parent of the node you have walked up from.

Since you are all code-heads you will probably understand the code better than that explanation:

function showCurrentSection()
{        
    var objCurrentSection = document.getElementById("navcurrentsection");        
    if (objCurrentSection != null)
    {
        objCurrentSection.style.display = "block";            
        objCurrentSection.parentElement.childNodes[0].className = "open";
        if (objCurrentSection.parentElement.parentElement.nodeName == "UL")
            showSection(objCurrentSection.parentElement.parentElement);        
    }
}

function showSection(objSection)
{    
    objSection.style.display = "block";
    objSection.parentElement.childNodes[0].className = "open";    
    if (objSection.parentElement.parentElement != null && objSection.parentElement.parentElement.nodeName == "UL")
        showSection(objSection.parentElement.parentElement);
}

You have two functions. showCurrentSection is kicked off normally by an onload event e.g. <body onload="showCurrentSection();">. showCurrentSection calls showSection which calls itself until no more valid parent elements are found.

showCurrentSection starts with the line document.getElementById("navcurrentsection"). There is one modification to the HTML tree list code you need to make:

<ul id="menu">
    <li><a href="">Section 1</a></li>
    <li><a href="">Section 2</a>
        <ul>
            <li><a href="">Section 2.1</a></li>
            <li><a href="">Section 2.2</a></li>
            <li><a href="">Section 2.3</a>
                <ul id="navcurrentsection" >
                    <li><a href="">Section 2.3.1</a></li>
                    <li><a href="">Section 2.3.2</a></li>
                </ul>
            </li>
            <li><a href="">Section 2.4</a></li>
        </ul>
    </li>
    <li><a href="">Section 3</a></li>
</ul>

You need to tag the section so that the walker can find where it should start from. document.getElementById("navcurrentsection") returns an object reference to whatever UL element has that specific ID.

The if (objCurrentSection != null) is just there to ensure that indeed a section is currently selected. For instance on the home page of your website probably no node will be selected, in which case the walker should not run.

Next we just set the navcurrentsection node to be displayed. We then change the classname of the node so that any visualisation of an open node are applied, e.g. an open folder graphic. Next the code checks wether the element two levels up is a UL. Remember that a sub node is a UL contained within a LI element i.e. The parent is actually a LI not a UL. You don't want to touch the LI, so two levels up you go.

If it is a UL then we call the showSection function and pass that parent-parent element to it.

showSection does the actual walking as it calls itself until objSection.parentElement.parentElement != null && objSection.parentElement.parentElement.nodeName == "UL" is false. i.e. Until no more parent-parent elements correspond to a UL element. nodeName returns the name of the element. e.g. A for <a href=""></a>, DIV for <div></div>, UL for <UL></UL> etc.

This obviously means that your HTML tree list code needs to be well coded, valid in otherwords.

Conclusion

All in all quite simple really. Certainly improvements can be made in the JavaScript code, but the idea is there and it works quite well. The main thing I like about this method is that you just output your HTML tree list code, bang in a tag and run the script. You do not have to change the code which generates your HTML tree list. It also gives a taste of the power of the DOM.