Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate Nested UL's Based Upon Variable Depth Data

I have some hierarchical data that I need to display in a series of nested UL's. For each item I have a name, an ID, and a depth value. Normally I would just group these items by depth, but I actually need to create a tree structure with my data, like this: My sample data in MySQL Workbench

Here is my question: is there a good way to generate valid markup (I would love it if I could make it print out with proper tabbing too, but that will be tough) where my data will be wrapped in nested UL's? I already have a solution that kinda works, but I am getting a single stray tag. Here is the code I have for that:

<?php
    include("includes/classes/Database.class.php");
    $db = new Database();
    $query = "SELECT COUNT(parent.Name) - 2 as level, node.Name AS Name, node.ID
    FROM Region AS node, Region AS parent
        WHERE node.LeftVal BETWEEN parent.LeftVal AND parent.RightVal and node.Name <> 'Earth'
            GROUP BY node.ID
            ORDER BY node.LeftVal";
    $results = $db->executeQuery($query);
?>
<!DOCTYPE HTML>
<html lang="en-US">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <?php
        $last_level = 0;
    ?>
    <ul id="regionTree">
    <?php
        while ($row = mysql_fetch_assoc($results)) {
            $link = '<li>'.PHP_EOL.'<a href="addChild.php?parentid='.$row["ID"].'">'.$row["Name"]."</a>".PHP_EOL;
            $diff = $last_level - $row["level"];
            if($diff == 0){
                // Sibling
                echo ($row["level"] != 0) ? '</li>'.PHP_EOL.$link:$link;
            }
            elseif($diff < 0){
                // Child
                $demoter = '<ul>'.PHP_EOL;
                for ($i=0; $i > $diff; $i--) { 
                    echo $demoter;
                }
                echo $link;
            }
            else{
                // Parent
                $promoter = '</li>'.PHP_EOL.'</ul>';
                for ($i=0; $i < $diff; $i++) { 
                    echo ($row["level"] != 0) ? $promoter.PHP_EOL."</li>":$promoter;
                }
                echo $link;
            }

            $last_level = $row["level"];
        }
    ?>
    </li>
    </ul>
</body>
</html>

Any Ideas?

::Edit:: I have created a pastebin with the generated source which does not validate. Pastebin.com

::EDIT 2:: Here is the schema for the Region table. It is designed using a hybrid of the nested set model and the adjacency list model.

CREATE TABLE Region (
    ID INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Stores the ID for the Region.',
    Name VARCHAR(45) NOT NULL COMMENT 'Stores the name of the Region',
    Region_Type VARCHAR(45) NOT NULL COMMENT 'Stores the Region type.',
    Parent INT COMMENT 'Stores the ID of the Parent Region',
    LeftVal INT NOT NULL,
    RightVal INT NOT NULL,
PRIMARY KEY (ID)
) COMMENT 'Stores information about all Regions.' ENGINE=INNODB
ROW_FORMAT=DEFAULT CHARACTER SET utf8 collate utf8_general_ci;
like image 578
Brendon Dugan Avatar asked Feb 14 '12 20:02

Brendon Dugan


1 Answers

This should work:

$query = "SELECT node.Name, (COUNT( parent.Name ) -1) AS depth FROM region AS node
            CROSS JOIN region AS parent
                WHERE node.LeftVal BETWEEN parent.LeftVal
                    AND parent.RightVal
            GROUP BY node.Name
            ORDER BY node.LeftVal";

$result = mysql_query($query);

// Build array
$tree = array();
while ($row = mysql_fetch_assoc($result)) {
    $tree[] = $row;
}

// Bootstrap loop
$result        = '';
$currDepth     = 0; 
$lastNodeIndex = count($tree) - 1;
// Start the loop
foreach ($tree as $index => $currNode) {
    // Level down? (or the first)
    if ($currNode['depth'] > $currDepth || $index == 0) {
        $result .= '<ul>';
    }
    // Level up?
    if ($currNode['depth'] < $currDepth) {
        $result .= str_repeat('</ul></li>', $currDepth - $currNode['depth']);
    }
    // Always open a node
    $t = ($index == 0) ? 1 : 2;
    $result .= '<li>' . $currNode['Name'];
    // Check if there's chidren
    if ($index != $lastNodeIndex && $tree[$index + 1]['depth'] <= $tree[$index]['depth']) {
        $result .= '</li>'; // If not, close the <li>
    }
    // Adjust current depth
    $currDepth = $currNode['depth'];
    // Are we finished?
    if ($index == $lastNodeIndex) {
        $result .= '</ul>' . str_repeat('</li></ul>', $currDepth);
    }
}

// Indent the code
// For UTF8: tidy_parse_string($result, array('indent' => true, 'show-body-only' => true), 'UTF8')
$result = tidy_parse_string($result, array('indent' => true, 'show-body-only' => true));
print $result;

Getting a modified preorder tree traversal model (nested set) into a <ul>

How to generate a tree view from this result set based on Tree Traversal Algorithm?

How to create an array from this result set (nested categories stored in databased with traversal model)?

like image 51
Keyne Viana Avatar answered Oct 05 '22 22:10

Keyne Viana