之前写过一篇文章,地址 http://www.cnblogs.com/Bond/p/3469798.html 大概说了下怎么通过反射来自动生成对应EasyUi datagrid的模板,然后贴了很多代码,看起来很乱,当时没用过easyui,没啥经验。 这次经过了项目的实际考验,我把它做了一些改动,在此分享下,并且附上源码,源码需要用vs2012打开,打开即可运行不要做任何设置。源码地址在 https://github.com/LittleBearBond/GenerateEasyUiDataGridTemplate 。都说现在程序员要玩GitHub,我一直都想用用,不过没时间,经过N久的加班,最近终于有时间学习了下,把源码也放到上面。
我为什么要去折腾写这个东西,之前博客也大概提了下,现在请看下图,如果我们用easyui的datagrid来展示这样的数据
页面必然对应这样一个datagrid模板
<table id="dt" class="easyui-datagrid" data-options="title:'商品列表'">
<thead>
<tr>
<th data-options="field:'Name',align:'center',formatter:format.formatVal"> 名称</th>
<th data-options="field:'Category',align:'center',formatter:format.formatVal"> 类别</th>
<th data-options="field:'Price',align:'center',sortable:true,formatter:format.formatVal"> 价格</th>
<th data-options="field:'CreateTime',align:'center',formatter:format.formatTime" sortable="true"> 创建时间</th>
</tr>
</thead>
</table>
其实这个模板他要展示的数据对象必然对应一个后台的类,比如这个模板对应的后台类是Product
public class Product
{
/// <summary>
///
/// </summary>
public int Id { get; set; } /// <summary>
/// 名称
/// </summary>public string Name { get; set; } /// <summary>
///
/// </summary>public string Category { get; set; } /// <summary>
/// 价格
/// </summary>public decimal Price { get; set; } /// <summary>
/// 创建时间
/// </summary>public DateTime CreateTime { get; set; }
}
而从后台返回的数据是这样的
通过以上观察我们会发现一些问题,datagrid要展示的数据后台必然是返回一个json对象,而单个对象里面的 Id 、CreateTime 、Name、 Price 其实是对应着Product实体对象的字段名,而datagrid模板里面要设置哪行显示哪个字段,比如Name 在datagrid模板里面对应着一行th,并设置Name这行的相关属性。
datagrid模板里面每个字段都是和后台的Product实体对象的字段名称一一对应的,而我们每次在设置模板的时候都要去拷贝字段名称,如果不小心弄错了数据就不会显示出来。如果我们能通过后台的实体对象Product来直接生成这个模板那就不会出错了。这个功能我已经做好了,用到的知识点很少,只是自定义属性加反射再加点字符串拼接就搞定啦。不过生成模板也是有很多问题需要考虑,大致有以下一些问题需要解决。
1:Product对象里面不是每个字段都要显示出来,比如Id不需要显示,我们希望能够动态设置需要显示的字段。
2:在设置单行th的时候fileid是Name,但是在thead表头上需要显示 “名称” 两个汉字而不是英文的Name
3:我们可以动态设置这个字段的data-options属性 以及这行的其他属性 如style class之类的
4:显示有先后顺序,Name显示在前面还是Price显示在前面我们可以做动态设置
5:可以额外添加和Product无关的字段,然后显示到页面上
上面说在很抽象来看具体事例:
现在我的类是这样的,标记了display属性,和DataOptions自定属性
[DataOptions(Options = "title:'商品列表'")]
public class Product
{
/// <summary>
///
/// </summary>
public int Id { get; set; } /// <summary>
/// 名称
/// </summary>
[Display(Name = "名称")]
[DataOptions(Order = )]
public string Name { get; set; } /// <summary>
///
/// </summary>
[Display(Name = "类别")]
[DataOptions(Order = )]
public string Category { get; set; } /// <summary>
/// 价格
/// </summary>
[Display(Name = "价格")]
[DataOptions(Order = , Options = "sortable:true")]
public decimal Price { get; set; } /// <summary>
/// 创建时间
/// </summary>
[Display(Name = "创建时间")]
[DataOptions(Order = , Property = "sortable=true")]
public DateTime CreateTime { get; set; }
现在我的cshtml页面代码是这样的
@using GenerateDataGridDemo.EasyUi
@using GenerateDataGridDemo.Extends
@using GenerateDataGridDemo.Models
@{
ViewBag.Title = "Test1";
Layout = "~/Views/Shared/_EasyUiLayout.cshtml";
var loadUrl = Url.Action("LoadTest1", "Home");
}
@section searchs
{
<div>
@EasyUiPageControls.SearchTimeInput()
@EasyUiPageControls.SearchKeyWordInput("产品名称:", "KeyWord", "init=\"请输入产品名称\"")
@EasyUiPageControls.SearchButton()
</div>
}
@Html.CreateDataGridTemplate(typeof(Product))
@section scripts{
<script type="text/javascript">
$(function () {
easyui.dg.LoadData('@loadUrl');
WebJs.SearchCreateTime();
$('#searchLoadList').click(function () {
easyui.dg.Search('@loadUrl');
});
});
</script>
}
在页面上得到的效果就是以下这样的,经过简单封装cshtml的页面代码基本上就只有几行,就搞定了数据显示、加载、搜索。
有时我们页面不会这么简单,我们的页面可能是这样的,前面有多复选框和后面的操作列。
此时我还是用的以前的Product,只是页面代拿略有改变,如下
@using GenerateDataGridDemo.EasyUi
@using GenerateDataGridDemo.Extends
@using GenerateDataGridDemo.Models
@{
ViewBag.Title = "Test2";
Layout = "~/Views/Shared/_EasyUiLayout.cshtml";
var loadUrl = Url.Action("LoadTest1", "Home");
}
@section searchs
{
<div>
@EasyUiPageControls.SearchTimeInput()
@EasyUiPageControls.SearchKeyWordInput("产品名称:", "KeyWord", "init=\"请输入产品名称\"")
@EasyUiPageControls.SearchButton()
<a class="easyui-linkbutton" id="GetAll">获取选择的ID</a>
</div>
}
@{
var end = EasyUiPageHtml.FormateOperate();
var start = EasyUiPageHtml.FirstCheckBox();
}
@Html.CreateDataGridTemplate(typeof(Product), end, start)
@section scripts{
<script type="text/javascript">
function formatOperate(val, row) {
return '<a href="###" onclick="Modify(' + row.Id + ',\'' + row.Name + '\')">编辑</a> ';
}
function Modify(id, name) {
WebJs.Dialog.Alert(utils.str.formatString('Id:{0},Name{1}', id, name));
}
$(function () {
easyui.dg.LoadData('@loadUrl');
WebJs.SearchCreateTime();
$('#searchLoadList').click(function () {
easyui.dg.Search('@loadUrl');
});
$('#GetAll').on('click', function () {
var ids = easyui.dg.GetSelectionIds();
if (utils.str.isNullOrWhiteSpace(ids)) {
WebJs.Dialog.Tip('请先选择!');
return;
}
WebJs.Dialog.Content(ids);
});
});
</script>
}
大家会发现其实就多了end和start,其他其实是没变动的,只是曾加了一个GetALL按钮。
真正用于生成面datagrid模板的代码只有@Html.CreateDataGridTemplate(typeof(Product))这一行,然后加上Product上面标记的一些属性,就完成了datagrid模板的自动生成,具体实现上篇博客有说明,这次把代码抽取出来然后奉献给大家做个参考。
demo代码是这样的,一切从简,只有一个程序集,页面只有两个页面,但是功能还是比较齐全的。
点击左边导航,就会添加一个页面,如果已经存在就刷新存在的页面,页面有右键菜单。
扩展datagrid的view 在没有数据的时候显示提示信息
对Jq这个日期控件做了点改动,起始日期级联验证
页面搜索和加载数据做了相应封装,调用的时候只需一句话,在项目中做了很多公共方法的提取和封装,这里提出来的是部分,多了影响大家看这个代码
支持排序,只有价格和创建时间支持排序,支持单个字段排序,多个字段也是可以的,后台ExtendClass.cs有相关代码只是我没具体做这个功能。
最后贴一下整个核心的代码,代码就一百多行,在项目源码中可以去看看,然后根据自己的需求去扩展和改进。
public class GenerateDataGrid
{
public static IList<PropertyInfo> GetAllPropertyInfoList(Type entity)
{
return entity.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
}
public static string GetDataGridTemplate(Type entity, string appendEnd, string appendStart)
{
var sb = new StringBuilder();
//先获取类的Attribute
var entityCustomAttr = entity.GetCustomAttributes(typeof(DataOptionsAttribute), false).FirstOrDefault() as DataOptionsAttribute; #region 对实体的Attibute属性进行处理 //是否显示没有 标记dataoptions的字段
var isShowNotAttr = false;//默认不显示
var options = string.Empty;
var tableId = string.Empty;
var tableProperty = string.Empty;
//并没有处理可能发生的异常情况, 比如在Property 指定了id="xxx" 而又指定了id的值
if (entityCustomAttr != null)
{
isShowNotAttr = entityCustomAttr.IsShowNotAttr;
options = string.IsNullOrWhiteSpace(entityCustomAttr.Options) ? string.Empty : entityCustomAttr.Options;
//默认ID为dt , 假设在Property 中没有设置了Id,如果设置了这里没做处理
tableId = string.IsNullOrWhiteSpace(entityCustomAttr.Id) ? "dt" : entityCustomAttr.Id;
tableProperty = string.IsNullOrWhiteSpace(entityCustomAttr.Property) ? string.Empty : entityCustomAttr.Property;
} #endregion //获取所有的Property
var properties = GetAllPropertyInfoList(entity);
//如果设置有不显示没有dataoptions标记的,值取出标记有dataoptions的字段
if (!isShowNotAttr)
{
properties = properties.Where(n => n.CustomAttributes.Any(a => a.AttributeType == typeof(DataOptionsAttribute))).ToList();
}
//没有打标记的也要取出来, 这里得到以字段name为key List<Attribute>为值的集合对象
Dictionary<string, List<Attribute>> colDicOpts = properties.ToDictionary(
property => property.Name,
property =>
{
var list = new List<Attribute>
{
property.GetCustomAttributes(typeof (DataOptionsAttribute), false).FirstOrDefault() as DataOptionsAttribute,
property.GetCustomAttributes(typeof (DisplayAttribute), false).FirstOrDefault() as DisplayAttribute
};
return list;
});
//在table上拼接 id data-options 和 Property
sb.AppendLine(string.Format("<table id=\"{0}\" class=\"easyui-datagrid\" data-options=\"{1}\" {2} > <thead> <tr>", tableId, options, tableProperty));
//没有直接遍历加入数据 这里先取得所有数据,然后进行排序,得到th 列表
var listThs = (from pro in properties
let custAttrs = colDicOpts.SingleOrDefault(n => n.Key == pro.Name)
select AppenedTemplate(Template.DataGridTh, custAttrs, pro)).ToList();
//1、添加到开始部分的 add start
if (!string.IsNullOrWhiteSpace(appendStart)) { sb.AppendLine(appendStart); }
//2、添加中间部分,先排序,得到显示顺序 add center
listThs = listThs.OrderBy(n => n.Key).Select(n => n.Value).ToList();
sb.AppendLine(string.Join("", listThs));
//3、追加后面的字符串 add end
if (!string.IsNullOrWhiteSpace(appendEnd)) { sb.AppendLine(appendEnd); }
sb.AppendLine(@"</tr></thead></table>");
return sb.ToString();
} //dynamic 可用 KeyValuePair
private static dynamic AppenedTemplate(string template, KeyValuePair<string, List<Attribute>> attributes, PropertyInfo proinfo = null)
{
var displayName = attributes.Value.SingleOrDefault(n => n is DisplayAttribute) as DisplayAttribute;
//设置字段显示的名称,直接设置 DisplayAttribute,这个大家肯定很熟悉的属性
var str = Template.RegV.Replace(template, displayName != null ? displayName.Name : attributes.Key); //设置显示的字段field ,即是当前th显示哪个字段,例如field:'Id'
str = Template.RegF.Replace(str, attributes.Key); //从该字段的CustomAttributes中取得DataOptionsAttribute
var dataOptions = attributes.Value.SingleOrDefault(n => n is DataOptionsAttribute) as DataOptionsAttribute; //设置Property, 如果property和data-options有设置相同的对象 这里没做异常处理
str = Template.RegP.Replace(str, dataOptions == null ? string.Empty : dataOptions.Property ?? ""); //没有设置排序的这里默认设置一个值
var order = dataOptions == null ? : dataOptions.Order; //由于我自己的需要,我要对DateTime类型进行特殊处理
if (proinfo != null && proinfo.PropertyType == typeof(DateTime))
{
//没有自定义属性的值
if (dataOptions == null)
{
//WebJs.Format.formatTime 自己的js时间格式化函数 这个一定程度上导致前后台耦合了
str = Template.RegD.Replace(str, "formatter:format.formatTime");//默认时间格式
}
else
{
str = dataOptions.Options != null && dataOptions.Options.IndexOf("formatter", StringComparison.CurrentCultureIgnoreCase) >= ?
//已经设置formatter
Template.RegD.Replace(str, dataOptions.Options) :
//默认设置formatter
Template.RegD.Replace(str, ((dataOptions.Options ?? "").TrimEnd(',') + ",formatter:format.formatTime").TrimStart(','));
}
}
else
{
//替换data-option 的值, 如果为空就直接替换为空
if (dataOptions == null)
{
str = Template.RegDi.Replace(str, string.Empty);
}
else
{
var opt = (dataOptions.Options ?? "");
//默认设置起格式化
var replaceStr = opt.IndexOf("formatter", StringComparison.CurrentCultureIgnoreCase) >= ? opt : opt.TrimEnd(',') + ",formatter:format.formatVal";
str = Template.RegD.Replace(str, replaceStr.TrimStart(','));
}
}
return new { Value = str, Key = order };
}
}
PS:为了追逐爱情,准备离开成都,辞职北漂,忘大神收留。