在开发网页应用时,经常需要为用户提供一个界面,让他们能够浏览文件系统并选择文件夹进行操作,比如备份。本文将介绍如何利用Bootstrap Treeview插件,快速实现一个简单的文件夹浏览器。
不久前,遇到了一个编码需求,需要为一个网页应用实现文件系统文件夹浏览器的功能。用户需要能够选择一个文件夹,以便将其备份到另一个位置。在寻找解决方案的过程中,发现了Bootstrap Treeview,这是一个简单易用且与项目风格一致的插件。
要将Bootstrap Treeview包含到项目中,首先需要下载或安装它,并在视图(或布局页面)中引用相应的脚本和样式。Bootstrap Treeview可以通过npm和bower安装,或者直接从GitHub克隆或下载整个仓库。选择将所需的文件放置在wwwroot/lib/bootstrap-treeview目录下,以便服务。
在开发环境中,通常会使用非压缩版本的文件,以便调试。以下是开发环境中样式的引用方式:
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="~/css/site.css">
<link href="~/lib/bootstrap-treeview/bootstrap-treeview.min.css" rel="stylesheet">
在非开发环境中,会使用所有样式表的压缩版本(如果可用)。
以下是开发环境中脚本的引用方式:
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/lib/bootstrap-treeview/bootstrap-treeview.min.js"></script>
通常,也会在开发中使用非压缩版本的脚本,除非不可用。
在视图中放置一个容器和脚本,理想情况下是一个div,其中TreeView将出现:
<div id="tree"></div>
function getTree() {
// 一些逻辑来检索或生成树结构
return data;
}
$('#tree').treeview({data: getTree()});
获取数据是Bootstrap Treeview的一个关键步骤,因为它需要在初始化TreeView之前准备好树形图数据。每个节点的最基本数据结构只有两个属性:Text是文件夹的名称,Nodes是该文件夹子节点的集合。
由于必须在初始化控件之前拥有数据,可以在jQuery Ajax方法的done回调中初始化treeview:
$.ajax("@Url.Action("TreeData", "TreeView")")
.done(function(resp) {
$("#bsTree").treeview({
data: resp
});
})
.fail(function(error) {
console.log(error);
});
简化节点类TreeNode旨在表示一个树节点,它是节点对象完整规范的很小一部分。由于无法在在线示例中看到除了‘+’和‘-’以外的任何图标,所以选择完全省略图标属性,以保持事情尽可能小而整洁。
使用这种节点结构,TreeView的JSON响应可能如下所示:
[
{
"text": "Folder1",
"nodes": []
},
{
"text": "Folder2",
"nodes": [
{
"text": "FolderB",
"nodes": [
{
"text": "FolderOne",
"nodes": []
}
]
}
]
},
{
"text": "Logs",
"nodes": []
}
]
TreeView需要一个递归数据结构(JSON)来表示整个文件夹树,因为该控件不支持延迟加载或初始加载之外的任何Ajax。因此,它需要一次性获得所有数据。发现这令人不安,因为文件系统上的嵌套非常普遍,深度未知,构建和传输巨大的JSON文档只会损害性能。
以下是实现所需递归的控制器代码:
public class TreeViewController : Controller
{
private FileTreeConfig _config;
public TreeViewController(IOptions<FileTreeConfig> config)
{
_config = config.Value;
}
public IActionResult TreeData(string dir = "")
{
var browsingRoot = Path.Combine(_config.BaseDir, dir);
var nodes = new List<TreeNode>();
nodes.AddRange(RecurseDirectory(browsingRoot));
return Json(nodes);
}
private List<TreeNode> RecurseDirectory(string directory)
{
var ret = new List<TreeNode>();
var dirInfo = new DirectoryInfo(directory);
try
{
var directories = dirInfo.GetDirectories("*", SearchOption.TopDirectoryOnly);
foreach (var dir in directories)
{
if (dir.FullName.ToLower() == dirInfo.FullName)
{
continue;
}
var thisNode = TreeNode.FromDirInfo(dir);
thisNode.Nodes.AddRange(RecurseDirectory(dir.FullName));
ret.Add(thisNode);
}
}
catch (UnauthorizedAccessException ux)
{
// NB Log.
}
return ret;
}
以上代码清晰地展示了如何递归地遍历目录结构,并将其转换为TreeNode对象的列表,然后返回JSON格式的数据。
使用这种仅支持递归的treeview,不希望用户导航到文件系统的根目录,并让代码递归遍历所有内容。设置一个浏览根目录,限制用户不能浏览超出某个文件系统文件夹,是一个理想的做法。虽然他们可以使用相对路径,但检查这些路径的复杂性超出了这个简单练习的范围。
有一个名为BaseDir的配置设置,它是treeview必须开始的根目录的路径。控制器代码中的Path.Combine帮助建立这个根目录。