--原文请见nikhilk.net的blog http://www.nikhilk.net/AtlasInPlaceEditSampleBehavior.aspx
作为我的上个随笔"用Atlas实现就地编辑"的续篇,本文深入的一步一步的讲解一下该例子中支持多种浏览器的script behavior的实现步骤,并且深入了解下Atlas式的组件开发。
距离发表上个随笔"用Atlas实现就地编辑"已经有一段时间了,本系列的目标是介绍一下Altas框架构造实现丰富体验Web应用是完全可实现的。本文将重点介绍Script behavior的实现。我将重点说说几个关键点而不是面面具到的罗列代码。下一次我将讲解属性扩展组件的构造。
不论是技术层面还是实践层面,Atlas的一个基础特性,就是将代码逻辑和数据封装成对象类或者组件(如果你需要)。这种实现手段使得开发人员尽量少的接触客户端脚本,并且使得大的应用可管理可实现。Atlas behavior就是实现这种封装的脚本组件。本质上来说,是事件处理逻辑的封装,打包了一些有用的应用模式功能,并且可以关联到HTML的元素或脚本上(这也是Altas的一个核心理念)。Behaviors可以用来扩展一个很简单的控件而不是去继承并创建一个新的控件。举例来说,尽管本文全部是关于InPlaceEditBehavior,可是如果需要的话我完全再实现一个MaskedEditBehavior并且关联到同一个TextBox控件上,这样即实现了就地编辑又实现了Masked编辑(这个没找到合适的词儿:)).
那么InPlaceEditBehavior到底是什么呢?它是一种行为规范可以让Textbox控件去执行。当TextBox控件没有获得焦点的时候,它被一个设置了相同的文本的Label控件所替换,从而让表单看上去不那么混乱(特别地,当编辑不是必须的时候)。并且这个Label控件本身可以显示可以呈现不同的外观,以表示活动或者非活动状态,当点击或者用键盘切换获得焦点的时候转换到编辑状态。
Step 1: 一个基本的Behavior
现在,我将说明behavior倒是什么东东,它到底背后包含了什么功能。让我们开始实现一个behavior 类的骨架。
Type.registerNamespace('nStuff.Samples');
nStuff.Samples.InPlaceEditBehavior = function() {
nStuff.Samples.InPlaceEditBehavior.initializeBase(this);
}
Type.registerClass('nStuff.Samples.InPlaceEditBehavior', Web.UI.Behavior);
本质上,我在nStuff.Samples的命名空间中定义了一个叫做InPlaceEditBehavior的类,该类继承自Web.UI.Behavior。在这个类的构造中调用了基类的构造。nStuff.Samples.InPlaceEditBehavior = function() {
nStuff.Samples.InPlaceEditBehavior.initializeBase(this);
}
Type.registerClass('nStuff.Samples.InPlaceEditBehavior', Web.UI.Behavior);
Step 2:添加属性
接下来的工作就是添加一些属性让这个类有用些。这个Behavior需要CSS class属性,这是用来实现Label控件在鼠标悬停时实现外观的变化。
nStuff.Samples.InPlaceEditBehavior = function() {
var _labelCssClass;
this.get_labelCssClass = function() {
return _labelCssClass;
}
this.set_labelCssClass = function(value) {
_labelCssClass = value;
}
}
JavaScript实际上没有属性这个语言特性,但是正如你看到的,我按照Altas的模式,以类似于C#语言中的方式实现了一个可读写的labelCssClass属性,同样地也可以实现其他的属性。var _labelCssClass;
this.get_labelCssClass = function() {
return _labelCssClass;
}
this.set_labelCssClass = function(value) {
_labelCssClass = value;
}
}
Step 3:支持XML-Script
这个Behavior需要可以声明的方式使用,通过简单的设置属性值就可以使用它。这需要作两件事:定义属性和关联的元数据(metadata),注册一个标记(Tag)映射到这个类。XML-script分析器需要使用这些来工作。
step3
首先,我重载了getDescriptor方法,开头先调用了基类的实现,这样类型描述符〔type desciptor〕中就包含了继承自Behavior和Component的属性的信息(比如说绑定的集合),然后,添加自己的属性.我同时指定了属性类型,以便分析器可以适当地从XML表示的属性字符串中创建属性的实例。其次,我申明了标记名称“nk:inPlaceEdit”。如你所见,XML-script是完全元数据驱动的。对象模型描述了合法的标记和属性。注意,在这里我同时可以描述事件和方法。事件信息用来绑定事件处理程序,方法信息用于在声明中调用方法(通过invokeMethodAction)以响应事件。Step 4:添加核心函数
好了,设置完成了,现在开始作些实质性的工作。就像大多数的behaviors,这个behavior同样需要在初始化时绑定事件处理程序,这需要重载initialize方法。不要侦听窗体的onload事件,因为它很可能早于你的behavior的初始化过程。
step4
在初始化过程中,behavior创建label控件并且添加到DOM中,这个label将覆盖textbox控件,并且开始监视用户的活动,例如hovering。一个behavior不仅仅能够监听Dom的事件,而且它可以监听绑定到DOM事件的脚本触发的事件。如果绑定到behavior的是一个输入控件(例如TextBox就是),那么behavior就会监听它的validated事件,如果输入不合理,即使用户把焦点切换走,输入控件将继续保持可见!它同样监听输入控件的blur事件和label控件的focus事件。注意,这里使用了代理,这是Atlas实现的对于JavaScript脚本语言OOP扩展的一部分。这些使得在事件处理程序中可以自然地使用“this”。同时应该注意到这里使用了addCssClass和removeCssClass方法,这些都是Atlas的Web.UI.Control中提供的辅助方法,友好的封装了DHTML APIs。
另一个值得注意的是isEditing属性,它的状态的改变通知是在beginEdit和endEdit方法中发起的。这些变化通知是构成Atlas的数据绑定机制的基础。任何其他组件都可以绑定到这个属性,并且这个绑定服务可以自动的在组件间传递数据。这种机制和可声明的标记引用机制极大地简化了代码的编写(作为对照,想象一下实现这些过程你需要手工所写的必要的代码!)。当你动手写组件的时候,基于绑定的概念考虑一下有意义的属性和事件,以便页面开发人员可以使用统一的代码声明机制。
Step 5:添加清除逻辑
一个非常重要的不应该忘记的逻辑就是实现dispose,虽然JavaScript本身实现了垃圾清除机制,但是脚本和DHTML DOM的联合使用并循环调用经常会导致内存泄漏。Atlas框架将会负责在适当地时候调用你的dispose实现逻辑。你所需要得就是要将你事件处理程序取消和DOM元素的绑定,打断这个循环引用。
step5
对于一个简单的behavior来说,像这样已经足够了。应用这些概念,你可以构造巨大的,具有挑战性的和有趣的组件。我应该写一个Behavior还是一个控件?
虽然我还没有谈论控件的创作,但是它们之间有很多重叠的部分。有一项技术是不同的:一个HTML元素只能绑定到一个控件,但是可以绑定多个behaviors。一个Behavior确实需要一个实际存在的控件,这样才能添加到behaviors集合中开始工作,这里面又一个很微妙的区别可以帮助你决定实现什么:你是需要一个具体的实现某功能的部件还是一个独立的功能组件。
那么,behavior应该绑定到那个控件呢?
这里面关键在于设计构思。我最初的想法就是起初又一个Label然后在需要的时候显示一个textbox控件。然而,我选择了相反的实现方案。我想我应该共享一下导致我这样选择的设计思路,这可能对实现其他的behaviors有用处。
如果扩展一个label控件,那么我必须动态的建立一个textbox控件。然而一个textbox控件需要很多的属性需要定制(在脚本中实现很麻烦!),然而一个label控件则可以很容易的
通过使用Css类来定制。而且atlas框架允许通过指定HTML/css来定制UI,这为设计者提供了完全的弹性机制。脚本应该着重在应用的行为表现方面。这是一个和ASP.NET服务器控件的关键区别,它们试图把UI绘制和behavior封装成一个实体。
atlas提供了一个模板机制,因此也许textbox控件应该通过一个模板来定制。但是这对于这样一个简单的场景来说将大大增加页面开发者的复杂性。模板是强大的,但是使用它们是不明智的(模板具有天生的复杂性)。
同时,如果让开发人员同时添加一个label和一个textbox,复杂的布局将会导致设计时的一些问题,和执行时的问题。最后我认为让textbox控件预先存在于页面上将使问题最简化,相应的简单的label控件将被动态的创建。
通常情况下,简单的设计就是最佳的设计,这在我实现的时候是显而易见的。由于textbox预先存在于网页之上,所以页面表单的逻辑就是定义好的不用再另外写代码。并且还可以添加验证控件逻辑,并且确保了用户输入会随着postback事件回传到服务器上,所有这些都是这样实现的优点。