ExtExtenders是由 Rodrigo Diniz开发的一组对Extjs 客户端控件的.net服务器端包装控件
由Extender可知道其运行必须ASP.NET AJAX框架的支持.
类似的项目有Coolite,但为什么我们更应该关注ExtExtenders呢? 开源,而且功能更加强大.
演示地址 http://www.extendersamples.qsh.eu/
项目主页 http://www.codeplex.com/ExtJsExtenderControl
开源软件的最大好处就是我们不仅能够利用它来为我们的工作带来方便,而且能够透过其源代码,学习作者的实现方法,
领悟其设计思路,加快我们进步的步伐,正所谓是站在巨人的肩膀上,好风凭借力.
一如往常的技术介绍文章,让我们从最简单的开始,Extjs的日历组件界面美观,功能也非常强大而且没有复杂的服务器端 - 客户端交互行为, 就让我们先拿它开刀吧
首先我们到Codeplex下载一份源代码,这里以版本31017作为示例.
解压好后,先看到GridControl目录下的几个文件: YUI.cs, YUI-ext.cs, ext-yui-adapter.cs .
可以看到这几个文件实现非常简单:就是一个静态类,标注了其需要使用的嵌入式资源.
2 namespace ExtExtenders
3 {
4 /**//// <summary>
5 /// Class to include the js file
6 /// </summary>
7 public static class YUI
8 {
9 }
10}
然后来到今天的主角Calendar,这个文件夹里分别有 CalendarExtenderExtender.cs 和一个脚本文件 CalendarExtenderBehavior.js(注意到整个项目里的js和css以及图片文件都设为了嵌入式文件)
看看 CalendarExtenderExtender.cs 里到底实现了啥
1[ToolboxBitmap(typeof(System.Web.UI.WebControls.Calendar))]
2 public class ExtCalendar : TextBox, IScriptControl
3 {
4 private ScriptManager sm;
5 private string _disabledDays;
6 /**//// <summary>
7 /// An array of days to disable, 0 based. For example, [0, 6] disables
8 /// Sunday and Saturday (defaults to null).
9 /// </summary>
10 public string disabledDays
11 {
12 get
13 {
14 return _disabledDays;
15 }
16 set
17 {
18 _disabledDays=value;
19 }
20 }
21 private string _disabledDaysText;
22 /**//// <summary>
23 /// The message that appears when a date is disabled
24 /// </summary>
25 public string disabledDaysText
26 {
27 get
28 {
29 return _disabledDaysText;
30 }
31 set
32 {
33 _disabledDaysText=value;
34 }
35 }
36 private string _disabledDates;
37 /**//// <summary>
38 /// An array of "dates" to disable, as strings. These strings will be used to build a dynamic regular expression so they are very powerful.
39 /// Some examples: * ["03/08/2003", "09/16/2003"] would disable those exact dates
40 /// * ["03/08", "09/16"] would disable those days for every year
41 ///* ["^03/08"] would only match the beginning (useful if you are using short years)
42 ///* ["03/../2006"] would disable every day in March 2006
43 ///* ["^03"] would disable every day in every March
44 ///In order to support regular expressions, if you are using a date format that has "." in it, you will have to escape the dot when restricting dates. For example: ["03\\.08\\.03"].
45 /// </summary>
46 public string disabledDates
47 {
48 get
49 {
50 return _disabledDates;
51 }
52 set
53 {
54 _disabledDates=value;
55 }
56 }
57 private string _disabledDatesText;
58 /**//// <summary>
59 /// The tooltip text to display when the date falls on a
60 /// disabled date (defaults to 'Disabled')
61 /// </summary>
62 public string disabledDatesText
63 {
64 get
65 {
66 return _disabledDatesText;
67 }
68 set
69 {
70 _disabledDatesText=value;
71 }
72 }
73 private string _format;
74 /**//// <summary>
75 /// The default date format string which can be overriden for localization support.
76 /// The format must be valid according to Date.parseDate (defaults to 'm/d/y').
77 /// </summary>
78 public string format
79 {
80 get
81 {
82 return _format;
83 }
84 set
85 {
86 _format=value;
87 }
88 }
89
90 /**//// <summary>
91 /// Raises the pre render event.
92 /// </summary>
93 /// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
94 protected override void OnPreRender(EventArgs e)
95 {
96 base.OnPreRender(e);
97 string resource = Page.ClientScript.GetWebResourceUrl(GetType(), "ExtExtenders.yui-ext.css");
98 string csslink = "<link href='" + resource + "' rel='stylesheet' type='text/css' />";
99 Page.Header.Controls.Add(new LiteralControl(csslink));
100 ClientScriptManager man = Page.ClientScript;
101 //render the yui-ext scripts
102 man.RegisterClientScriptResource(typeof(YUI), "ExtExtenders.yui.js");
103 man.RegisterClientScriptResource(typeof(YUI_ext), "ExtExtenders.yui-ext.js");
104 man.RegisterClientScriptResource(typeof(ext_yui_adapter), "ExtExtenders.ext-yui-adapter.js");
105 if (!this.DesignMode)
106 {
107 // Test for ScriptManager and register if it exists
108 sm = ScriptManager.GetCurrent(Page);
109
110 if (sm == null)
111 throw new HttpException("A ScriptManager control must exist on the current page.");
112
113 sm.RegisterScriptControl(this);
114 }
115
116 }
117 /**//// <summary>
118 /// Renders the control to the specified HTML writer.
119 /// </summary>
120 /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"></see> object that receives the control content.</param>
121 protected override void Render(HtmlTextWriter writer)
122 {
123 base.Render(writer);
124 if (!this.DesignMode)
125 sm.RegisterScriptDescriptors(this);
126
127 }
128
129 IScriptControl Members#region IScriptControl Members
130
131 /**//// <summary>
132 /// Gets the script descriptors.
133 /// </summary>
134 /// <returns></returns>
135 public IEnumerable<ScriptDescriptor> GetScriptDescriptors()
136 {
137 ScriptControlDescriptor descriptor = new ScriptControlDescriptor(
138 "ExtExtenders.CalendarExtenderBehavior", this.ClientID);
139
140 Type t = this.GetType();
141 //properties that will be serialized
142 string[] propsToSerialize = {
143 "disabledDays","disabledDaysText","disabledDates",
144 "disabledDatesText","format"
145 };
146 foreach (string prop in propsToSerialize)
147 {
148 PropertyInfo p = t.GetProperty(prop);
149 if (p == null)
150 {
151 throw new Exception(prop);
152 }
153 descriptor.AddProperty(p.Name, p.GetValue(this, null));
154 }
155 return new ScriptDescriptor[] { descriptor };
156 }
157
158 /**//// <summary>
159 /// Gets the script references.
160 /// </summary>
161 /// <returns></returns>
162 public IEnumerable<ScriptReference> GetScriptReferences()
163 {
164 ScriptReference reference = new ScriptReference();
165 if (Page != null)
166 reference.Path = Page.ClientScript.GetWebResourceUrl(this.GetType(),
167 "ExtExtenders.calendar.CalendarExtenderBehavior.js");
168
169 return new ScriptReference[] { reference };
170 }
171
172 #endregion
173 }
由声明可知,ExtCalendar直接继承了TextBox,且实现了IScriptControl.
让我们来看看IScriptControl的接口规范, msdn之
2 {
3 IEnumerable<ScriptDescriptor> GetScriptDescriptors();
4 IEnumerable<ScriptReference> GetScriptReferences();
5}
当一个类实现了IScriptControl,其需要分别实现两个方法.
GetScriptDescriptors()方法用来返回客户端元素是如何与该服务器端控件进行数据交互的Descriptor
GetScriptReferences()方法则告诉ScriptManager在程序运行时需要引入哪些script以及路径.
其后,ExtCalendar声明了一系列对应于Extjs库中Calendar客户端组件的属性. 并且重写了TextBox的OnPreRender方法注册了所需要的客户端资源.
这里需要注意的是,如果我们要使用中文版的ExtCalender的话,需要从Extjs的资源包中拷贝ext-lang-zh_CN.js到项目中设为嵌入式资源并且修改YUI.cs
在类声明上添加[assembly: System.Web.UI.WebResource("ExtExtenders.ext-lang-zh_CN.js", "text/javascript")]
接下来,看看作者是如何实现IScriptControl接口的 .
GetScriptReferences()很简单, 返回路径为ExtExtenders.calendar.CalendarBehaviorExtender.js的ScriptReference对象实例.
GetScriptDescriptors()呢 ?
由代码可知. 作者先新建了一个ScriptControlDescriptor对象实例,
然后把ExtCalendar的各个对应Extjs的客户端Calendar组件的属性添加上述的descriptror中,在控件的Render()事件中利用控件所处页面的ScriptManager注册这个descriptor,达到与客户端元素交互的目的.
来到客户端的实现
1
2Type.registerNamespace('ExtExtenders');
3ExtExtenders.CalendarExtenderBehavior = function(element) {
4
5 ExtExtenders.CalendarExtenderBehavior.initializeBase(this, [element]);
6
7}
8ExtExtenders.CalendarExtenderBehavior.prototype = {
9 initialize: function() {
10
11 ExtExtenders.CalendarExtenderBehavior.callBaseMethod(this, 'initialize');
12 var id=this.get_element().id;
13 var objConfig={};
14 objConfig.fieldClass=document.getElementById(id).className;
15 objConfig.focusClass=document.getElementById(id).className;
16 if(this._disabledDaysText!=null){
17 objConfig.disabledDaysText=this._disabledDaysText;
18 }
19 if(this._disabledDays !=null){
20 objConfig.disabledDays= eval(this._disabledDays);
21 }
22 if(this._disabledDates!=""){
23 objConfig.disabledDates=eval(this._disabledDates);
24 }
25 if(this._disabledDatesText!=""){
26 objConfig.disabledDatesText=this._disabledDatesText;
27 }
28 objConfig.applyTo=id;
29 var dtField= new Ext.form.DateField(objConfig);
30 if(this._format!=""){
31 dtField.format=this._format;
32 }
33
34 dtField.render();
35
36 },
37
38 get_disabledDates: function() {
39
40 return this._disabledDates;
41 },
42 set_disabledDates: function(value) {
43 this._disabledDates = value;
44 },
45
46 get_disabledDatesText: function() {
47
48 return this._disabledDatesText;
49 },
50 set_disabledDatesText: function(value) {
51 this._disabledDatesText = value;
52 },
53 get_format: function() {
54
55 return this._format;
56 },
57 set_format :function(value) {
58 this._format = value;
59 },
60 get_disabledDays:function(){
61 return this._disabledDays;
62 },
63 set_disabledDays:function(value){
64 this._disabledDays=value;
65 },
66 get_disabledDaysText:function(){
67 return this._disabledDaysText;
68 },
69 set_disabledDaysText:function(value){
70 this._disabledDaysText=value;
71 }
72
73}
74ExtExtenders.CalendarExtenderBehavior.registerClass('ExtExtenders.CalendarExtenderBehavior', Sys.UI.Control);
脚本代码的第2,3行分明声明了一个名为ExtExtenders的命名空间以及该命名空间下的CalendarExtenderBehavior类. 在其后的原型声明中,initialize方法构造了一个客户端的Ext.form.DateField对象并且在页面呈现.
注意到js文件里有一系列get_XX, set_XX方法 它的作用就是按照get(set)_PropertyName的格式来传递服务器端ScriptDescriptor对象所具有的Property值.
最后一行, 作者把CalendarExtenderBehavior作为Sys.UI.Control的子类注册.
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
在下一节中,我们将继续更为复杂的控件,并且尝试着改良作者的设计.