Ember应用结构

开发 前端
在高层,你通过设计一系列符合嵌套的应用状态的嵌套的路由来组织 Ember 应用。本 指导会首先涵盖高层概念,然后用一个例子贯穿整个讲解。

在高层,你通过设计一系列符合嵌套的应用状态的嵌套的路由来组织 Ember 应用。本 指导会首先涵盖高层概念,然后用一个例子贯穿整个讲解。

路由

用户通过决定浏览什么来在你的应用中穿梭。例如,如果你有一个 blog,你的用户会 首先在“关于”页面和“文章列表”间选择。一般地,你想要给这个首先的选择一个默认值 (在这种情况下,可能是“文章列表”)。

一旦用户做出了他们的***次选择,他们通常没有完成。在“文章列表”的上下文中,用 户***会选择某篇文章和它的评论。在单篇文章页面中,他们可以在评论列表和引用通 知列表中选择。

重要的是,在所有这些情况中,用户只是在页面上显示的东西中做选择。如果你深入应 用的状态,这些选择只影响页面上的更小的区域。

在接下来的一节,我们会介绍如何控制页面上的这些区域。那么现在,让我们看看如何 构建你的模板。

当用户最开始访问到应用时,应用显示在屏幕上,并且有一个空的、路由可控制的插 座。在 Ember 中,一个 outlet 是模板上的一个区域,这个区域由子模板在运行时 基于用户交互来决定。

 

应用模板( application.handlebars )看起来会是这样:

  1. <h1>我的应用</h1> 
  2. {{outlet}} 

​默认情况下,路由起先会进入 文章列表 状态,然后把插座用 posts.handlebars填充。之后我们会看到这如何确切地奏效。

与期待一致, 文章列表 模板会渲染一个文章列表。点击单篇文章的链接会用单篇文 章的模板来替换应用的插座中的内容。

模板看起来是这样:

  1. {{#each post in controller}} 
  2. <h1><a {{action showPost post href=true}}>{{post.title}}</a></h1> 
  3. <div>{{post.intro}}</div> 
  4. {{/each}} 

当点击单篇文章的连接,应用会转移到 单篇文章 状态,并用 post.handlebars 来替换应用插座中的 posts.handlebars

在这种情况下,单篇文章也可以有插座。插座会允许用户在评论和引用通知之间选择。

单篇文章的模板看起来是这样:

  1. <h1>{{title}}</h1> 
  2. <div class="body"> 
  3.   {{body}} 
  4. </div> 
  5. {{outlet}} 

{{outlet}} 再次指定了路由来决定这个区域放置什么的模板。

因为 {{outlet}} 是所有模板的特性,当你深入路由层级,每个路由会自然地控制页 面上的更小区域。

#p#

它如何工作

现在你理解了基本理论,让我们看看路由是如何控制你的插座的。

模板、控制器以及视图

首先,对于每个高层 Handlebars 模板,你同样也会有一个同名的视图和控制器。例 如:

  • application.handlebars: 应用主视图的模板
  • App.ApplicationController: 上述模板的控制器。 application.handlebars的初始变量上下文是这个控制器的一个实例
  • App.ApplicationView: 上述模板的视图对象

一般地,你会用视图对象来处理事件,并用控制器对象来向模板提供数据。

Ember 提供两种基本类型的控制器, ObjectController 和 ArrayController 。 这些控制器充当模型对象和模型对象列表的代理。

我们以控制器开始,而不是直接向模板暴露模型对象,这样你才有余地使用视图关联的 计算属性,并且最终视图关系不会污染你的模型。

你也可以用模板关联的控制器连接 {{outlet}} 。

路由

应用的路由负责让应用在状态见转移来响应用户的动作。

我们以一个简单的路由开始:

  1. App.Router = Ember.Router.extend({ 
  2.   root: Ember.Route.extend({ 
  3.     index: Ember.Route.extend({ 
  4.       route: '/', 
  5.       redirectsTo: 'posts' 
  6.     }), 
  7.     posts: Ember.Route.extend({ 
  8.       route: '/posts' 
  9.     }), 
  10.     post: Ember.Route.extend({ 
  11.       route: '/posts/:post_id' 
  12.     }) 
  13.   }) 
  14. }); 

这个路由设置了三个顶层的状态:一个索引页状态。一个显示文章列表的状态和一个 显示单篇文章的状态。

在我们的案例中,我们会简单重定向索引页路由到 posts 状态。在其它应用中,你 也许会需要一个独立的主页。

目前为止,我们已经有了一个状态列表,并且我们的应用也会尽职尽责地进入到 posts 状态,但这不会做任何事。当应用进入到 posts 状态,我们要它连接到应 用模板中的 {{outlet}} 。我们用 connectOutlets 回调来完成这个工作。

  1. App.Router = Ember.Router.extend({ 
  2.   root: Ember.Route.extend({ 
  3.     index: Ember.Route.extend({ 
  4.       route: '/'
  5.       redirectsTo: 'posts' 
  6.     }), 
  7.     posts: Ember.Route.extend({ 
  8.       route: '/posts'
  9.       connectOutlets: function(router) { 
  10.         router.get('applicationController').connectOutlet('posts', App.Post.find()); 
  11.       } 
  12.     }), 
  13.     post: Ember.Route.extend({ 
  14.       route: '/posts/:post_id' 
  15.     }) 
  16.   }) 
  17. }); 

connectOutlet 调用会为我们做这些事:

  • 它创建一个 App.PostsView 的新实例,使用 posts.handlebars 模板。
  • 它设置 postsControllercontent 属性为一个所有可用文章(App.Post.find() ) 的列表,并让 postsController 作为新的App.PostsView 的控制器。
  • 它把新视图连接到 application.handlebars 的插座上。

一般地,你应该值考虑这些对象为串联的操作。当你创建一个视图,你总是会为视图的 控制器提供内容。

过渡和 URL

下一步,我们要为 posts 状态中的应用提供一种迁移到 post 状态的方法。我们 通过指定一个过渡来完成这个工作。

  1. posts: Ember.Route.extend({ 
  2.   route: '/posts'
  3.   showPost: Ember.Route.transitionTo('post'), 
  4.   connectOutlets: function(router) { 
  5.     router.get('applicationController').connectOutlet('posts', App.Post.find()); 
  6.   } 
  7. }) 

你用当前模板中的 {{action}} 辅助标记调用这个过渡。

  1. {{#each post in controller}} 
  2.   <h1><a {{action showPost post href=true}}>{{post.title}}</a></h1> 
  3. {{/each}} 

当用户点击一个带有 {{action}} 辅助标记的链接时,Ember 会把一个事件分配到指 定名称的当前状态。在这种情况下,事件是一个过渡。

因为我们使用了一个过渡,Ember 也可以为这个链接生成 URL。Ember 用上下文中的 id 属性来填充 post 状态中的 :post_id 动态段。

下一步,我们会需要在 post 状态上实现 connectOutlets 。这次, connectOutlets 方法会接受 {{action}} 辅助标记上下文指定的文章对象。

  1. post: Ember.Route.extend({ 
  2.   route: '/posts/:post_id'
  3.   connectOutlets: function(router, post) { 
  4.     router.get('applicationController').connectOutlet('post', post); 
  5.   } 
  6. }) 

connectOutlet 调用执行的一系列步骤可以概括为如下:

  • 它用 post.handlebars 模板创建了一个 App.PostView 的新实例。
  • 它设置了用户点击的文章的 postControllercontent 属性。
  • 它把新视图连接到 application.handlebars 中的插座。

如果用户把页面存为书签并在之后返回,你不需要任何额外的操作来让链接( /posts/1 ) 正常工作。

如果用户***次以 posts/1 URL 访问页面,路由会执行这几个步骤:

  • 断定 URL 符合的状态(在本例中是 post )。
  • 从 URL 中解压动态段(在本例中是 :post_id )并调用App.Post.find(post_id) 。这使用一个命名约定来奏效::post_id 动态段对应 App.Post
  • App.Post.find 的返回值调用 connectOutlets

这意味着不管用户是否从页面中的另一部分或是通过 URL 进入到 post 状态,路由 都会以相同的对象调用 connectOutlets 方法。

#p#

嵌套

***,让我们实现评论和引用通知功能。

因为 post 状态使用和 root 状态相同的模式,它看起来非常类似。

  1. post: Ember.Route.extend({ 
  2.   route: '/posts/:post_id'
  3.   connectOutlets: function(router, post) { 
  4.     router.get('applicationController').connectOutlet('post', post); 
  5.   }, 
  6.   index: Ember.Route.extend({ 
  7.     route: '/'
  8.     redirectsTo: 'comments' 
  9.   }), 
  10.   comments: Ember.Route.extend({ 
  11.     route: '/comments'
  12.     showTrackbacks: Ember.Route.transitionTo('trackbacks'), 
  13.     connectOutlets: function(router) { 
  14.       var postController = router.get('postController'); 
  15.       postController.connectOutlet('comments', postController.get('comments')); 
  16.     } 
  17.   }), 
  18.   trackbacks: Ember.Route.extend({ 
  19.     route: '/trackbacks'
  20.     showComments: Ember.Route.transitionTo('comments'), 
  21.     connectOutlets: function(router) { 
  22.       var postController = router.get('postController'); 
  23.       postController.connectOutlet('trackbacks', postController.get('trackbacks')); 
  24.     } 
  25.   }) 
  26. }) 

这里只发生了这些变化:

  • 我们只在状态内指定了 showTrackbacks 和 showComments 过渡,而状态里过渡 才有意义。
  • 既然我们正在获取给 post.handlebars 使用的视图,我们调用 postController 上的 connectOutlet
  • 这种情况下,我们从当前文章中获取 commentsController 和 trackbacksController 的内容。 postController 是一个底层文章模型的代 理,所以我们可以直接用 postController 直接检索关联。

这是单篇文章的模板:

  1. <h1>{{title}}</h1> 
  2. <div class="body"> 
  3.   {{body}} 
  4. </div> 
  5. <p> 
  6.   <a {{action showComments href=true}}>评论</a> | 
  7.   <a {{action showTrackbacks href=true}}>引用通知</a> 
  8. </p> 
  9. {{outlet}} 

***,这个嵌套配置下,从书签链接返回页面也会正常工作。让我们看一下当用户从posts/1/trackbacks 访问站点时发生了什么。

  • 路由决定 URL 关联的状态( post.trackbacks ),然后进入状态。
  • 对经过的每个状态,路由解压任何的动态段并调用 connectOutlets 。这镜像了用 户用来在应用中浏览的路径。与之前一样,路由会在文章上以 App.Post.find(1)的结果调用 connectOutlet 。
  • 当路由进入到引用通知的状态,它会调用 connectOutlets 。因为 post 的connectOutlets 方法已经设置了 postController 的 content ,引用通知状 态会检索关联。

再一次,由于 connectOutlets 回调与动态 URL 段协同工作,由 {{action}} 辅 助标记生成的 URL 会之后会保证奏效。

异步

***一点:你会问你自己,当应用在 App.Post.find(1) 调用时还没有加载“文章1” 这个系统如何正常工作。

这会奏效的原因是 ember-data 总是立即返回一个对象,即使它需要开启一个查询。 那个对象以一个空的 data 散列值开始。当服务器返回数据, ember-data 更新对 象的 data ,这也出发所有定义的属性(用 DS.attr 定义的属性)上的绑定。

当你要这个对象查询它的 trackbacks ,它也会返回一个空的 ManyArray 。当服 务器一同返回文章和与之关联的内容时, ember-data 会自动更新 trackbacks 数 组。

在你的 trackbacks.handlebars 模板中,你会做好这些:

  1. <ul> 
  2. {{#each trackback in controller}} 
  3.   <li><a {{bindAttr href="trackback.url"}}>{{trackback.title}}</a></li> 
  4. {{/each}} 
  5. </ul> 

当 ember-data 更新 trackbacks 数组,变更会通过 trackbacksController 传 播至 DOM。

你也会想要避免展示尚未加载的局部数据。在这种情况下,你可以这么做:

  1. <ul> 
  2. {{#if controller.isLoaded}} 
  3.   {{#each trackback in controller}} 
  4.     <li><a {{bindAttr href="trackback.url"}}>{{trackback.title}}</a></li> 
  5.   {{/each}} 
  6. {{else}} 
  7.   <li><img src="/spinner.gif">加载引用通知……</li> 
  8. {{/if}} 
  9. </ul> 

当 ember-data 用服务器提供的数据把引用通知填入到 ManyArray 里,它也会设 置 isLoaded 属性。因为所有的包含 #if 的模板结构会在底层属性变化时自动更 新 DOM,这会“恰好奏效”。

责任编辑:陈四芳 来源: emberjs.torriacg.org
相关推荐

2013-12-24 11:11:27

ember.jsJavascript

2013-12-24 15:56:20

2013-12-25 10:08:42

ember.js异步处理

2013-12-24 16:03:26

Ember.js视图

2013-12-24 13:20:28

EmberEmber.js

2013-05-30 15:16:26

javaScriptMVC模式

2013-12-24 14:50:39

Ember.js框架

2013-12-20 14:47:23

ember.js

2013-09-02 15:53:16

Windows

2009-12-16 14:40:14

Ruby控制结构

2024-09-19 08:22:41

2010-05-07 15:32:13

Oracle物理结构

2010-02-22 17:12:23

应用层交换机

2013-09-10 14:01:40

WebEmber.jsAngular.js

2024-04-09 16:19:16

2010-01-27 17:04:42

应用层交换机

2022-03-14 08:16:00

Java程序开发

2023-09-15 10:33:41

算法数据结构

2009-08-13 14:24:44

C#结构体构造函数

2010-07-08 13:06:34

UDP协议
点赞
收藏

51CTO技术栈公众号