如何用JavaScript从JSON自动生成嵌套侧边栏导航?
Hey there! Let's work through this together nice and slowly—no stress, I’ll break every part down so it’s easy to follow. You’re right that document.write isn’t a good approach (it can overwrite your whole page or cause weird timing issues), so we’ll build this properly using DOM-friendly methods, adapting your existing code.
核心思路
First, we need to do two key things:
- Transform the flat JSON data into a nested hierarchy matching your required order:
class → order → family → genus → subgenus (if not "None") → vernacularName → elementName(with number suffixes for duplicate element names). - Convert that hierarchy into the nested
<ul>/<li>structure you need, using the correct CSS classes (sidebar-menu,treeview,treeview-menu).
Step 1: Set Up Your HTML Container
First, add a container to your page where the sidebar will live—this replaces the need for document.write:
<div id="sidebar-container"></div>
Step 2: Full JavaScript Implementation (Adapted for Your Code)
We’ll use your existing PHP/JS setup, either pulling data via AJAX or using the pre-fetched PHP data (I’ll show both options).
Option 1: Use Pre-Fetched PHP Data (Faster, No AJAX)
Since you already have $data from PHP, we can pass it directly to JavaScript without another AJAX call:
// Get the pre-fetched data from PHP const jsonData = <?php echo json_encode($data); ?>; const version = "<?php echo $version; ?>"; // 1. Build the nested hierarchy from the JSON data const sidebarHierarchy = {}; jsonData.forEach(item => { // Define the exact hierarchy order, skipping subgenus if it's "None" const hierarchyLevels = [ item.class, item.order, item.family, item.genus, ...(item.subgenus !== "None" ? [item.subgenus] : []), // Only include subgenus if it's not "None" item.vernacularName, item.elementName ]; // Traverse the hierarchy to add each node let currentNode = sidebarHierarchy; hierarchyLevels.forEach((levelName, index) => { // Handle duplicate element names (last level only) if (index === hierarchyLevels.length - 1 && currentNode[levelName]) { let suffix = 2; let uniqueName = `${levelName} ${suffix}`; // Keep incrementing until we find an unused name while (currentNode[uniqueName]) { suffix++; uniqueName = `${levelName} ${suffix}`; } levelName = uniqueName; } // Create the node if it doesn't exist if (!currentNode[levelName]) { currentNode[levelName] = { children: {}, isLeaf: index === hierarchyLevels.length - 1 // Mark if this is the final element }; } // Move to the next level of the hierarchy currentNode = currentNode[levelName].children; }); }); // 2. Recursive function to generate the nested HTML menu function buildSidebarHTML(node, isTopLevel = false) { // Top level uses "sidebar-menu", submenus use "treeview-menu" let menuHTML = isTopLevel ? '<ul class="sidebar-menu">' : '<ul class="treeview-menu">'; // Loop through each node in the current hierarchy level Object.entries(node).forEach(([nodeName, nodeData]) => { // Add "treeview" class if the node has children const liClass = Object.keys(nodeData.children).length > 0 ? 'class="treeview"' : ''; menuHTML += `<li ${liClass}>${nodeName}`; // If there are children, recursively build the submenu if (Object.keys(nodeData.children).length > 0) { menuHTML += buildSidebarHTML(nodeData.children); } menuHTML += '</li>'; }); menuHTML += '</ul>'; return menuHTML; } // 3. Generate the full sidebar HTML and insert it into the container const finalSidebarHTML = buildSidebarHTML(sidebarHierarchy, true); $('#sidebar-container').html(finalSidebarHTML);
Option 2: Use AJAX (If You Need to Fetch Data Separately)
If you still want to use $.getJSON like your original code, here’s the adapted version:
const version = "<?php echo $version; ?>"; $.getJSON(`bonify${version}/app/json/data2.json`, function(jsonData) { // 1. Build the nested hierarchy (same code as Option 1) const sidebarHierarchy = {}; jsonData.forEach(item => { const hierarchyLevels = [ item.class, item.order, item.family, item.genus, ...(item.subgenus !== "None" ? [item.subgenus] : []), item.vernacularName, item.elementName ]; let currentNode = sidebarHierarchy; hierarchyLevels.forEach((levelName, index) => { if (index === hierarchyLevels.length - 1 && currentNode[levelName]) { let suffix = 2; let uniqueName = `${levelName} ${suffix}`; while (currentNode[uniqueName]) { suffix++; uniqueName = `${levelName} ${suffix}`; } levelName = uniqueName; } if (!currentNode[levelName]) { currentNode[levelName] = { children: {}, isLeaf: index === hierarchyLevels.length - 1 }; } currentNode = currentNode[levelName].children; }); }); // 2. Build HTML (same as Option 1) function buildSidebarHTML(node, isTopLevel = false) { let menuHTML = isTopLevel ? '<ul class="sidebar-menu">' : '<ul class="treeview-menu">'; Object.entries(node).forEach(([nodeName, nodeData]) => { const liClass = Object.keys(nodeData.children).length > 0 ? 'class="treeview"' : ''; menuHTML += `<li ${liClass}>${nodeName}`; if (Object.keys(nodeData.children).length > 0) { menuHTML += buildSidebarHTML(nodeData.children); } menuHTML += '</li>'; }); menuHTML += '</ul>'; return menuHTML; } // 3. Insert into page const finalSidebarHTML = buildSidebarHTML(sidebarHierarchy, true); $('#sidebar-container').html(finalSidebarHTML); });
What This Does (Breakdown for Learning)
Let’s go over the key parts so you understand:
- Hierarchy Building: We loop through each JSON item and build a nested object where each level maps to your required taxonomy order. For duplicate element names, we add a number suffix (like "Calcaneus 2") to avoid duplicates.
- Recursive HTML Generation: The
buildSidebarHTMLfunction calls itself to create submenus. It adds the correct CSS classes:- Top-level
<ul>usessidebar-menu - Parent
<li>elements with children get thetreeviewclass - Submenus use
treeview-menu
- Top-level
- DOM Insertion: Instead of
document.write, we use$('#sidebar-container').html(...)to safely add the menu to your page without breaking existing content.
Testing with Your Sample Data
Using your sample JSON, this will generate exactly the structure you want:
<ul class="sidebar-menu"> <li class="treeview">Mammalia <ul class="treeview-menu"> <li class="treeview">Artiodactyla <ul class="treeview-menu"> <li class="treeview">Bovidae <ul class="treeview-menu"> <li class="treeview">Capra <ul class="treeview-menu"> <li class="treeview">goat <ul class="treeview-menu"> <li>Calcaneus</li> <li>Femur</li> </ul> </li> </ul> </li> </ul> </li> </ul> </li> </ul> </li> </ul>
内容的提问来源于stack exchange,提问作者Spatial Digger

