Chassis视图使用指南
View是Chassis中非常重要的组成部分,APP中可以没有Model但是不会没有View。View在Chassis中担当的职责包括:数据读写、UI渲染、页面切换以及用户交互。由此可见,Chassis中的View并不是传统MVC中的 V ,而是相当于 Controller 的角色。
分类
在Chassis中,按功能划分View被分为PageView、SubView以及GlobalView:
- PageView: 页面视图,代表的是一个完整的逻辑页面,是APP路由的对象;
- SubView: 子视图,代表的是逻辑页面中的某个子模块,PageView可以包含零个或多个子视图;子视图可以被定义为子页面(SubPage)并进行子页面路由处理;
- GlobalView: 全局视图,代表的是APP中全局固定的部分,例如全局导航栏等;全局视图可以监听路由事件,也可以向指定的PageView派发事件,但是本身不参与任何路由逻辑;
在Chassis的内部实现中,PageView、SubView以及GlobalView都继承于Chassis.View类,而且绝大部分实现代码也都位于基类中,因此接下来我们主要会针对Chassis.View进行分析。
生命周期管理
在View的基类中有三个类方法以及一个实例方法直接参与着View的生命周期管理。需要注意的是,基类是一个虚类,不要直接使用基类创建类实例或者自定义视图类。
自定义视图类
在使用Chassis开发APP时,通常都是通过继承方式来自定义视图类,这里会用到基类中的define方法。
使用define方法定义一个PageView类:
Chassis.PageView.define( 'home', {
el: '#home',
events: {},
init: function(){}
} );
define执行时实际上是在Chassis.PageView命名空间下新增了一个key为home的Chassis.View子类。
实际上它等同于:
Chassis.PageView.home = Chassis.PageView.extend( {
el: '#home',
events: {},
init: function(){}
} );
而在实际开发中,你确实也可以使用第二种方法来自定义类,但是我们还是强烈建议你使用define。一方面代码会更简洁,另一方面如果key中包含特殊字符时也会更便利,此外对于PageView而言,key就是路由中的action,因此必须得小写,通过define的方式可以避免大小写不一致的问题。
此外,SubView和GlobalView都有define方法,因此通过相同的方式自定义视图。
实例化视图
通过前面的介绍大家知道了,视图的定义实际上是通过继承的方式得到一个新的视图类,并放在了对应视图类的命名空间下,那么实例化视图可以简单的使用这种方式:
var homeView = new Chassis.PageView.home( {} );
不过,我们还是强烈推荐使用以下方式来创建实例:
var homeView = Chassis.PageView.create( 'home', {} );
获取视图类
通过define( id, definition );可以定义一个视图类,相应的通过get( id ); 接口可以从相应的视图空间中获得类定义。
var HomeView = Chassis.PageView.get( 'home' );
当然也可以直接获取:
var HomeView = Chassis.PageView.home;
但是同样会有大小写问题,或者有特殊字符需要使用引号访问的问题。
销毁视图
当你需要销毁某个视图时,务必记得使用视图实例上的destroy接口。调用该接口不仅仅会销毁视图对应的DOM结点,还包括所有通过events对象注册的事件以及当前视图中的所有子视图。
var homeView = Chassis.PageView.create( 'home', {} );
homeView.destroy();
层级管理
Chassis的视图支持多级嵌套,这样便于开发者灵活的拆分模块。为了实现上的简洁性,层级管理的接口也是在基类中,包括以下几个接口:
- append(view): 将目标视图的DOM结点append到当前视图的DOM结点中,同时建立视图层级关系;
- prepend(view): 将目标视图的DOM结点prepend到当前视图的DOM结点中,同时建立视图层级关系;
- setup(view): 仅建立视图层级关系;
这里的视图关系包括:
- 将目标视图的
parent属性指向当前视图; - 将目标视图放入当前视图的
children属性中;
如果你的SubView是新创建的DOM结点,而并不是在已有DOM上操作时,创建层级关系需要调用append或prepend方法,否则可以使用setup方法。
这里需要注意,虽然接口在基类中,但是实际上PageView是不允许相互嵌套的,同时PageView和GlobalView也不应该相互嵌套。除此之外,PageView和GlobalView都可以可以嵌套多个SubView,而SubView中可以继续嵌套SubView。
GlobalView PageView
| |
SubView SubView
|
SubView
事件管理
events对象
当你自定义视图类或者创建视图类的实例时都可以定义一个events对象,例如:
Chassis.PageView.define( 'home', {
el: '#home',
events: {
'click .btn': 'onBtnClick'
},
init: function(){},
onBtnClick: function(){}
} );
或
Chassis.PageView.define( 'home', {
el: '#home',
init: function(){},
onBtnClick: function(){}
} );
var homeView = Chassis.PageView.create( 'home', {
events: {
'click .btn': 'onBtnClick'
}
} );
在视图中,通过events对象几乎可以将所有的事件注册都通过配置的方式来实现,像上面的例子所述,events对象的格式如下:
{ 'type target': 'handler' || handler }
其中handler表示的是事件的callback函数,可以是字符串,也可以是直接的函数定义。如果是字符串的话会通过this[handler]来查找callback。callback被调用时的参数为事件参数,执行context为当前视图;
type表示事件名,根据不同的target而不同。
target为需要监听的对象,这里包括多种类型:
- dom selector: 表示监听指定的DOM结点;这类事件注册时,会将当前视图元素中selector所选中DOM结点的事件通过代理的方式注册到当前视图的元素中;即当前视图所在元素会代理一切此类DOM事件;例如'click .btn',会将
this.$el中所有class为.btn的元素的click事件代理到this.$el上;此外,如果selector为空则表示直接注册到this.$el上; - view: 表示监听当前视图所触发的事件,例如
beforepagein view; - model: 表示监听当前视图中的model所触发的事件,例如
change model; - window: 表示监听window上的事件;
- document:表示监听document上的事件;
例如:
{
'mousedown .title': 'edit',
'click .button': 'save',
'click .open': function( e ){},
'orientationchange window': 'refresh',
'click document': 'close',
'beforepagein view': 'onBeforePageIn',
'change model': 'render'
}
在定义视图时配置events对象以及在实例化时传入events对象都能使视图自动进行事件注册,这种方式是强烈推荐的,因为在视图销毁时会自动解除这一过程中绑定的事件,如果使用其他方法注册则无法保证能自动解除。
delegateEvents方法
如果需要在视图运行中注册事件,同样建议调用delegateEvents(events)方法来处理,因为该方法会用相同的方法来注册事件,在视图销毁时可以确保事件被解除。
触发的事件
视图本身在不同的状态下也会触发一些事件。
beforepagein
在视图切换中,当前视图即将进入可视区之前会触发该事件,如果要监听可以使用以下方式:
Chassis.PageView.define( 'home', {
events: {
'beforepagein view': 'onBeforePageIn'
},
onBeforePageIn: function(){}
} );
但实际上,由于该事件监听比较频繁,Chassis对此做了简化,无需在events对象再进行配置,可以直接定义onBeforePageIn方法。即上述代码等同于:
Chassis.PageView.define( 'home', {
onBeforePageIn: function(){}
} );
afterpagein
与beforepagein对应,在视图已经完全进入可视区时触发,同样可以省略events对象的配置,可以直接定义onAfterPageIn方法。
beforedestroy 与 afterdestroy
在视图销毁之前以及销毁完成后触发。
如果要响应beforedestroy事件可以直接定义onDestroy方法,而无需配置events对象。
实例化参数
在实例化视图时可以配置的参数包括:model、el、id、attributes、className、tagName、events。
- model: 视图对应的Model实例;
- events: 事件注册配置;
其他的都与视图所在的DOM结点相关:
- el: 视图对应的DOM元素,可以是$对象,也可以是html代码;
- id: DOM ID;
- attributes: DOM属性;
- className: DOM CSS class;
- tagName: 自动创建DOM时的tagName;
这里需要注意的是,其中el参数和其他参数是互斥的,如果设置了el参数则会直接将el转换成this.$el而不会进行DOM属性的设置;如果未设置el则视图会自动创建DOM并设置DOM属性。
其他方法与属性
- $el属性: 通过
this.$el可以获取到当前视图对应的DOM结点; - $方法: 通过
this.$方法可以在当前DOM结点中进行元素查找;