Ext.Net 1.2.0_Ext.Net.GridPanel 实现多级Grid
实现多级 Grid 在 Ext.Net Demo 里有,本文旨在进一步说明它的实现,以及在此基础上,说明如何在多级Grid上,进行增删改等操作。
本文内容
- 多级 Grid 概述
- 实现多级 Grid
多级 Grid 概述
有这样一个实现:Grid 数据的某行下,也包含数据,单击或双击时,需要展开。默认情况,Grid 只显示第一级,当点击某行记录最前边的“+”,或双击某行记录时,展开与该记录相关的数据。对于数据库来说,最一般的是——自连表。
在暂不考虑数据分页的前提下,问问自己:
- 首先,假设我们处于 Ajax 时代之前,没有任何三方组件可以提供这个功能,你对 JavaScript 只有一定的了解,但是对 C# 或是 Java 很熟练,那么若想实现这个功能你能想到什么?
- 其次,假设我们处于 Ajax 时代之前,没有任何三方组件可以提供这个功能,但你已经对 JSON 和 JavaScript 都很熟悉,那么若想实现这个功能你能想到什么?
- 最后,假设我们已经处于 Ajax 时代,没有现成的三方组件可以很好地提供这个功能,但是你对 JSON 和 jQuery 很熟悉,那么若想实现这个功能你能想到什么?
对于第一个假设,因为没有异步、没有局部刷新,我们只能将数据全部从数据库读出来,因为你对JavaScript 还不甚了解,所以只能利用 C# 或 JavaScrit 在服务器端创建嵌套的 <div> 或是 <table>(这需要一个相对复杂的逻辑),还要为 Grid 的展开创建客户端代码,绝大多数操作都在服务器端——这个实现绝不简单。要命的是,效率实在太低,暂且不说它完全需要回发,用户体验也差到极致。
对于第二个假设,没有异步、没有局部刷新,更没有现成三方组件可用,但是你已对 JSON 和 JavaScrit 有很好的了解,此时,你能想到的,也许是,从服务器端把数据全部读出来,因为数据库表本身就是自连表(比如,有 ID,有 PARENTID),无需进行复杂的业务处理,只需要将数据转换成 JSON 格式,剩下的工作,完全在客户端完成(利用 JavaScript 解析 JSON)——这个也不大容易。但至少比第一个强,客户端完成绝大多数操作。
对于第三个假设,有异步、有局部刷新,更关键的是你对 JSON 和 jQuery 都很熟悉,那完全可以从数据库先获得第一级的数据,当用户单击或双击时,利用 jQuery,发出 Ajax 请求,从数据库获得指定记录下的相关数据,然后在回调成功时,添加到指定记录下……——这个思路不仅清晰,而且实现相对简单,用户体验也好,都是局部刷新,效率也高。
其实,说了这么多,仅仅是锻炼你的思维。因为现在很多三方组件都提供这个功能。如果你能体会人家的设计思想,以及实现一个功能时的思路,使用三方组件其实很容易,虽然看上去很复杂。
实现多级 Grid
首先看看,客户端代码都有什么,如下所示:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ExtNetMultiGridPanel._Default" %>
<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title></title>
<ext:ResourcePlaceHolder ID="ResourcePlaceHolder1" runat="server" Mode="Script" />
<script type="text/javascript">1:
2: window.lookup = {};
3:
4: var params = {5: action: " ",6: ui: gridUIParams,
7: setAction: function(action) {8: this.action = action;9: },
10: setParams: function(recId, level) {11: if (arguments.length == 2) {12: if (this.ui == undefined) {13: this.ui = gridUIParams;14: }
15: this.ui.id = recId;16: this.ui.level = level;17: }
18: else if (arguments.length == 0) {19: if (this.ui == undefined) {20: this.ui = gridUIParams;21: }
22: this.ui.id = " ";23: this.ui.level = " ";24: }
25: },
26: toJson: function() {27: return Ext.encode(this);28: }
29: };
30:
31: var gridUIParams = {32: id: "",33: level: ""34: };
35:
36: var selectedRow = function(recId, level, obj) {37: params.setParams(recId, level);38: obj.setValue(params.toJson());39: };
40:
41: var setAction = function(action, obj) {42: var paras = Ext.decode(obj.getValue());43: params.action = action;44: if (paras.ui.id != undefined) params.ui.id = paras.ui.id;45: if (paras.ui.level != undefined) params.ui.level = paras.ui.level;46: obj.setValue(params.toJson());47: };
48:
49: var clean = function(view, isDestroy) {50: var controls = window.lookup[view.grid.id] || {},51: ids = [];
52:
53: for (var c in controls) {54: ids.push(controls[c].id || controls[c].storeId);
55: }
56:
57: if (ids.length > 0) {58: if (isDestroy !== true) {59: view.grid.getRowExpander().collapseAll();
60: }
61:
62: for (var i = 0; i < ids.length; i++) {63: removeFromCache(ids[i], view.grid.id);
64: }
65: }
66: };
67:
68: var addToCache = function(c, parent) {69: window.lookup[parent] = window.lookup[parent] || {};
70: window.lookup[parent][c] = window[c];
71: };
72:
73: var removeFromCache = function(c, parent) {74: window.lookup[parent] = window.lookup[parent] || {};
75:
76: var ctrl = window.lookup[parent][c];77: delete window.lookup[parent][c];
78: if (ctrl) {79: if (ctrl.view) {80: clean(ctrl.view, true);81: }
82: ctrl.destroy();
83: }
84: };
85:
86: var loadLevel = function(expander, record, body, row) {87: if (body.rendered) {88: return;89: }
90: var recId = record.id,91: gridId = expander.grid.id,
92: level = record.data.Level;
93: Ext.net.DirectMethods.BuildGrid(level + 1, recId, gridId, {
94: eventMask: {
95: showMask: true,96: tartget: "customtarget",97: customTarget: expander.grid.body
98: },
99:
100: success: function() {101: body.rendered = true;102: },
103:
104: failure: function() {105: alert('服务器错误.');106: }
107: });
108: };
109:
110: Ext.onReady(function() {111: var obj = Ext.getCmp("Params");112: params.setParams();113: obj.setValue(params.toJson());114: });
115:
</script>
</head>
<body>
<form id="form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<ext:Toolbar ID="Toolbar1" runat="server">
<Items>
<ext:Toolbar ID="Toolbar2" runat="server">
<Items>
<ext:Button ID="Button1" runat="server" Text="添加">
<Listeners>
<Click Handler="setAction('add',#{Params});" />
</Listeners>
<DirectEvents>
<Click OnEvent="btn_action_Click">
<ExtraParams>
<ext:Parameter Name="action" Value="Ext.decode(#{Params}.getValue()).action" Mode="Raw">
</ext:Parameter>
<ext:Parameter Name="id" Value="Ext.decode(#{Params}.getValue()).ui.id" Mode="Raw">
</ext:Parameter>
</ExtraParams>
</Click>
</DirectEvents>
</ext:Button>
<ext:Button ID="Button2" runat="server" Text="修改">
<Listeners>
<Click Handler="setAction('edit',#{Params});" />
</Listeners>
<DirectEvents>
<Click OnEvent="btn_action_Click">
<ExtraParams>
<ext:Parameter Name="action" Value="Ext.decode(#{Params}.getValue()).action" Mode="Raw">
</ext:Parameter>
<ext:Parameter Name="id" Value="Ext.decode(#{Params}.getValue()).ui.id" Mode="Raw">
</ext:Parameter>
</ExtraParams>
</Click>
</DirectEvents>
</ext:Button>
<ext:Button ID="Button3" runat="server" Text="删除">
<Listeners>
<Click Handler="setAction('del',#{Params});" />
</Listeners>
<DirectEvents>
<Click OnEvent="btn_action_Click">
<ExtraParams>
<ext:Parameter Name="action" Value="Ext.decode(#{Params}.getValue()).action" Mode="Raw">
</ext:Parameter>
<ext:Parameter Name="id" Value="Ext.decode(#{Params}.getValue()).ui.id" Mode="Raw">
</ext:Parameter>
</ExtraParams>
</Click>
</DirectEvents>
</ext:Button>
</Items>
</ext:Toolbar>
</Items>
</ext:Toolbar>
<ext:Hidden ID="Params" runat="server" Text="">
</ext:Hidden>
</form>
</body>
</html>
说明:
1,自定义两个类:params 和 gridUIParams,用来保存从Grid上获得的相关数据,如当前选择的行ID等,已经当前页面要进行的操作,如add、del 和 edit。
2,定义两个脚本方法 selectedRow 和 setAction,分别用来将从Grid上获得的相关信息,保存到隐藏域;以及将当前页面的操作保存到隐藏域。
3,onReady 里的代码,用来初始化隐藏域的内容。
4,方法 clean、addToCache、removeFromCache 和 loadLevel 是创建多级 Grid 的相关客户端方法。其中,loadLevel 方法通过Ext.net.DirectMethods,直接调用服务器端方法,以异步方式创建多级 Grid。
接下来,看看后台代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Data;
using Ext.Net;
using Ext.Net.Utilities;
namespace ExtNetMultiGridPanel
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!X.IsAjaxRequest)
{
this.MultiLevel = 4;
}
else
{
return;
}
this.BuildGrid(1, "r0", "r0");
}
protected void btn_action_Click(object sender, DirectEventArgs e)
{
string id = string.Empty;
string action = string.Empty;
if (e.ExtraParams["id"] != null) id = e.ExtraParams["id"].Trim().ToString();
if (e.ExtraParams["action"] != null) action = e.ExtraParams["action"].Trim().ToString();
string currentAction = string.Empty;
switch (action)
{
case "add": currentAction = "添加"; break;
case "edit": currentAction = "编辑"; break;
case "del": currentAction = "删除"; break;
}
string currentSelect = id.Trim().Length > 0 ? "选择行ID=" + id : "未选择行。";
X.Js.Alert("操作:" + currentAction + ";" + currentSelect);
// TODO
}
[DirectMethod]
public void BuildGrid(int level, string recId, string gridId)
{
DataTable dt = DS.GetDataSource();
DataSet ds = new DataSet();
if (level == 1)
{
ds.Merge(dt.Select("parentid=-1"));
dt = ds.Tables[0];
}
else
{
DataRow[] drs = dt.Select("parentid=" + recId);
if (drs.Length > 0)
{
ds.Merge(drs);
dt = ds.Tables[0];
}
else { dt = new DataTable(); }
}
if (dt != null && dt.Rows.Count > 0)
{
var storeId = "L".ConcatWith(level, "_", recId, "_Store");
var newGridId = "L".ConcatWith(level, "_", recId, "_Grid");
// 创建 Store
var store = new Store { ID = storeId };
var reader = new JsonReader
{
IDProperty = "ID",
Fields = { new RecordField("ID", RecordFieldType.Int),
new RecordField("PARENTID", RecordFieldType.Int),
new RecordField("NAME", RecordFieldType.String) }
};
reader.Fields.Add(new RecordField
{
Name = "Level",
Convert = { Handler = "return ".ConcatWith(level, ";") }
});
store.Reader.Add(reader);
store.CustomConfig.Add(new ConfigItem("level", level.ToString(), ParameterMode.Raw));
// 创建 GridPanel
var grid = new GridPanel
{
ID = newGridId,
Store = {
store
},
AutoHeight = true,
HideHeaders = false,
ColumnModel =
{
Columns = { new Ext.Net.Column { Header = "标识", DataIndex = "ID" },
new Ext.Net.Column {Header="父标识",DataIndex="PARENTID"},
new Ext.Net.Column {Header="名称",DataIndex="NAME"}
}
}
};
// 创建 GridView
var view = new Ext.Net.GridView
{
ID = newGridId + "_View",
ForceFit = true
};
grid.View.Add(view);
// 创建 RowSelectionModel
var sm = new RowSelectionModel { ID = newGridId + "_SM", SingleSelect = true };
sm.Listeners.RowSelect.Handler = "selectedRow(" + "record.id," + level + ",#{Params});";
grid.SelectionModel.Add(sm);
// 创建 BeforeExpand, except last (last level is 5)
if (level <= this.MultiLevel)
{
view.Listeners.BeforeRefresh.Fn = "clean";
var re = new RowExpander
{
ID = newGridId + "_RE",
EnableCaching = true,
Template = { ID = newGridId + "_TPL", Html = "<div id=\"row" + level + "_{ID}\" style=\"background-color:white;\"></div>" }
};
re.Listeners.BeforeExpand.Fn = "loadLevel";
grid.Plugins.Add(re);
}
store.DataSource = dt;
store.DataBind();
if (level == 1)
{
grid.AutoHeight = true;
grid.AutoWidth = true;
this.Form.Controls.Add(grid);
grid.Plugins.Add(new PanelResizer());
}
else
{
var renderEl = "row" + (level - 1) + "_" + recId;
X.Get(renderEl).SwallowEvent(new string[] { "click", "mousedown", "mouseup", "dblclick" }, true);
this.RemoveFromCache(newGridId, gridId);
grid.Render(renderEl, RenderMode.RenderTo);
this.AddToCache(newGridId, gridId);
}
}
}
/// <summary>
/// GridPanel 的级别
/// </summary>
/// <remarks>
/// 默认值为1,最大为4。即 GridPanel 默认为1级,最大4级
/// </remarks>
protected int MultiLevel
{
get
{
if (this.ViewState["MultiLevel"] != null)
{
if (Convert.ToInt16(this.ViewState["MultiLevel"].ToString()) <= 4)
return Convert.ToInt16(this.ViewState["MultiLevel"].ToString());
else return 4;
}
return 1;
}
set
{
this.ViewState["MultiLevel"] = value;
}
}
/// <summary>
/// 清除缓存
/// </summary>
/// <param name="id">子GridPanel的ID</param>
/// <param name="parentId">父GridPanel的ID</param>
private void RemoveFromCache(string id, string parentId)
{
X.ResourceManager.AddScript("removeFromCache({0}, {1});", JSON.Serialize(id), JSON.Serialize(parentId));
}
/// <summary>
/// 添加缓存
/// </summary>
/// <param name="id">子GridPanel的ID</param>
/// <param name="parentId">父GridPanel的ID</param>
private void AddToCache(string id, string parentId)
{
X.ResourceManager.AddScript("addToCache({0}, {1});", JSON.Serialize(id), JSON.Serialize(parentId));
}
}
}