經典的MSDN/CSDN左側樹狀導航,核心在於「按需加載子節點」與「無刷新展開收縮」。本文拆解一套基於JSP+JavaScript的完整實現,巧用隱藏<iframe>異步獲取子樹HTML,再注入主頁DOM——這是在AJAX普及前的經典模式,至今仍具參考價值。
id="hiddenframe"隱藏幂PID(父節點ID)、PNode(父DOM ID),返回子節點HTML片段ExpandNode():控制展開/收縮,首次展開時通過hiddenframe.location.replace()請求SubTree.jsp,拿到HTML後注入對應<div><iframe width="100%" height="100" id="hiddenframe" style="display:none"></iframe>
<script>
function ExpandNode(ParentNode, ParentId) {
var childDiv = document.getElementById(ParentNode.id + '_0');
if (childDiv.style.display == 'none') {
childDiv.style.display = 'block';
if (childDiv.getAttribute('loaded') == 'no') {
// 關鍵:通過隱藏iframe異步加載子樹
document.frames['hiddenframe'].location.replace(
"SubTree.jsp?PID=" + ParentId + "&PNode=" + ParentNode.id
);
childDiv.setAttribute('loaded', 'yes');
}
} else {
childDiv.style.display = 'none';
}
}
</script>
<%-- 根節點查詢:帶子節點計數 --%>
<%
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
ResultSet rs = DriverManager.getConnection(
"jdbc:odbc:driver={Microsoft Access Driver (*.mdb)};DBQ=tree.mdb","",""
).createStatement().executeQuery(
"SELECT *, (SELECT COUNT(*) FROM tree WHERE parentid = T.id) AS children FROM tree T WHERE parentid = 0"
);
int i = 0;
while (rs.next()) {
int children = rs.getInt("children");
int id = rs.getInt("id");
%>
<div id='Node_<%=i%>'>
<a href='#'
<% if (children > 0) { %> onclick='ExpandNode(Node_<%=i%>,<%=id%>)'>+</a>
<% } else { %>>-</a> <% } %>
<a href='#'
<% if (children > 0) { %> ondblclick='ExpandNode(Node_<%=i%>,<%=id%>)' <% } %>
><%=rs.getString("remark")%></a>
</div>
<div id='Node_<%=i%>_0' style='display:none' loaded='no'>正在加載...</div>
<% i++; } %>
<%
String parentNode = request.getParameter("PNode");
int pid = Integer.parseInt(request.getParameter("PID"));
// 計算縮進層級
int level = parentNode.length() - parentNode.replace("_", "").length();
String indent = " ".repeat(level); // JDK 11+,舊版用循環拼接
ResultSet rs = DriverManager.getConnection(...).createStatement().executeQuery(
"SELECT *, (SELECT COUNT(*) FROM tree WHERE parentid = T.id) AS children FROM tree T WHERE parentid = " + pid
);
StringBuilder html = new StringBuilder();
int i = 0;
while (rs.next()) {
int children = rs.getInt("children");
int id = rs.getInt("id");
String nodeId = parentNode + "_" + (i+1);
html.append("<div id='").append(nodeId).append("'>")
.append(indent).append("<a href='#' ");
if (children > 0) html.append("onclick='ExpandNode(").append(nodeId).append(",").append(id).append(")'>+");
else html.append(">-");
html.append("</a><a href='#' ");
if (children > 0) html.append("ondblclick='ExpandNode(").append(nodeId).append(",").append(id).append(")'");
html.append(">").append(id).append(": ").append(rs.getString("remark")).append("</a></div>");
if (children > 0)
html.append("<div id='").append(nodeId).append("_0' style='display:none' loaded='no'>")
.append(indent).append(" 正在加載...</div>");
i++;
}
%>
<script>
var container = eval('parent.' + '<%=parentNode%>_0');
container.innerHTML = '<%=html.toString().replace("'", "\\'")%>';
</script>
fetch('SubTree.jsp?...').then(r=>r.text()).then(html=>container.innerHTML=html)template標籤)渲染,徹底分離<tree-nav>),屬性傳遞數據源URL隱藏幂+DOM注入是Web 1.0時代的智慧結晶。理解其原理,有助於深刻體會AJAX、Fetch、WebSocket等現代技術解決的核心痛點——局部更新與異步交互。重構時,保留「懶加載、按需渲染」的核心體驗即可。