其实像AutoCompleteExtender之类的其它扩展也是与此类似,这里只是用CalendarExtender举例子了。
AjaxControlToolkit确实是个好东西,但是我实在不明白设计者是出于什么目的一定要把它捆在某一个文本框上,使得这组控件变得很难用。对日期控件来说,可能很多人用过梅花雨做的那个日期控件,那个控件确实很好用,而且其setday(this)这样的调用方式也非常简单明了,但是它存在许多问题(跟主题无关,不再细说),为了说服我的同事转用CalendarExtender,我决定想个办法使CalendarExtender也能像梅花雨日期控件一样简单好用。
由于AjaxControlToolkit天生的设计,不修改AjaxControlToolkit.dll是绝不可能达到目的的,不过好在微软提供了源代码(不知道应该说感谢微软,还是应该说感谢多年来坚持呼吁开源的人士- -!),所以修改dll变得一点也不复杂了。
首先,在结构上CalendarBehavior类继承自BehaviorBase类, BehaviorBase又继承自Sys.UI.Behavior类,这几个类中,CalendarBehavior和BehaviorBase类存在于AjaxControlToolkit项目中,可以直接修改,但是Sys.UI.Behavior存在于.NET Framework附带的System.Web.Extension.dll中,所以修改只能止于BehaviorBase了,要修改Sys.UI.Behavior的话,代价太大,不考虑。
但是,偏偏把扩展绑定到一个固定的控件上这个规则,就是Sys.UI.Behavior定义的!在这个类的构造函数中,把目标文本框传入,此后只能从子类调用继承的方法get_element()来获取这个文本框,而无法去更改它,因为没有基类Sys.UI.Behavior的实例,所以访问它的私有属性自然也是不可能的事。本来,如果给基类增加一个set_element()方法,既简单效率又最高,但是偏偏这个最佳途径被堵死了。于是只能退而求其次,从CalendarBehavior类身上做文章。
为了使目标文本框可以动态地变化,首先给CalendarBehavior类增加一个私有属性:
this._element = element;
然后要有读取它的公共方法:
return this._element;
}
把CalendarBehavior类中所有的get_element()方法替换成get_targetElement()方法,就使得Sys.UI.Behavior没有用武之地了,现在只要再有一个改变_element私有属性的方法即可大功靠成。
但是这个set方法并不是这么简单。首先,在构造CalendarExtender的时候,向目标文本框附加了好几个事件,现在既然要换文本框,原来的宿主文本框上绑定的事件必须被解除,并且把它们附加到新的文本框上;其次,CalendarBehavior有一个_popupBehavior私有属性,它是一个PopupExtender类的一个实例,PopupBehavior类实现了弹出div,定位扩展控件等功能,包括AutoCompleteExtender等其它弹出式控件都使用这个类的功能。CalendarBehavior在构造的时候,_popupBehavior也被按照第一个文本框的参数进行了构造,现在虽然目标文本框换了,但是_popupBehavior仍然是相对于第一个文本框而言的,需要将这个对象按照新的目标文本框重新构建;与_popupBehavior类似的还包括其它几个私有属性,set方法的代码如下:
/// <summary>
/// switch target textbox
/// </summary>
if (this._element.id == element.id)
return;
$common.removeHandlers(this.get_targetElement(), this._element$delegates);
$addHandlers(element, this._element$delegates);
this._element = element;
this._textbox = AjaxControlToolkit.TextBoxWrapper.get_Wrapper(element);
if (this._popupBehavior) {
this._popupBehavior.dispose();
this._popupBehavior = null;
}
if (this._container) {
if (this._container.parentNode) { // added this check before calling removeChild WI: 8486
this._container.parentNode.removeChild(this._container);
}
this._container = null;
}
if (this._popupDiv) {
$common.removeHandlers(this._popupDiv, this._popup$delegates);
this._popupDiv = null;
}
},
其中,第一句先判断要设置的目标控件是不是与当前控件一样,是非常重要的一步,否则会造成无谓的效率浪费,而且这一比较几乎不需要什么代价。
经过这样的包装后,项目中就可以调用set_targetElement()方法进行动态切换目标文本框了,因为我们的项目使用母版页,所以我在母版页中增加了这样一个
函数:
var ce = $find("pm_ce1");
ce.set_targetElement(element);
ce.show();
}
其中,pm_ce1是ajaxToolkit:CalendarExtender的BehaviorID,我在母版页中直接放了一个CalendarExtender,这样内容页就可以完全忽略CalendarExtender的细节,像调用梅花雨日期控件一样了:
顺便贴一个CalendarExtender的样式,这是从一个国外的网站上抄来的,忘了是哪个网站了,估计作者也看不懂中文,不至于来骂我一顿。:)
{
background-color: #e2e2e2;
border: solid 1px #cccccc;
}
.cal_Theme1 .ajax__calendar_header
{
background-color: #ffffff;
margin-bottom: 4px;
}
.cal_Theme1 .ajax__calendar_title, .cal_Theme1 .ajax__calendar_next, .cal_Theme1 .ajax__calendar_prev
{
color: #004080;
padding-top: 3px;
}
.cal_Theme1 .ajax__calendar_body
{
background-color: #e9e9e9;
border: solid 1px #cccccc;
}
.cal_Theme1 .ajax__calendar_dayname
{
text-align: center;
font-weight: bold;
margin-bottom: 4px;
margin-top: 2px;
}
.cal_Theme1 .ajax__calendar_day
{
text-align: center;
}
.cal_Theme1 .ajax__calendar_hover .ajax__calendar_day, .cal_Theme1 .ajax__calendar_hover .ajax__calendar_month, .cal_Theme1 .ajax__calendar_hover .ajax__calendar_year, .cal_Theme1 .ajax__calendar_active
{
color: #004080;
font-weight: bold;
background-color: #ffffff;
}
.cal_Theme1 .ajax__calendar_today
{
font-weight: bold;
}
.cal_Theme1 .ajax__calendar_other, .cal_Theme1 .ajax__calendar_hover .ajax__calendar_today, .cal_Theme1 .ajax__calendar_hover .ajax__calendar_title
{
color: #bbbbbb;
}
指定CalendarExtender的CssClass属性等于cal_Theme1即可。
这个主题运行时的截图:
的最后,为了展示主题,我贴了一张截图,也许大家注意到了,我的截图的右下角有一个“清除”按钮,这就是本文要介绍的更改。
在我们的项目中,大多数的录入日期的文本框是不允许用户手输的,只能通过点击--弹出日期选择框的方式选择,这样可以避免烦人的检查动作,当然也可以用MaskedEdit过滤,不过感觉没必要,有日期控件了,就不弄那么复杂了,另外,项目中大部分代码是以前写的,当时没有MaskedEdit可用,一直以来使用只读的办法来限制日期文本框。
由于文本框只读,所以只要用户一旦选择了某个日期,就没办法使这个文本框变空了,大多数情况下,这并不是问题,但是在少数页面,这一点相当烦人,更重要的是,以前的控件有这个功能,用户已经习惯了这样操作,现在突然告诉用户你没办法清空它,这不怎么有说服力。而且从系统设计的角度讲,那个文本框本来就是允许为空的,而且默认就是空的,后来选择了日期以后,就再也没办法使它恢复到空值状态,也不怎么说得过去。
因此我觉得有必要给CalendarExtender增加清除功能,使它能够清空目标文本框。
清空的动作倒是非常简单,但是要把它正确地显示出来比较棘手。首先,要了解CalendarBehavior类的展示方法:首先有一个_popupDiv属性,它是整个控件的载体,其它的控件都附加在这个div之上;而底部显示“今天”的部分,是直接附加在总载体_popupDiv上的,因为本来底部只有它一个控件,它当然可以这样做,但是现在要在它右侧增加一个按钮,就要改变一下这种行为: 增加一个_footer层作为底部控件的载体, 把今天日期作为子控件附加到_footer上面, 再增加一个_clearButton控件, 也附加到_footer上面. 实现这一流程的代码如下:
1. 增加私有属性:
this._footer = null;
2. 在_buildFooter方法中, 改写_today的展示方式:
/// <summary>
/// Builds the footer for the calendar
/// </summary>
this._footer = $common.createElementFromTemplate({
nodeName: "div",
properties: { id: this.get_id() + "_footer" },
cssClasses: ["ajax__calendar_footer"]
}, this._popupDiv);
var todayWrapper = $common.createElementFromTemplate({ nodeName: "div" }, this._footer);
this._today = $common.createElementFromTemplate({
nodeName: "div",
properties: {
id: this.get_id() + "_today",
mode: "today"
},
events: this._cell$delegates,
//cssClasses: ["ajax__calendar_footer", "ajax__calendar_today"]
cssClasses: ["ajax__calendar_today"]
}, todayWrapper);
var clearWrapper = $common.createElementFromTemplate({ nodeName: "div" }, this._footer);
this._clearButton = $common.createElementFromTemplate({
nodeName: "div",
properties: {
id: this.get_id() + "_clearButton",
mode: "clear"
},
events: this._cell$delegates,
cssClasses: ["ajax__calendar_clear"]
}, clearWrapper);
},
这里我给它指定了一个新的css属性ajax__calendar_clear, 允许用户任意定义这个按钮的样式. 并且将它的事件也绑定到统一的_cell$delegates中, 为此要给_cell_onclick方法增加一个处理器:
this.get_targetElement().value = "";
this._blur.post(true);
this.raiseDateSelectionChanged();
break;
进行到这里, 最重要的外观"清除" 两个字还没看到, 所以显然工作还没有完成: 然后在_performLayout方法中把清除这两个字附加上去:
this._clearButton.appendChild(document.createTextNode("\u6E05\u9664"));
}
这里我用了\u的方法表示汉字, 因为如果直接写汉字的话, 至少在我的环境中看来是乱码, 所以要直接用unicode码表示. 另外, 我顺便把today和最上方显示年月的地方的显示格式改了改, 默认的先月后年的显示方法实在不符合中国人的习惯. 这两个显示格式都在_performLayout方法中.
最后在dispose方法中, 把清除按钮清理掉:
$common.removeHandlers(this._clearButton, this._cell$delegates);
this._clearButton = null;
}
这就差不多算是大功告成了, 除了底部显示了两行, 比较丑以外......
显示两行显然是不能接受的, 所以打开Calendar.css , 继续修改.
因为要把今天的日期和清除按钮放在同一行, 所以先修改.ajax__calendar_today和.ajax__calendar_footer:
.ajax__calendar_today {cursor:pointer;padding-top:3px; float:left;}
然后增加一个ajax__calendar_clear类:
{
font-weight: bold;
color:Blue;
text-decoration:underline;
cursor:pointer;
float:right;
padding-top:3px;
}
现在就真的大功告成了.