[转载]使用NVelocity快速实现Flexigrid – Ariel Lu – 博客园.
自从去年偶然间发现Flexigrid这款JQuery的表格控件,就被它深深地吸引住了,华丽的外表、强大的功能、灵活的扩展性,最关键的是它不臃肿。如果追求功能强大,倒不如去使用ExtJs了。前段时间园友华磊给出了Flexigrid的ASP.NET解决方案,但我觉得封装成用户控件丧失了灵活性,而且它不再是一个客户端控件,不能按照我们的想法去随心所欲地操纵它。
既 然我们想保持它在前端的灵活性,只有另辟蹊径,从其它方面着手去简化工作。每次使用它,在后台获取数据,转成JSON格式也是一个比较繁琐的工作,有没有 一种方案能简化这些步骤,每次只需通过简单的配置和一两条SQL语句就能达到我们想要的效果呢?答案当然是肯定的。于是我想到了用模板引擎 NVelcity来实现这个方案。只要能把Flexigrid异步请求的参数能在SQL语句中使用,就可以解决这个问题了。
于是我们需要一个通用的WebService方法来接受、处理Flexigrid的参数:首先处理Flexigrid本身在获取数据的时候用的六个参数,代码如下:
//处理FlexiGrid的参数 //$page 第几页 //$rp 每页的条数 //$sortname 排序字段 //$sortorder 排序方向 string[] flexiGridParameters = { "page", "rp", "sortname", "sortorder", "query", "qtype" }; Dictionary<string, object> parameters = new Dictionary<string, object>(); foreach (string flexiGridParameter in flexiGridParameters) { string valueOfFlexiGridParameter = Context.Request.Params[flexiGridParameter]; if (!string.IsNullOrEmpty(valueOfFlexiGridParameter)) { parameters.Add(flexiGridParameter, valueOfFlexiGridParameter); } }
有时候除了默认的这几个参数,我们还需要一些自定义的参数,为了方便处理,自定义参数需以”param_”开头:
//加入自定义参数,以 "param_" 开头 foreach (string userDefinedParameterName in Context.Request.Params.AllKeys .Where(o => o.StartsWith("param_"))) { parameters.Add(userDefinedParameterName, Context.Request.Params[userDefinedParameterName]); }
另外page和rp为别表示页数和每页的条数,而我们需要的是起始条数和结束条数:
//将page和rp两个参数转成整数,再加入下面两个参数,方便写sql语句 //$startIndex 开始的行号 //$endIndex 结束的行号 int page = 1; if (parameters.ContainsKey("page") && parameters.ContainsKey("rp")) { page = Convert.ToInt32(parameters["page"]); int rp = Convert.ToInt32(parameters["rp"]); parameters["page"] = page; parameters["rp"] = rp; parameters.Add("startIndex", (page - 1) * rp + 1); parameters.Add("endIndex", page * rp); }
好了,需要的参数都有了,剩下的工作就是如果写出一条用上上述参数的SQL语句并且动态地去解析它,在这里我选择了模板引擎NVelcity(本文中使用的是Castle的1.1版,这里有详细介绍)。现在我们要使用模板语言返回一个我们需要的数据结构,模板中定义如下:
#* * 已有参数说明 $page 第几页 $rp 每页的条数 $sortname 排序字段 $sortorder 排序方向 $startIndex 开始的行号 $endIndex 结束的行号 $param_xxxx 自定义参数,xxxx为任意字符串 * 需要返回的数据结构说明 $result.rowsSql 获取数据的sql语句(必须有) $result.totalSql 分页时取数据总数的sql语句(分页时必须有) $result.id FlexiGrid中的行id所对应的字段名(需要行id时指定,不指定时行id为空) *#
这个时候又出现新的问题了,很多情况下SQL语句是一段很长的文本,而模板语言不支持换行,写在一行中可阅读性很差,显然是不符合要求的。这个时候就需要扩展一个模板语言中解析字符串的函数,把SQL语句写在另一个文本文件中,在模板中使用这个方法就可以啦。
static object ReadFileContent(object[] parameters) { if (parameters.Length < 1) { return string.Empty; } DirectoryInfo info = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory + NVelocityConstants.TEMPLATE_DIRECTORY); FileInfo[] files = info.GetFiles(parameters[0].ToString(), SearchOption.AllDirectories); if (files.Length > 0) { string content = File.ReadAllText(files[0].FullName); List<string> argList = new List<string>(); if (!string.IsNullOrEmpty(content)) { for (int i = 1; i < parameters.Length; i++) { argList.Add(parameters[i] != null ? parameters[i].ToString() : string.Empty); } } if (argList.Count > 0) { string[] ps = argList.ToArray(); content = string.Format(content, ps); } return content; } return string.Empty; }
回到刚才的WebService方法中,使用NVelocity解析刚才的模板:
//解析模板,取回FlexiGrid所需的sql语句,result类型为KeyValueObject //$result.rowsSql 获取数据的sql语句(必须有) //$result.totalSql 分页时取数据总数的sql语句(分页时必须有) //$result.id FlexiGrid中的行id所对应的字段名(需要行id时指定,不指定时行id为空) NVelocityHelper velocity = new NVelocityHelper(); //注入工具类和参数 velocity.Add("helper", new HelperDuck()); foreach (string parameterName in parameters.Keys) velocity.Add(parameterName, parameters[parameterName]); //得到模板执行结果 object result = velocity.GetResultFromVm(templateFileName); if (result is KeyValueObject) { KeyValueObject keyValueResult = (KeyValueObject)result; string rowsSql = string.Format("{0}", keyValueResult.GetInvoke("rowsSql")); string totalSql = string.Format("{0}", keyValueResult.GetInvoke("totalSql")); string id = string.Format("{0}", keyValueResult.GetInvoke("id"));
现在SQL语句已经有了,执行它,并序列化成JSON结构:
DataTable dt = Utils.ExecuteTable(rowsSql); List<FlexiGridRow> rows = new List<FlexiGridRow>(); foreach (DataRow dr in dt.Rows) { rows.Add(new FlexiGridRow() { id = dt.Columns.Contains(id) ? string.Format("{0}", dr[id]) : string.Empty, cell = Utils.DbObjectArrToCsStringArr(dr.ItemArray) }); } int total = -1; if (!string.IsNullOrEmpty(totalSql)) total = Convert.ToInt32(Utils.ExecuteTable(totalSql).Rows[0][0]); object flexiGridResult = new { page = page, total = total > -1 ? total : rows.Count, rows = rows }; Context.Response.Write(new JavaScriptSerializer().Serialize(flexiGridResult));
至此准备工作就全部完成了,下面我们通过一个示例来看一下效果如何:
1.首先新建一个文本文件用来存放SQL语句:
SELECT CompanyName, ContactName, ContactTitle, Address, City, Phone FROM ( SELECT ROW_NUMBER() OVER (ORDER BY {0} {1}) ROW, CompanyName, ContactName, ContactTitle, Address, City, Phone FROM Customers ) A where Row between {2} and {3}
2.添加一个NVelocity模板,用来获取我们想要的SQL语句
#set($result = $helper.createkeyvalueobject()) #set($result.rowsSql = $helper.readfilecontent("FlexiGrid_获取数据.sql", $sortname, $sortorder, $startIndex, $endIndex)) #set($result.totalSql = "SELECT COUNT(*) FROM Customers") #set($result.id = "CompanyName")
3.前端调用
jQuery(document).ready(function () { var maiheight = document.documentElement.clientHeight; var w = jQuery("#ptable").width() - 3; var otherpm = 150; //GridHead,toolbar,footer,gridmargin var gh = maiheight - otherpm; var option = { height: gh, //flexigrid插件的高度,单位为px width: w, url: "WebService/CommonWebService.asmx/GetFlexiGridDataByTemplate", colModel: [ { display: "公司名称", name: "CompanyName", width: 200, sortable: true }, { display: "联系人名称", name: "ContactName", width: 120, sortable: true }, { display: "标题", name: "ContactTitle", width: 150, sortable: false }, { display: "地址", name: "Address", width: 250, sortable: true, hide: true }, { display: "城市", name: "City", width: 100, sortable: true }, { display: "电话", name: "Phone", width: 100, sortable: true } ], params: [ { name: "templateFileName", value: "FlexiGrid_获取数据.htm" } ], buttons: [ { name: "Add", displayname: "新增", onpress: toolbarItem_onclick }, { name: "Delete", displayname: "删除", onpress: toolbarItem_onclick } ], sortname: "CompanyName", sortorder: "asc", title: "联系人列表", usepager: true, useRp: false, //rp: 5, rowbinddata: true, showcheckbox: true, rowhandler: rowEditHandler, selectedonclick: false, singleselected: true, //gridClass: "flexigrid" gridClass: "bbit-grid" }; var grid = jQuery("#curtomerGrid").flexigrid(option); });
怎么样,是不是很简单。最后让我们看看效果如何?