5.2 呈现脚本
接下来,我们将重构前面完成的AutoFlexTextArea控件。
在前面的AutoFlexTextArea控件中,我们将脚本直接包含在控件标签中,其实这是一种违反Web标准的做法。应该尽可能地将网页的数据、结构、呈现、行为的代码进行分离,HTML代码表达网页的结构,CSS控制网页外观,JavaScript表达行为,如图5-3所示。我们应该把之前混在一起的结构和行为脚本分离开来,也就是说自动伸展的脚本代码不能直接内嵌在标签中。
而且我们最好能把自动伸展这种行为封装成组件化的脚本,便于对脚本进行重用和管理。在Vista等新一代平台姗姗来迟,Ajax应用异军突起的今天,对脚本的应用将大大超过以前,以组件化的思路和方式编写脚本将是大势所趋。

图5-3 结构、表现、行为相分离
让我们先实现组件化的AutoFlex脚本:
function AutoFlex(id,maxHeight){
this._id = id;
this.maxHeight = maxHeight;
}
AutoFlex.prototype._onPropertyChange = function(){
if(this.maxHeight){
this._element.style.height =
( this._element.scrollHeight > this.maxHeight ) ? this.maxHeight :
this._element.scrollHeight + this._element.offsetHeight
- this._element.clientHeight;
}
else{
this._element.style.height = this._element.scrollHeight
+ ( this._element.offsetHeight - this._element.clientHeight );
}
}
AutoFlex.prototype._getPropertyChangeHandler = function(){
var obj = this;
return function (){
obj._onPropertyChange.call(obj);
}
}
AutoFlex.prototype.initiate = function(){
this._element = document.getElementById(this._id);
if(this._element){
this._element.onpropertychange = this._getPropertyChangeHandler();
}
}
上面这段代码我们遵循这样的代码规范:
(1)类名使用Pascal命名法,函数使用骆驼法,这一点很重要,因为JavaScript本没有类这个概念,我们使用Function模拟类,为了区别function定义的是类的概念还是函数的概念,我们使用不同的命名法。
(2)类的属性在构造函数中定义和初始化,类的函数中通过prototype添加。
(3)非对外的成员名使用_开头。
这段代码需要注意以下几点:
(1)在JavaScript中,0相当于false,null也相当于false,所以我们只需判断if(this.maxHeight)就能覆盖没有传maxHeight参数和maxHeight参数为0的两种情况。
(2)在附加事件响应程序时,我们不是直接写this._element.onpropertychange = this._onPropertyChange而是this._element.onpropertychange = this._getPropertyChangeHandler()。这是为什么呢?原来如果我们使用前一种写法,当_onPropertyChange方法被事件触发时,它的执行上下文,也就是this指针是指向触发事件的对象或window对象,这是JavaScript中事件的一个特别需要重视的特点,如果是这样的话,_onPropertyChange()方法中的代码将不能正确执行。在后一种写法中,我们通过JavaScript的闭包特性和Function.call()方法使得_onPropertyChange()方法在执行时能得到期待的this指针。
注:闭包——函数始终可以使用定义它时在它外围的(即它可访问到的)变量,而无论它在何时执行。如上例的_getPropertyChangeHandler()方法中的代码,我们用obj变量保存this的指针,那么接下来定义的函数始终可以访问到obj变量,即使是在被事件触发时也是如此。call——Javascript中的Function原型上有两个功能类似的方法——call和apply,它们都用来执行当前函数,但被执行函数中的this指针指向call或apply方法的第一个参数,如上例中的obj._onPropertyChange.call(obj),这个语句被事件触发时指针this本来是指向window对象的,然而我们使用call方法使得_onPropertyChange方法内部的语句访问的this指针指向obj对象。call方法和apply方法的区别在于传递参数的方式不同,call方法把第一个参数作为被调用方法的this指针,其余参数作为被调用方法的参数,apply也把第一个参数作为被调用方法的this指针,然后用一个数组把参数打包传给被调函数。如,myFunction.call(context,arg0,arg1,arg2)和myFunction.apply(context,[arg0,arg1,arg2]);。
准备好了组件化的脚本后,控件的实现变得非常简单:
public class AutoFlexTextArea:TextBox
{
bool _supportJS;
const string AUTOFLEXJS = @"
function AutoFlex(id,maxHeight){
this._id = id;
this.maxHeight = maxHeight;
}
AutoFlex.prototype._onPropertyChange = function(){
if(this.maxHeight){
this._element.style.height =
( this._element.scrollHeight > this.maxHeight ) ? this.maxHeight :
this._element.scrollHeight
+ ( this._element.offsetHeight - this._element.clientHeight);
}
else{
this._element.style.height = this._element.scrollHeight
+ ( this._element.offsetHeight - this._element.clientHeight );
}
}
AutoFlex.prototype._getPropertyChangeHandler = function(){
var obj = this;
return function (){
obj._onPropertyChange.call(obj);
}
}
AutoFlex.prototype.initiate = function(){
this._element = document.getElementById(this._id);
if(this._element){
this._element.onpropertychange = this._getPropertyChangeHandler();
}
}";
[DefaultValue(0)]
[Category("Behavior")]
[Description("最大伸展高度")]
public int MaxHeight
{
get
{
if (ViewState["MaxHeight"] == null)
{
return 0;
}
return (int)ViewState["MaxHeight"];
}
set
{
ViewState["MaxHeight"] = value;
}
}
[DesignerSerializationVisibility
(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
public override TextBoxMode TextMode
{
get
{
return TextBoxMode.MultiLine;
}
set
{
throw new NotSupportedException(
"Can not change the TextMode property");
}
}
void DetermineJS()
{
if (!DesignMode)
{
if (Page.Request.Browser.EcmaScriptVersion.Major > 0
&& Page. Request.Browser.W3CDomVersion.Major > 0)
{
this._supportJS = true;
}
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
DetermineJS();
if (_supportJS)
{
if (!Page.ClientScript.IsClientScriptBlockRegistered(
this. GetType(),"AUTOFLEXJS"))
{
Page.ClientScript.RegisterClientScriptBlock(
this.GetType(), "AUTOFLEXJS", AUTOFLEXJS, true);
}
}
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
if (_supportJS)
{
Page.ClientScript.RegisterStartupScript(
this.GetType(), this.UniqueID,
string.Format(" var {0}_AutoFlex =
new AutoFlex('{0}',{1});\r\n {0}_AutoFlex.initiate();\r\n",
this.UniqueID,this.MaxHeight.ToString()), true);
}
}
这段代码非常简单,AutoFlexTextArea类从TextBox类继承。为了隐藏TextBox的TextMode属性,重写了这个属性,并且不允许用户设置TextMode属性。此外新增了MaxHeight属性,用来指定最大扩展高度。然后重写OnPreRender()方法,检测访问浏览器对脚本的支持能力,并输出AutoFlex组件脚本,通过IsClientScriptBlockRegistered()方法保证在同样的页面中,这段脚本只会输出一次。最后重写Render()方法,在页面表单的最后注册初始化客户端组件所需的脚本。
生成控件,放入页面中进行测试:
<cc1:autoflextextarea id="AutoFlexTextArea1" runat="server"></cc1:autoflextextarea>
<cc1:autoflextextarea id="AutoFlexTextArea2"
runat="server" MaxHeight="200"></cc1:autoflextextarea>
最终呈现的页面内容如下:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>
无标题页
</title></head>
<body>
<form name="form1" method="post" action="TestAutoFlexTextArea.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKMTEyMj U4MzUwMGRkE8+2KGaUuMKOsu+evZ76vSgAYMU=" />
</div>
<script type="text/javascript">
<!--
function AutoFlex(id,maxHeight){
//省略AutoFlex组件代码;
}
// -->
</script>
<div>
<textarea name="AutoFlexTextArea1" rows="2" cols="20" id="AutoFlexTextArea1"></textarea>
<textarea name="AutoFlexTextArea2" rows="2" cols="20" id="AutoFlexTextArea2"></textarea>
</div>
<div>
<input type="hidden" name="__EVENTVALIDATION" id="__EVENTVALIDATION" value="/wEWAwL/88OJCQKmoPGEDAKloPGEDNkP9kNJ1Pg3kQ7qvP+7/kqXZYV0" />
</div>
<script type="text/javascript">
<!--
var AutoFlexTextArea1_AutoFlex = new AutoFlex('AutoFlexTextArea1',0);
AutoFlexTextArea1_AutoFlex.initiate();
var AutoFlexTextArea2_AutoFlex = new AutoFlex('AutoFlexTextArea2',200);
AutoFlexTextArea2_AutoFlex.initiate();
// -->
</script>
</form>
</body>
</html>







