Chassis icon indicating copy to clipboard operation
Chassis copied to clipboard

Chassis视图使用指南

Open miller opened this issue 12 years ago • 0 comments

<<返回目录

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上操作时,创建层级关系需要调用appendprepend方法,否则可以使用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方法。

beforedestroyafterdestroy

在视图销毁之前以及销毁完成后触发。 如果要响应beforedestroy事件可以直接定义onDestroy方法,而无需配置events对象。

实例化参数

在实例化视图时可以配置的参数包括:modelelidattributesclassNametagNameevents

  • 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结点中进行元素查找;

<<返回目录

miller avatar Jun 06 '13 15:06 miller