Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Ok, let's do some bounty hunting ;)</p> <p><strong>Step 0 - Sanitize example:</strong><br> As already mentioned, your example data is broken, as it does not define a valid nested set. If you took this data from an app, you should check the insert/delete logic.</p> <p>So for testing, I used a sanitized version like so:<br> (MySQL here, as it was the first at hand)</p> <pre><code>CREATE TABLE t_categories`( `id` INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, `title` VARCHAR(45) NOT NULL, `lft` INTEGER UNSIGNED NOT NULL, `rght` INTEGER UNSIGNED NOT NULL, PRIMARY KEY (`id`) ); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 1',1,16); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 2',2,3); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 3',4,7); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 4',5,6); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 5',8,13); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 6',9,12); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 7',10,11); INSERT INTO t_categories (title, lft, rght) VALUES ('Cat 8',14,15); </code></pre> <p><strong>Step 1 - Let the database do the ordering</strong><br> Nested sets where primarily invented as a convenient way of storing trees in databases, as they make it pretty easy to query for subtrees, parent relations and, especially interesting in this case, for order and depth:</p> <pre><code>SELECT node.title, (COUNT(parent.title) - 1) AS depth FROM t_categories AS node CROSS JOIN t_categories AS parent WHERE node.lft BETWEEN parent.lft AND parent.rght GROUP BY node.title ORDER BY node.lft </code></pre> <p>This will return your set neatly ordered, starting with the root node and continuing to the end in preorder. Most importantly, it will add the depth of each node as a positive integer, indicating how many levels the node is below root (level 0). For the above example data, the result will be:</p> <pre><code>title, depth 'Cat 1', 0 'Cat 2', 1 'Cat 3', 1 'Cat 4', 2 'Cat 5', 1 'Cat 6', 2 'Cat 7', 3 'Cat 8', 1 </code></pre> <p>In code:</p> <pre><code>// Grab ordered data $query = ''; $query .= 'SELECT node.title, (COUNT(parent.title) - 1) AS depth'; $query .= ' FROM t_categories AS node'; $query .= ' CROSS JOIN t_categories AS parent'; $query .= ' WHERE node.lft BETWEEN parent.lft AND parent.rght'; $query .= ' GROUP BY node.title'; $query .= ' ORDER BY node.lft'; $result = mysql_query($query); // Build array $tree = array(); while ($row = mysql_fetch_assoc($result)) { $tree[] = $row; } </code></pre> <p>The resulting array will look like this:</p> <pre><code>Array ( [0] =&gt; Array ( [title] =&gt; Cat 1 [depth] =&gt; 0 ) [1] =&gt; Array ( [title] =&gt; Cat 2 [depth] =&gt; 1 ) ... ) </code></pre> <p><strong>Step 2 - Output as HTML list fragment:</strong> </p> <p>Using while loop:</p> <pre><code>// bootstrap loop $result = ''; $currDepth = -1; // -1 to get the outer &lt;ul&gt; while (!empty($tree)) { $currNode = array_shift($tree); // Level down? if ($currNode['depth'] &gt; $currDepth) { // Yes, open &lt;ul&gt; $result .= '&lt;ul&gt;'; } // Level up? if ($currNode['depth'] &lt; $currDepth) { // Yes, close n open &lt;ul&gt; $result .= str_repeat('&lt;/ul&gt;', $currDepth - $currNode['depth']); } // Always add node $result .= '&lt;li&gt;' . $currNode['title'] . '&lt;/li&gt;'; // Adjust current depth $currDepth = $currNode['depth']; // Are we finished? if (empty($tree)) { // Yes, close n open &lt;ul&gt; $result .= str_repeat('&lt;/ul&gt;', $currDepth + 1); } } print $result; </code></pre> <p>Same logic as recursive function:</p> <pre><code>function renderTree($tree, $currDepth = -1) { $currNode = array_shift($tree); $result = ''; // Going down? if ($currNode['depth'] &gt; $currDepth) { // Yes, prepend &lt;ul&gt; $result .= '&lt;ul&gt;'; } // Going up? if ($currNode['depth'] &lt; $currDepth) { // Yes, close n open &lt;ul&gt; $result .= str_repeat('&lt;/ul&gt;', $currDepth - $currNode['depth']); } // Always add the node $result .= '&lt;li&gt;' . $currNode['title'] . '&lt;/li&gt;'; // Anything left? if (!empty($tree)) { // Yes, recurse $result .= renderTree($tree, $currNode['depth']); } else { // No, close remaining &lt;ul&gt; $result .= str_repeat('&lt;/ul&gt;', $currNode['depth'] + 1); } return $result; } print renderTree($tree); </code></pre> <p>Both will output the following structure:</p> <pre><code>&lt;ul&gt; &lt;li&gt;Cat 1&lt;/li&gt; &lt;li&gt; &lt;ul&gt; &lt;li&gt;Cat 2&lt;/li&gt; &lt;li&gt;Cat 3&lt;/li&gt; &lt;li&gt; &lt;ul&gt; &lt;li&gt;Cat 4&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;Cat 5&lt;/li&gt; &lt;li&gt; &lt;ul&gt; &lt;li&gt;Cat 6&lt;/li&gt; &lt;li&gt; &lt;ul&gt; &lt;li&gt;Cat 7&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;li&gt;Cat 8&lt;/li&gt; &lt;/ul&gt; &lt;/li&gt; &lt;/ul&gt; </code></pre> <hr> <p>Nitpickers corner: Questioner explicitly asked for <code>&lt;ul&gt;</code>, but <em>ordered</em> unordered lists!? Come on...<br> <strong>;-)</strong></p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload