引子
Ext4JSLint是使用
ExtAspNet来展示JSLint-Toolkit检查结果的开源项目。
JSLint-Toolkit是一个使用Rhino和JSLint的开源项目,可以对一个文件夹中的所有JavaScript进行语法检查,并显示友好的检查结果。
下面是JSLint-Toolkit生成的检查结果:
[{
"name": "source",
"type": "folder",
"kids": [
{
"name": "config.js",
"type": "file",
"errors": [0, 0, 0]
},
{
"name": "lint.js",
"type": "file",
"errors": [3, 0, 3]
},
{
"name": "main.js",
"type": "file",
"errors": [0, 0, 0]
},
{
"name": "util.js",
"type": "file",
"errors": [0, 0, 0]
}
],
"basePath": "scripts/source",
"fileCount": 4,
"errors": [3, 0, 3]
},
{
"name": "jquery-1.3.2.js",
"type": "file",
"basePath": "scripts/jquery-1.3.2.js",
"errors": [51, 43, 8]
}]
这样一个JSON字符串其实描述了一个如下的文件结构:
+scripts/source
-config.js
-lint.js
-main.js
-util.js
-scripts/jquery-1.3.2.js
特别注意的是,根节点多了一个属性basePath用来表示根路径。
errors表示此JavaScript文件中的错误数(这是一个数组,第一个表示总数,第二个表示严重错误的个数)。
页面效果:
ASPX标签定义
<ext:Tree runat="server" ID="Tree1" ShowBorder="false" ShowHeader="false" AutoScroll="true"
EnableArrows="true" OnNodeCommand="Tree1_NodeCommand">
</ext:Tree>
标签定义非常简单,因为所有的逻辑都在后台实现了。
同时定义了点击树节点的事件处理OnNodeCommand="Tree1_NodeCommand",因为我们要在点击树节点时更新中间的Grid,也即是当前JavaScript的错误列表。
树与递归
只要有树的地方就少不了递归函数,因此这段代码对于我们理解递归也很有帮助。
首先看看页面初始化代码:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
LoadData(true);
}
}
#region LoadData
private void LoadData(bool showAllErrors)
{
btnExpandAll.OnClientClick = Tree1.GetExpandAllNodesReference();
btnCollapseAll.OnClientClick = Tree1.GetCollapseAllNodesReference();
string treestr = GetFileContent("~/data/json/tree.json");
List<string> treePathList = new List<string>();
ResolveMenuTree(new JSONArray(treestr), treePathList, showAllErrors, Tree1.Nodes);
}
private string GetFileContent(string path)
{
string treestr = String.Empty;
using (StreamReader sr = new StreamReader(Server.MapPath(path)))
{
treestr = sr.ReadToEnd();
}
return treestr;
}
private void ResolveMenuTree(JSONArray ja, List<string> treePathList, bool showAllErrors, ExtAspNet.TreeNodeCollection nodes)
{
// TODO 递归生成树
}
private string GetTreePath(List<string> treePath)
{
string path = String.Empty;
foreach (string node in treePath)
{
path += node + "/";
}
return path.TrimEnd('/');
}
我们通过Nii.JSON这个开源类库(已经包含在ExtAspNet中),来将JSON字符串转换为JSONArray对象,以便在递归中使用:
JSONArray ja = new JSONArray(treestr);
递归生成树
为了让大家看清问题的本质,我首先放出一个简单的递归,只完成基本功能:
private void ResolveMenuTree2(JSONArray ja, List<string> treePathList, bool showAllErrors, ExtAspNet.TreeNodeCollection nodes)
{
for (int i = 0; i < ja.Count; i++)
{
JSONObject kid = ja[i] as JSONObject;
string name = kid.getString("name");
// 当前路径,如果basePath存在说明是根目录
if (kid.has("basePath"))
{
treePathList.Add(kid.getString("basePath"));
}
else
{
treePathList.Add(name);
}
string currentPath = GetTreePath(treePathList);
string type = kid.getString("type");
// 如果文件夹中没有文件,则不添加此文件夹
if (type == "folder" && kid.getInt("fileCount") == 0)
{
treePathList.RemoveAt(treePathList.Count - 1);
continue;
}
ExtAspNet.TreeNode node = new ExtAspNet.TreeNode();
nodes.Add(node);
node.Text = name;
node.Text = String.Format("<span qtip=\"{0}\">{1}</span>", currentPath, node.Text);
if (type == "folder")
{
node.SingleClickExpand = true;
ResolveMenuTree2(kid.getJSONArray("kids"), treePathList, showAllErrors, node.Nodes);
}
else
{
node.Leaf = true;
}
treePathList.RemoveAt(treePathList.Count - 1);
}
}
在这段代码中,我们通过treePathList来记录当前节点的路径,这也是一个关键点。
此时生成的页面截图:
完整的代码(根据错误设置节点颜色,同时为有错误的节点可回发):
private void ResolveMenuTree(JSONArray ja, List<string> treePathList, bool showAllErrors, ExtAspNet.TreeNodeCollection nodes)
{
for (int i = 0; i < ja.Count; i++)
{
JSONObject kid = ja[i] as JSONObject;
string name = kid.getString("name");
// 当前路径,如果basePath存在说明是根目录
if (kid.has("basePath"))
{
treePathList.Add(kid.getString("basePath"));
}
else
{
treePathList.Add(name);
}
string currentPath = GetTreePath(treePathList);
// 获取JSLint错误数
JSONArray errors = kid.getJSONArray("errors");
int errorCount = errors.getInt(0);
int criticalErrorCount = errors.getInt(1);
if (showAllErrors)
{
if (errorCount > 0)
{
name += String.Format(" ({0})", errorCount);
}
}
else
{
if (criticalErrorCount > 0)
{
name += String.Format(" ({0})", criticalErrorCount);
}
}
string type = kid.getString("type");
// 如果文件夹中没有文件,则不添加此文件夹
if (type == "folder" && kid.getInt("fileCount") == 0)
{
treePathList.RemoveAt(treePathList.Count - 1);
continue;
}
ExtAspNet.TreeNode node = new ExtAspNet.TreeNode();
nodes.Add(node);
node.Text = name;
//node.ToolTip = currentPath;
// 节点的显示颜色
string style = "";
if (showAllErrors)
{
if (errorCount == 0)
{
style = "color:green;";
}
else
{
if (criticalErrorCount == 0)
{
style = "color:#FF9900;";
}
else
{
style = "color:#FF0000;";
}
}
}
else
{
if (criticalErrorCount != 0)
{
style = "color:#FF0000;";
}
else
{
style = "color:green;";
}
}
node.Text = String.Format("<span qtip=\"{2}\" style=\"{0}\">{1}</span>", style, node.Text, currentPath);
if (type == "folder")
{
node.SingleClickExpand = true;
ResolveMenuTree(kid.getJSONArray("kids"), treePathList, showAllErrors, node.Nodes);
}
else
{
node.Leaf = true;
if (showAllErrors)
{
if (errorCount != 0)
{
node.EnablePostBack = true;
node.CommandName = currentPath;
}
}
else
{
if (criticalErrorCount != 0)
{
node.EnablePostBack = true;
node.CommandName = currentPath;
}
}
}
treePathList.RemoveAt(treePathList.Count - 1);
}
}
下一章将讲述如何点击左侧树节点时更新中间的Grid控件,并加载右侧的IFrame(即JavaScript文件的内容)。
下载全部源代码