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 模板。