让我们来做些很Cool的事:使用Dojo做自己的.NET MVC控件
自己最近的口头禅换成了:这将会很Cooooooooooooooool,非常Cooooooooooooooool。所以这篇博文的名字也就变得如此奇怪
打算把这个写成一个系列,与大家分享我在工作中弄出来的一些很Cool的功能/效果/思路/等等等等
一直都觉得程序员交流的最好手段是通过代码。强烈建议配合代码阅读本文
源代码下载:点此下载源代码
页面效果如下:点击这里下载Html(文件很小,建议大家都下载一下看看效果,第一次加载会比较慢,因为Js文件和Css文件都是从谷歌取的)
我的博客Index:Index & Writing Plan
首先介绍一下Dojo。Jquery想必大家已经耳熟能详,一个轻量级的js库,而Dojo是一个重量型的js库。相比Jquery,Dojo在OOP上表现更好,更适合大型应用
扫盲贴:http://www.mhtml5.com/2012/06/5174.html
正文开始:
给出一个Model。这个Model很简单,只有一个String,然后有一个正则的Attribute:
public class Test
{
[RegularExpression(@"[\\w]+", ErrorMessage = "Invalid Non-Space Text.")]
public string b { get; set; }
}
使用过Razor的朋友,相信已经对于这种代码已经司空见惯了:
@model Test @Html.TextBoxFor(o=>o.b)
这会向页面输出如下HTML代码:
<input data-val="true" data-val-regex="Invalid Non-Space Text." data-val-regex-pattern="[\\w]+" id="b" name="b" type="text" value="String with Space">
可以看到,.NET MVC聪明反射了属性b上的Attribute,为我们生成了一个正则验证。
但是这样会有两个问题:
1、这个正则验证不是很好看,而且要在表单提交的时候才会触发,而不是焦点离开就触发
2、如果我想使用Jquery为这个TextBox添加一个onmousemove事件,要加上如下代码:
<script>
$(document).ready(function () {
$("#b").mousemove(function () {
alert(1);
})
})
</script>
这样会为刚刚的TextBox添加一个mousemove事件
但是这样写一点都不Cool;比起使用Jquery捕获控件,然后绑定事件,我更希望在写控件的时候就完成JS事件绑定;换句话说,我希望如下写法:
<script>
function show(str) {
alert(str);
}
</script>
<input data-val="true" data-val-regex="Invalid Non-Space Text." data-val-regex-pattern="[\\w]+" id="b" name="b" type="text" value="" onmousemove="show(this.value);">
那么我想要这么一种效果:使用这个Model
public class Test
{
[Required]
public DateTime a { get; set; }
[RegularExpression(@"[\\w]+", ErrorMessage = "Invalid Non-Space Text.")]
public string b { get; set; }
[Required]
[Range(1, 11111)]
public int c { get; set; }
public List<Test> d { get; set; }
public bool e { get; set; }
}
再在CShtml中添加如下代码:
@model Test
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.8.1/dijit/themes/claro/claro.css" media="screen" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/dojo/1.8.1/dojo/dojo.js" djconfig="parseOnLoad: true"></script>
<script>
function show(str) {
alert(str);
}
</script>
<body class="claro">
@Html.MyDateTextBoxFor(o => o.a).ToHtml()
@Html.MyTimeTextBox(o => o.a).ToHtml()
@Html.MyTextBoxFor(o => o.b).OnBlur("show(this.value)").ToHtml()
@Html.MyTextBoxFor(o => o.c).ToHtml()
@Html.MyDropDownListFor(o => o.c, a => a.b, a => a.c.ToString(), Model.d).OnChange("show(this.value)").ToHtml()
@Html.MyButton("Submit", true).ToHtml()
@Html.MyMultiSelectFor(o => o.c, a => a.b, a => a.c.ToString(), Model.d).OnClick("show(this.value)").ToHtml()
@Html.MyCheckBoxFor(o => o.e).ToHtml()
</body>
然后生成出来的页面如下:点击这里下载Html(文件很小,建议大家都下载一下看看效果)
上面链接第一次加载会比较慢,因为Js文件和Css文件都是从谷歌取的

为了达到这种效果,我们要重写.NET MVC的控件代码。
下文以DropDownList与MultiSelect为例解释下代码
//=======================================
//Create By Jinn 2012.12.10 只做了MyDropDownListFor与MyMultiSelectFor,不带For的版本注释掉了,在最下面
//Edit By 请完善这个注释
//=======================================
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
namespace System.Web.Mvc
{
public static class MySelectExtensions
{
public static TagBuilder MyDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
Func<TModel, string> textField, Func<TModel, string> valueField,
IList<TModel> selectList)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
return SelectHelper<TModel, TProperty>(htmlHelper, name, textField, valueField, selectList, metadata,SelectType.DropDownList);
}
public static TagBuilder MyMultiSelectFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
Func<TModel, string> textField, Func<TModel, string> valueField,
IList<TModel> selectList)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string name = ExpressionHelper.GetExpressionText(expression);
return SelectHelper<TModel, TProperty>(htmlHelper, name, textField, valueField, selectList, metadata, SelectType.MultiSelect);
}
private static TagBuilder SelectHelper<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,string name,
Func<TModel, string> textField /*显示*/, Func<TModel, string> valueField /*值*/,
IList<TModel> selectList /*数据源*/, ModelMetadata metadata /*元数据*/, SelectType tpye)
{
string fullName = htmlHelper.ViewBag.ID;
string ID = fullName + "_" + name;
StringBuilder sb = new StringBuilder();
if (selectList != null)
{
foreach (var item in selectList)
{
TagBuilder tag = new TagBuilder("option");
tag.MergeAttribute("value", valueField(item));
string selectedValue = valueField(item);
if (selectedValue == Convert.ToString(metadata.Model))
{
tag.MergeAttribute("selected", "selected");
}
tag.SetInnerText(textField(item));
sb.AppendLine(tag.ToString(TagRenderMode.Normal));
}
}
TagBuilder tagBuilder = new TagBuilder("select")
{
InnerHtml = sb.ToString()
};
switch (tpye)
{
case SelectType.DropDownList:
tagBuilder.MergeAttribute("data-dojo-type", "dijit/form/FilteringSelect");
break;
case SelectType.MultiSelect:
tagBuilder.MergeAttribute("data-dojo-type", "dijit/form/MultiSelect");
break;
default:
tagBuilder.MergeAttribute("data-dojo-type", "dijit/form/FilteringSelect");
break;
}
tagBuilder.MergeAttribute("name", name, true);
tagBuilder.GenerateId(ID);
return tagBuilder;
}
public enum SelectType
{
DropDownList = 1,
MultiSelect = 2,
}
}
}
在.NET MVC中,控件返回类型都是MvcHtmlString,但是这样就不方便添加JS事件了。所以我这里把所有返回类型改为TagBuilder,在所有事件添加完成之后,再通过ToHtml方法转换成MvcHtmlString
添加JS事件的代码如下:
//=======================================
//Create By Jinn 2012.12.5 最基础的版本,完成了OnClick、OnBlur等5个常用事件;
// 我知道其实这5个都可以合起来写,但是为了可拓展性与页面代码量尽可能少的原则,我选择了这种写法
//Edit By 请完善这个注释
//=======================================
namespace System.Web.Mvc
{
public static class Function
{
public static TagBuilder OnClick(this TagBuilder tagBuilder, string functionName)
{
tagBuilder.MergeAttribute("onclick", functionName);
return tagBuilder;
}
public static TagBuilder OnMouseMove(this TagBuilder tagBuilder, string functionName)
{
tagBuilder.MergeAttribute("onmousemove", functionName);
return tagBuilder;
}
public static TagBuilder OnFocus(this TagBuilder tagBuilder, string functionName)
{
tagBuilder.MergeAttribute("onfocus", functionName);
return tagBuilder;
}
public static TagBuilder OnChange(this TagBuilder tagBuilder, string functionName)
{
tagBuilder.MergeAttribute("onchange", functionName);
return tagBuilder;
}
public static TagBuilder OnBlur(this TagBuilder tagBuilder, string functionName)
{
tagBuilder.MergeAttribute("onblur", functionName);
return tagBuilder;
}
//自闭和与默认闭和的方式都能解析
//如<asd/>与<asd></asd>是一样的
//所以这里默认闭合的形式
//By Jinn 2012.12.8
/// <summary>
/// 默认闭和的方式
/// </summary>
public static MvcHtmlString ToHtml(this TagBuilder tagBuilder)
{
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
}
}
这样写完,在CShtml中添加
@Html.MyDropDownListFor(o => o.c, a => a.b, a => a.c.ToString(), Model.d).OnChange("show(this.value)").ToHtml()
就可以生成一个很Coooooooooooool的DropDownList控件了
就此搁笔
PS:为了偷懒,我的控件的NameSpace都是System.Web.Mvc
再PS:博客园随笔的代码着色功能貌似挂了,按一下Backspace会去掉文章中所有的颜色
/*=============================================================*/
作者:CrazyJinn
本文版权归作者和博客园共有,欢迎转载.但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
如果看完这篇文章让您有所收获,请点击右下角"推荐".
如果这篇文章让您觉得不知所云,或者通篇谬误,请点击右下角"反对".并且欢迎您留言给我提出宝贵的意见.
如果您想获知我最新的动态,可以在绿色通道中点击"关注我".
/*=============================================================*/

浙公网安备 33010602011771号