Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Displaying Data in CSS/Javascript Treeviews

ASP 102

Displaying Data in CSS/Javascript Treeviews

by  Tarwn  Posted    (Edited  )
A treeview lets you diosplay a whole lot of data without totally blowing your end users mind. They can be extended to allow for selection of nodes for a form, browsing large numbers of resources by category, or even simple displays for programming examples.
Since most of us will be basing our treeview's off of data in a database I will lead through an example of how we would like our recordset to be formatted and then through building the actual treeview.

Basically the first hurdle is going to be outputting parents followed by children. Rather than just blast everything to the screen we want to be able to print a parent category one time and then all of it's children right after it. This will be key to building our treeview. We will begin with outputting the data in a list with the parents bolded and their children following.

However you do your selection statement your going to want your resulting recordset to come back with category as a column and sub-category as a second column. When we create our SQL to get the data we want to make sure everything is grouped together and, for he sake of argument, alphabetical. So say we have the following db setup:
Table tblMake: fields: make_id, make_name
Table tblModel: fields: model_id, model_name, make_id

We want to select something like this: SELECT make_name, model_name FROM tblMake, tblModel WHERE tblMake.make_id = tblModel.make_id ORDER BY tblMake.make_name, tblModel.model_name

Now we get back all the data, first alphabetized by Make, with the children model records alphabetized by model under each. What we want to do now is loop through and output that data in groups. Easiest way is to hold onto the last make name we worked with so we can tell when we switch makes:
Code:
'assume our recordset is called rsCats
Dim last_name
'very important to MoveFirst befoe we do anything, re-ordering sometimes sends back a recordset with the pointer pointing somewhere in the midle instead of the top
If Not rsCats.EOF The rsCats.MoveFirst
Do Until rsCats.EOF
   'is this a new groups of make/models?
   If last_name <> lcase(rsCats("make_name")) Then
      'output the make name
      Response.Write "<b>" & rsCats("make_name") & "</b><br>"
      'set the last name
      last_name = lcase(rsCats("make_name"))
   End If

   'output the model name every time
   Response.Write rsCats("model") & "<br>"

   'next record
   rsCats.MoveNext
Loop

So now we will get everything printed to the screen by category. We're a third of the way there already.

The next challenge will be to make a treeview like interface. The easiest way to do this is to setup a set of div's and span's to enclose the data. We will need one div to hold each category, and one div to hold each list of children. Later it will be easier if we have a span for the +/- tree sign and another for the parent text, so we'll add in names for those as well. We're going to give these div's some CSS class names to help us keep them apart and make it easier to mak them look prettier later on:
CODE
<html>
<head>
<style>
.node{}
.node_children{}
.node_toggle{}
.node_name{}
</style>
</head>
node class will contain the entire node
node_children will hold the list of children so we can make it appear and disappear as a group
node_toggle will hold the +/- toggle so we can detect it easier
node_name will hold the name of the parent node

The layout of this data will be something like this (copy and paste to a temp HTML page to view it properly):
Code:
<html>
<head>
<style>
span, div{ margin: 2px; }
.node{ border: 1px solid blue; }
.node_children{ border: 1px solid green; margin-left: 25px}
.node_toggle{ border: 1px solid #999999; width: 20px; text-align: Center;}
.node_name{ border: 1px solid #FF0099; }
</style>
</head>
<body>
<div class="node"><span class="node_toggle">+</span><span class="node_name">Parent Node</span>
   <div class="node_children">
      child<br>
      child<br>
      child
   </div>
</div>
Node: Blue<br>
Node CHildren: Green<br>
Node Toggle: Grey<br>
Node Name: Purple/Pink<br>
</body>
</html>
That is just an example for purposes of illustration, don't bother extending it further.

So now we need to print out our tree inside those div's. In order to do this we will just modify the code we created earlier that was outputting everything to individual lines:
Code:
Dim last_name
If Not rsCats.EOF The rsCats.MoveFirst
Do Until rsCats.EOF
   'is this a new groups of make/models?
   If last_name <> lcase(rsCats("make_name")) Then
      '--- We need to start a node div and end the previous one if there was one
      'check for previous open category
      If len(last_name) > 0 Then
         'end the node_children and the node divs
         Response.Write "</div></div>"
      End If

      '--- Output a node div to enclose everything
      Response.Write "<div class=""node"">"
      'output a toggle span to precede the name
      Response.Write "<span class=""node_toggle"">+</span>"

      'output the make name --- with span tags
      Response.Write "<span class=""node_name"">" & rsCats("make_name") & "</span>"

      '--- Now we need to start the children's node
      Response.Write "<div class=""node_children"">"

      'set the last name
      last_name = lcase(rsCats("make_name"))
   End If

   'output the model name every time
   Response.Write rsCats("model") & "<br>"

   'next record
   rsCats.MoveNext
Loop

'--- We need to end any outstanding nodes and node_children
If len(last_name) > 0 Then
   Response.Write "</div></div>"
End If

ok, so now we have a bunch of categories and sub-categories. We should probably use some CSS to make them more obviously tree-like, so I'll alter the style stuff real quick and put it altogether:
Code:
<%
'Somewhere up here we're making our recordset
%>
<html>
<head>
<style>
.node{
   margin-left: 25px
   padding-left: 5px;
}
.node_children{
   /* by default we will hide the children */
   display: none;
}
.node_toggle{
   /* we need a fixed width because +/- aren't same width
      and will make stuff bounce around when we change them.
      We also want to add a hand cursor when they mouse over
      a toggle to make it more obvious they can click it */
   width: 20px;
   text-align: center;
   cursor: hand;
}
</style>
</head>
<body>
<%
Dim last_name
If Not rsCats.EOF The rsCats.MoveFirst
Do Until rsCats.EOF
   'is this a new groups of make/models?
   If last_name <> lcase(rsCats("make_name")) Then
      'We need to start a node div and end the previous one if there was one
      'check for previous open category
      If len(last_name) > 0 Then
         'end the node_children and the node divs
         Response.Write "</div></div>"
      End If

      'output a node div to enclose everything
      Response.Write "<div class=""node"">"
      'output a toggle div to precede the name
      Response.Write "<div class=""node_toggle"">+</div>"

      'output the make name
      Response.Write rsCats("make_name")

      'we need to start the children's node
      Response.Write "<div class=""node_children"">"

      'set the last name
      last_name = lcase(rsCats("make_name"))
   End If

   'output the model name every time
   Response.Write rsCats("model") & "<br>"

   'next record
   rsCats.MoveNext
Loop

'end any outstanding nodes and node_children
If len(last_name) > 0 Then
   Response.Write "</div></div>"
End If
%>
</body>
</html>
Alright, so we now have the data outputting in a categorical fashion, we have the display looking all tree-like, we even have a little han cursor when they hover over he toggle, but where's the action?

Alright, last but not least we need to use some client-side scripting to open and close ou children collections. To make this browser compatible we have a couple important things to look out for. In Netscape/Mozilla/et all browsers the event is passed directly to the function that is assigned as the handler, so we want to check if it gets passed or not and use that as our decision maker on what type of browser we are working with. Rather then lead you through creation ofthe script, I will display it and let you follow along with the comments:
Code:
<script language="javascript">//first redirect all click events to the navClicked function
document.onclick = navClicked;
function switchPoint(aNavPoint){
    var collapsed;
    if(aNavPoint.innerHTML == "+"){
        collapsed=false;
        aNavPoint.innerHTML = "-";
    }
    else{
        collapsed=true;
        aNavPoint.innerHTML = "+";
    }
    return collapsed;
}
function navClicked(e){
    //assume the click was on a switchPoint and that it is collapsed
    var isCollapsed = true;
    //get a reference to the element that was clicked
    // non-IE browsers get passed the event, IE needs to grab it from window
    if(!e){
        var elem = window.event.srcElement;
    }
    else{
        var elem = e.target;
    }
    var nodelist = elem.parentNode.childNodes[2];

    //if the element that was clicked on was one of the toggles
    if(elem.className == "node_toggle"){
        //call the switchpoint function to change it's state, ie "+" to "-" and "-" to "+"
        isCollapsed = switchPoint(elem);
        //if the item is now collapsed after switching
        if(isCollapsed)
            //get it's parent element, get the parents third child (element_contents), hide it
            nodelist.style.display = "none";
        else
            //otherwise open it's element_contents sibling
            nodelist.style.display = "inline";
    }
}
</script>

And there we have it, all the components necessary for building a two layer tree. Two layer trees are great, but what happens wen you want an unknown number of layers? or just three layers even?

I'll leave the alterations to you, but I can point you in the right direction. The way the node styles are setup you can have multiple nodes inside each other without having to make alterations to the styles. I'll add an example of multiple levels, the only real work invoilved for you will be altering your recordset loop to output the data correctly:
Code:
<style>	
	.node{
		margin-left:25px;
		padding-left: 5px;
	}
	.node_children{
		display:none;
	}
	.node_toggle{
		width:20px;
		text-decoration:none;
		text-align:center;
		color:#666666;
		cursor:hand;
	}
	.node_no_toggle{
		width:20px;
		text-decoration:none;
	}
</style>
<div class="tree_view">
	<div class="node"><span class="node_toggle">+</span><span class="node_text">Parent 1</span>
		<div class="node_children" style="display:none;">
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 1 (Parent 1)</span></div>
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 2 (Parent 1)</span></div>
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 3 (Parent 1)</span></div>
		</div>
	</div>
	<div class="node"><span class="node_toggle">+</span><span class="node_text">Parent 2</span>
		<div class="node_children" style="display:none;">
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 1 (Parent 2)</span></div>
			<div class="node"><span class="node_toggle">+</span><span class="node_text">Child 2 (Parent 2)</span>
				<div class="node_children" style="display:none;">
					<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 1 (Parent 2-2)</span></div>
					<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 2 (Parent 2-2)</span></div>
					<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 3 (Parent 2-2)</span></div>
				</div>
			</div>
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 3 (Parent 2)</span></div>
		</div>
	</div>
	<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Parent 3</span></div>
	<div class="node"><span class="node_toggle">+</span><span class="node_text">Parent 4</span>
		<div class="node_children" style="display:none;">
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 1 (Parent 4)</span></div>
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 2 (Parent 4)</span></div>
			<div class="node"><span class="node_no_toggle">&nbsp;</span><span class="node_text">Child 3 (Parent 4)</span></div>
		</div>
	</div>
</div>

This example may not be the best in the world for one reason: It displays multiple levels that are not the same depth from parent to parent. While this was originally generted from a script very similar to the one above, I wouldn't expect someone to jump directly from the above two-layer script to this one, it is just for example purposes so you can see the nesting of the node tags. In this example you will also se a new class, node_no_toggle. Basically this is used by me to make up for the spacing differences between children with no sub-cvhildren and children with a toglle (and subchildren).

I hope this has proven helpful and eventually I will be posting the Treeview objects (used to generate above treeview) to my website, once I get them fomatted all pretty and get back to work on it.
Register to rate this FAQ  : BAD 1 2 3 4 5 6 7 8 9 10 GOOD
Please Note: 1 is Bad, 10 is Good :-)

Part and Inventory Search

Back
Top