Nested Tree Display : Comments appreciated

Coding Critique is the place to post source code for peer review by other members of DevNetwork. Any kind of code can be posted. Code posted does not have to be limited to PHP. All members are invited to contribute constructive criticism with the goal of improving the code. Posted code should include some background information about it and what areas you specifically would like help with.

Popular code excerpts may be moved to "Code Snippets" by the moderators.

Moderator: General Moderators

Post Reply
User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Nested Tree Display : Comments appreciated

Post by CoderGoblin »

Note: Placing here for both comments on the thing in general and also to know if you feel this would be useful as a code snippit or if fleshed out more as a tutorial...

Overview
One of the frequent problems I have to solve is displaying a nested tree with collapseable branches. Solutions do exists including one called dtree but that ran into problems with Firefox when the tree was large (an alert asking user if they want to continue the script). Also it didn't allow me to insert additional nodes etc using Ajax whatever. This is my solution, a combination of HTML Lists/Javascript and CSS. PHP can create the lists from whatever source (arrays, database trees whatever).

Setup
For the purposes of this explanation I will create the HTML using the $_SESSION variable.

The directory/file structure is
Base Dir
..index.php
..css
....tree.css
..img
....<images here>
..javascript
....tree.js

Obviously if you change the paths you will have to change the CSS and php files if you wish to try this (path names at present are relative).

We need some images which unfortunately I cannot post here. I use 16x16 transparent gifs. The images in question are...
base.gif (a root image),
line.gif (a vertical line),
nolines_plus.gif (a square box with + inside),
nolines_minus.gif (a square box with - inside),
join.gif (a horizontal line),
folder.gif (a standard folder image),
folder_open.gif (a standard open folder image),
gfolder.gif (a grey copy of the folder image),
gfolder_open.gif (a grey copy of the open folder image),
page.gif (a 'page' icon)

The Code

base_path/index.php

Code: Select all

<?php

function cssTreeList($data,$first=0)
{
    static $level=0;
    static $node_no=1;
    $node_no++;
    $out='';
    if (!$first) $out.='<ul style="display:none;" id="subnode'.($node_no-1).'">';
    ksort($data);
    foreach ($data as $key=>$value) {
      if (is_array($value)) {
        if (count($value)) {
          $out.='<li class="closed" id="node'.$node_no.'"><a href="javascript:treeToggle('.$node_no.',\'node\');" class="tclosed" id="nodelink'.($node_no).'">'.$key.'</a>';
        } else {
          $out.='<li class="closed" id="node'.$node_no.'"><a href="javascript:treeToggle('.$node_no.',\'node\');" class="tgclosed" id="nodelink'.($node_no).'">'.$key.'</a>';
        }
        $out.=cssTreeList($value);
        $out.='</li>';
      } else {
        $out.='<li class="opened"><span class="tpage">'.$key.' = '.$value.'</span></li>';
      }
    }
    if (!$first) $out.='</ul>';
    return $out;                
}
    
  $_SESSION['lvl1']=array();
  $_SESSION['lvl2']=array();
  $_SESSION['lvl3']=array();
  $_SESSION['lvl4']=array();
  $_SESSION['lvl5']='Page5';
  $_SESSION['lvl6']='Page6';
  
  $_SESSION['lvl1']['lvl1_1']=array();
  $_SESSION['lvl1']['lvl1_2']=array();
  $_SESSION['lvl1']['lvl1_3']=array();
  $_SESSION['lvl1']['lvl1_4']=array();
  $_SESSION['lvl1']['lvl1_5']='Page 1.5';
  $_SESSION['lvl1']['lvl1_6']='Page 1.6';
  
  $_SESSION['lvl2']['lvl2_1']=array();
  $_SESSION['lvl2']['lvl2_2']=array();
  $_SESSION['lvl2']['lvl2_3']=array();
  $_SESSION['lvl2']['lvl2_4']=array();
  $_SESSION['lvl2']['lvl2_5']='Page 2.5';
  $_SESSION['lvl2']['lvl2_6']='Page 2.6';

  $_SESSION['lvl2']['lvl2_3']['lvl2_3_1']='Page 2_3_1';
  $_SESSION['lvl2']['lvl2_4']['lvl2_3_2']='Page 2_3_2';

  $_SESSION['lvl3']['lvl3_1']=array();
  $_SESSION['lvl3']['lvl3_2']=array();
  $_SESSION['lvl3']['lvl3_3']=array();
  $_SESSION['lvl3']['lvl3_4']=array();
  $_SESSION['lvl3']['lvl3_5']='Page 3.5';
  $_SESSION['lvl3']['lvl3_6']='Page 3.6';

  $_SESSION['lvl4']['lvl4_1']=array();
  $_SESSION['lvl4']['lvl4_2']=array();
  $_SESSION['lvl4']['lvl4_3']=array();
  $_SESSION['lvl4']['lvl4_4']=array();
  $_SESSION['lvl4']['lvl4_5']='Page 4.5';
  $_SESSION['lvl4']['lvl4_6']='Page 4.6';
  
  if (!empty($_SESSION)) {
      $out.='<ul class="treelist"><li class="root">_SESSION<ul class="treenodes">';
      $out.=cssTreeList($_SESSION,1);
      $out.='</ul></li></ul>';
  } 
?>
<html>
  <head>
    <title>Css Tree List</title>
    <script src="./javascript/tree.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="./css/tree.css" />
  </head>
  <body>
    <?php echo($out); ?>
  </body>
</html>
base_path/javascript/tree.js

Code: Select all

function treeToggle(node,identifier)
{
  var cname='';
  var element=document.getElementById(identifier+node);
  if (element) {
    switch (element.className) {
      case 'opened':
        element.className='closed';
        break;
      case 'closed':
        element.className='opened';
        break;
    }
    cname=element.className;
  }
  element=document.getElementById(identifier+'link'+node);
  if (element) {
    switch (element.className) {
      case 'topened':
        element.className='tclosed';
        break;
      case 'tclosed':
        element.className='topened';
        break;
      case 'tgopened':
        element.className='tgclosed';
        break;
      case 'tgclosed':
        element.className='tgopened';
        break;
    }
  }
  element=document.getElementById('sub'+identifier+node);
  if (element) {
    switch (cname) {
      case 'opened':
        element.style.display='block';
        break;
      case 'closed':
        element.style.display='none';
        break;
    }
  }  
}
base_path/css/tree.css

Code: Select all

ul.treelist
{
  font-family: Arial, Helvetica, sans-serif;
  font-size: 12px;
  color: #000;
  white-space: nowrap;
  list-style-type:none;
  padding:0;
}

ul.treelist li.root:before
{
  content:url(../img/base.gif);
}

ul.treenodes
{
list-style-type: none;
padding: 0;
}

ul.treenodes ul {
 list-style-type:none;
 padding:0;
}

ul.treenodes li 
{
  padding:0 0 0 15px;
  background: url(../img/line.gif) repeat-y 0 0;
}

ul.treenodes li:last-child {
  background-repeat: no-repeat;
}


ul.treenodes li.closed a.tclosed
{
  margin:-15px;
  padding: 3px 0 0 0px;
  background:url(../img/nolines_plus.gif) no-repeat 0 -0.15em;
  text-decoration:none;
  color:#00F;
}

ul.treenodes li.opened span.tpage
{
  margin:-15px;
  padding: 3px 0 0 0px;
  text-decoration:none;
  color:#00F;
}

ul.treenodes li.closed a.tgclosed
{
  margin:-15px;
  padding: 3px 0 0 0;
  background:url(../img/nolines_plus.gif) no-repeat 0 -0.15em;
  text-decoration:none;
  color:#00F;
}

ul.treenodes li.opened a.topened
{
  margin:-15px;
  padding: 3px 0 0 0px;
  background:url(../img/nolines_minus.gif) no-repeat 0 -0.15em; 
  text-decoration:none;
  color:#00F;
}

ul.treenodes li.opened a.tgopened
{
  margin:-15px;
  padding: 3px 0 0 0;
  background:url(../img/nolines_minus.gif) no-repeat 0 -0.15em;  
  text-decoration:none;
  color:#00F;
  width:100%;
}


li a.tclosed:before {
  content: url(../img/join.gif) url(../img/folder.gif);
}

li a.topened:before {
  vertical-align:middle;
  content: url(../img/join.gif) url(../img/folder_open.gif);
}

li a.tgopened:before {
  vertical-align:middle;
  content: url(../img/join.gif) url(../img/gfolder_open.gif);
}

li a.tgclosed:before {
  vertical-align:middle;
  content: url(../img/join.gif) url(../img/gfolder.gif);
}

li span.tclosed:before {
  content: url(../img/join.gif) url(../img/gfolder_open.gif);
}

li span.tpage:before {
  content: url(../img/join.gif) url(../img/page.gif);
}

ul.treenodes li.closed span.tclosed
{
  margin:-15px;
  padding: 3px 0 0 0;
  text-decoration:none;
  color:#000;
}
User avatar
CoderGoblin
DevNet Resident
Posts: 1425
Joined: Tue Mar 16, 2004 10:03 am
Location: Aachen, Germany

Post by CoderGoblin »

To make things a bit clearer, without PHP or Javascript, the following HTML file with the CSS will display a tree

Code: Select all

<html>
  <head>
    <title>Css Simple Tree List</title>
    <link rel="stylesheet" type="text/css" href="./css/tree.css" />
  </head>
  <body>
    <ul class="treelist">
      <li class="root">Simple Tree
        <ul class="treenodes">
          <li class="opened"><a class="topened">One</a>
            <ul>
              <li class="opened"><span class="tpage">One A</span></li>
              <li class="opened"><span class="tpage">One B</span></li>
              <li class="opened"><span class="tpage">One C</span></li>
            </ul>
          </li>
          <li class="opened"><a class="topened">Two</a>
            <ul>
              <li class="opened"><a class="topened">2 One</a>
                <ul>
                  <li class="opened"><span class="tpage">2 One A</span></li>
                  <li class="opened"><span class="tpage">2 One B</span></li>
                  <li class="opened"><span class="tpage">2 One C</span></li>
                </ul>
              </li>
              <li class="opened"><a class="topened">2 Two</a></li>
              <li class="opened"><a class="topened">2 Three</a></li>
            </ul>
          </li>
          <li class="opened"><a class="topened">Three</a>
          </li>
          <li class="opened"><a class="topened">Four</a>
          </li>
          <li class="opened"><a class="topened">Five</a>
          </li>
          <li class="opened"><span class="tpage">A</span></li>
          <li class="opened"><span class="tpage">B</span></li>
          <li class="opened"><span class="tpage">C</span></li>
        </ul>
      </li>
    </ul>
  </body>
</html>
Post Reply