Emberjs-Routing

指定URL类型:
http://guides.emberjs.com/v1.11.0/routing/specifying-the-location-api/


默认情况下路由器使用浏览器的hash来加载应用程序的开始状态并在后续访问过程中保持同步。现在,这个依赖于浏览器中的hashchange 事件。


下面的router定义,在浏览器中输入/#/posts/new 会导航到posts.new 路由。
//app/router.js
Router.map(function(){
this.route('posts', function(){
this.route('new');
});
});
如果我们想用/posts/new来替换,我们可以告诉Router使用浏览器的history API。


记住你的服务器必须为你的Ember应用程序提供在Router.map函数中定义的所有URL提供服务。


app/router.js
Ember.Router.extend({
location:'history'
});


最后,如果我们不想浏览器的URL和我们的应用程序交互,我们可以完全关闭本地API。这在测试时或者需要使用Router管理状态时很有用。
但是,暂时不要让URL困扰我们,比如当我们把应用程序嵌入到一个大的页面时。






查询参数:
http://guides.emberjs.com/v1.11.0/routing/query-params/


查询参数是一些出现在URL中?后面的可选的键值对。比如,下面的URL有两个查询参数, sort和page,其值分别为 ASC和2:
http://example.com/articales?sort=ASC&page=2


查询参数使得那些额外的应用程序状态可以被序列化到URL,这些额外的内容不能被放到URL但是适合放到URL的path里。比如 这些内容都放到 ? 的右边。
查询参数通常在分页集合中表示当前页码,过滤条件或者排序条件。


指定查询参数:
查询参数将被定义在路由驱动的controller里,比如, 配置查询参数在articles路由中可用, 它们必须在controller:articles上声明:


让我们添加一个category查询参数来过滤所有的未分类的文章,我们指定category为controller:articles的queryParams:
app/controllers/articles.js
export default Ember.Controller.extend({
queryParams: ['category'],
category: null
}):
这里在URL的查询参数category和controller:articles的category属性之间建立了绑定。换句话说,一旦进入articles路由,对URL内的category查询参数的任何变化
都会被更新到controller:articles的category属性上,反之亦然。


现在我们只需要定义一个articles模板要渲染的category-filtered数组的计算属性:
app/controllers/articles.js


export default Ember.Controller.extend({
queryParams:['category'],
category: null,

filterArticles: function(){
var category = this.get('category');
var articles = this.get('model');

if(category){
return articles.filterBy('category', category);
}else{
return articles;
}
}.property('category','model')
});


上面的代码中,我们创建了如下内容:
1. 如果用户导航到/articles , category 将为null, 所以articles不会被过滤。
2. 如果用户导航到/articles?category=recent, category将被设置为"recent", 所以articles将被过滤。
3. 只要在articles路由内部,任何controller:articles的category属性的改变都会导致URL更新其查询参数。
默认情况下,一个查询参数变化不会导致整个路由器转换。(比如,它们不会调用model钩子和setupController等),它只会更新URL。


LINK-TO助手:
{{link-to}}支持使用query-params子表达助手来指定查询参数。
//Explicitly set target query params
{{#link-to 'posts' (query-params direction="asc")}} Sort {{/link-to}}


//Binding is also supported
{{#link-to 'posts' (query-params direction=otherDirection}} Sort {{/link-to}}


上例中,direction可能是controller:post的查询参数属性,但是它还可以指向一个posts路由关联的controller的层级中的direction属性。
匹配最低的叶级controller和属性。


link-to助手在判断其活动状态时使用查询参数,来设置合适的类。 通过一个链接查看查询参数是否一致来决定其活动状态。
我们不必提供所有当前,活动查询参数。


转换:TransitionTo
Route#transitionTo(和Controller#transtionToRoute)现在接收一个最终参数,该参数是一个键值为queryParams的对象。


this.transitionTo('post', object,{queryParams:{showDetails: true}});
this.transitionTo('posts',{queryParams: {sort: 'title'}});


//如果我们执行转换查询参数而不改变路由
this.transitionTo({queryParams:{direction: 'asc'}});


我们还可以添加查询参数到URL 转换:
this.transitionTo("/posts/1?sort=date&showDetails=true");


看一个完整的转换定义:
如果提供给transitionTo或者{{link-to}}的参数只是关系到查询参数值的变化,而不涉及到路由层级,那不是全转换,它们默认没有触发model和setupController,
只是controller属性和URL被新查询参数更新。


一些查询参数变化需要从服务器加载数据,在这种情况下,被认为i进入一个全转换,当查询字符串发生变化,我们可以使用可选的queryParams 在该controller的关联的Route
上配置。设置其查询参数 refreshModel为true。


app/routes/articles.js
export default Ember.Route.extend({
queryParams:{
category: {
refreshModel: true
}
},
model: function(params){
//This get called upon entering 'articles' route for the first time, and we opt into refiring it upon
// query param changes by setting 'refreshModel:true' above.
//params has format of { category:"someValueOrJustNull"}, which we can just forward to the server.
return this.store.findQuery('articles', params);
}
});


app/controller/articles.js
export default Ember.ArrayController.extend({
queryParams: ['category'],
category: null
});


使用replaceState 替代更新URL
默认情况下,Ember将使用pushState 来更新地址栏里URL来响应一个controller查询参数属性的变化。但是我们将使用replaceState替代(防止有额外的项目被添加到浏览器历史中)
我们可以在Route的queryParams配置hash中设置它。
app/articles/route.js
export default Ember.Route.extend({
queryParams:{
category:{
replace: true
}
}
}):
注意,这个配置属性和它的默认值false类似于link-to助手类,它也可以让我们通过设置replace=true来选择replaceState转换方式。


映射Controller的属性到一个不同的查询参数键:
默认情况,指定foo作为controller查询参数属性将会绑定的查询参数键名也是foo。 比如?foo=123
我们可以为Controller的查询参数属性绑定一个不同的查询参数名:
app/articles/controller.js
export default Ember.ArrayController.extend({
queryParams: {
category: "article_category"
},
category: null
});


设置以后,我们的controller:articles的category属性的变化将会更新articles_category查询参数,反之依然。


注意,需要额外自定义的查询参数时可以在queryParams数组中 以字符串形式提供。
app/controller/articles.js
export default Ember.Controller.extend({
queryParams:["page","filter",{
category:"articles_category"
}],
category:null,
page: 1,
filter:"recent"
});


默认值和反序列化:
下面实例中,controller查询字符串属性page有一个默认值 1。
app/controller/example.js
export default Ember.ArrayController.extend({
queryParam: 'page',
page: 1
});
下面两种行为会影响到查询参数:
1.查询参数值会转换为和默认值相同数值类型。比如URL从/?page=3 变为 /?page=2 将使controller:articles的page属性之便为数字2 而不是 字符串“2”,同样的布尔值也是。
2.当controller的查询参数属性被设置为默认值时,该值不会被序列化到URL中。 所以上例中如果page值是默认值 1,URL中可能不会有查询参数出现,而是
/articles ,但是一旦controller的查询参数被设置为默认值以外的,2 或者 3,则URL会被立刻更新为/articles?page=2 或者 /articles?page=3




粘性(sticky)查询参数值:
默认情况下,查询参数值在Ember里是粘性的。也就是说如果你改变查询参数退出然后重新进入该路由(或URL),修改后的新值会被保留下来(而不是重新恢复为默认值)
这样的功能使得我们在路由间来回导航时能够保留排序或者过滤参数。


更进一步说,这些粘性查询参数值根据加载到route的model被记住/重新保存。所以,我们假定一个名为team的route有动态分段/:team_name 和 controller的查询参数 "filter"
如果我们导航到 /badgers 并通过"rookies"过滤,然后导航到/bears 用"best"过滤,然后再导航到/potatoes 用"lamest"过滤,给出下面的导航工具条链接:
{{#link-to 'team' 'badgers'}} Badgers{{/link-to}}
{{#link-to 'team' 'bears'}}Bears{{/link-to}}
{{#link-to 'team' 'potatoes'}}Potatoes{{/link-to}}


输出的HTML内容如下:
<a href="/badgers?filter=rookies">Badgers</a>
<a href="/bears?filter=best">Bears</a>
<a href="/potatoes?filter=lamest">Potatoes</a>


这说明,一旦你改变了查询参数,它就会被保存并试着加载相应的model到route。


如果我们想重置某个查询参数,我们有如下两个选择:
1. 在link-to或着transitionTo中为查询参数显式传入默认值。
2. 使用Route.resetController钩子在退出route或者改变route的model的时候将查询参数设置会默认值。


下面的示例中,controller的page查询参数被重置为1,但是仍然被限制在前一个ArticlesRoute的 model.结果就是所有指向回退到已有route的链接
都将使用新的重置的值1作为查询参数值。
app/articles/route.js


export default Ember.Route.extend({
resetController: function(controller,isExiting, transition){
if(isExiting){
//isExiting would be false if only the route's model was changing
controller.set('page',1);
}
}
});


有些时候,我们可能不需要查询参数这种粘性来限定route的model,但是想重用查询参数值来改变一个route的model。
这时我们可以通过在controller的queryParams配置hash中设置的scope选项值为"controller"。
app/articles/controller.js


export default Ember.ArrayController.extend({
queryParams:[{
showMagnifyingGlass:{
scope:"controller"
}
}]
});
下面的代码说明我们如何重写范围和单个controller查询参数属性的查询参数URL键:
app/articles/controller.js


export default Ember.Controller.extend({
queryParams: ["page","filter",
{
showMagnifyingGlass:{
scope:"controller",
as: "glass",
}
}
]
});


实例:
搜索查询
http://output.jsbin.com/tukoye


排序:基于客户端排序,不重新触发model钩子
http://jsbin.com/joboje


排序: 基于服务端排序,重新触发model钩子
http://jsbin.com/hiyalu


分页 + 排序
http://jsbin.com/fayug


布尔值. False 值从URL中移除 QP
http://jsbin.com/sisebi


app路由中的全局查询参数
http://jsbin.com/duhof




通过refresh()方法进入完全转换
http://jsbin.com/yanigu


通过改变controller的QP(queryParams)属性更新查询参数
http://jsbin.com/zerojo


通过使用replaceState改变controller的QP属性来更新查询参数
http://emberjs.jsbin.com/birugi


w/ {{partial}} 助手用于简化列表tabbing 
http://jsbin.com/baguc




没有route名link-to ,只改变QP
http://jsbin.com/rujawi


Complex: 序列化textarea 内容到URL(子表达式)
http://emberjs.jsbin.com/tudiz


数组:
http://jsbin.com/gacihe


=====================================================
异步路由
http://guides.emberjs.com/v1.11.0/routing/asynchronous-routing/


本节涉及到路由器一些更高级的内容以及处理复杂的异步逻辑的能力


一句话概括Promises:
Ember的router中处理异步逻辑的方式大量使用了Promise的概念。简单说,promise是表示一个最终结果值的对象。
一个Promise既可以fulfill(成功的解析出值)或者reject(没能解析出值)。
我们通过promise的then方法来获取最终的结果值或者处理promise的reject情况,该方法接收两个可选回调,一个处理满足,一个处理拒绝。
如果promise得到满足,满足处理器被调用,获取的结果将作为唯一的输入参数。如果promise是reject结果,拒绝处理器会被调用,其唯一输入参数变为拒绝原因。
比如:
var promise = fetchTheAnswer();


promise.then(fullfill,reject);


function fullfill(answer){
console.log("The answer is " + answer);
}


function reject(reason){
console.log("Couldn't get the answer! Reason: "+ reason);
}


promise强力之处在于它可以链式执行后续的异步操作:
//Note: jQuery Ajax methods return promise
var usernamesPromise = Ember.$.getJSON('/usernames.json');


usernamesPromise.then(fetchPhotosOfUsers)
.then(applyInstagramFilters)
.then(uploadTrendyPhotoAlbum)
.then(displaySuccessMessage,handleErrors);
上面的代码中,如果fetchPhotoOfUsers,applyInstagramFilters 和 uploadTrendyPhotoAlbum任何一个返回一个reject promise, handleErrors都将会被调用,并传入失败原因。
如此,promise appromimate近似于一个由try-catche语句格式过的异步表单,防止嵌套回调后的嵌套回调流,简化了管理复杂异步逻辑 。


本说明目的不是深入介绍promise的所有可用方式,如果想了解更多,请查看RSVP的readme 文件https://github.com/tildeio/rsvp.js
Ember使用的promise类库。


路由器为Promise暂停:
当我们在路由之间转换时, Ember 路由器通过(model钩子)收集所有的models在转换结束时传入路由的controller。
如果model钩子(或者相关的beforeModel或afterModel钩子)返回标准的对象或数组而不是promise, 转换会立刻完成。
但是如果model钩子(或者beforeModel或afterModel钩子)返回一个promise(或者一个promise被提供给transitionTo作为参数),这时转换就会暂停,直到promise得到满足或者被拒绝。


路由器会把任何定义了then方法的对象认为是一个promise。


如果promise得到满足,转换会重拾中断开始解析下一个route(可能是子route)的model, 如果其子route得到的也是个promise,那么继续暂停。
直到所有目标路由 model都被解析完成。 结果值被传入每一个路由的setupController钩子,变成promise的满足结果值。


一个基本实例:
app/routes/tardy.js
export default Ember.Route.extend({
model:function(){
return new Ember.RSVP.Promise(function(resolve){
Ember.run.later(function(){
resolve({msg: "Hold Your Horses"});
},3000);
});
},

setupController:function(controller,model){
console.log(model.msg); // Hold Your horses
}

当我们转换到route:tardy 时,model钩子将会被调用并返回一个promise但不会立刻被解析,而是在3秒后才被解析。
在这段时间内路由器会在转换中间暂停。当promise最终被满足时,路由器将继续转换并且最终调用route:tardy的
setupController钩子并传入解析的对象。

当我们需要保证一个路由的数据在显示一个新模板是能够完全加载时,promise暂停的行为非常有用。



当PROMISE是拒绝时:
上面我们说了Promise得到满足时结果,那么被拒绝时又如何呢?


默认情况下,如果一个model promise在转换过程中被拒绝,转换便会被中止,不会渲染新的目标模板,错误日志被发送到控制台。


我们可以通过路由的actions hash上的error处理器来配置错误处理逻辑。
当一个promise被拒绝后,会在route上触发一个error事件,并向上传递到route:application的默认错误处理器上,直到它被一个自定义的错误处理器处理。


app/routes/good-for-nothing.js
export default Ember.Route.extend({
model:function(){
return Ember.RSVP.reject("FAIL");
},

actions:{
error:function(reason){
alert(reason);//"FAIL"

//can transition to another route here
//this.transitionTo('index');

//Uncomment the line below to bubble this error event:
//return true;
}
}
});


上例中,错误事件将在route:good-for-nothing的错误事件处理器中被处理并终止而不会继续bubble。 如果想让事件继续bubble up到route:application, 我们可以
从错误处理器函数中返回true。




从拒绝中恢复:
拒绝模式的promise将停止转换,因为promise是可链式操作的,我们可以在model钩子里捕获promise的拒绝并转换成满足而不阻止转换继续。
app/routes/funky.js
export default Ember.Route.extend({
model: function(){
return iHopeThisWorks().then(null,function(){
//Promise rejected, fulfill with some default value to use as the route's model and continue on with the transition
return { msg: "Recovered from rejected promise"};
});
}
});


BeforeModel和AfterModel:
model钩子包含很多暂停promise转换情形,但是有时候我们需要关联钩子beforeModel和afterModel的帮助。 最常见的原因是如果我们通过{{link-to}}或者transitionTo
转换进入一个带有动态分段变量URL的route,(或者相反,转换导致URL发生变化),我们要转入route的model已经被指定(比如 {{#link-to 'article' article}}或者
this.transitionTo('article',article)), 在这种情况下,model钩子将不会被调用。 我们需要在路由器仍在收集所有路由的model来执行转换时,使用beforeModel或者afterModel钩子来装配任何需要的逻辑。


beforeModel
两个钟更为有用的一个,该钩子在路由器试图为路由解析model之前被调用。也就是说,它会在model钩子被调用之前被调用,或者model钩子不会被调用时被调用。
beforeModel钩子将在router试图解析任何传入该路由的model promise之前被调用。


类似于model, beforeModel将返回一个promise,并暂停转换执行直到返回的promise被解析,或者是一个拒绝promise触发一个error事件.


下面简单列出几个beforeModel方便使用的情形:
在执行浪费资源的model钩子中服务器查询之前决定是否转换到其它路由。
在继续处理model钩子之前确认用户有一个合法身份
加载该路由需要的应用程序代码


app/routes/secret-articles.js


export default Ember.Route.extend({
beforeModel: function(){
if(!this.controllerFor('auth').get('isLoggedIn')){
this.transitionTo('login');
}
}
});




afetrModel:
该钩子在路由的model被解析之后调用,遵循了和beforeModel一样的暂停继续promis和model 语法。
已经解析的model将被作为参数传给它,它可以根据一个model的解析 来执行一些额外的逻辑。


app/routes.article.js


export default Ember.Route.extend({
model: function(){
//this.store.find('article') returns a promise-like object(it has a 'then' method that can be used like a promise)
return this.store.find('article');
},
afterModel:function(articles){
if(articles.get('length') === 1){
this.transitionTo('article.show', artciles.get('firstObject'));
}
}
});


这里也许我们有些疑惑,为什么不能只把afterModel的逻辑放到一个满足的promise处理器,从model钩子中返回呢?
原因上面提到过,转换初始化时,通过{[link-to}}或者transitionTo早已为路由本身提供了model,所以 在这种情况下model钩子不会被调用。


更多资源查看:
Embercasts: 客户端验证第二部分
http://www.embercasts.com/episodes/client-side-authentication-part-2


RC6 博客描述了这些新内容
http://guides.emberjs.com/blog/2013/06/23/ember-1-0-rc6.html

你可能感兴趣的:(Emberjs-Routing)