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...
and
you want this on-load of your page:
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.
My name is Paul Watson and I have been a professional web-developer since 1997. I live and work remotely from Cape Town, South Africa.
I have many years of experience with HTML, CSS, JavaScript, PostgreSQL, and Ruby on Rails. I am capable in Python and Machine Learning too.
Currently I am the CTO of CaliberAI. Formerly I worked with Kinzen (CTO & co-founder), Storyful (CTO, acquired by News Corp), FeedHenry (co-founder, acquired by Red Hat), and ChangeX.
Now that you know a bit about me why not say hello.