chassis's People
Forkers
miller zswang kristinzhang wchaowu cmono byphper campaign the1sky anehing udbmnm venyxiong torome qdsang lgzhang lucky-mouse-14 liuguofeng719 mscq searchen simonbaker angck qiqi57 aisvoyage chyftom jango2015 eetuuuu desertfoxzhou lianjie5664 snayan bryant1410chassis's Issues
事件
Events
是一个可以被mix到任意对象的模块,它拥有让对象绑定和触发自定义事件
的能力。 事件在被绑定之前是不需要事先声明的,还可以携带参数。
看下面的例子:
var object = Chassis.mixin({}, Chassis.Events);
object.bind("alert", function(msg) {
alert("Triggered " + msg);
});
object.trigger("alert", "hi,Chassis!");
事件还提供了几种非常有用的API,详细的使用方法可以参考文档API。
在Chassis的里,事件无处不在,Model
、View
(PageView、SubView、GlobalView)、Router
、History
等都自动继承了Events的各种方法,比如,Model的属性被变更时会触发change
事件,PageView切换时会触发onBeforePageIn
/onAfterPageIn
事件。
Chassis入门
模块化按需加载
按需加载
和异步加载
是不同的,按需加载是指:当需要某些SubView
时,该SubView
才及时创建并可用。
按需加载需要基于异步加载
,并使用一些简单的方法,如示例中helloRocket里的代码:
(function($){
Chassis.PageView.sayhello = Chassis.PageView.extend({
el: '#sayhello_page'
,init: function(options){
var me = this,opt;
opt = $.extend({}, options);
me.prepend('say_header',opt);
me.setup('sayhello_content',opt);
// render异步子模块,也可以这样写:me.renderAsyncSubView(['say_header','sayhello_content']);
me.renderAsyncSubView();
}
});
})(Zepto);
代码中所示me.prepend('say_header',opt);
及me.setup('sayhello_content',opt);
不会直接render到页面,只有在显示的调用renderAsyncSubView
方法后才会生效。
如果renderAsyncSubView
参数为空,则render当前PageView下所有异步的模块。支持指定render特定的异步模块,参数为renderAsyncSubView([asyncModule,...])
。
按需加载
叫按需渲染
可能更恰当一些,因为它并不是等到需要渲染时才异步加载再渲染的,而是提前异步加载,这样有助于减少这类模块下载等待的时间。
PageView
PageView是Chassis中非常重要的组成部分,它是APP UI的主要展现载体。PageView与PageView之间是不允许嵌套的,它主要由SubView组成。
PageView的定义
使用以下方式来自定义PageView。
Chassis.PageView.define( 'detail', {
el: '#detail',
events: {},
init: function(){}
} );
这里需要注意的是define
方法的第一个参数,它并不是随意定义的,而是与APP的路由规则密切相关。
如果有如下的路由规则:
Chassis.history.start( {
router: {
routes: [ 'detail/:id' ]
}
} );
则当APP切换到类似http://yourdomain.com/#/detail/1233
URL时,路由处理器会在PageView中寻找ID为detail
的PageView并自动初始化。
关于路由的更多信息请参考《Chassis的路由使用》。
PageView的中可以通过以下的方式来添加SubView:
Chassis.PageView.define( 'detail', {
el: '#detail',
events: {},
init: function(){
this.append( Chassis.SubView.create( 'detail.section' ) );
}
} );
此外,还可以调用prepend
方法和setup
方法。其中append
和prepend
都会将SubView的DOM结点添加到PageView中,而setup
不会执行该操作,只是建立父子关系。如果SubView中的结点是自动创建的话使用前两种方式,如果结点已经在DOM树中了则直接调用setup
即可。
关于PageView定义中的其他参数解释请参考《Chassis视图使用指南》。
PageView的实例化
通常情况下,PageView的实例化是由Chassis路由自动完成的,但是在某些特殊情况下也可以通过以下方式来实例化一个PageView:
Chassis.PageView.create( 'detail', {
id: 'detail',
className: 'detail'
} );
build
Chassis的最终代码(完整版、精简版)、API文档等都是通过build工具来构建的。
构建过程请按照以下步骤执行:
- 安装nodejs、npm
- 安装grunt
npm uninstall -g grunt
npm install -g grunt-cli
- 下载最新代码并解压。
- 进入Chassis的源码根目录,执行以下命令
npm install
- 构建
grunt
项目构建时导出来的命名空间是Chassis
,可以根据自己的需求配置新的命名空间
:
打开根目录下的文件Gruntfile.js
,修改第15行
即可:
return src.replace( /__Chassis__/g, '新的命名空间' );
_常见问题_
- 默认的第三方开源库经常会被墙,可以更改设置
npm config set registry "http://npm.tuna.tsinghua.edu.cn/registry"
- 安装phantomjs出错时,可以手动更新包
下载phantomjs-1.9.1-windows.zip到当前盘符的/tmp/phantomjs/目录下。然后执行
npm install phantomjs
构架失败
使用grunt构建时出现错误,以下截取部分失败时才错误。
Linting src/view/view.fx.slider.js...ERROR
[L104:C32] Unexpected space after '('.
setTimeout( function() {
[L103:C28] Unexpected space after '('.
setTimeout( function() {
[L62:C24] Unexpected space after '('.
setTimeout( function() {
Linting src/view/view.js...ERROR
[L22:C33] Unexpected space after '('.
this.cid = Chassis.uniqueId( 'view' );
[L22:C40] Unexpected space after 'view'.
this.cid = Chassis.uniqueId( 'view' );
[L184:C35] Missing space after '('.
Chassis.$(window[ selector ])
[L184:C53] Missing space after ']'.
Chassis.$(window[ selector ])
[L371:C35] Unexpected space after '('.
attrs = Chassis.mixin( {}, this.attributes || {} );
[L412:C29] Unexpected space after '('.
throw new Error( 'view is not an instance of Chassis.View.' );
[L412:C72] Unexpected space after 'view is not an instance of Chassis.View.'.
throw new Error( 'view is not an instance of Chassis.View.' );
[L415:C2] Unexpected space after '}'.
} );
[L495:C2] Unexpected space after '}'.
} );
Linting src/view/view.loading.js...ERROR
[L20:C23] Unexpected space after '('.
Chassis.$( 'body' ).append( $el );
[L20:C30] Unexpected space after 'body'.
Chassis.$( 'body' ).append( $el );
[L152:C14] Unexpected space after '}'.
} );
Warning: Task "jsbint:all" failed. Use --force to continue.
Aborted due to warnings.
PageView回收
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结点中进行元素查找;
Chassis的路由使用---pushState
pushState
的使用和hash
没什么太大的区别,使用上非常简单:
Chassis.history.start( {pushState:true} );
默认会使用当前路径作为pushState的根目录,比如http://example.com/music/index
,pushS
tate会使用/music/
作为根目录。整个路由都会被控制在http://example.com/music/
下。
比如Chassis.navigate( 'info' )
,地址就会变为http://example.com/music/info
如果想指定新的root,可以这样:
Chassis.history.start( {pushState:true,root:'/'} );
有个问题是:当页面刷新后,因为当前地址并不存在,所以需要做一些重写。
模块化异步加载
由于SPA类应用的特殊性,为了提高首屏可操作性的速度,大部分的模块都可以使用异步加载来完成。
异步加载框架可以使用常见的seajs或百度的FIS异步加载包。Chassis本身不包含这类框架。Chassis对异步加载框架的需求仅是:能异步的获取代码并执行,对是否满足AMD/CMD没有要求。
在使用异步加载之前,需要先做一个简单的配置,以指定异步加载框架及指定异步加载的路径。
F.load = seajs.use;
//如果需要特殊的指定加载路径规则,可以这样
Chassis.load.config.ruler = function( pkg ){
return '../test/data/' + pkg;
};
下面展示subview异步加载的一个例子:
(function($){
Chassis.PageView.sayhello = Chassis.PageView.extend({
el: '#sayhello_page'
,init: function(options){
var me = this,opt;
opt = $.extend({}, options);
//异步加载subview
me.prepend('say_header',opt);
me.setup('sayhello_content',opt);
/* 这样写就不能使用异步和按需加载
me.setup(new rocket.subview.sayhello_header(
$.extend({}, options)
,me
));
me.setup(new rocket.subview.sayhello_content(
$.extend({}, options)
,me
));
*/
// render异步子模块,也可以这样写:me.renderAsyncSubView(['say_header','sayhello_content']);
me.renderAsyncSubView();
}
});
})(Zepto);
异步加载唯一的要求就是:不要自己生成subview的实例,交给框架来做。
再比如SubPage
也是如此:
(function($){
Chassis.SubView.say_content = Chassis.SubView.extend({
className: 'say_page_content'
,init: function(options){
var me = this,
title = options.title,
subView,
spm;
this.spm = new Chassis.SubPageMgr({
owner: this,
max: 15,
klass: 'say_content_detail' //rocket.subview.say_content_detail
});
}
, onBeforePageIn : function(){
this.$el.show();
}
});
Chassis的路由使用---hash
源码结构介绍
SubView复用
在SPA类应用中,会常常使用一些相同的SubView
,Chassis提供了内部的机制来完成SubView
的复用。
SubView
的复用不需要使用额外的代码或则特殊处理,只需要按照模块的异步加载要求即可:不要手动的产生新实例(TODO去掉这一要求,使得每次产生相同的实例)
(function($){
Chassis.PageView.sayhello = Chassis.PageView.extend({
el: '#sayhello_page'
,init: function(options){
var me = this,opt;
opt = $.extend({}, options);
me.prepend('say_header',opt);
me.setup('sayhello_content',opt);
/* 这样写就不能异步加载
me.setup(new rocket.subview.sayhello_header(
$.extend({}, options)
,me
));
me.setup(new rocket.subview.sayhello_content(
$.extend({}, options)
,me
));
*/
}
});
})(Zepto);
Chassis的路由使用
Router
为SPA类应用提供前进、后退、刷新以及可分享、收藏的URLs的功能。
示例
看一个简单的例子:
假设我们的webapp应用有两个页面,首页(index
)和详情页(info
),满足以下规则时则实例化对应的页面类:
页面 | URLs | PageView |
首页 | http://example.com/# | Chassis.PageView.index |
详情页 | http://example.com/#info/(id) | Chassis.PageView.info |
相应的路由规则写法如下:
var Router = {
routes :[ "info/:id" ]
};
Chassis.history.start( {router:Router} );
routes配置指定了路由的规则,info/:id
的配置更有趣,它映射形式如#info/100
这样的路由到info类,在info类里,参数值(100)会被当作参数传入。
另外,routes仅指定了一个配置,实际上还隐含的指定了一个空配置,指向index类。代码等同于下面这段代码:
routes :[ "","info/:id" ]
这段短小的代码其实还隐藏了一些秘密
:
其一是页面切换的顺序
,Chassis的一大亮点是页面的动画切换,两个页面之间存在着先与后的顺序问题,当从index切换至info页面时,页面会从右切换至左边(也可能是从下切换到上等),回退时则从左切换到右。routes的数组值隐含的指定了这个顺序。如果你期望的顺序恰好相反,可以这样:
var Router = {
routes : {
'' : 'index', //空或'#'的写法效果是一样的
'info/:id' : 'info'
},
pageOrder: [ 'info', 'index' ]
};
其二是自动实例化相应的View类
。当路由规则匹配info时,框架自动实例化Chassis.PageView.info(即:new Chassis.PageView.info),如果它存在的话。如果你期望在实例化之前做点什么,比如修正参数之类的什么,可以这样:
var Router = {
routes :[ "", "info/:id" ],
index : function(){
// new Chassis.PageView.index( this.Request );
new Chassis.PageView.home( this.Request );
},
info : function(){
if( !this.request.id ){
new Chassis.PageView[ '404' ];
return false; // return false后将不会继续调用默认的Chassis.PageView.info
}
new Chassis.PageView.info( this.Request );
}
};
其三是默认使用 hash的路由方案
,如果想使用pushState的话,可以这样:
Chassis.history.start( {router:Router,pushState:true} );
pushState
的使用和hash
没什么太大的区别,使用上非常简单:
Chassis.history.start( {pushState:true} );
默认会使用当前路径作为pushState的根目录,比如http://example.com/music/index
,pushS
tate会使用/music/
作为根目录。整个路由都会被控制在http://example.com/music/
下。
比如Chassis.navigate( 'info' )
,地址就会变为http://example.com/music/info
如果想指定新的root,可以这样:
Chassis.history.start( {pushState:true,root:'/'} );
有个问题是:当页面刷新后,因为当前地址并不存在,所以需要做一些URL Rewrite。
其四是默认自动触发当前路由
,如果直接打开http://example.com/#info/100
这类的地址,info类就会自动被实例化。你可以传入trigger参数阻止默认行为的发生:
Chassis.history.start( {router:Router,trigger:false} );
模型
Model
是SPA类应用程序的核心,包括数据的获取、转换、验证、访问控制等。Model
也提供了一组基本的管理变化的功能。
下面的示例演示了如何定义一个模型,包括自定义方法、设置属性、以及触发该属性变化的事件。
Chassis.Model.define( 'sidebar', {
promptColor: function() {
var cssColor = prompt( '请输入一个CSS颜色值:' );
this.set({ color: cssColor });
}
} );
var sidebar = Chassis.Model.create( 'sidebar' );
sidebar.bind('change:color', function(model, color) {
$('#sidebar').css({background: color});
});
sidebar.set({color: 'white'});
sidebar.promptColor();
Hello,Chassis!
创建index.html:
<!doctype html>
<html>
<head>
<meta charset='utf8'>
<title>Hello,Chassis!</title>
</head>
<body>
<div id="example"></div>
<script type="text/template" id="demo">
hello,<%= name %>!
</script>
<script src="lib/baidutemplate.js"></script>
<script src="../../build/chassis.js"></script>
<script src="lib/app.js"></script>
</body>
</html>
创建lib/app.js:
var Router = Chassis.Router.extend({
routes : [ '' ]
});
Chassis.PageView.index = Chassis.PageView.extend({
el: '#example',
init : function(){
var tpl = $('#demo').html(), data;
this.model = new Chassis.Model( {name : 'Chassis'} );
data = this.model.toJSON();
this.$el.html( baidu.template( tpl, data ) );
}
});
var router = new Router;
Chassis.history.start();
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.