Ember.js 拥有一些命名约定以便把各种对象整合在一起,遵循这些命名约定是必要的。
首先,创建应用的顶级命名空间:
window.App = Ember.Application.create();
App
就是你的应用(充当命名空间是它的职责),因为它是 Ember.Application
的一个实例。你可以把 App
替换成任意一个名字,比如 Blog
什么的,只要它不会在 window
下存在命名冲突。这暗示着:任何时候我们使用 Ember.js,App
是你唯一需要暴露给 window
的对象,其他后续创建的一切都是从属于 App
这个顶级命名空间的,这样 Ember.js 才能很好地组织和管理它们,并且也杜绝了和其他 JS 类库冲突的情况发生。
在这之后,当应用程序启动时(在浏览器中加载)Ember.js 会开始查找以下部分:
App.ApplicationRoute
App.ApplicationController
application
模板
对 Ember.js 来说,application
模板是整个应用的默认入口,是主模板。如果显式定义了 App.ApplicationController
,那么 Ember.js 会创建一个 App.ApplicationController
的实例作为 application
模板的控制器。这意味着 template
模板可以获得控制器中的属性。
如果显式定义了 App.ApplicationRoute
,那么 Ember.js 在渲染 template
模板之前会先执行位于 App.ApplicationRoute
内的路由器钩子方法(Router Hooks)。
写一个简单的例子来说明以上细节:
App.ApplicationRoute = Ember.Route.extend({ // 1.1
setupController: function(controller) { // 1.2
controller.set('title', 'Nice to meet you, Ember.js!'); // 1.3
}
});
App.ApplicationController = Ember.Controller.extend({ // 2.1
appName: 'An Example Ember.js Application' // 2.2
});
<!-- 3 -->
<h1>{{appName}}</h1>
<h2>{{title}}</h2>
再详细的过一遍,看看都发生了什么:
App.ApplicationRoute
- 这是
application
的路由定义,它引导 Ember.js 渲染 application
模板,这一点即使没有显式定义它也是一样的。此处显式定义是为了编写路由器钩子方法。
setupController
就是一个路由器钩子方法(不是唯一的一个),它将对应的控制器实例 controller
传进方法内以便进一步的设置。此时对应的模板是 application
,这就体现了 Ember.js 的命名约定:模板是 application
,对应的路由就是 ApplicationRoute
,下面的 ApplicationController
也是一样的道理。
set
方法为 controller
这个对象设置属性,之后可以在 application
模板里获取这个属性的值。
App.ApplicationController
- 这是
application
的控制器定义,它的实例对象会成为 application
模板的控制器。
- 这是为控制器实例对象定义的另外一个属性,稍后也会在模板里调用。
- 这是
application
模板了,{{appName}}
和 {{title}}
的值分别来自上面路由里的钩子方法和控制器里的属性定义。
在 Ember.js 应用里,控制器总是以类定义的形式编写,你无须去实例化它们,Ember.js 会负责去实例化并提供给对应的模板。这使得测试变得更容易,并且能保证每一个控制器在整个应用里只有一个对应的实例。
应用中的每一个路由都会有一个控制器和一个模板,它们的名称都和路由的一样。
因此像这样的一个路由定义:
App.Router.map(function() {
this.route('favorites');
});
当用户导航至 /favorites
时,Ember.js 会寻找这些对象:
App.FavoritesRoute
App.FavoritesController
favorites
模板
模板的渲染是这样的:Ember.js 会把 favorites
模板的内容插入到 application
模板内的 {{outlet}}
,同时实例化 App.FavoritesController
作为这个模板的控制器。
再重复一遍,如果显示声明了 App.FavoritesRoute
,那么 Ember.js 会在渲染模板前先调用它。
除了 setupController
这个钩子方法之外,还有一个很常用的钩子方法 model
,该方法可以让你为模板指定一个模型。例如:
App.PostsRoute = Ember.Route.extend({
model: function() {
return this.store.find('posts');
}
});
this.store
会找到应用的数据仓库,之后我们会详细介绍它。现在就把它看作是我们向数据源获取数据的途径便可以了。
在这个例子中,我们没有定义 PostsController
,这是因为模型是一个数组,Ember.js 会自动为我们提供一个 Ember.ArrayController
的实例对象来持有这个数组。因此你可以把 ArrayController
看做是模型本身,这有两大好处:
模板遍历控制器中的项了:
<ul>
{{#each controller}}
<li>{{title}}</li>
{{/each}}
</ul>
若路由使用了动态片段,那么其模型是什么取决于用户为该片段提供的值。
以这段代码为例:
App.Router.map(function() {
this.resource('post', { path: '/posts/:post_id' });
});
在本例中,首先 Ember.js 寻找以下对象:
App.PostRoute
App.PostController
post
模板
接着路由处理 model
钩子方法把动态片段 :post_id
转换为模型;serialize
是另一个钩子方法,用于把模型对象反转,为路由提供动态片段部分对应的 URL 参数:
App.PostRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('post', params.post_id);
},
serialize: function(post) {
return { post_id: post.get('id') };
}
});
因为这个模式太通用了,所以实际上它是路由的默认行为,也就是说你无须显式声明上述代码路由就会自动做这些处理:
若动态片段是以 _id
结尾,那么缺省的 model
钩子方法将会把前面的部分转换为模型的类名,并且这个类名是位于应用程序的命名空间之下的。如 post
-> App.Post
。接着就调用 find
方法,以模型方法名和动态片段值为参数查找模型实例。
缺省的 serialize
钩子方法会把模型实例的 id
属性值赋给动态片段。
如果你不给 post
指定路由(即 App.PostRoute
),Ember.js 仍然会渲染 post
模板并且给它一个 App.PostController
的控制器实例。
如果你不给 post
指定控制器(即 App.PostController
),Ember.js 会自动为你创造一个实例。这个实例的类型基于路由里 model
钩子方法的返回值,如果是数组就创建一个 ArrayController
;如果不是就创建一个 ObjectController
。
如果你连 post
模板都不写,那么 Ember.js 什么都不会渲染。
你可以在 resource
下嵌套简单路由或 resource
。
App.Router.map(function() {
this.resource('posts', function() { // the `posts` route
this.route('favorites'); // the `posts.favorites` route
this.resource('post'); // the `post` route
});
});
resource
是路由、控制器、以及模板命名的开头。即使像 post
这样的资源(resource)是嵌套的,它的路由依旧是 App.PostRoute
,控制器依旧是 App.PostController
,模板依旧是 post
(因为它是一个资源而不是简单路由)。
当你在资源内部嵌套了单个路由的时候,路由名称附加在资源的后面,以 .
分隔。
路由名称 |
控制器 |
路由 |
模板 |
posts |
PostsController |
PostsRoute |
posts |
posts.favorites |
PostsFavoritesController |
PostsFavoritesRoute |
posts/favorites |
post |
PostController |
PostRoute |
post |
首要原则是:名词(posts, articles...)用资源,形容词或动词(favorites, edit)用路由。这样保证了嵌套不会制造出夸张的长域名,也避免了常用形容词和动词的相互冲突。
在每一级嵌套中(包括顶级),Ember.js 为 /
路径自动提供了一个 index
路由。
比如说你写了一个简单路由:
App.Router.map(function() {
this.route('favorites');
});
它实际等价于:
App.Router.map(function() {
this.route('index', { path: '/' });
this.route('favorites');
});
如果用户导航至 /
,Ember.js 会寻找以下对象:
App.IndexRoute
App.IndexController
index
模板
Ember.js 会把 index
模板的内容插入到 application
模板内的 {{outlet}}
。若用户导航至 /favorites
,那么 favorites
模板将替换 index
模板。
嵌套路由也是一样的,比如:
App.Router.map(function() {
this.resource('posts', function() {
this.route('favorites');
});
});
其实等价于:
App.Router.map(function() {
this.route('index', { path: '/' });
this.resource('posts', function() {
this.route('index', { path: '/' });
this.route('favorites');
});
});
所以,当用户导航至 /posts
时,当前的路由其实是 posts.index
,Ember.js 会寻找下列对象:
App.PostsIndexRoute
App.PostsIndexController
posts/index
模板
模板的渲染过程如下:
posts
模板渲染至 application
模板的 {{outlet}}
;
posts/index
模板渲染至 posts
模板的 {{outlet}}
。
最后,假设用户导航至了 /posts/favorites
,那么 posts/favorites
模板将替换 posts/index
模板。