使用JavaScript实现动态展开N阶Table和Row功能
在Web开发中,我们经常需要实现动态展开和折叠的表格功能,类似于FineReport中的效果。本文将介绍如何使用JavaScript实现一个可以动态展开N阶的表格和行功能。
实现思路
实现动态展开N阶表格的核心思路是:
- 使用递归方式构建表格结构
- 通过数据驱动视图更新
- 利用事件委托处理展开/折叠操作
- 动态计算并应用缩进样式
数据结构设计
首先我们需要设计一个合适的数据结构来存储表格的层级关系:
// 示例数据结构
const tableData = [
{
id: 1,
name: '一级节点1',
children: [
{
id: 11,
name: '二级节点1-1',
children: [
{ id: 111, name: '三级节点1-1-1' },
{ id: 112, name: '三级节点1-1-2' }
]
},
{ id: 12, name: '二级节点1-2' }
]
},
{
id: 2,
name: '一级节点2'
}
];核心实现代码
下面是完整的实现代码,包含HTML结构和JavaScript逻辑:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>动态展开表格</title>
<style>
.tree-table {
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
}
.tree-table th, .tree-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
.tree-table th {
background-color: #f2f2f2;
}
.expand-icon {
cursor: pointer;
margin-right: 5px;
display: inline-block;
width: 16px;
text-align: center;
}
.indent {
display: inline-block;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<table class="tree-table" id="dynamicTable">
<thead>
<tr>
<th>名称</th>
<th>层级</th>
<th>操作</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- 表格内容将通过JavaScript动态生成 -->
</tbody>
</table>
<script>
// 示例数据
const tableData = [
{
id: 1,
name: '一级节点1',
children: [
{
id: 11,
name: '二级节点1-1',
children: [
{ id: 111, name: '三级节点1-1-1' },
{ id: 112, name: '三级节点1-1-2' }
]
},
{ id: 12, name: '二级节点1-2' }
]
},
{
id: 2,
name: '一级节点2'
}
];
// 存储展开状态
const expandedState = new Set();
// 渲染表格
function renderTable(data, parentId = null, level = 0) {
const tbody = document.getElementById('tableBody');
let html = '';
data.forEach(item => {
const hasChildren = item.children && item.children.length > 0;
const isExpanded = expandedState.has(item.id);
const indentStyle = `padding-left: ${level * 20}px;`;
html += `
<tr data-id="${item.id}" data-level="${level}" data-parent-id="${parentId}">
<td>
<span class="indent" style="${indentStyle}"></span>
${hasChildren ?
`<span class="expand-icon" onclick="toggleExpand(${item.id})">${isExpanded ? '-' : '+'}</span>` :
'<span class="expand-icon"> </span>'}
${item.name}
</td>
<td>${level + 1}</td>
<td>
<button onclick="addChild(${item.id})">添加子节点</button>
<button onclick="deleteNode(${item.id})">删除</button>
</td>
</tr>
`;
// 如果有子节点且处于展开状态,递归渲染子节点
if (hasChildren && isExpanded) {
html += renderTable(item.children, item.id, level + 1);
}
});
return html;
}
// 切换展开/折叠状态
function toggleExpand(id) {
if (expandedState.has(id)) {
expandedState.delete(id);
} else {
expandedState.add(id);
}
refreshTable();
}
// 刷新表格
function refreshTable() {
const tbody = document.getElementById('tableBody');
tbody.innerHTML = renderTable(tableData);
}
// 添加子节点
function addChild(parentId) {
const parentNode = findNodeById(tableData, parentId);
if (!parentNode.children) {
parentNode.children = [];
}
const newNode = {
id: Date.now(), // 使用时间戳作为临时ID
name: `新节点-${parentNode.children.length + 1}`,
children: []
};
parentNode.children.push(newNode);
expandedState.add(parentId); // 确保父节点展开
refreshTable();
}
// 删除节点
function deleteNode(id) {
if (confirm('确定要删除这个节点及其所有子节点吗?')) {
deleteNodeById(tableData, id);
refreshTable();
}
}
// 根据ID查找节点
function findNodeById(data, id) {
for (const item of data) {
if (item.id === id) {
return item;
}
if (item.children) {
const found = findNodeById(item.children, id);
if (found) {
return found;
}
}
}
return null;
}
// 根据ID删除节点
function deleteNodeById(data, id) {
for (let i = 0; i < data.length; i++) {
if (data[i].id === id) {
data.splice(i, 1);
return true;
}
if (data[i].children) {
if (deleteNodeById(data[i].children, id)) {
return true;
}
}
}
return false;
}
// 初始化表格
document.addEventListener('DOMContentLoaded', () => {
refreshTable();
});
</script>
</body>
</html>代码解析
1. 数据结构
我们使用嵌套的对象数组来表示树形结构,每个节点包含id、name和可选的children属性。
2. 渲染逻辑
renderTable函数是核心,它递归地遍历数据并生成HTML:
- 根据层级计算缩进样式
- 为有子节点的行添加展开/折叠图标
- 根据展开状态决定是否渲染子节点
3. 状态管理
使用Set对象expandedState来存储当前展开的节点ID,这样可以高效地判断和更新展开状态。
4. 交互功能
实现了以下交互功能:
- 点击+/-图标展开/折叠子节点
- 添加子节点
- 删除节点及其所有子节点
扩展功能
基于上述基础实现,我们可以进一步扩展以下功能:
1. 懒加载
对于大数据量的场景,可以实现懒加载:
// 懒加载示例
async function loadChildren(parentId) {
// 模拟API调用
const response = await fetch(`/api/children/${parentId}`);
const children = await response.json();
const parentNode = findNodeById(tableData, parentId);
parentNode.children = children;
expandedState.add(parentId);
refreshTable();
}2. 拖拽排序
可以添加拖拽排序功能,让用户能够重新排列节点顺序。
3. 编辑功能
实现双击单元格编辑节点名称的功能。
性能优化建议
对于大型数据集,可以考虑以下优化措施:
- 使用虚拟滚动技术只渲染可见区域的内容
- 对频繁操作的DOM元素进行缓存
- 使用文档片段减少重排次数
- 考虑使用Web Worker处理复杂的数据操作
总结
本文介绍了如何使用JavaScript实现一个动态展开N阶的表格功能。通过递归渲染、状态管理和事件处理,我们可以创建出灵活且功能丰富的树形表格。该实现可以进一步扩展,以满足更复杂的需求,如懒加载、拖拽排序和实时编辑等。
这种实现方式的优点在于代码结构清晰,易于理解和维护,同时具有良好的扩展性,可以根据具体需求进行定制和增强。