Here is my initial pass at creating a nested and collapsible list of taxonomy terms with unlimited levels. This code makes the deepest term in the nesting a link to the term page.

i started with a node template file ('node-page.tpl.php') so i can wrap it and then make a menu item to it. Make a page type node and give it a title that designates a root level taxonomy page. Inside 'node-page.tpl.php' put the following code: (please note that it would be best to put the code inside the case into template.php and pass it as var)

<?php
  // $title of course is just the value entered in the title field for the designated node.
  switch ($title) {
      case 'My Taxonomy Categories':
        // $vid = the ID of taxonomy vocabulary to create collapsible hierarchical lists
        $vid = 1;
        $tree = taxonomy_get_tree($vid);
        if ($tree) {
          drupal_add_js('misc/collapse.js');
          $total_terms = count($tree);
          foreach ($tree as $key => $term) {
            $depth = $term->depth;
            if ($key == 0) { // first term
              if ($total_terms == 1) {
                print ('<ul><li>' . l($tree[$key]->name, 'taxonomy/term/' . $tree[$key]->tid) . '</li></ul>');
              }
            } else { // all terms except first
              if ($depth > $last_depth) { // previous term is a parent
                print ('<fieldset class="collapsible collapsed"><legend><a href="#">' . $tree[$key-1]->name . '</a></legend>');
                if ($depth >= $tree[$key+1]->depth) {
                  print ('<ul>');
                }
              } else if ($depth == $last_depth) { // previous term is either same level linked term or same level parent
                if ($depth >= $tree[$key+1]->depth) { // same level link term
                  print ('<li>' . l($tree[$key-1]->name, 'taxonomy/term/' . $tree[$key-1]->tid) . '</li>');
                } else { // same level parent
                  if ($key > 1) { // previous parent is not the first so close fieldset
                    print ('</fieldset>');
                  }
                  print ('<fieldset class="collapsible collapsed"><legend><a href="#">' . $tree[$key-1]->name . '</a></legend>');
                }
              } else { // $depth < $last_depth - ie previous term is deeper 
                print ('<li>' . l($tree[$key-1]->name, 'taxonomy/term/' . $tree[$key-1]->tid) . '</li></ul>');
                $difference_in_depth = $last_depth-$depth;
                for ($i = 1; $i <= $difference_in_depth; $i++) {
                  print ('</fieldset>');
                }
              }
              if ($key == ($total_terms-1)) { // last term
                print ('<li>' . l($tree[$key]->name, 'taxonomy/term/' . $tree[$key]->tid) . '</li></ul>');
                $difference_in_depth = $last_depth-$depth;
                for ($i = 1; $i <= $difference_in_depth; $i++) {
                  print ('</fieldset>');
                }
              }
            } // end ifs ('depth' compared to 'last_depth')
            $last_depth = $depth;
          } // end foreach
        } // end if ($tree)
       break;
  }
?>

This code could probably use a little logic and comments cleaning but it seems to work for me and provides a relatively decent structure to extend logic or theming.

[EDIT - doesn't work in all cases of nesting multiple categories - works for two level deep - need logic work]

Comments

Hunabku’s picture

OK the code above is a mess. Furthermore it is improper html to place fieldsets inside lists.

Since i posted the above code over a year ago, i've delved deeply into recursive functions and came up with an elegant solution.

In your template file insert the following:

// $vid = the ID of the vocabulary to create collapsible hierarchical lists
$vid = 4;
if ($tree = taxonomy_get_nested_tree($vid)) {
	drupal_add_js('misc/collapse.js');
	// Optional alphabetic sort of top level terms
	usort($tree, "your_compare");
	print (taxonomy_theme_nested_tree($tree, count($tree)));
}

In your custom module insert the following (exclude opening & closing php tag):

function taxonomy_get_nested_tree($terms = array(), $max_depth = NULL, $parent = 0, $parents_index = array(), $depth = 0) {
	// This recursive function creates nested arrays of child terms with unlimited term depth

	if (is_int($terms)) {
		$terms = taxonomy_get_tree($terms);
	}
	foreach($terms as $term) {
		foreach($term->parents as $term_parent) {
			if ($term_parent == $parent) {
				$nested_tree[$term->tid] = $term;
			}
			else {
				$parents_index[$term_parent][$term->tid] = $term;
			}
		}
	}
	foreach($nested_tree as &$term) {
		if (isset($parents_index[$term->tid]) && (is_null($max_depth) || $depth < $max_depth)) {
			// Execute recursion and pass parameters for children
			$children = taxonomy_get_nested_tree($parents_index[$term->tid], $max_depth, $term->tid, $parents_index, $depth + 1);
			// Optional alphabetic sort of child terms
			usort($children, "your_compare");
			$term->children = $children;
		}
	}
	return $nested_tree;
}


function your_compare($a, $b, $order = 1) {
	// $order == -1 would reverse the natural sort order (ie make it descending)
	// If you want a case sensitive sort, swap out strcasecmp() for strcmp()
	return $order * strcasecmp($a->name, $b->name);
}


function taxonomy_theme_nested_tree($tree, $num_at_depth) {
	// This recusive function wraps a nested term array of unlimited depth with fieldsets and divs
	
	// Initialize a static array so that recursion does not re-initialize it
	static $depth_counter = array();
	// $depth_counter is used to determine how many </fieldset> to add
	
	foreach ($tree as $term) {
		if (!$counter_at_depth = $depth_counter[$term->depth]['counter_at_depth']) {
			// Set $depth_counter initial values for the current term depth
			$depth_counter[$term->depth] = array('num_at_depth'=>$num_at_depth, 'counter_at_depth'=>0, 'in_fieldset'=>TRUE);
			if ($term->depth == 0) {
				$depth_counter[$term->depth]['in_fieldset'] = FALSE;
			}
		}
		$depth_counter[$term->depth]['counter_at_depth'] = $depth_counter[$term->depth]['counter_at_depth'] + 1;
		if ($term->children) {
			$output .= ("<fieldset class=\"collapsible collapsed\"><legend><a href=\"#\">" . $term->name . "</a></legend>\n");
			$num_at_depth = count($term->children);
			// Execute recursion and pass parameters for children
			$output .= taxonomy_theme_nested_tree($term->children, $num_at_depth);
		} else { 
			$output .= "\n<div>\n" . l($term->name, taxonomy_term_path($term)) . "\n</div>\n";
		}
		if (!$term->children && ($depth_counter[$term->depth]['counter_at_depth'] == $depth_counter[$term->depth]['num_at_depth']) && ($depth_counter[$term->depth]['in_fieldset'] == TRUE) ) {
			$depth_key = count($depth_counter) - 1;
			while ($depth_key > 0) {
				$output .= $temp;
				if ( ($depth_counter[$depth_key]['counter_at_depth'] == $depth_counter[$depth_key]['num_at_depth']) && ($depth_counter[$depth_key]['in_fieldset'] == TRUE) ) {
					unset ($depth_counter[$depth_key]);
					$output .= "</fieldset>\n";
				} else {
					break;
				}
				$depth_key --;
			}
		}
	}	
	return $output;
}

ajohnh’s picture

Wow, this is great! Is there a simple way to display an array of nodes associated with each tid? Or is this way out of scope?

nehasapra1’s picture

I am getting an error.

Undefined variable: nested_tree in taxonomy_get_nested_tree() (line 18 of C:\wamp\www\drupal-7.2\sites\all\modules\taxanomyterm\taxanomyterm.module).

Can you please tell me what is this about and where i need to paste the code.I have created nmy own module on which i have written your code in .module file and written the template code in the template.php file.
What else i need to do on this.Please let me know

kzrdata’s picture

I was searching for a long time to make the same thing with the display of the nodes in each terms until I found the Taxonomy Lineage module. I'm trying to make the list collapsible the way you did now. Great work.

adepentane’s picture

Okay, this is an answer to what i have been looking for. Please, for thos of us new to drupal or not the code lords, where do we create these files to....? THanks.